1use super::*;
2
3#[derive(Debug, PartialEq, Clone, Copy)]
4pub struct FeeRate(f64);
5
6impl FromStr for FeeRate {
7 type Err = Error;
8
9 fn from_str(s: &str) -> Result<Self, Self::Err> {
10 Self::try_from(f64::from_str(s)?)
11 }
12}
13
14impl TryFrom<f64> for FeeRate {
15 type Error = Error;
16
17 fn try_from(rate: f64) -> Result<Self, Self::Error> {
18 if rate.is_sign_negative() | rate.is_nan() | rate.is_infinite() {
19 bail!("invalid fee rate: {rate}")
20 }
21 Ok(Self(rate))
22 }
23}
24
25impl FeeRate {
26 pub fn fee(&self, vsize: usize) -> Amount {
27 #[allow(clippy::cast_possible_truncation)]
28 #[allow(clippy::cast_sign_loss)]
29 Amount::from_sat((self.0 * vsize as f64).round() as u64)
30 }
31
32 pub(crate) fn n(&self) -> f64 {
33 self.0
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
42 fn parse() {
43 assert_eq!("1.1".parse::<FeeRate>().unwrap().0, 1.1);
44 assert_eq!("11.19".parse::<FeeRate>().unwrap().0, 11.19);
45 assert_eq!("11.1111".parse::<FeeRate>().unwrap().0, 11.1111);
46 assert!("-4.2".parse::<FeeRate>().is_err());
47 assert!(FeeRate::try_from(f64::INFINITY).is_err());
48 assert!(FeeRate::try_from(f64::NAN).is_err());
49 }
50
51 #[test]
52 fn fee() {
53 assert_eq!(
54 "2.5".parse::<FeeRate>().unwrap().fee(100),
55 Amount::from_sat(250)
56 );
57 assert_eq!(
58 "2.0".parse::<FeeRate>().unwrap().fee(1024),
59 Amount::from_sat(2048)
60 );
61 assert_eq!(
62 "1.1".parse::<FeeRate>().unwrap().fee(100),
63 Amount::from_sat(110)
64 );
65 assert_eq!(
66 "1.0".parse::<FeeRate>().unwrap().fee(123456789),
67 Amount::from_sat(123456789)
68 );
69 }
70}