1use std::{
2 cmp::Ordering,
3 ops::{Add, AddAssign, Div},
4};
5
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use vecdb::{CheckedSub, Formattable, Pco};
9
10use super::{Sats, VSize};
11
12#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Pco, JsonSchema)]
14pub struct FeeRate(f64);
15
16impl FeeRate {
17 pub const MIN: Self = Self(0.1);
18
19 pub fn new(fr: f64) -> Self {
20 Self(fr)
21 }
22}
23
24impl From<(Sats, VSize)> for FeeRate {
25 #[inline]
26 fn from((sats, vsize): (Sats, VSize)) -> Self {
27 if sats.is_zero() {
28 return Self(0.0);
29 }
30 let sats = u64::from(sats);
31 let vsize = u64::from(vsize);
32 if vsize == 0 {
33 return Self(f64::NAN);
34 }
35 Self((sats * 1000).div_ceil(vsize) as f64 / 1000.0)
36 }
37}
38
39impl From<f64> for FeeRate {
40 #[inline]
41 fn from(value: f64) -> Self {
42 Self(value)
43 }
44}
45impl From<FeeRate> for f64 {
46 #[inline]
47 fn from(value: FeeRate) -> Self {
48 value.0
49 }
50}
51
52impl Add for FeeRate {
53 type Output = Self;
54 fn add(self, rhs: Self) -> Self::Output {
55 Self(self.0 + rhs.0)
56 }
57}
58
59impl AddAssign for FeeRate {
60 fn add_assign(&mut self, rhs: Self) {
61 *self = *self + rhs
62 }
63}
64
65impl Div<usize> for FeeRate {
66 type Output = Self;
67 fn div(self, rhs: usize) -> Self::Output {
68 if rhs == 0 {
69 Self(f64::NAN)
70 } else {
71 Self(self.0 / rhs as f64)
72 }
73 }
74}
75
76impl From<usize> for FeeRate {
77 #[inline]
78 fn from(value: usize) -> Self {
79 Self(value as f64)
80 }
81}
82
83impl PartialEq for FeeRate {
84 fn eq(&self, other: &Self) -> bool {
85 match (self.0.is_nan(), other.0.is_nan()) {
86 (true, true) => true,
87 (true, false) => false,
88 (false, true) => false,
89 (false, false) => self.0 == other.0,
90 }
91 }
92}
93
94impl Eq for FeeRate {}
95
96#[allow(clippy::derive_ord_xor_partial_ord)]
97impl PartialOrd for FeeRate {
98 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
99 Some(self.cmp(other))
100 }
101}
102
103#[allow(clippy::derive_ord_xor_partial_ord)]
104impl Ord for FeeRate {
105 fn cmp(&self, other: &Self) -> Ordering {
106 match (self.0.is_nan(), other.0.is_nan()) {
107 (true, true) => Ordering::Equal,
108 (true, false) => Ordering::Less,
109 (false, true) => Ordering::Greater,
110 (false, false) => self.0.partial_cmp(&other.0).unwrap(),
111 }
112 }
113}
114
115impl CheckedSub for FeeRate {
116 #[inline]
117 fn checked_sub(self, rhs: Self) -> Option<Self> {
118 let result = self.0 - rhs.0;
119 if result.is_nan() {
120 None
121 } else {
122 Some(Self(result))
123 }
124 }
125}
126
127impl std::fmt::Display for FeeRate {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 let mut buf = ryu::Buffer::new();
130 let str = buf.format(self.0);
131 f.write_str(str)
132 }
133}
134
135impl Formattable for FeeRate {
136 #[inline(always)]
137 fn write_to(&self, buf: &mut Vec<u8>) {
138 if self.0.is_finite() {
139 let mut b = ryu::Buffer::new();
140 buf.extend_from_slice(b.format(self.0).as_bytes());
141 }
142 }
143
144 #[inline(always)]
145 fn fmt_json(&self, buf: &mut Vec<u8>) {
146 if self.0.is_finite() {
147 self.write_to(buf);
148 } else {
149 buf.extend_from_slice(b"null");
150 }
151 }
152}