ant_node/payment/
pricing.rs1use evmlib::common::Amount;
28
29const PRICING_DIVISOR: u128 = 6000;
31
32const DIVISOR_SQUARED: u128 = PRICING_DIVISOR * PRICING_DIVISOR;
34
35const PRICE_BASELINE_WEI: u128 = 3_906_250_000_000_000;
39
40const PRICE_COEFFICIENT_WEI: u128 = 35_156_250_000_000_000;
44
45const PRICE_PER_RECORD_SQUARED_WEI: u128 = PRICE_COEFFICIENT_WEI / DIVISOR_SQUARED;
47
48#[must_use]
61pub fn derive_records_stored_from_price(price: Amount) -> u64 {
62 let baseline = Amount::from(PRICE_BASELINE_WEI);
63 if price <= baseline {
64 return 0;
65 }
66
67 let excess = price - baseline;
68 let n_squared = excess / Amount::from(PRICE_PER_RECORD_SQUARED_WEI);
69 let root = n_squared.root(2);
70 if root > Amount::from(u64::MAX) {
76 u64::MAX
77 } else {
78 root.to::<u64>()
79 }
80}
81
82#[must_use]
89pub fn calculate_price(close_records_stored: usize) -> Amount {
90 let n = Amount::from(close_records_stored);
91 let n_squared = n.saturating_mul(n);
92 let quadratic_wei = n_squared.saturating_mul(Amount::from(PRICE_COEFFICIENT_WEI))
93 / Amount::from(DIVISOR_SQUARED);
94 Amount::from(PRICE_BASELINE_WEI).saturating_add(quadratic_wei)
95}
96
97#[cfg(test)]
98#[allow(clippy::unwrap_used, clippy::expect_used)]
99mod tests {
100 use super::*;
101
102 const WEI_PER_TOKEN: u128 = 1_000_000_000_000_000_000;
104
105 fn expected_price(n: u64) -> Amount {
107 let n_amt = Amount::from(n);
108 let quad =
109 n_amt * n_amt * Amount::from(PRICE_COEFFICIENT_WEI) / Amount::from(DIVISOR_SQUARED);
110 Amount::from(PRICE_BASELINE_WEI) + quad
111 }
112
113 #[test]
114 fn test_zero_records_gets_baseline() {
115 let price = calculate_price(0);
117 assert_eq!(price, Amount::from(PRICE_BASELINE_WEI));
118 }
119
120 #[test]
121 fn test_baseline_is_nonzero_spam_barrier() {
122 assert!(calculate_price(0) > Amount::ZERO);
125 assert!(calculate_price(1) > calculate_price(0));
126 }
127
128 #[test]
129 fn test_one_record_above_baseline() {
130 let price = calculate_price(1);
131 assert_eq!(price, expected_price(1));
132 assert!(price > Amount::from(PRICE_BASELINE_WEI));
133 }
134
135 #[test]
136 fn test_at_divisor_is_baseline_plus_k() {
137 let price = calculate_price(6000);
140 let expected = Amount::from(PRICE_BASELINE_WEI + PRICE_COEFFICIENT_WEI);
141 assert_eq!(price, expected);
142 }
143
144 #[test]
145 fn test_double_divisor_is_baseline_plus_four_k() {
146 let price = calculate_price(12000);
148 let expected = Amount::from(PRICE_BASELINE_WEI + 4 * PRICE_COEFFICIENT_WEI);
149 assert_eq!(price, expected);
150 }
151
152 #[test]
153 fn test_triple_divisor_is_baseline_plus_nine_k() {
154 let price = calculate_price(18000);
156 let expected = Amount::from(PRICE_BASELINE_WEI + 9 * PRICE_COEFFICIENT_WEI);
157 assert_eq!(price, expected);
158 }
159
160 #[test]
161 fn test_smooth_pricing_no_staircase() {
162 let price_6k = calculate_price(6000);
164 let price_11k = calculate_price(11999);
165 assert!(
166 price_11k > price_6k,
167 "11999 records ({price_11k}) should cost more than 6000 ({price_6k})"
168 );
169 }
170
171 #[test]
172 fn test_price_increases_with_records() {
173 let price_low = calculate_price(6000);
174 let price_mid = calculate_price(12000);
175 let price_high = calculate_price(18000);
176 assert!(price_mid > price_low);
177 assert!(price_high > price_mid);
178 }
179
180 #[test]
181 fn test_price_increases_monotonically() {
182 let mut prev_price = Amount::ZERO;
183 for records in (0..60000).step_by(100) {
184 let price = calculate_price(records);
185 assert!(
186 price >= prev_price,
187 "Price at {records} records ({price}) should be >= previous ({prev_price})"
188 );
189 prev_price = price;
190 }
191 }
192
193 #[test]
194 fn test_large_value_no_overflow() {
195 let price = calculate_price(usize::MAX);
196 assert!(price > Amount::ZERO);
197 }
198
199 #[test]
200 fn test_price_deterministic() {
201 let price1 = calculate_price(12000);
202 let price2 = calculate_price(12000);
203 assert_eq!(price1, price2);
204 }
205
206 #[test]
207 fn test_quadratic_growth_excluding_baseline() {
208 let base = Amount::from(PRICE_BASELINE_WEI);
211 let quad_6k = calculate_price(6000) - base;
212 let quad_12k = calculate_price(12000) - base;
213 let quad_24k = calculate_price(24000) - base;
214 assert_eq!(quad_12k, quad_6k * Amount::from(4u64));
215 assert_eq!(quad_24k, quad_6k * Amount::from(16u64));
216 }
217
218 #[test]
219 fn test_small_record_counts_near_baseline() {
220 let price = calculate_price(100);
222 assert_eq!(price, expected_price(100));
223 assert!(price < Amount::from(WEI_PER_TOKEN)); assert!(price > Amount::from(PRICE_BASELINE_WEI)); }
226
227 #[test]
228 fn test_derive_records_stored_from_price_round_trips() {
229 for records in [0usize, 1, 5, 100, 6_000, 12_000, 60_000] {
230 let price = calculate_price(records);
231 assert_eq!(derive_records_stored_from_price(price), records as u64);
232 }
233 }
234
235 #[test]
236 fn test_derive_records_stored_from_baseline_or_lower_is_zero() {
237 assert_eq!(derive_records_stored_from_price(Amount::ZERO), 0);
238 assert_eq!(
239 derive_records_stored_from_price(Amount::from(PRICE_BASELINE_WEI)),
240 0
241 );
242 }
243
244 #[test]
245 fn test_derive_records_stored_from_max_price_saturates_no_panic() {
246 let v = derive_records_stored_from_price(Amount::MAX);
251 assert_eq!(v, u64::MAX);
252 }
253}