1use ant_evm::{Amount, QuotingMetrics};
24
25const MIN_PRICE: u64 = 3;
27
28const SCALING_FACTOR: f64 = 1.0;
31
32const ANT_PRICE: f64 = 1.0;
34
35#[allow(
47 clippy::cast_precision_loss,
48 clippy::cast_possible_truncation,
49 clippy::cast_sign_loss
50)]
51#[must_use]
52pub fn calculate_price(metrics: &QuotingMetrics) -> Amount {
53 let min_price = Amount::from(MIN_PRICE);
54
55 if metrics.max_records == 0 {
57 return min_price;
58 }
59
60 let total_records = metrics.close_records_stored as u64;
62
63 let max_records = metrics.max_records as f64;
64
65 let r_lower = total_records as f64 / max_records;
67 let r_upper = (total_records + 1) as f64 / max_records;
69
70 if r_lower >= 1.0 || r_upper >= 1.0 {
72 return Amount::from(u64::MAX);
73 }
74 if (r_upper - r_lower).abs() < f64::EPSILON {
75 return min_price;
76 }
77
78 let upper_diff = (r_upper - 1.0).abs();
80 let lower_diff = (r_lower - 1.0).abs();
81
82 if upper_diff < f64::EPSILON || lower_diff < f64::EPSILON {
84 return min_price;
85 }
86
87 let log_upper = upper_diff.ln();
88 let log_lower = lower_diff.ln();
89 let log_diff = log_upper - log_lower;
90
91 let linear_part = r_upper - r_lower;
92
93 let part_one = (-SCALING_FACTOR / ANT_PRICE) * log_diff;
95 let part_two = MIN_PRICE as f64 * linear_part;
96 let part_three = linear_part / ANT_PRICE;
97
98 let price = part_one + part_two - part_three;
99
100 if price <= 0.0 || !price.is_finite() {
101 return min_price;
102 }
103
104 let data_size_factor = metrics.data_size.max(1) as f64;
106 let scaled_price = price * data_size_factor;
107
108 if !scaled_price.is_finite() {
109 return min_price;
110 }
111
112 let price_u64 = if scaled_price > u64::MAX as f64 {
114 u64::MAX
115 } else {
116 (scaled_price as u64).max(MIN_PRICE)
117 };
118
119 Amount::from(price_u64)
120}
121
122#[cfg(test)]
123#[allow(clippy::unwrap_used, clippy::expect_used)]
124mod tests {
125 use super::*;
126
127 fn make_metrics(
128 records_stored: usize,
129 max_records: usize,
130 data_size: usize,
131 data_type: u32,
132 ) -> QuotingMetrics {
133 let records_per_type = if records_stored > 0 {
134 vec![(data_type, u32::try_from(records_stored).unwrap_or(u32::MAX))]
135 } else {
136 vec![]
137 };
138 QuotingMetrics {
139 data_type,
140 data_size,
141 close_records_stored: records_stored,
142 records_per_type,
143 max_records,
144 received_payment_count: 0,
145 live_time: 0,
146 network_density: None,
147 network_size: Some(500),
148 }
149 }
150
151 #[test]
152 fn test_empty_node_gets_min_price() {
153 let metrics = make_metrics(0, 1000, 1, 0);
154 let price = calculate_price(&metrics);
155 assert_eq!(price, Amount::from(MIN_PRICE));
157 }
158
159 #[test]
160 fn test_half_full_node_costs_more() {
161 let empty = make_metrics(0, 1000, 1024, 0);
162 let half = make_metrics(500, 1000, 1024, 0);
163 let price_empty = calculate_price(&empty);
164 let price_half = calculate_price(&half);
165 assert!(
166 price_half > price_empty,
167 "Half-full price ({price_half}) should exceed empty price ({price_empty})"
168 );
169 }
170
171 #[test]
172 fn test_nearly_full_node_costs_much_more() {
173 let half = make_metrics(500, 1000, 1024, 0);
174 let nearly_full = make_metrics(900, 1000, 1024, 0);
175 let price_half = calculate_price(&half);
176 let price_nearly_full = calculate_price(&nearly_full);
177 assert!(
178 price_nearly_full > price_half,
179 "Nearly-full price ({price_nearly_full}) should far exceed half-full price ({price_half})"
180 );
181 }
182
183 #[test]
184 fn test_full_node_returns_max_price() {
185 let metrics = make_metrics(1000, 1000, 1024, 0);
187 let price = calculate_price(&metrics);
188 assert_eq!(price, Amount::from(u64::MAX));
189 }
190
191 #[test]
192 fn test_price_increases_monotonically() {
193 let max_records = 1000;
194 let data_size = 1024;
195 let mut prev_price = Amount::ZERO;
196
197 for pct in 0..100 {
199 let records = pct * max_records / 100;
200 let metrics = make_metrics(records, max_records, data_size, 0);
201 let price = calculate_price(&metrics);
202 assert!(
203 price >= prev_price,
204 "Price at {pct}% ({price}) should be >= price at previous step ({prev_price})"
205 );
206 prev_price = price;
207 }
208 }
209
210 #[test]
211 fn test_zero_max_records_returns_min_price() {
212 let metrics = make_metrics(0, 0, 1024, 0);
213 let price = calculate_price(&metrics);
214 assert_eq!(price, Amount::from(MIN_PRICE));
215 }
216
217 #[test]
218 fn test_different_data_sizes_same_fullness() {
219 let small = make_metrics(500, 1000, 100, 0);
220 let large = make_metrics(500, 1000, 10000, 0);
221 let price_small = calculate_price(&small);
222 let price_large = calculate_price(&large);
223 assert!(
224 price_large > price_small,
225 "Larger data ({price_large}) should cost more than smaller data ({price_small})"
226 );
227 }
228
229 #[test]
230 fn test_price_with_multiple_record_types() {
231 let metrics = QuotingMetrics {
233 data_type: 0,
234 data_size: 1024,
235 close_records_stored: 500,
236 records_per_type: vec![(0, 300), (1, 200)],
237 max_records: 1000,
238 received_payment_count: 0,
239 live_time: 0,
240 network_density: None,
241 network_size: Some(500),
242 };
243 let price_multi = calculate_price(&metrics);
244
245 let metrics_single = make_metrics(500, 1000, 1024, 0);
247 let price_single = calculate_price(&metrics_single);
248
249 assert_eq!(price_multi, price_single);
251 }
252
253 #[test]
254 fn test_price_at_95_percent() {
255 let metrics = make_metrics(950, 1000, 1024, 0);
256 let price = calculate_price(&metrics);
257 let min = Amount::from(MIN_PRICE);
258 assert!(
259 price > min,
260 "Price at 95% should be above minimum, got {price}"
261 );
262 }
263
264 #[test]
265 fn test_price_at_99_percent() {
266 let metrics = make_metrics(990, 1000, 1024, 0);
267 let price = calculate_price(&metrics);
268 let price_95 = calculate_price(&make_metrics(950, 1000, 1024, 0));
269 assert!(
270 price > price_95,
271 "Price at 99% ({price}) should exceed price at 95% ({price_95})"
272 );
273 }
274
275 #[test]
276 fn test_over_capacity_returns_max_price() {
277 let metrics = make_metrics(1100, 1000, 1024, 0);
279 let price = calculate_price(&metrics);
280 assert_eq!(
281 price,
282 Amount::from(u64::MAX),
283 "Over-capacity should return max price"
284 );
285 }
286
287 #[test]
288 fn test_price_deterministic() {
289 let metrics = make_metrics(500, 1000, 1024, 0);
290 let price1 = calculate_price(&metrics);
291 let price2 = calculate_price(&metrics);
292 let price3 = calculate_price(&metrics);
293 assert_eq!(price1, price2);
294 assert_eq!(price2, price3);
295 }
296}