arb_stylus/
multi_gas.rs

1//! Per-host-call multi-gas dimension assignment for Stylus execution: each host
2//! op contributes the same per-dimension gas as the equivalent EVM opcode.
3//! Framework gas (host ink, the EVM-API surcharge, the per-call storage cache)
4//! is left undimensioned here and falls into the `WasmComputation` residual.
5
6use alloy_primitives::U256;
7use arb_primitives::multigas::{MultiGas, ResourceKind};
8
9const WARM: u64 = 100; // WarmStorageReadCostEIP2929
10const COLD_SLOAD: u64 = 2_100; // ColdSloadCostEIP2929
11const COLD_ACCOUNT: u64 = 2_600; // ColdAccountAccessCostEIP2929
12const SSTORE_SET: u64 = 20_000; // SstoreSetGasEIP2200
13const SSTORE_RESET: u64 = 5_000; // SstoreResetGasEIP2200
14const NEW_ACCOUNT: u64 = 25_000; // CallNewAccountGas
15const VALUE_TRANSFER: u64 = 9_000; // CallValueTransferGas
16const LOG_TOPIC_HISTORY: u64 = 256; // LogTopicHistoryGas (LogDataGas * 32)
17const LOG_DATA: u64 = 8; // LogDataGas
18
19/// Dimension split of a Stylus `storage_load`, matching SLOAD.
20pub fn state_load(is_cold: bool) -> MultiGas {
21    if is_cold {
22        MultiGas::from_pairs(&[
23            (ResourceKind::StorageAccessRead, COLD_SLOAD - WARM),
24            (ResourceKind::Computation, WARM),
25        ])
26    } else {
27        MultiGas::computation_gas(WARM)
28    }
29}
30
31/// Dimension split of a Stylus `storage_store`, matching SSTORE. The reentrancy
32/// sentry check is the caller's responsibility.
33pub fn state_store(is_cold: bool, original: U256, present: U256, new: U256) -> MultiGas {
34    use ResourceKind::*;
35    let mut pairs = Vec::with_capacity(2);
36    if is_cold {
37        pairs.push((StorageAccessRead, COLD_SLOAD));
38    }
39    if present == new {
40        pairs.push((Computation, WARM));
41    } else if original == present {
42        if original.is_zero() {
43            pairs.push((StorageGrowth, SSTORE_SET));
44        } else {
45            pairs.push((StorageAccessWrite, SSTORE_RESET - COLD_SLOAD));
46        }
47    } else {
48        pairs.push((Computation, WARM));
49    }
50    MultiGas::from_pairs(&pairs)
51}
52
53/// Dimension split of touching an account in Stylus, matching EIP-2929 account
54/// access. `ext_code_cost` is the extra read charged when the code is loaded.
55pub fn account_touch(is_cold: bool, ext_code_cost: u64) -> MultiGas {
56    let read = if is_cold {
57        ext_code_cost + (COLD_ACCOUNT - WARM)
58    } else {
59        ext_code_cost
60    };
61    MultiGas::from_pairs(&[
62        (ResourceKind::StorageAccessRead, read),
63        (ResourceKind::Computation, WARM),
64    ])
65}
66
67/// History-growth dimension of a Stylus `emit_log`.
68pub fn log(num_topics: u64, data_bytes: u64) -> MultiGas {
69    MultiGas::history_growth_gas(LOG_TOPIC_HISTORY * num_topics + LOG_DATA * data_bytes)
70}
71
72/// Dimension split of the caller's access cost for a Stylus sub-call, matching
73/// EIP-2929 call gas (the forwarded gas the callee consumes is attributed by
74/// the callee, not here).
75pub fn call_cost(is_cold: bool, transfers_value: bool, new_account: bool) -> MultiGas {
76    let computation = WARM + if transfers_value { VALUE_TRANSFER } else { 0 };
77    let read = if is_cold { COLD_ACCOUNT - WARM } else { 0 };
78    let growth = if transfers_value && new_account {
79        NEW_ACCOUNT
80    } else {
81        0
82    };
83    MultiGas::from_pairs(&[
84        (ResourceKind::Computation, computation),
85        (ResourceKind::StorageAccessRead, read),
86        (ResourceKind::StorageGrowth, growth),
87    ])
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    fn dims(mg: &MultiGas) -> (u64, u64, u64, u64, u64) {
95        (
96            mg.get(ResourceKind::Computation),
97            mg.get(ResourceKind::StorageAccessRead),
98            mg.get(ResourceKind::StorageAccessWrite),
99            mg.get(ResourceKind::StorageGrowth),
100            mg.get(ResourceKind::HistoryGrowth),
101        )
102    }
103
104    #[test]
105    fn load_cold_warm() {
106        assert_eq!(dims(&state_load(true)), (100, 2_000, 0, 0, 0));
107        assert_eq!(state_load(true).single_gas(), COLD_SLOAD);
108        assert_eq!(dims(&state_load(false)), (100, 0, 0, 0, 0));
109    }
110
111    #[test]
112    fn store_cold_create() {
113        let mg = state_store(true, U256::ZERO, U256::ZERO, U256::from(1));
114        assert_eq!(dims(&mg), (0, 2_100, 0, 20_000, 0));
115        assert_eq!(mg.single_gas(), COLD_SLOAD + SSTORE_SET);
116    }
117
118    #[test]
119    fn store_warm_write_and_noop_and_dirty() {
120        let write = state_store(false, U256::from(7), U256::from(7), U256::from(9));
121        assert_eq!(dims(&write), (0, 0, 2_900, 0, 0));
122        let noop = state_store(false, U256::from(5), U256::from(5), U256::from(5));
123        assert_eq!(dims(&noop), (100, 0, 0, 0, 0));
124        let dirty = state_store(false, U256::from(1), U256::from(2), U256::from(3));
125        assert_eq!(dims(&dirty), (100, 0, 0, 0, 0));
126    }
127
128    #[test]
129    fn account_cold_warm_and_code() {
130        assert_eq!(dims(&account_touch(true, 0)), (100, 2_500, 0, 0, 0));
131        assert_eq!(dims(&account_touch(false, 0)), (100, 0, 0, 0, 0));
132        assert_eq!(dims(&account_touch(true, 700)), (100, 3_200, 0, 0, 0));
133        assert_eq!(account_touch(true, 700).single_gas(), 700 + COLD_ACCOUNT);
134    }
135
136    #[test]
137    fn log_history_growth() {
138        let mg = log(2, 10);
139        assert_eq!(dims(&mg), (0, 0, 0, 0, 256 * 2 + 8 * 10));
140    }
141
142    #[test]
143    fn call_cost_variants() {
144        assert_eq!(dims(&call_cost(false, false, false)), (100, 0, 0, 0, 0));
145        assert_eq!(dims(&call_cost(true, false, false)), (100, 2_500, 0, 0, 0));
146        assert_eq!(dims(&call_cost(true, true, false)), (9_100, 2_500, 0, 0, 0));
147        assert_eq!(
148            dims(&call_cost(true, true, true)),
149            (9_100, 2_500, 0, 25_000, 0)
150        );
151        assert_eq!(
152            call_cost(true, true, true).single_gas(),
153            100 + 2_500 + 9_000 + 25_000
154        );
155    }
156}