Skip to main content

tempo_revm/
gas_params.rs

1use auto_impl::auto_impl;
2use revm::context_interface::cfg::{GasId, GasParams};
3use tempo_chainspec::hardfork::TempoHardfork;
4
5/// Extending [`GasParams`] for Tempo use case.
6#[auto_impl(&, Arc, Box, &mut)]
7pub trait TempoGasParams {
8    fn gas_params(&self) -> &GasParams;
9
10    fn tx_tip1000_auth_account_creation_cost(&self) -> u64 {
11        self.gas_params().get(GasId::new(255))
12    }
13
14    fn tx_tip1000_auth_account_creation_state_gas(&self) -> u64 {
15        self.gas_params().get(GasId::new(254))
16    }
17}
18
19impl TempoGasParams for GasParams {
20    fn gas_params(&self) -> &GasParams {
21        self
22    }
23}
24
25// TIP-1000 total gas costs (used by T1)
26const SSTORE_SET_COST: u64 = 250_000;
27const CREATE_COST: u64 = 500_000;
28const NEW_ACCOUNT_COST: u64 = 250_000;
29const CODE_DEPOSIT_COST_T1: u64 = 1_000;
30const AUTH_ACCOUNT_CREATION_COST: u64 = 250_000;
31const EIP7702_PER_EMPTY_ACCOUNT_COST_T1: u64 = 12_500;
32
33// TIP-1016 regular gas (computational overhead) — matches pre-TIP-1000 EVM costs.
34// These values are "at least the pre-TIP-1000 (standard EVM) cost" per spec invariant 15.
35//
36// For SSTORE: revm decomposes the cost as sstore_static(WARM_STORAGE_READ=100) +
37// sstore_set_without_load_cost(20,000), with the cold-slot surcharge applied separately.
38const T4_SSTORE_SET_REGULAR: u64 = 20_000;
39const T4_NEW_ACCOUNT_REGULAR: u64 = 25_000;
40const T4_CREATE_REGULAR: u64 = 32_000;
41const T4_CODE_DEPOSIT_REGULAR: u64 = 200;
42const T4_EIP7702_PER_AUTH_TOTAL: u64 = 250_000; // 25k regular + 225k state
43
44// TIP-1016 state gas (permanent storage burden)
45const T4_SSTORE_SET_STATE: u64 = SSTORE_SET_COST - T4_SSTORE_SET_REGULAR; // 230,000
46const T4_NEW_ACCOUNT_STATE: u64 = NEW_ACCOUNT_COST - T4_NEW_ACCOUNT_REGULAR; // 225,000
47const T4_CREATE_STATE: u64 = CREATE_COST - T4_CREATE_REGULAR; // 468,000
48const T4_CODE_DEPOSIT_STATE: u64 = 2_300;
49
50// TIP-1016 SSTORE set refund for 0→X→0 restoration (combined state + regular).
51// Spec: state_gas(230,000) + regular(GAS_STORAGE_UPDATE - GAS_COLD_SLOAD - GAS_WARM_ACCESS)
52//      = 230,000 + (20,000 - 2,100 - 100) = 247,800
53const T4_SSTORE_SET_REFUND: u64 = T4_SSTORE_SET_STATE + 17_800; // 230,000 + 17,800 = 247,800
54
55/// Tempo gas params override.
56///
57/// `amsterdam_eip8037_enabled` mirrors `CfgEnv::enable_amsterdam_eip8037` and gates the
58/// TIP-1016 regular/state gas split. When `false`, TIP-1000 (T1) costs are used regardless
59/// of the spec, so TIP-1016 can be deferred independently of the T4 hardfork activation.
60#[inline]
61pub fn tempo_gas_params_with_amsterdam(
62    spec: TempoHardfork,
63    amsterdam_eip8037_enabled: bool,
64) -> GasParams {
65    let mut gas_params = GasParams::new_spec(spec.into());
66    let mut overrides = vec![];
67    if amsterdam_eip8037_enabled {
68        // TIP-1016: Split storage creation costs into regular gas + state gas.
69        // Regular gas (computational overhead) = at least pre-TIP-1000 EVM cost.
70        // State gas (permanent storage burden) = total - regular.
71        overrides.extend([
72            // SSTORE (zero → non-zero): 20k regular + 230k state
73            (GasId::sstore_set_without_load_cost(), T4_SSTORE_SET_REGULAR),
74            (GasId::sstore_set_state_gas(), T4_SSTORE_SET_STATE),
75            (GasId::sstore_set_refund(), T4_SSTORE_SET_REFUND),
76            // Contract metadata (CREATE base): 32k regular + 468k state
77            (GasId::tx_create_cost(), T4_CREATE_REGULAR),
78            (GasId::create(), T4_CREATE_REGULAR),
79            (GasId::create_state_gas(), T4_CREATE_STATE),
80            // Account creation: 25k regular + 225k state
81            (GasId::new_account_cost(), T4_NEW_ACCOUNT_REGULAR),
82            (GasId::new_account_state_gas(), T4_NEW_ACCOUNT_STATE),
83            (
84                GasId::new_account_cost_for_selfdestruct(),
85                T4_NEW_ACCOUNT_REGULAR,
86            ),
87            // Code deposit: 200 regular + 2,300 state per byte
88            (GasId::code_deposit_cost(), T4_CODE_DEPOSIT_REGULAR),
89            (GasId::code_deposit_state_gas(), T4_CODE_DEPOSIT_STATE),
90            // EIP-7702 delegation: 25k regular + 225k state = 250k per auth
91            (
92                GasId::tx_eip7702_per_empty_account_cost(),
93                T4_EIP7702_PER_AUTH_TOTAL,
94            ),
95            (
96                GasId::tx_eip7702_per_auth_state_gas(),
97                T4_NEW_ACCOUNT_STATE, // 225,000
98            ),
99            // Auth refund is zeroed by apply_eip7702_auth_list override (TIP-1000:
100            // "no refund if the account already exists"), but set the value for
101            // upstream split_eip7702_refund correctness if the override is bypassed.
102            (GasId::tx_eip7702_auth_refund(), 0),
103            // Auth account creation (keychain): same split as account creation
104            (GasId::new(255), T4_NEW_ACCOUNT_REGULAR),
105            (GasId::new(254), T4_NEW_ACCOUNT_STATE),
106        ]);
107    } else if spec.is_t1() {
108        // TIP-1000: All storage creation costs in regular gas (no state gas split).
109        overrides.extend([
110            (GasId::sstore_set_without_load_cost(), SSTORE_SET_COST),
111            (GasId::tx_create_cost(), CREATE_COST),
112            (GasId::create(), CREATE_COST),
113            (GasId::new_account_cost(), NEW_ACCOUNT_COST),
114            (GasId::new_account_cost_for_selfdestruct(), NEW_ACCOUNT_COST),
115            (GasId::code_deposit_cost(), CODE_DEPOSIT_COST_T1),
116            (
117                GasId::tx_eip7702_per_empty_account_cost(),
118                EIP7702_PER_EMPTY_ACCOUNT_COST_T1,
119            ),
120            (GasId::new(255), AUTH_ACCOUNT_CREATION_COST),
121        ]);
122    }
123
124    gas_params.override_gas(overrides);
125    gas_params
126}
127
128/// Backward-compatible alias for [`tempo_gas_params_with_amsterdam`] with TIP-1016 disabled.
129///
130/// External consumers (e.g. foundry) that depend on the single-argument signature continue
131/// to work: TIP-1016 is opt-in via `tempo_gas_params_with_amsterdam(spec, true)`.
132#[inline]
133pub fn tempo_gas_params(spec: TempoHardfork) -> GasParams {
134    tempo_gas_params_with_amsterdam(spec, false)
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_t1_gas_params_no_state_gas_split() {
143        let gas_params = tempo_gas_params_with_amsterdam(TempoHardfork::T1, false);
144
145        // T1 has full 250k costs in regular gas, no state gas split
146        assert_eq!(
147            gas_params.get(GasId::sstore_set_without_load_cost()),
148            250_000
149        );
150        assert_eq!(gas_params.get(GasId::new_account_cost()), 250_000);
151        assert_eq!(gas_params.get(GasId::tx_create_cost()), 500_000);
152        assert_eq!(gas_params.get(GasId::create()), 500_000);
153        assert_eq!(gas_params.get(GasId::code_deposit_cost()), 1_000);
154
155        // State gas params should remain at upstream defaults (not Tempo-bumped)
156        let upstream = GasParams::new_spec(TempoHardfork::T1.into());
157        assert_eq!(
158            gas_params.get(GasId::sstore_set_state_gas()),
159            upstream.get(GasId::sstore_set_state_gas()),
160            "T1 should not override state gas params"
161        );
162        assert_eq!(
163            gas_params.get(GasId::new_account_state_gas()),
164            upstream.get(GasId::new_account_state_gas()),
165        );
166        assert_eq!(
167            gas_params.get(GasId::create_state_gas()),
168            upstream.get(GasId::create_state_gas()),
169        );
170    }
171
172    /// TIP-1016 spec table: regular/state gas splits must match the spec exactly.
173    ///
174    /// | Operation                      | Execution Gas | Storage Gas | Total   |
175    /// |--------------------------------|---------------|-------------|---------|
176    /// | Cold SSTORE (zero → non-zero)  | 22,200        | 230,000     | 252,200 |
177    /// | Account creation (nonce 0 → 1) | 25,000        | 225,000     | 250,000 |
178    /// | Contract metadata (CREATE)     | 32,000        | 468,000     | 500,000 |
179    /// | Contract code storage (/byte)  | 200           | 2,300       | 2,500   |
180    /// | EIP-7702 delegation (per auth) | 25,000        | 225,000     | 250,000 |
181    ///
182    /// Note: The cold SSTORE total keeps Berlin's access charging. In revm terms the
183    /// zero->non-zero write path is: warm read (100) + `sstore_set_without_load_cost` (20,000)
184    /// + cold slot surcharge (2,100) + state gas (230,000) = 252,200.
185    #[test]
186    fn test_t4_gas_params_splits_storage_costs() {
187        let gas_params = tempo_gas_params_with_amsterdam(TempoHardfork::T4, true);
188
189        // T4 execution gas (regular/computational overhead)
190        // SSTORE keeps revm's decomposed accounting: static(100) + sstore_set_without_load(20,000),
191        // with cold slot access (2,100) retained separately through `cold_storage_cost`.
192        assert_eq!(
193            gas_params.get(GasId::sstore_set_without_load_cost()),
194            20_000,
195            "SSTORE set_without_load matches the retained zero->non-zero write component"
196        );
197        assert_eq!(
198            gas_params.get(GasId::new_account_cost()),
199            25_000,
200            "Account creation regular gas per spec"
201        );
202        assert_eq!(
203            gas_params.get(GasId::new_account_cost_for_selfdestruct()),
204            25_000
205        );
206        assert_eq!(
207            gas_params.get(GasId::tx_create_cost()),
208            32_000,
209            "CREATE base regular gas per spec"
210        );
211        assert_eq!(
212            gas_params.get(GasId::create()),
213            32_000,
214            "CREATE base regular gas per spec"
215        );
216        assert_eq!(gas_params.get(GasId::code_deposit_cost()), 200);
217
218        // T4 state gas (permanent storage burden)
219        assert_eq!(
220            gas_params.get(GasId::sstore_set_state_gas()),
221            230_000,
222            "SSTORE state gas per spec"
223        );
224        assert_eq!(
225            gas_params.get(GasId::new_account_state_gas()),
226            225_000,
227            "Account creation state gas per spec"
228        );
229        assert_eq!(
230            gas_params.get(GasId::create_state_gas()),
231            468_000,
232            "CREATE base state gas per spec"
233        );
234        assert_eq!(gas_params.get(GasId::code_deposit_state_gas()), 2_300);
235
236        // Auth account creation: same split as account creation
237        assert_eq!(
238            gas_params.get(GasId::new(255)),
239            25_000,
240            "Auth account creation regular gas per spec"
241        );
242        assert_eq!(
243            gas_params.get(GasId::new(254)),
244            225_000,
245            "Auth account creation state gas per spec"
246        );
247
248        // EIP-7702 delegation: 25,000 regular + 225,000 state per auth
249        assert_eq!(
250            gas_params.get(GasId::tx_eip7702_per_empty_account_cost()),
251            250_000,
252            "EIP-7702 per auth total = 25k regular + 225k state per spec"
253        );
254        assert_eq!(
255            gas_params.tx_eip7702_per_auth_state_gas(),
256            225_000,
257            "EIP-7702 per auth state gas per spec"
258        );
259        assert_eq!(
260            gas_params.tx_eip7702_per_empty_account_cost()
261                - gas_params.tx_eip7702_per_auth_state_gas(),
262            25_000,
263            "EIP-7702 per auth regular gas = total - state = 25k"
264        );
265        assert_eq!(
266            gas_params.tx_eip7702_auth_refund(),
267            0,
268            "TIP-1000: no refund for existing accounts on T1+"
269        );
270
271        // SSTORE set refund for 0→X→0 restoration (combined state + regular)
272        // Spec: state_gas(230,000) + regular(20,000 - 2,100 - 100 = 17,800) = 247,800
273        assert_eq!(
274            gas_params.get(GasId::sstore_set_refund()),
275            247_800,
276            "SSTORE set refund = state(230k) + regular(17.8k) per spec"
277        );
278    }
279
280    /// TIP-1016: Verify totals (regular + state) match the clarified spec table.
281    /// Note: SSTORE total comparison needs to account for revm decomposition and the cold-slot charge.
282    ///
283    /// T1 sstore_set_without_load_cost = 250,000 (full TIP-1000 cost as override).
284    /// T4 warm SSTORE = sstore_set_without_load_cost(20,000) + warm_read(100) + state(230,000) = 250,100.
285    /// T4 cold SSTORE = warm path + cold_slot_access(2,100) = 252,200.
286    #[test]
287    fn test_t4_totals_match_spec() {
288        let t4 = tempo_gas_params_with_amsterdam(TempoHardfork::T4, true);
289
290        // Warm SSTORE total: write component(20,000) + warm read(100) + state(230,000)
291        let warm_sstore_regular =
292            t4.get(GasId::sstore_set_without_load_cost()) + t4.warm_storage_read_cost();
293        assert_eq!(
294            warm_sstore_regular + t4.get(GasId::sstore_set_state_gas()),
295            250_100,
296            "warm SSTORE total must be 250,100"
297        );
298
299        // Cold SSTORE total: warm path + Berlin cold slot access(2,100)
300        let cold_sstore_regular = warm_sstore_regular + t4.cold_storage_cost();
301        assert_eq!(
302            cold_sstore_regular + t4.get(GasId::sstore_set_state_gas()),
303            252_200,
304            "cold SSTORE total must include Berlin cold slot access charging"
305        );
306
307        // New account: 25,000 + 225,000 = 250,000
308        assert_eq!(
309            t4.get(GasId::new_account_cost()) + t4.get(GasId::new_account_state_gas()),
310            250_000,
311            "new_account total must be 250,000"
312        );
313
314        // CREATE: 32,000 + 468,000 = 500,000
315        assert_eq!(
316            t4.get(GasId::create()) + t4.get(GasId::create_state_gas()),
317            500_000,
318            "CREATE total must be 500,000"
319        );
320
321        // Code deposit: 200 + 2,300 = 2,500/byte
322        assert_eq!(
323            t4.get(GasId::code_deposit_cost()) + t4.get(GasId::code_deposit_state_gas()),
324            2_500,
325            "code_deposit total must be 2,500/byte"
326        );
327
328        // Auth account creation: 25,000 + 225,000 = 250,000
329        assert_eq!(
330            t4.get(GasId::new(255)) + t4.get(GasId::new(254)),
331            250_000,
332            "auth_account_creation total must be 250,000"
333        );
334
335        // EIP-7702: 25,000 regular + 225,000 state = 250,000 per auth
336        assert_eq!(
337            (t4.tx_eip7702_per_empty_account_cost() - t4.tx_eip7702_per_auth_state_gas())
338                + t4.tx_eip7702_per_auth_state_gas(),
339            250_000,
340            "EIP-7702 per auth total must be 250,000"
341        );
342    }
343}