arbos/programs/
data_pricer.rs

1use alloy_primitives::U256;
2use revm::Database;
3
4use arb_storage::{Storage, StorageBackedUint32, StorageBackedUint64};
5
6const DEMAND_OFFSET: u64 = 0;
7const BYTES_PER_SECOND_OFFSET: u64 = 1;
8const LAST_UPDATE_TIME_OFFSET: u64 = 2;
9const MIN_PRICE_OFFSET: u64 = 3;
10const INERTIA_OFFSET: u64 = 4;
11
12/// The day it all began (Arbitrum genesis timestamp).
13pub const ARBITRUM_START_TIME: u64 = 1421388000;
14
15const INITIAL_DEMAND: u32 = 0;
16/// 1TB total footprint per year, refilled each second.
17pub const INITIAL_HOURLY_BYTES: u64 = (1u64 << 40) / (365 * 24);
18const INITIAL_BYTES_PER_SECOND: u32 = (INITIAL_HOURLY_BYTES / (60 * 60)) as u32;
19const INITIAL_LAST_UPDATE_TIME: u64 = ARBITRUM_START_TIME;
20const INITIAL_MIN_PRICE: u32 = 82928201; // 5Mb = $1
21const INITIAL_INERTIA: u32 = 21360419; // expensive at 1Tb
22
23/// One in basis points (10000).
24const ONE_IN_BIPS: u64 = 10000;
25
26/// Stylus data pricing model using exponential demand curve.
27pub struct DataPricer<D> {
28    pub demand: StorageBackedUint32<D>,
29    pub bytes_per_second: StorageBackedUint32<D>,
30    pub last_update_time: StorageBackedUint64<D>,
31    pub min_price: StorageBackedUint32<D>,
32    pub inertia: StorageBackedUint32<D>,
33}
34
35pub fn init_data_pricer<D: Database>(sto: &Storage<D>) {
36    let state = sto.state_ptr();
37    let base_key = sto.base_key();
38    let _ = StorageBackedUint32::new(state, base_key, DEMAND_OFFSET).set(INITIAL_DEMAND);
39    let _ = StorageBackedUint32::new(state, base_key, BYTES_PER_SECOND_OFFSET)
40        .set(INITIAL_BYTES_PER_SECOND);
41    let _ = StorageBackedUint64::new(state, base_key, LAST_UPDATE_TIME_OFFSET)
42        .set(INITIAL_LAST_UPDATE_TIME);
43    let _ = StorageBackedUint32::new(state, base_key, MIN_PRICE_OFFSET).set(INITIAL_MIN_PRICE);
44    let _ = StorageBackedUint32::new(state, base_key, INERTIA_OFFSET).set(INITIAL_INERTIA);
45}
46
47pub fn open_data_pricer<D: Database>(sto: &Storage<D>) -> DataPricer<D> {
48    let state = sto.state_ptr();
49    let base_key = sto.base_key();
50    DataPricer {
51        demand: StorageBackedUint32::new(state, base_key, DEMAND_OFFSET),
52        bytes_per_second: StorageBackedUint32::new(state, base_key, BYTES_PER_SECOND_OFFSET),
53        last_update_time: StorageBackedUint64::new(state, base_key, LAST_UPDATE_TIME_OFFSET),
54        min_price: StorageBackedUint32::new(state, base_key, MIN_PRICE_OFFSET),
55        inertia: StorageBackedUint32::new(state, base_key, INERTIA_OFFSET),
56    }
57}
58
59impl<D: Database> DataPricer<D> {
60    /// Update the pricing model with new data usage and return cost in wei.
61    pub fn update_model(&self, temp_bytes: u32, time: u64) -> Result<U256, ()> {
62        let demand = self.demand.get().unwrap_or(0);
63        let bytes_per_second = self.bytes_per_second.get().unwrap_or(0);
64        let last_update_time = self.last_update_time.get().unwrap_or(0);
65        let min_price = self.min_price.get().unwrap_or(0);
66        let inertia = self.inertia.get()?;
67
68        if inertia == 0 {
69            return Ok(U256::ZERO);
70        }
71
72        let passed = (time.saturating_sub(last_update_time)) as u32;
73        let credit = bytes_per_second.saturating_mul(passed);
74        let demand = demand.saturating_sub(credit).saturating_add(temp_bytes);
75
76        self.demand.set(demand)?;
77        self.last_update_time.set(time)?;
78
79        let exponent = ONE_IN_BIPS * (demand as u64) / (inertia as u64);
80        let multiplier = approx_exp_basis_points(exponent, 12);
81        let cost_per_byte = saturating_mul_by_bips(min_price as u64, multiplier);
82        let cost_in_wei = cost_per_byte.saturating_mul(temp_bytes as u64);
83        Ok(U256::from(cost_in_wei))
84    }
85}
86
87/// Approximate e^(x/10000) * 10000 using a Taylor series with `terms` terms.
88fn approx_exp_basis_points(x: u64, terms: u32) -> u64 {
89    if x == 0 {
90        return ONE_IN_BIPS;
91    }
92
93    let mut result = ONE_IN_BIPS;
94    let mut term = ONE_IN_BIPS;
95
96    for k in 1..=terms {
97        term = term * x / (ONE_IN_BIPS * k as u64);
98        result = result.saturating_add(term);
99        if term == 0 {
100            break;
101        }
102    }
103    result
104}
105
106/// Multiply a u64 by a bips value, saturating on overflow.
107fn saturating_mul_by_bips(value: u64, bips: u64) -> u64 {
108    (value as u128 * bips as u128 / ONE_IN_BIPS as u128).min(u64::MAX as u128) as u64
109}