Skip to main content

ant_node/payment/
pricing.rs

1//! Quadratic pricing with a baseline floor for ant-node (Phase 1 recalibration).
2//!
3//! Formula: `price_per_chunk_ANT(n) = BASELINE + K × (n / D)²`
4//!
5//! This recalibration introduces a non-zero `BASELINE` so that empty nodes
6//! charge a meaningful spam-barrier price, and re-anchors `K` so per-GB USD
7//! pricing matches real-world targets at the current ~$0.10/ANT token price.
8//! The legacy formula produced ~$25/GB at the lower stable boundary and ~$0/GB
9//! when nodes were empty — both unreasonable.
10//!
11//! ## Parameters
12//!
13//! | Constant  | Value         | Role                                            |
14//! |-----------|---------------|-------------------------------------------------|
15//! | BASELINE  | 0.00390625 ANT| Price at empty (bootstrap-phase spam barrier)   |
16//! | K         | 0.03515625 ANT| Quadratic coefficient                           |
17//! | D         | 6000          | Lower stable boundary (records stored)          |
18//!
19//! ## Design Rationale
20//!
21//! - **Empty / lightly loaded nodes** charge the `BASELINE` floor, preventing
22//!   free storage and acting as a bootstrap-phase spam barrier.
23//! - **Moderately loaded nodes** add a small quadratic contribution on top.
24//! - **Heavily loaded nodes** charge quadratically more, pushing clients
25//!   toward less-loaded nodes elsewhere in the network.
26
27use evmlib::common::Amount;
28
29/// Lower stable boundary of the quadratic curve, in records stored.
30const PRICING_DIVISOR: u128 = 6000;
31
32/// `PRICING_DIVISOR²`, precomputed to avoid repeated multiplication.
33const DIVISOR_SQUARED: u128 = PRICING_DIVISOR * PRICING_DIVISOR;
34
35/// Baseline price at empty / bootstrap-phase spam barrier.
36///
37/// `0.00390625 ANT × 10¹⁸ wei/ANT = 3_906_250_000_000_000 wei`.
38const PRICE_BASELINE_WEI: u128 = 3_906_250_000_000_000;
39
40/// Quadratic coefficient `K`.
41///
42/// `0.03515625 ANT × 10¹⁸ wei/ANT = 35_156_250_000_000_000 wei`.
43const PRICE_COEFFICIENT_WEI: u128 = 35_156_250_000_000_000;
44
45/// Calculate storage price in wei from the number of close records stored.
46///
47/// Formula: `price_wei = BASELINE + n² × K / D²`
48///
49/// where `BASELINE = 0.00390625 ANT`, `K = 0.03515625 ANT`, and `D = 6000`.
50/// U256 arithmetic prevents overflow for large record counts.
51#[must_use]
52pub fn calculate_price(close_records_stored: usize) -> Amount {
53    let n = Amount::from(close_records_stored);
54    let n_squared = n.saturating_mul(n);
55    let quadratic_wei = n_squared.saturating_mul(Amount::from(PRICE_COEFFICIENT_WEI))
56        / Amount::from(DIVISOR_SQUARED);
57    Amount::from(PRICE_BASELINE_WEI).saturating_add(quadratic_wei)
58}
59
60#[cfg(test)]
61#[allow(clippy::unwrap_used, clippy::expect_used)]
62mod tests {
63    use super::*;
64
65    /// 1 token = 10¹⁸ wei (used for test sanity-checks).
66    const WEI_PER_TOKEN: u128 = 1_000_000_000_000_000_000;
67
68    /// Helper: expected price matching the formula `BASELINE + n² × K / D²`.
69    fn expected_price(n: u64) -> Amount {
70        let n_amt = Amount::from(n);
71        let quad =
72            n_amt * n_amt * Amount::from(PRICE_COEFFICIENT_WEI) / Amount::from(DIVISOR_SQUARED);
73        Amount::from(PRICE_BASELINE_WEI) + quad
74    }
75
76    #[test]
77    fn test_zero_records_gets_baseline() {
78        // At n = 0 the quadratic term vanishes, leaving the baseline floor.
79        let price = calculate_price(0);
80        assert_eq!(price, Amount::from(PRICE_BASELINE_WEI));
81    }
82
83    #[test]
84    fn test_baseline_is_nonzero_spam_barrier() {
85        // The baseline ensures even empty nodes charge a meaningful price,
86        // making the legacy MIN_PRICE_WEI = 1 sentinel redundant.
87        assert!(calculate_price(0) > Amount::ZERO);
88        assert!(calculate_price(1) > calculate_price(0));
89    }
90
91    #[test]
92    fn test_one_record_above_baseline() {
93        let price = calculate_price(1);
94        assert_eq!(price, expected_price(1));
95        assert!(price > Amount::from(PRICE_BASELINE_WEI));
96    }
97
98    #[test]
99    fn test_at_divisor_is_baseline_plus_k() {
100        // At n = D the quadratic contribution equals K × 1² = K.
101        // price = BASELINE + K = 0.00390625 + 0.03515625 = 0.0390625 ANT
102        let price = calculate_price(6000);
103        let expected = Amount::from(PRICE_BASELINE_WEI + PRICE_COEFFICIENT_WEI);
104        assert_eq!(price, expected);
105    }
106
107    #[test]
108    fn test_double_divisor_is_baseline_plus_four_k() {
109        // At n = 2D the quadratic contribution is 4K.
110        let price = calculate_price(12000);
111        let expected = Amount::from(PRICE_BASELINE_WEI + 4 * PRICE_COEFFICIENT_WEI);
112        assert_eq!(price, expected);
113    }
114
115    #[test]
116    fn test_triple_divisor_is_baseline_plus_nine_k() {
117        // At n = 3D the quadratic contribution is 9K.
118        let price = calculate_price(18000);
119        let expected = Amount::from(PRICE_BASELINE_WEI + 9 * PRICE_COEFFICIENT_WEI);
120        assert_eq!(price, expected);
121    }
122
123    #[test]
124    fn test_smooth_pricing_no_staircase() {
125        // 11999 should give a strictly higher price than 6000 (no integer-division plateau).
126        let price_6k = calculate_price(6000);
127        let price_11k = calculate_price(11999);
128        assert!(
129            price_11k > price_6k,
130            "11999 records ({price_11k}) should cost more than 6000 ({price_6k})"
131        );
132    }
133
134    #[test]
135    fn test_price_increases_with_records() {
136        let price_low = calculate_price(6000);
137        let price_mid = calculate_price(12000);
138        let price_high = calculate_price(18000);
139        assert!(price_mid > price_low);
140        assert!(price_high > price_mid);
141    }
142
143    #[test]
144    fn test_price_increases_monotonically() {
145        let mut prev_price = Amount::ZERO;
146        for records in (0..60000).step_by(100) {
147            let price = calculate_price(records);
148            assert!(
149                price >= prev_price,
150                "Price at {records} records ({price}) should be >= previous ({prev_price})"
151            );
152            prev_price = price;
153        }
154    }
155
156    #[test]
157    fn test_large_value_no_overflow() {
158        let price = calculate_price(usize::MAX);
159        assert!(price > Amount::ZERO);
160    }
161
162    #[test]
163    fn test_price_deterministic() {
164        let price1 = calculate_price(12000);
165        let price2 = calculate_price(12000);
166        assert_eq!(price1, price2);
167    }
168
169    #[test]
170    fn test_quadratic_growth_excluding_baseline() {
171        // Subtracting the baseline, quadratic contribution should scale with n².
172        // At 2× records the quadratic portion is 4×; at 4× records it is 16×.
173        let base = Amount::from(PRICE_BASELINE_WEI);
174        let quad_6k = calculate_price(6000) - base;
175        let quad_12k = calculate_price(12000) - base;
176        let quad_24k = calculate_price(24000) - base;
177        assert_eq!(quad_12k, quad_6k * Amount::from(4u64));
178        assert_eq!(quad_24k, quad_6k * Amount::from(16u64));
179    }
180
181    #[test]
182    fn test_small_record_counts_near_baseline() {
183        // At small n, price is dominated by the baseline — quadratic term is tiny.
184        let price = calculate_price(100);
185        assert_eq!(price, expected_price(100));
186        assert!(price < Amount::from(WEI_PER_TOKEN)); // well below 1 ANT
187        assert!(price > Amount::from(PRICE_BASELINE_WEI)); // strictly above baseline
188    }
189}