use std::collections::BTreeMap;
use fedimint_core::core::ModuleKind;
use fedimint_core::encoding::{Decodable, Encodable};
use fedimint_core::module::serde_json;
use fedimint_core::{Amount, PeerId, Tiered, plugin_types_trait_impl_config};
use serde::{Deserialize, Serialize};
use tbs::{AggregatePublicKey, PublicKeyShare};
use crate::MintCommonInit;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MintConfig {
pub private: MintConfigPrivate,
pub consensus: MintConfigConsensus,
}
#[derive(Clone, Debug, Serialize, Deserialize, Decodable, Encodable)]
pub struct MintConfigLocal;
#[derive(Clone, Debug, Serialize, Deserialize, Encodable, Decodable)]
pub struct MintConfigConsensus {
pub peer_tbs_pks: BTreeMap<PeerId, Tiered<PublicKeyShare>>,
pub fee_consensus: FeeConsensus,
pub max_notes_per_denomination: u16,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MintConfigPrivate {
pub tbs_sks: Tiered<tbs::SecretKeyShare>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable, Hash)]
pub struct MintClientConfig {
pub tbs_pks: Tiered<AggregatePublicKey>,
pub fee_consensus: FeeConsensus,
pub peer_tbs_pks: BTreeMap<PeerId, Tiered<tbs::PublicKeyShare>>,
pub max_notes_per_denomination: u16,
}
impl std::fmt::Display for MintClientConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"MintClientConfig {}",
serde_json::to_string(self).map_err(|_e| std::fmt::Error)?
)
}
}
plugin_types_trait_impl_config!(
MintCommonInit,
MintConfig,
MintConfigPrivate,
MintConfigConsensus,
MintClientConfig
);
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
pub struct FeeConsensus {
base: Amount,
parts_per_million: u64,
}
impl FeeConsensus {
pub fn new(parts_per_million: u64) -> anyhow::Result<Self> {
anyhow::ensure!(
parts_per_million <= 1_000,
"Relative fee over one thousand parts per million is excessive"
);
Ok(Self {
base: Amount::from_msats(100),
parts_per_million,
})
}
pub fn zero() -> Self {
Self {
base: Amount::ZERO,
parts_per_million: 0,
}
}
pub fn fee(&self, amount: Amount) -> Amount {
Amount::from_msats(self.fee_msats(amount.msats))
}
pub fn min_economical_denomination(&self) -> Amount {
Amount::from_msats(self.base.msats.saturating_mul(4).next_power_of_two())
}
pub fn round_up(&self, amount: Amount) -> Amount {
let msats = amount
.msats
.next_multiple_of(self.base.msats.saturating_mul(4).next_power_of_two());
Amount::from_msats(msats)
}
fn fee_msats(&self, msats: u64) -> u64 {
msats
.saturating_mul(self.parts_per_million)
.saturating_div(1_000_000)
.checked_add(self.base.msats)
.expect("The division creates sufficient headroom to add the base fee")
}
}
#[test]
fn test_fee_consensus() {
let fee_consensus = FeeConsensus::new(1_000).expect("Relative fee is within range");
assert_eq!(
fee_consensus.fee(Amount::from_msats(999)),
Amount::from_msats(100)
);
assert_eq!(
fee_consensus.fee(Amount::from_sats(1)),
Amount::from_msats(100) + Amount::from_msats(1)
);
assert_eq!(
fee_consensus.fee(Amount::from_sats(1000)),
Amount::from_sats(1) + Amount::from_msats(100)
);
assert_eq!(
fee_consensus.fee(Amount::from_bitcoins(1)),
Amount::from_sats(100_000) + Amount::from_msats(100)
);
assert_eq!(
fee_consensus.fee(Amount::from_bitcoins(100_000)),
Amount::from_bitcoins(100) + Amount::from_msats(100)
);
}