fedimint_mint_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::serde_json;
7use fedimint_core::{Amount, PeerId, Tiered, plugin_types_trait_impl_config};
8use serde::{Deserialize, Serialize};
9use tbs::{AggregatePublicKey, PublicKeyShare};
10
11use crate::MintCommonInit;
12
13#[derive(Clone, Debug, Serialize, Deserialize)]
14pub struct MintConfig {
15 pub private: MintConfigPrivate,
16 pub consensus: MintConfigConsensus,
17}
18
19#[derive(Clone, Debug, Serialize, Deserialize, Decodable, Encodable)]
20pub struct MintConfigLocal;
21
22#[derive(Clone, Debug, Serialize, Deserialize, Encodable, Decodable)]
23pub struct MintConfigConsensus {
24 pub peer_tbs_pks: BTreeMap<PeerId, Tiered<PublicKeyShare>>,
27 pub fee_consensus: FeeConsensus,
29 pub max_notes_per_denomination: u16,
31}
32
33#[derive(Clone, Debug, Serialize, Deserialize)]
34pub struct MintConfigPrivate {
35 pub tbs_sks: Tiered<tbs::SecretKeyShare>,
37}
38
39#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable, Hash)]
40pub struct MintClientConfig {
41 pub tbs_pks: Tiered<AggregatePublicKey>,
42 pub fee_consensus: FeeConsensus,
43 pub peer_tbs_pks: BTreeMap<PeerId, Tiered<tbs::PublicKeyShare>>,
44 pub max_notes_per_denomination: u16,
45}
46
47impl std::fmt::Display for MintClientConfig {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(
50 f,
51 "MintClientConfig {}",
52 serde_json::to_string(self).map_err(|_e| std::fmt::Error)?
53 )
54 }
55}
56
57plugin_types_trait_impl_config!(
59 MintCommonInit,
60 MintConfig,
61 MintConfigPrivate,
62 MintConfigConsensus,
63 MintClientConfig
64);
65
66#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
67pub struct FeeConsensus {
68 base: Amount,
69 parts_per_million: u64,
70}
71
72impl FeeConsensus {
73 pub fn new(parts_per_million: u64) -> Result<Self, ExcessiveRelativeFeeError> {
84 const MAX_PARTS_PER_MILLION: u64 = 1_000;
85
86 if parts_per_million > MAX_PARTS_PER_MILLION {
87 return Err(ExcessiveRelativeFeeError {
88 parts_per_million,
89 max: MAX_PARTS_PER_MILLION,
90 });
91 }
92
93 Ok(Self {
94 base: Amount::from_msats(100),
95 parts_per_million,
96 })
97 }
98
99 pub fn zero() -> Self {
100 Self {
101 base: Amount::ZERO,
102 parts_per_million: 0,
103 }
104 }
105
106 pub fn fee(&self, amount: Amount) -> Amount {
107 Amount::from_msats(self.fee_msats(amount.msats))
108 }
109
110 pub fn min_economical_denomination(&self) -> Amount {
115 Amount::from_msats(self.base.msats.saturating_mul(4).next_power_of_two())
116 }
117
118 pub fn round_up(&self, amount: Amount) -> Amount {
121 let msats = amount
122 .msats
123 .next_multiple_of(self.base.msats.saturating_mul(4).next_power_of_two());
124
125 Amount::from_msats(msats)
126 }
127
128 fn fee_msats(&self, msats: u64) -> u64 {
129 msats
130 .saturating_mul(self.parts_per_million)
131 .saturating_div(1_000_000)
132 .checked_add(self.base.msats)
133 .expect("The division creates sufficient headroom to add the base fee")
134 }
135}
136
137#[test]
138fn test_fee_consensus() {
139 let fee_consensus = FeeConsensus::new(1_000).expect("Relative fee is within range");
140
141 assert_eq!(
142 fee_consensus.fee(Amount::from_msats(999)),
143 Amount::from_msats(100)
144 );
145
146 assert_eq!(
147 fee_consensus.fee(Amount::from_sats(1)),
148 Amount::from_msats(100) + Amount::from_msats(1)
149 );
150
151 assert_eq!(
152 fee_consensus.fee(Amount::from_sats(1000)),
153 Amount::from_sats(1) + Amount::from_msats(100)
154 );
155
156 assert_eq!(
157 fee_consensus.fee(Amount::from_bitcoins(1)),
158 Amount::from_sats(100_000) + Amount::from_msats(100)
159 );
160
161 assert_eq!(
162 fee_consensus.fee(Amount::from_bitcoins(100_000)),
163 Amount::from_bitcoins(100) + Amount::from_msats(100)
164 );
165}
166
167#[test]
168fn a_relative_fee_over_the_limit_is_rejected() {
169 let err: ExcessiveRelativeFeeError =
170 FeeConsensus::new(1_001).expect_err("A fee over one per mille is excessive");
171
172 assert_eq!(err.parts_per_million, 1_001);
173}