fedimint_mint_common/
config.rs

1use std::collections::BTreeMap;
2
3use fedimint_core::config::EmptyGenParams;
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(Debug, Clone, Serialize, Deserialize)]
14pub struct MintGenParams {
15    pub local: EmptyGenParams,
16    pub consensus: MintGenParamsConsensus,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct MintGenParamsConsensus {
21    denomination_base: u16,
22    fee_consensus: FeeConsensus,
23}
24
25// The maximum size of an E-Cash note (1,000,000 coins)
26// Changing this value is considered a breaking change because it is not saved
27// in `MintGenParamsConsensus` but instead is hardcoded here
28const MAX_DENOMINATION_SIZE: Amount = Amount::from_bitcoins(1_000_000);
29
30impl MintGenParamsConsensus {
31    pub fn new(denomination_base: u16, fee_consensus: FeeConsensus) -> Self {
32        Self {
33            denomination_base,
34            fee_consensus,
35        }
36    }
37
38    pub fn denomination_base(&self) -> u16 {
39        self.denomination_base
40    }
41
42    pub fn fee_consensus(&self) -> FeeConsensus {
43        self.fee_consensus.clone()
44    }
45
46    pub fn gen_denominations(&self) -> Vec<Amount> {
47        Tiered::gen_denominations(self.denomination_base, MAX_DENOMINATION_SIZE)
48            .tiers()
49            .copied()
50            .collect()
51    }
52}
53
54#[derive(Clone, Debug, Serialize, Deserialize)]
55pub struct MintConfig {
56    pub local: MintConfigLocal,
57    pub private: MintConfigPrivate,
58    pub consensus: MintConfigConsensus,
59}
60
61#[derive(Clone, Debug, Serialize, Deserialize, Decodable, Encodable)]
62pub struct MintConfigLocal;
63
64#[derive(Clone, Debug, Serialize, Deserialize, Encodable, Decodable)]
65pub struct MintConfigConsensus {
66    /// The set of public keys for blind-signing all peers and note
67    /// denominations
68    pub peer_tbs_pks: BTreeMap<PeerId, Tiered<PublicKeyShare>>,
69    /// Fees charged for ecash transactions
70    pub fee_consensus: FeeConsensus,
71    /// The maximum amount of change a client can request
72    pub max_notes_per_denomination: u16,
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize)]
76pub struct MintConfigPrivate {
77    /// Secret keys for blind-signing ecash of varying note denominations
78    pub tbs_sks: Tiered<tbs::SecretKeyShare>,
79}
80
81#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable, Hash)]
82pub struct MintClientConfig {
83    pub tbs_pks: Tiered<AggregatePublicKey>,
84    pub fee_consensus: FeeConsensus,
85    pub peer_tbs_pks: BTreeMap<PeerId, Tiered<tbs::PublicKeyShare>>,
86    pub max_notes_per_denomination: u16,
87}
88
89impl std::fmt::Display for MintClientConfig {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        write!(
92            f,
93            "MintClientConfig {}",
94            serde_json::to_string(self).map_err(|_e| std::fmt::Error)?
95        )
96    }
97}
98
99// Wire together the configs for this module
100plugin_types_trait_impl_config!(
101    MintCommonInit,
102    MintGenParams,
103    EmptyGenParams,
104    MintGenParamsConsensus,
105    MintConfig,
106    MintConfigLocal,
107    MintConfigPrivate,
108    MintConfigConsensus,
109    MintClientConfig
110);
111
112#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
113pub struct FeeConsensus {
114    base: Amount,
115    parts_per_million: u64,
116}
117
118impl FeeConsensus {
119    /// The mint module will charge a non-configurable base fee of one hundred
120    /// millisatoshis per transaction input and output to account for the costs
121    /// incurred by the federation for processing the transaction. On top of
122    /// that the federation may charge a additional relative fee per input and
123    /// output of up to one thousand parts per million which is equal to one
124    /// tenth of one percent.
125    ///
126    /// # Errors
127    /// - This constructor returns an error if the relative fee is in excess of
128    ///   one thousand parts per million.
129    pub fn new(parts_per_million: u64) -> anyhow::Result<Self> {
130        anyhow::ensure!(
131            parts_per_million <= 1_000,
132            "Relative fee over one thousand parts per million is excessive"
133        );
134
135        Ok(Self {
136            base: Amount::from_msats(100),
137            parts_per_million,
138        })
139    }
140
141    pub fn zero() -> Self {
142        Self {
143            base: Amount::ZERO,
144            parts_per_million: 0,
145        }
146    }
147
148    pub fn fee(&self, amount: Amount) -> Amount {
149        Amount::from_msats(self.fee_msats(amount.msats))
150    }
151
152    fn fee_msats(&self, msats: u64) -> u64 {
153        msats
154            .saturating_mul(self.parts_per_million)
155            .saturating_div(1_000_000)
156            .checked_add(self.base.msats)
157            .expect("The division creates sufficient headroom to add the base fee")
158    }
159}
160
161#[test]
162fn test_fee_consensus() {
163    let fee_consensus = FeeConsensus::new(1_000).expect("Relative fee is within range");
164
165    assert_eq!(
166        fee_consensus.fee(Amount::from_msats(999)),
167        Amount::from_msats(100)
168    );
169
170    assert_eq!(
171        fee_consensus.fee(Amount::from_sats(1)),
172        Amount::from_msats(100) + Amount::from_msats(1)
173    );
174
175    assert_eq!(
176        fee_consensus.fee(Amount::from_sats(1000)),
177        Amount::from_sats(1) + Amount::from_msats(100)
178    );
179
180    assert_eq!(
181        fee_consensus.fee(Amount::from_bitcoins(1)),
182        Amount::from_sats(100_000) + Amount::from_msats(100)
183    );
184
185    assert_eq!(
186        fee_consensus.fee(Amount::from_bitcoins(100_000)),
187        Amount::from_bitcoins(100) + Amount::from_msats(100)
188    );
189}