Skip to main content

fedimint_mint_common/
config.rs

1use std::collections::BTreeMap;
2
3use fedimint_core::core::ModuleKind;
4use fedimint_core::encoding::{Decodable, Encodable};
5use fedimint_core::module::serde_json;
6use fedimint_core::{Amount, PeerId, Tiered, plugin_types_trait_impl_config};
7use serde::{Deserialize, Serialize};
8use tbs::{AggregatePublicKey, PublicKeyShare};
9
10use crate::MintCommonInit;
11
12#[derive(Clone, Debug, Serialize, Deserialize)]
13pub struct MintConfig {
14    pub private: MintConfigPrivate,
15    pub consensus: MintConfigConsensus,
16}
17
18#[derive(Clone, Debug, Serialize, Deserialize, Decodable, Encodable)]
19pub struct MintConfigLocal;
20
21#[derive(Clone, Debug, Serialize, Deserialize, Encodable, Decodable)]
22pub struct MintConfigConsensus {
23    /// The set of public keys for blind-signing all peers and note
24    /// denominations
25    pub peer_tbs_pks: BTreeMap<PeerId, Tiered<PublicKeyShare>>,
26    /// Fees charged for ecash transactions
27    pub fee_consensus: FeeConsensus,
28    /// The maximum amount of change a client can request
29    pub max_notes_per_denomination: u16,
30}
31
32#[derive(Clone, Debug, Serialize, Deserialize)]
33pub struct MintConfigPrivate {
34    /// Secret keys for blind-signing ecash of varying note denominations
35    pub tbs_sks: Tiered<tbs::SecretKeyShare>,
36}
37
38#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable, Hash)]
39pub struct MintClientConfig {
40    pub tbs_pks: Tiered<AggregatePublicKey>,
41    pub fee_consensus: FeeConsensus,
42    pub peer_tbs_pks: BTreeMap<PeerId, Tiered<tbs::PublicKeyShare>>,
43    pub max_notes_per_denomination: u16,
44}
45
46impl std::fmt::Display for MintClientConfig {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        write!(
49            f,
50            "MintClientConfig {}",
51            serde_json::to_string(self).map_err(|_e| std::fmt::Error)?
52        )
53    }
54}
55
56// Wire together the configs for this module
57plugin_types_trait_impl_config!(
58    MintCommonInit,
59    MintConfig,
60    MintConfigPrivate,
61    MintConfigConsensus,
62    MintClientConfig
63);
64
65#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
66pub struct FeeConsensus {
67    base: Amount,
68    parts_per_million: u64,
69}
70
71impl FeeConsensus {
72    /// The mint module will charge a non-configurable base fee of one hundred
73    /// millisatoshis per transaction input and output to account for the costs
74    /// incurred by the federation for processing the transaction. On top of
75    /// that the federation may charge a additional relative fee per input and
76    /// output of up to one thousand parts per million which is equal to one
77    /// tenth of one percent.
78    ///
79    /// # Errors
80    /// - This constructor returns an error if the relative fee is in excess of
81    ///   one thousand parts per million.
82    pub fn new(parts_per_million: u64) -> anyhow::Result<Self> {
83        anyhow::ensure!(
84            parts_per_million <= 1_000,
85            "Relative fee over one thousand parts per million is excessive"
86        );
87
88        Ok(Self {
89            base: Amount::from_msats(100),
90            parts_per_million,
91        })
92    }
93
94    pub fn zero() -> Self {
95        Self {
96            base: Amount::ZERO,
97            parts_per_million: 0,
98        }
99    }
100
101    pub fn fee(&self, amount: Amount) -> Amount {
102        Amount::from_msats(self.fee_msats(amount.msats))
103    }
104
105    /// Returns the smallest denomination where the note's value is at least
106    /// 4x the base fee, rounded up to the next power of two msats. Notes
107    /// below this denomination are not economical since fees consume a
108    /// significant fraction of their value.
109    pub fn min_economical_denomination(&self) -> Amount {
110        Amount::from_msats(self.base.msats.saturating_mul(4).next_power_of_two())
111    }
112
113    /// Rounds an amount up to the nearest multiple of the smallest economical
114    /// denomination.
115    pub fn round_up(&self, amount: Amount) -> Amount {
116        let msats = amount
117            .msats
118            .next_multiple_of(self.base.msats.saturating_mul(4).next_power_of_two());
119
120        Amount::from_msats(msats)
121    }
122
123    fn fee_msats(&self, msats: u64) -> u64 {
124        msats
125            .saturating_mul(self.parts_per_million)
126            .saturating_div(1_000_000)
127            .checked_add(self.base.msats)
128            .expect("The division creates sufficient headroom to add the base fee")
129    }
130}
131
132#[test]
133fn test_fee_consensus() {
134    let fee_consensus = FeeConsensus::new(1_000).expect("Relative fee is within range");
135
136    assert_eq!(
137        fee_consensus.fee(Amount::from_msats(999)),
138        Amount::from_msats(100)
139    );
140
141    assert_eq!(
142        fee_consensus.fee(Amount::from_sats(1)),
143        Amount::from_msats(100) + Amount::from_msats(1)
144    );
145
146    assert_eq!(
147        fee_consensus.fee(Amount::from_sats(1000)),
148        Amount::from_sats(1) + Amount::from_msats(100)
149    );
150
151    assert_eq!(
152        fee_consensus.fee(Amount::from_bitcoins(1)),
153        Amount::from_sats(100_000) + Amount::from_msats(100)
154    );
155
156    assert_eq!(
157        fee_consensus.fee(Amount::from_bitcoins(100_000)),
158        Amount::from_bitcoins(100) + Amount::from_msats(100)
159    );
160}