ant_protocol/payment/
pricing.rs1use evmlib::common::Amount;
26
27const PRICING_DIVISOR: u128 = 6000;
29
30const DIVISOR_SQUARED: u128 = PRICING_DIVISOR * PRICING_DIVISOR;
32
33const PRICE_BASELINE_WEI: u128 = 3_906_250_000_000_000;
37
38const PRICE_COEFFICIENT_WEI: u128 = 35_156_250_000_000_000;
42
43const PRICE_PER_RECORD_SQUARED_WEI: u128 = PRICE_COEFFICIENT_WEI / DIVISOR_SQUARED;
45
46#[must_use]
57pub fn derive_records_stored_from_price(price: Amount) -> u64 {
58 let baseline = Amount::from(PRICE_BASELINE_WEI);
59 if price <= baseline {
60 return 0;
61 }
62
63 let excess = price - baseline;
64 let n_squared = excess / Amount::from(PRICE_PER_RECORD_SQUARED_WEI);
65 let root = n_squared.root(2);
66 if root > Amount::from(u64::MAX) {
70 u64::MAX
71 } else {
72 root.to::<u64>()
73 }
74}
75
76#[must_use]
83pub fn calculate_price(close_records_stored: usize) -> Amount {
84 let n = Amount::from(close_records_stored);
85 let n_squared = n.saturating_mul(n);
86 let quadratic_wei = n_squared.saturating_mul(Amount::from(PRICE_COEFFICIENT_WEI))
87 / Amount::from(DIVISOR_SQUARED);
88 Amount::from(PRICE_BASELINE_WEI).saturating_add(quadratic_wei)
89}
90
91#[cfg(test)]
92#[allow(clippy::unwrap_used, clippy::expect_used)]
93mod tests {
94 use super::*;
95
96 const WEI_PER_TOKEN: u128 = 1_000_000_000_000_000_000;
98
99 fn expected_price(n: u64) -> Amount {
101 let n_amt = Amount::from(n);
102 let quad =
103 n_amt * n_amt * Amount::from(PRICE_COEFFICIENT_WEI) / Amount::from(DIVISOR_SQUARED);
104 Amount::from(PRICE_BASELINE_WEI) + quad
105 }
106
107 #[test]
108 fn test_zero_records_gets_baseline() {
109 let price = calculate_price(0);
110 assert_eq!(price, Amount::from(PRICE_BASELINE_WEI));
111 }
112
113 #[test]
114 fn test_baseline_is_nonzero_spam_barrier() {
115 assert!(calculate_price(0) > Amount::ZERO);
116 assert!(calculate_price(1) > calculate_price(0));
117 }
118
119 #[test]
120 fn test_one_record_above_baseline() {
121 let price = calculate_price(1);
122 assert_eq!(price, expected_price(1));
123 assert!(price > Amount::from(PRICE_BASELINE_WEI));
124 }
125
126 #[test]
127 fn test_at_divisor_is_baseline_plus_k() {
128 let price = calculate_price(6000);
129 let expected = Amount::from(PRICE_BASELINE_WEI + PRICE_COEFFICIENT_WEI);
130 assert_eq!(price, expected);
131 }
132
133 #[test]
134 fn test_double_divisor_is_baseline_plus_four_k() {
135 let price = calculate_price(12000);
136 let expected = Amount::from(PRICE_BASELINE_WEI + 4 * PRICE_COEFFICIENT_WEI);
137 assert_eq!(price, expected);
138 }
139
140 #[test]
141 fn test_triple_divisor_is_baseline_plus_nine_k() {
142 let price = calculate_price(18000);
143 let expected = Amount::from(PRICE_BASELINE_WEI + 9 * PRICE_COEFFICIENT_WEI);
144 assert_eq!(price, expected);
145 }
146
147 #[test]
148 fn test_smooth_pricing_no_staircase() {
149 let price_6k = calculate_price(6000);
150 let price_11k = calculate_price(11999);
151 assert!(
152 price_11k > price_6k,
153 "11999 records ({price_11k}) should cost more than 6000 ({price_6k})"
154 );
155 }
156
157 #[test]
158 fn test_price_increases_with_records() {
159 let price_low = calculate_price(6000);
160 let price_mid = calculate_price(12000);
161 let price_high = calculate_price(18000);
162 assert!(price_mid > price_low);
163 assert!(price_high > price_mid);
164 }
165
166 #[test]
167 fn test_price_increases_monotonically() {
168 let mut prev_price = Amount::ZERO;
169 for records in (0..60000).step_by(100) {
170 let price = calculate_price(records);
171 assert!(
172 price >= prev_price,
173 "Price at {records} records ({price}) should be >= previous ({prev_price})"
174 );
175 prev_price = price;
176 }
177 }
178
179 #[test]
180 fn test_large_value_no_overflow() {
181 let price = calculate_price(usize::MAX);
182 assert!(price > Amount::ZERO);
183 }
184
185 #[test]
186 fn test_price_deterministic() {
187 let price1 = calculate_price(12000);
188 let price2 = calculate_price(12000);
189 assert_eq!(price1, price2);
190 }
191
192 #[test]
193 fn test_quadratic_growth_excluding_baseline() {
194 let base = Amount::from(PRICE_BASELINE_WEI);
195 let quad_6k = calculate_price(6000) - base;
196 let quad_12k = calculate_price(12000) - base;
197 let quad_24k = calculate_price(24000) - base;
198 assert_eq!(quad_12k, quad_6k * Amount::from(4u64));
199 assert_eq!(quad_24k, quad_6k * Amount::from(16u64));
200 }
201
202 #[test]
203 fn test_small_record_counts_near_baseline() {
204 let price = calculate_price(100);
205 assert_eq!(price, expected_price(100));
206 assert!(price < Amount::from(WEI_PER_TOKEN));
207 assert!(price > Amount::from(PRICE_BASELINE_WEI));
208 }
209
210 #[test]
211 fn test_derive_records_stored_from_price_round_trips() {
212 for records in [0usize, 1, 5, 100, 6_000, 12_000, 60_000] {
213 let price = calculate_price(records);
214 assert_eq!(derive_records_stored_from_price(price), records as u64);
215 }
216 }
217
218 #[test]
219 fn test_derive_records_stored_from_baseline_or_lower_is_zero() {
220 assert_eq!(derive_records_stored_from_price(Amount::ZERO), 0);
221 assert_eq!(
222 derive_records_stored_from_price(Amount::from(PRICE_BASELINE_WEI)),
223 0
224 );
225 }
226
227 #[test]
228 fn test_derive_records_stored_from_max_price_saturates_no_panic() {
229 let v = derive_records_stored_from_price(Amount::MAX);
230 assert_eq!(v, u64::MAX);
231 }
232}