fedimint_mintv2_common/
config.rs1use std::collections::BTreeMap;
2
3use fedimint_core::config::ExcessiveRelativeFeeError;
4use fedimint_core::core::ModuleKind;
5use fedimint_core::encoding::{Decodable, Encodable};
6use fedimint_core::module::{AmountUnit, serde_json};
7use fedimint_core::{Amount, PeerId, plugin_types_trait_impl_config};
8use serde::{Deserialize, Serialize};
9use tbs::{AggregatePublicKey, PublicKeyShare};
10
11use crate::{Denomination, MintCommonInit};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct MintGenParams {
15 pub fee_consensus: FeeConsensus,
16}
17
18pub fn consensus_denominations() -> impl DoubleEndedIterator<Item = Denomination> {
19 (0..42).map(Denomination)
20}
21
22pub fn client_denominations() -> impl DoubleEndedIterator<Item = Denomination> {
23 (9..42).map(Denomination)
24}
25
26#[derive(Clone, Debug, Serialize, Deserialize)]
27pub struct MintConfig {
28 pub private: MintConfigPrivate,
29 pub consensus: MintConfigConsensus,
30}
31
32#[derive(Clone, Debug, Serialize, Deserialize, Encodable, Decodable)]
33pub struct MintConfigConsensus {
34 pub tbs_agg_pks: BTreeMap<Denomination, AggregatePublicKey>,
35 pub tbs_pks: BTreeMap<Denomination, BTreeMap<PeerId, PublicKeyShare>>,
36 pub fee_consensus: FeeConsensus,
37 pub amount_unit: AmountUnit,
38}
39
40#[derive(Clone, Debug, Serialize, Deserialize)]
41pub struct MintConfigPrivate {
42 pub tbs_sks: BTreeMap<Denomination, tbs::SecretKeyShare>,
43}
44
45#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable, Hash)]
46pub struct MintClientConfig {
47 pub tbs_agg_pks: BTreeMap<Denomination, AggregatePublicKey>,
48 pub tbs_pks: BTreeMap<Denomination, BTreeMap<PeerId, PublicKeyShare>>,
49 pub fee_consensus: FeeConsensus,
50 pub amount_unit: AmountUnit,
51}
52
53impl std::fmt::Display for MintClientConfig {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 write!(
56 f,
57 "MintClientConfig {}",
58 serde_json::to_string(self).map_err(|_e| std::fmt::Error)?
59 )
60 }
61}
62
63plugin_types_trait_impl_config!(
65 MintCommonInit,
66 MintConfig,
67 MintConfigPrivate,
68 MintConfigConsensus,
69 MintClientConfig
70);
71
72#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
73pub struct FeeConsensus {
74 base: Amount,
75 parts_per_million: u64,
76}
77
78impl FeeConsensus {
79 pub fn new(parts_per_million: u64) -> Result<Self, ExcessiveRelativeFeeError> {
90 const MAX_PARTS_PER_MILLION: u64 = 1_000;
91
92 if parts_per_million > MAX_PARTS_PER_MILLION {
93 return Err(ExcessiveRelativeFeeError {
94 parts_per_million,
95 max: MAX_PARTS_PER_MILLION,
96 });
97 }
98
99 Ok(Self {
100 base: Amount::from_msats(100),
101 parts_per_million,
102 })
103 }
104
105 pub fn zero() -> Self {
107 Self {
108 base: Amount::ZERO,
109 parts_per_million: 0,
110 }
111 }
112
113 pub fn base_fee(&self) -> Amount {
114 self.base
115 }
116
117 pub fn fee(&self, amount: Amount) -> Amount {
118 Amount::from_msats(self.fee_msats(amount.msats))
119 }
120
121 fn fee_msats(&self, msats: u64) -> u64 {
122 msats
123 .saturating_mul(self.parts_per_million)
124 .saturating_div(1_000_000)
125 .checked_add(self.base.msats)
126 .expect("The division creates sufficient headroom to add the base fee")
127 }
128}
129
130#[test]
131fn test_fee_consensus() {
132 let fee_consensus = FeeConsensus::new(1_000).expect("Relative fee is within range");
133
134 assert_eq!(
135 fee_consensus.fee(Amount::from_msats(999)),
136 Amount::from_msats(100)
137 );
138
139 assert_eq!(
140 fee_consensus.fee(Amount::from_sats(1)),
141 Amount::from_msats(100) + Amount::from_msats(1)
142 );
143
144 assert_eq!(
145 fee_consensus.fee(Amount::from_sats(1000)),
146 Amount::from_sats(1) + Amount::from_msats(100)
147 );
148
149 assert_eq!(
150 fee_consensus.fee(Amount::from_bitcoins(1)),
151 Amount::from_sats(100_000) + Amount::from_msats(100)
152 );
153
154 assert_eq!(
155 fee_consensus.fee(Amount::from_bitcoins(100_000)),
156 Amount::from_bitcoins(100) + Amount::from_msats(100)
157 );
158}
159
160#[test]
161fn a_relative_fee_over_the_limit_is_rejected() {
162 let err: ExcessiveRelativeFeeError =
163 FeeConsensus::new(1_001).expect_err("A fee over one per mille is excessive");
164
165 assert_eq!(err.parts_per_million, 1_001);
166}