Skip to main content

tempo_precompiles/storage/
evm.rs

1use crate::{
2    error::TempoPrecompileError, storage::PrecompileStorageProvider,
3    tip1060_storage_credits::sstore_storage_credits,
4};
5use alloy::primitives::{Address, Log, LogData, U256};
6use alloy_evm::EvmInternals;
7use revm::{
8    context::{Block, CfgEnv, journaled_state::JournalCheckpoint},
9    context_interface::cfg::{GasParams, gas},
10    interpreter::{SStoreResult, StateLoad, gas::GasTracker},
11    state::{AccountInfo, Bytecode},
12};
13use tempo_chainspec::hardfork::TempoHardfork;
14
15/// Production [`PrecompileStorageProvider`] backed by the live EVM journal.
16///
17/// Wraps `EvmInternals` and tracks gas consumption for storage operations.
18pub struct EvmPrecompileStorageProvider<'a> {
19    internals: EvmInternals<'a>,
20    gas_tracker: GasTracker,
21    spec: TempoHardfork,
22    amsterdam_eip8037_enabled: bool,
23    is_static: bool,
24    gas_params: GasParams,
25    tip1060_storage_credits_enabled: bool,
26    /// Debug-only LIFO checkpoint validator. See [`Self::assert_lifo`].
27    #[cfg(debug_assertions)]
28    checkpoint_stack: Vec<(usize, usize)>,
29}
30
31impl<'a> EvmPrecompileStorageProvider<'a> {
32    /// Creates a new storage provider with the given gas limit, hardfork, and static flag.
33    pub fn new(
34        internals: EvmInternals<'a>,
35        gas_limit: u64,
36        reservoir: u64,
37        spec: TempoHardfork,
38        amsterdam_eip8037_enabled: bool,
39        is_static: bool,
40        gas_params: GasParams,
41    ) -> Self {
42        Self {
43            internals,
44            gas_tracker: GasTracker::new(gas_limit, gas_limit, reservoir),
45            spec,
46            amsterdam_eip8037_enabled,
47            is_static,
48            gas_params,
49            tip1060_storage_credits_enabled: spec.is_t7(),
50            #[cfg(debug_assertions)]
51            checkpoint_stack: Vec::new(),
52        }
53    }
54
55    /// Creates a new storage provider with maximum gas limit and non-static context.
56    pub fn new_max_gas(internals: EvmInternals<'a>, cfg: &CfgEnv<TempoHardfork>) -> Self {
57        Self::new(
58            internals,
59            u64::MAX,
60            0,
61            cfg.spec,
62            cfg.enable_amsterdam_eip8037,
63            false,
64            cfg.gas_params.clone(),
65        )
66    }
67
68    /// Creates a new storage provider with the given gas limit, deriving spec from `cfg`.
69    pub fn new_with_gas_limit(
70        internals: EvmInternals<'a>,
71        cfg: &CfgEnv<TempoHardfork>,
72        gas_limit: u64,
73        reservoir: u64,
74    ) -> Self {
75        Self::new(
76            internals,
77            gas_limit,
78            reservoir,
79            cfg.spec,
80            cfg.enable_amsterdam_eip8037,
81            false,
82            cfg.gas_params.clone(),
83        )
84    }
85
86    #[inline]
87    fn deduct_state_gas(&mut self, gas: u64) -> Result<(), TempoPrecompileError> {
88        if !self.gas_tracker.record_state_cost(gas) {
89            return Err(TempoPrecompileError::OutOfGas);
90        }
91        Ok(())
92    }
93}
94
95impl crate::tip1060_storage_credits::StorageCreditsBackend for EvmPrecompileStorageProvider<'_> {
96    type Error = TempoPrecompileError;
97
98    #[inline]
99    fn gas_tracker(&mut self) -> &mut GasTracker {
100        &mut self.gas_tracker
101    }
102
103    #[inline]
104    fn gas_params(&self) -> &GasParams {
105        &self.gas_params
106    }
107
108    #[inline]
109    fn sload(
110        &mut self,
111        address: Address,
112        key: U256,
113        skip_cold_load: bool,
114    ) -> Result<StateLoad<U256>, Self::Error> {
115        let mut account = self.internals.load_account_mut(address)?;
116        let val = account.sload(key, skip_cold_load)?;
117        Ok(StateLoad::new(val.present_value, val.is_cold))
118    }
119
120    #[inline]
121    fn sstore(
122        &mut self,
123        address: Address,
124        key: U256,
125        value: U256,
126        skip_cold_load: bool,
127    ) -> Result<StateLoad<SStoreResult>, Self::Error> {
128        self.internals
129            .load_account_mut(address)?
130            .sstore(key, value, skip_cold_load)
131            .map_err(Into::into)
132    }
133
134    #[inline]
135    fn tload(&mut self, address: Address, key: U256) -> U256 {
136        self.internals.tload(address, key)
137    }
138
139    #[inline]
140    fn tstore(&mut self, address: Address, key: U256, value: U256) {
141        self.internals.tstore(address, key, value);
142    }
143
144    #[inline]
145    fn emit_event(&mut self, address: Address, event: LogData) -> Result<(), Self::Error> {
146        PrecompileStorageProvider::emit_event(self, address, event)
147    }
148}
149
150impl<'a> PrecompileStorageProvider for EvmPrecompileStorageProvider<'a> {
151    fn chain_id(&self) -> u64 {
152        self.internals.chain_id()
153    }
154
155    fn timestamp(&self) -> U256 {
156        self.internals.block_timestamp()
157    }
158
159    fn beneficiary(&self) -> Address {
160        self.internals.block_env().beneficiary()
161    }
162
163    fn block_number(&self) -> u64 {
164        self.internals.block_env().number().to::<u64>()
165    }
166
167    #[inline]
168    fn set_code(&mut self, address: Address, code: Bytecode) -> Result<(), TempoPrecompileError> {
169        let code_len = code.len();
170        self.deduct_gas(self.gas_params.code_deposit_cost(code_len))?;
171
172        // Track state gas for code deposit
173        self.deduct_state_gas(self.gas_params.code_deposit_state_gas(code_len))?;
174
175        let was_empty = {
176            let mut account = self.internals.load_account_mut(address)?;
177            let was_empty = account.data.account().info.is_empty();
178            account.set_code_and_hash_slow(code);
179            was_empty
180        };
181
182        // TIP-1016: charge TIP20 deployments as CREATE.
183        if self.amsterdam_eip8037_enabled && was_empty {
184            self.deduct_gas(self.gas_params.create_cost())?;
185            self.deduct_state_gas(self.gas_params.create_state_gas())?;
186            self.deduct_gas(self.gas_params.keccak256_cost(code_len.div_ceil(32)))?;
187        }
188
189        Ok(())
190    }
191
192    #[inline]
193    fn with_account_info(
194        &mut self,
195        address: Address,
196        f: &mut dyn FnMut(&AccountInfo),
197    ) -> Result<(), TempoPrecompileError> {
198        let additional_cost = self.gas_params.cold_account_additional_cost();
199
200        // T4+: pre-charge static gas to avoid cheap useless work.
201        let insufficient_gas_for_cold_load = if self.spec.is_t4() {
202            self.deduct_gas(self.gas_params.warm_storage_read_cost())?;
203            self.gas_tracker.remaining() < additional_cost
204        } else {
205            false
206        };
207
208        let mut account = self
209            .internals
210            .load_account_mut_skip_cold_load(address, insufficient_gas_for_cold_load)?;
211
212        if !self.spec.is_t4() {
213            deduct_gas(
214                &mut self.gas_tracker,
215                self.gas_params.warm_storage_read_cost(),
216            )?;
217        }
218
219        // dynamic gas
220        if account.is_cold {
221            deduct_gas(&mut self.gas_tracker, additional_cost)?;
222        }
223
224        account.load_code()?;
225
226        f(&account.data.account().info);
227        Ok(())
228    }
229
230    #[inline]
231    fn sstore(
232        &mut self,
233        address: Address,
234        key: U256,
235        value: U256,
236    ) -> Result<(), TempoPrecompileError> {
237        // T4+: pre-charge static gas before loading storage to avoid cheap useless work.
238        let insufficient_gas_for_cold_load = if self.spec.is_t4() {
239            self.deduct_gas(self.gas_params.sstore_static_gas())?;
240            self.gas_tracker.remaining() < self.gas_params.cold_storage_additional_cost()
241        } else {
242            false
243        };
244
245        let result = self.internals.load_account_mut(address)?.sstore(
246            key,
247            value,
248            insufficient_gas_for_cold_load,
249        )?;
250
251        if !self.spec.is_t4() {
252            self.deduct_gas(self.gas_params.sstore_static_gas())?;
253        }
254
255        // TIP-1060 (T7+): run the storage credits policy so precompile-driven storage
256        // writes honor the same accounting as the opcode-level SSTORE hook.
257        if self.tip1060_storage_credits_enabled {
258            sstore_storage_credits(self, address, &result)?
259        }
260
261        // dynamic gas
262        self.deduct_gas(
263            self.gas_params
264                .sstore_dynamic_gas(true, &result.data, result.is_cold),
265        )?;
266
267        // Track state gas (cold SSTORE zero->non-zero only)
268        self.deduct_state_gas(self.gas_params.sstore_state_gas(&result.data))?;
269
270        // refund gas.
271        self.refund_gas(self.gas_params.sstore_refund(true, &result.data));
272
273        Ok(())
274    }
275
276    #[inline]
277    fn tstore(
278        &mut self,
279        address: Address,
280        key: U256,
281        value: U256,
282    ) -> Result<(), TempoPrecompileError> {
283        self.deduct_gas(self.gas_params.warm_storage_read_cost())?;
284        self.internals.tstore(address, key, value);
285        Ok(())
286    }
287
288    #[inline]
289    fn emit_event(&mut self, address: Address, event: LogData) -> Result<(), TempoPrecompileError> {
290        self.deduct_gas(
291            gas::LOG
292                + self
293                    .gas_params
294                    .log_cost(event.topics().len() as u8, event.data.len() as u64),
295        )?;
296
297        self.internals.log(Log {
298            address,
299            data: event,
300        });
301
302        Ok(())
303    }
304
305    #[inline]
306    fn sload(&mut self, address: Address, key: U256) -> Result<U256, TempoPrecompileError> {
307        let additional_cost = self.gas_params.cold_storage_additional_cost();
308
309        // T4+: pre-charge static gas to avoid cheap useless work.
310        let insufficient_gas_for_cold_load = if self.spec.is_t4() {
311            self.deduct_gas(self.gas_params.warm_storage_read_cost())?;
312            self.gas_tracker.remaining() < additional_cost
313        } else {
314            false
315        };
316
317        let value;
318        let is_cold;
319        {
320            let mut account = self.internals.load_account_mut(address)?;
321            let val = account.sload(key, insufficient_gas_for_cold_load)?;
322
323            value = val.present_value;
324            is_cold = val.is_cold;
325        };
326
327        if !self.spec.is_t4() {
328            self.deduct_gas(self.gas_params.warm_storage_read_cost())?;
329        }
330
331        // dynamic gas
332        if is_cold {
333            self.deduct_gas(additional_cost)?;
334        }
335
336        Ok(value)
337    }
338
339    #[inline]
340    fn tload(&mut self, address: Address, key: U256) -> Result<U256, TempoPrecompileError> {
341        self.deduct_gas(self.gas_params.warm_storage_read_cost())?;
342
343        Ok(self.internals.tload(address, key))
344    }
345
346    #[inline]
347    fn deduct_gas(&mut self, gas: u64) -> Result<(), TempoPrecompileError> {
348        deduct_gas(&mut self.gas_tracker, gas)
349    }
350
351    #[inline]
352    fn refund_gas(&mut self, gas: i64) {
353        self.gas_tracker.record_refund(gas);
354    }
355
356    #[inline]
357    fn gas_limit(&self) -> u64 {
358        self.gas_tracker.limit()
359    }
360
361    #[inline]
362    fn gas_used(&self) -> u64 {
363        self.gas_tracker.limit() - self.gas_tracker.remaining()
364    }
365
366    #[inline]
367    fn state_gas_used(&self) -> u64 {
368        // SAFETY: we never decrement the state gas spent counter
369        self.gas_tracker.state_gas_spent() as u64
370    }
371
372    #[inline]
373    fn gas_refunded(&self) -> i64 {
374        self.gas_tracker.refunded()
375    }
376
377    #[inline]
378    fn reservoir(&self) -> u64 {
379        self.gas_tracker.reservoir()
380    }
381
382    #[inline]
383    fn spec(&self) -> TempoHardfork {
384        self.spec
385    }
386
387    #[inline]
388    fn amsterdam_eip8037_enabled(&self) -> bool {
389        self.amsterdam_eip8037_enabled
390    }
391
392    #[inline]
393    fn is_static(&self) -> bool {
394        self.is_static
395    }
396
397    #[inline]
398    fn checkpoint(&mut self) -> JournalCheckpoint {
399        let cp = self.internals.checkpoint();
400        #[cfg(debug_assertions)]
401        self.track_checkpoint(&cp);
402        cp
403    }
404
405    #[inline]
406    fn checkpoint_commit(&mut self, _checkpoint: JournalCheckpoint) {
407        #[cfg(debug_assertions)]
408        self.assert_lifo(&_checkpoint, "commit");
409        self.internals.checkpoint_commit()
410    }
411
412    #[inline]
413    fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) {
414        #[cfg(debug_assertions)]
415        self.assert_lifo(&checkpoint, "revert");
416        self.internals.checkpoint_revert(checkpoint)
417    }
418
419    #[inline]
420    fn set_tip1060_storage_credits(&mut self, enabled: bool) {
421        self.tip1060_storage_credits_enabled = enabled && self.spec.is_t7();
422    }
423}
424
425/// LIFO checkpoint validation (debug builds only).
426///
427/// Since `EvmInternals` doesn't expose revm's journal depth, we mirror it by
428/// recording each checkpoint's (`journal_i`, `log_i`) on creation and asserting
429/// that commits/reverts always resolve the most recent checkpoint first.
430#[cfg(debug_assertions)]
431impl EvmPrecompileStorageProvider<'_> {
432    /// Records a newly created checkpoint for later LIFO validation.
433    fn track_checkpoint(&mut self, cp: &JournalCheckpoint) {
434        self.checkpoint_stack.push((cp.journal_i, cp.log_i));
435    }
436
437    /// Panics if `cp` is not the most recently created checkpoint.
438    fn assert_lifo(&mut self, cp: &JournalCheckpoint, op: &str) {
439        let top = self
440            .checkpoint_stack
441            .pop()
442            .unwrap_or_else(|| panic!("checkpoint_{op}: no active checkpoint"));
443
444        assert_eq!(
445            (cp.journal_i, cp.log_i),
446            top,
447            "out-of-order checkpoint {op} (expected top of stack)"
448        );
449    }
450}
451
452/// Deducts gas from the remaining gas and returns an error if insufficient.
453#[inline]
454pub fn deduct_gas(
455    gas_tracker: &mut GasTracker,
456    additional_cost: u64,
457) -> Result<(), TempoPrecompileError> {
458    if !gas_tracker.record_regular_cost(additional_cost) {
459        return Err(TempoPrecompileError::OutOfGas);
460    }
461    Ok(())
462}
463
464#[cfg(test)]
465mod tests {
466    use super::*;
467    use alloy::primitives::{B256, b256, bytes, keccak256};
468    use alloy_evm::{EvmEnv, EvmFactory, EvmInternals, revm::context::Host};
469    use alloy_signer::SignerSync;
470    use alloy_signer_local::PrivateKeySigner;
471    use revm::{
472        database::{CacheDB, EmptyDB},
473        interpreter::StateLoad,
474    };
475    use tempo_chainspec::hardfork::TempoHardfork;
476    use tempo_evm::{TempoEvmFactory, evm::TempoEvm};
477    use tempo_revm::gas_params::tempo_gas_params_with_amsterdam;
478
479    struct TestEvm(TempoEvm<CacheDB<EmptyDB>>);
480
481    impl TestEvm {
482        fn new(spec: TempoHardfork) -> Self {
483            Self::with_amsterdam(spec, false)
484        }
485
486        /// Constructs a [`TestEvm`] with TIP-1016 (EIP-8037) manually enabled.
487        ///
488        /// Used by tests that exercise TIP-1016 behavior (state gas split, reservoir
489        /// accounting). TIP-1016 is otherwise opt-in via `cfg.enable_amsterdam_eip8037`,
490        /// which defaults to `false` in production.
491        fn new_with_tip1016(spec: TempoHardfork) -> Self {
492            Self::with_amsterdam(spec, true)
493        }
494
495        fn with_amsterdam(spec: TempoHardfork, amsterdam_eip8037_enabled: bool) -> Self {
496            let db = CacheDB::new(EmptyDB::new());
497            let mut cfg = revm::context::CfgEnv::<TempoHardfork>::default();
498            cfg.spec = spec;
499            cfg.enable_amsterdam_eip8037 = amsterdam_eip8037_enabled;
500            cfg.gas_params = tempo_gas_params_with_amsterdam(spec, amsterdam_eip8037_enabled);
501
502            Self(TempoEvmFactory::default().create_evm(
503                db,
504                EvmEnv {
505                    cfg_env: cfg,
506                    ..Default::default()
507                },
508            ))
509        }
510
511        fn provider_with_gas_limit(
512            &mut self,
513            gas_limit: u64,
514            reservoir: u64,
515        ) -> EvmPrecompileStorageProvider<'_> {
516            let ctx = self.0.ctx_mut();
517            let spec = ctx.cfg.spec;
518            let amsterdam_eip8037_enabled = ctx.cfg.enable_amsterdam_eip8037;
519            let gas_params = ctx.cfg.gas_params.clone();
520            let evm_internals =
521                EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
522
523            EvmPrecompileStorageProvider::new(
524                evm_internals,
525                gas_limit,
526                reservoir,
527                spec,
528                amsterdam_eip8037_enabled,
529                false,
530                gas_params,
531            )
532        }
533
534        fn provider_with_reservoir(&mut self, reservoir: u64) -> EvmPrecompileStorageProvider<'_> {
535            self.provider_with_gas_limit(u64::MAX, reservoir)
536        }
537
538        fn provider_max_gas(&mut self) -> EvmPrecompileStorageProvider<'_> {
539            let ctx = self.0.ctx_mut();
540            let evm_internals =
541                EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
542            EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg)
543        }
544    }
545
546    impl Default for TestEvm {
547        fn default() -> Self {
548            Self::new(TempoHardfork::default())
549        }
550    }
551
552    impl std::ops::Deref for TestEvm {
553        type Target = TempoEvm<CacheDB<EmptyDB>>;
554        fn deref(&self) -> &Self::Target {
555            &self.0
556        }
557    }
558
559    impl std::ops::DerefMut for TestEvm {
560        fn deref_mut(&mut self) -> &mut Self::Target {
561            &mut self.0
562        }
563    }
564
565    #[test]
566    fn test_sstore_sload() -> eyre::Result<()> {
567        let mut evm = TestEvm::default();
568        let mut provider = evm.provider_max_gas();
569
570        let addr = Address::random();
571        let key = U256::random();
572        let value = U256::random();
573
574        provider.sstore(addr, key, value)?;
575        let sload_val = provider.sload(addr, key)?;
576        assert_eq!(sload_val, value);
577        Ok(())
578    }
579
580    #[test]
581    fn test_set_code() -> eyre::Result<()> {
582        let mut evm = TestEvm::default();
583        let mut provider = evm.provider_max_gas();
584
585        let addr = Address::random();
586        let code = Bytecode::new_raw(vec![0xff].into());
587
588        provider.set_code(addr, code.clone())?;
589        std::mem::drop(provider);
590
591        let Some(StateLoad { data, is_cold: _ }) = evm.load_account_code(addr) else {
592            panic!("Failed to load account code")
593        };
594
595        assert_eq!(data, *code.original_bytes());
596        Ok(())
597    }
598
599    #[test]
600    fn test_get_account_info() -> eyre::Result<()> {
601        let mut evm = TestEvm::default();
602        let mut provider = evm.provider_max_gas();
603
604        // Get account info for a new account
605        provider.with_account_info(Address::random(), &mut |info| {
606            // Should be an empty account
607            assert!(info.balance.is_zero());
608            assert_eq!(info.nonce, 0);
609            // Note: load_account_code may return empty bytecode as Some(empty) for new accounts
610            if let Some(ref code) = info.code {
611                assert!(code.is_empty(), "New account should have empty code");
612            }
613        })?;
614
615        Ok(())
616    }
617
618    #[test]
619    fn test_emit_event() -> eyre::Result<()> {
620        let mut evm = TestEvm::default();
621        let mut provider = evm.provider_max_gas();
622
623        let topic = b256!("0000000000000000000000000000000000000000000000000000000000000001");
624        let data = bytes!(
625            "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001"
626        );
627
628        let log_data = LogData::new_unchecked(vec![topic], data);
629
630        // Should not error even though events can't be emitted from handlers
631        provider.emit_event(Address::random(), log_data)?;
632
633        Ok(())
634    }
635
636    #[test]
637    fn test_multiple_storage_operations() -> eyre::Result<()> {
638        let mut evm = TestEvm::default();
639        let mut provider = evm.provider_max_gas();
640        let address = Address::random();
641
642        // Store multiple values
643        for i in 0..10 {
644            let key = U256::from(i);
645            let value = U256::from(i * 100);
646            provider.sstore(address, key, value)?;
647        }
648
649        // Verify all values
650        for i in 0..10 {
651            let key = U256::from(i);
652            let expected_value = U256::from(i * 100);
653            let loaded_value = provider.sload(address, key)?;
654            assert_eq!(loaded_value, expected_value);
655        }
656
657        Ok(())
658    }
659
660    #[test]
661    fn test_overwrite_storage() -> eyre::Result<()> {
662        let mut evm = TestEvm::default();
663        let mut provider = evm.provider_max_gas();
664        let address = Address::random();
665        let key = U256::from(99);
666
667        // Store initial value
668        let initial_value = U256::from(111);
669        provider.sstore(address, key, initial_value)?;
670        assert_eq!(provider.sload(address, key)?, initial_value);
671
672        // Overwrite with new value
673        let new_value = U256::from(999);
674        provider.sstore(address, key, new_value)?;
675        assert_eq!(provider.sload(address, key)?, new_value);
676
677        Ok(())
678    }
679
680    #[test]
681    fn test_different_addresses() -> eyre::Result<()> {
682        let mut evm = TestEvm::default();
683        let mut provider = evm.provider_max_gas();
684        let (address1, address2) = (Address::random(), Address::random());
685        let key = U256::from(42);
686
687        // Store different values at the same key for different addresses
688        let value1 = U256::from(100);
689        let value2 = U256::from(200);
690
691        provider.sstore(address1, key, value1)?;
692        provider.sstore(address2, key, value2)?;
693
694        // Verify values are independent
695        assert_eq!(provider.sload(address1, key)?, value1);
696        assert_eq!(provider.sload(address2, key)?, value2);
697
698        Ok(())
699    }
700
701    #[test]
702    fn test_multiple_transient_storage_operations() -> eyre::Result<()> {
703        let mut evm = TestEvm::default();
704        let mut provider = evm.provider_max_gas();
705        let address = Address::random();
706
707        // Store multiple values
708        for i in 0..10 {
709            let key = U256::from(i);
710            let value = U256::from(i * 100);
711            provider.tstore(address, key, value)?;
712        }
713
714        // Verify all values
715        for i in 0..10 {
716            let key = U256::from(i);
717            let expected_value = U256::from(i * 100);
718            let loaded_value = provider.tload(address, key)?;
719            assert_eq!(loaded_value, expected_value);
720        }
721
722        Ok(())
723    }
724
725    #[test]
726    fn test_overwrite_transient_storage() -> eyre::Result<()> {
727        let mut evm = TestEvm::default();
728        let mut provider = evm.provider_max_gas();
729        let address = Address::random();
730        let key = U256::from(99);
731
732        // Store initial value
733        let initial_value = U256::from(111);
734        provider.tstore(address, key, initial_value)?;
735        assert_eq!(provider.tload(address, key)?, initial_value);
736
737        // Overwrite with new value
738        let new_value = U256::from(999);
739        provider.tstore(address, key, new_value)?;
740        assert_eq!(provider.tload(address, key)?, new_value);
741
742        Ok(())
743    }
744
745    #[test]
746    fn test_transient_storage_different_addresses() -> eyre::Result<()> {
747        let mut evm = TestEvm::default();
748        let mut provider = evm.provider_max_gas();
749        let (address1, address2) = (Address::random(), Address::random());
750        let key = U256::ONE;
751
752        // Store different values at the same key for different addresses
753        let value1 = U256::from(100);
754        let value2 = U256::from(200);
755
756        provider.tstore(address1, key, value1)?;
757        provider.tstore(address2, key, value2)?;
758
759        // Verify values are independent
760        assert_eq!(provider.tload(address1, key)?, value1);
761        assert_eq!(provider.tload(address2, key)?, value2);
762
763        Ok(())
764    }
765
766    #[test]
767    fn test_transient_storage_isolation_from_persistent() -> eyre::Result<()> {
768        let mut evm = TestEvm::default();
769        let mut provider = evm.provider_max_gas();
770        let address = Address::random();
771        let key = U256::from(123);
772        let persistent_value = U256::from(456);
773        let transient_value = U256::from(789);
774
775        // Store in persistent storage
776        provider.sstore(address, key, persistent_value)?;
777
778        // Store in transient storage with same key
779        provider.tstore(address, key, transient_value)?;
780
781        // Verify they are independent
782        assert_eq!(provider.sload(address, key)?, persistent_value);
783        assert_eq!(provider.tload(address, key)?, transient_value);
784
785        Ok(())
786    }
787
788    #[test]
789    fn test_keccak256_gas() -> eyre::Result<()> {
790        let mut evm = TestEvm::default();
791        let mut provider = evm.provider_max_gas();
792
793        // 1 word: KECCAK256(30) + KECCAK256WORD(6) * ceil(11/32) = 36
794        assert_eq!(
795            provider.keccak256(b"hello world")?,
796            keccak256(b"hello world")
797        );
798        assert_eq!(provider.gas_used(), 36);
799        // 2 words: 30 + 6*2 = 42, cumulative = 78
800        provider.keccak256(&[0u8; 64])?;
801        assert_eq!(provider.gas_used(), 78);
802        std::mem::drop(provider);
803
804        // OOG: 30 gas is not enough (needs 36 for 1 word)
805        let mut provider = evm.provider_with_gas_limit(30, 0);
806        assert!(matches!(
807            provider.keccak256(b"hello"),
808            Err(TempoPrecompileError::OutOfGas)
809        ));
810
811        Ok(())
812    }
813
814    #[test]
815    fn test_recover_signer_gas() -> eyre::Result<()> {
816        let mut evm = TestEvm::default();
817        let mut provider = evm.provider_max_gas();
818
819        let signer = PrivateKeySigner::random();
820        let digest = keccak256(b"test message");
821        let sig = signer.sign_hash_sync(&digest).unwrap();
822        let v = u8::from(sig.v()) + 27;
823        let r: B256 = sig.r().into();
824        let s: B256 = sig.s().into();
825
826        // Invalid v → None, gas still charged
827        assert!(
828            provider
829                .recover_signer(B256::ZERO, 0, B256::ZERO, B256::ZERO)?
830                .is_none()
831        );
832        assert_eq!(provider.gas_used(), crate::ECRECOVER_GAS);
833
834        // Valid signature → correct recovery
835        assert_eq!(
836            provider.recover_signer(digest, v, r, s)?,
837            Some(signer.address())
838        );
839        assert_eq!(provider.gas_used(), crate::ECRECOVER_GAS * 2);
840        std::mem::drop(provider);
841
842        // OOG: 100 gas is not enough (needs 3000)
843        let mut provider = evm.provider_with_gas_limit(100, 0);
844        assert!(matches!(
845            provider.recover_signer(digest, v, r, s),
846            Err(TempoPrecompileError::OutOfGas)
847        ));
848
849        Ok(())
850    }
851
852    #[test]
853    fn test_state_gas_used_only_counts_state_creating_ops() -> eyre::Result<()> {
854        let mut evm = TestEvm::new_with_tip1016(TempoHardfork::T4);
855        let gas_params = evm.ctx().cfg.gas_params.clone();
856        let mut provider = evm.provider_with_reservoir(0);
857
858        let (address, code_address, slot) = (Address::random(), Address::random(), U256::ONE);
859
860        // SLOADs should not add state gas
861        provider.sload(address, slot)?;
862        assert_eq!(
863            provider.state_gas_used(),
864            0,
865            "SLOAD should not add state gas"
866        );
867        assert!(provider.gas_used() > 0, "SLOAD should consume regular gas");
868
869        // SSTORE zero->non-zero should add state gas
870        let gas_before = provider.gas_used();
871        provider.sstore(address, slot, U256::from(1))?;
872        let state_gas_after_set = provider.state_gas_used();
873        assert_eq!(
874            state_gas_after_set, 230_000,
875            "SSTORE zero->non-zero should add 230k state gas"
876        );
877        assert!(
878            provider.gas_used() > gas_before,
879            "SSTORE should consume gas"
880        );
881
882        // SSTORE non-zero->non-zero should NOT add more state gas
883        provider.sstore(address, slot, U256::from(2))?;
884        assert_eq!(
885            provider.state_gas_used(),
886            state_gas_after_set,
887            "SSTORE non-zero->non-zero should not add state gas"
888        );
889
890        // Code deposit should add state gas (2,300 per byte)
891        let state_gas_before_code = provider.state_gas_used();
892        provider.set_code(
893            code_address,
894            revm::state::Bytecode::new_raw(vec![0xef].into()),
895        )?;
896        assert_eq!(
897            provider.state_gas_used(),
898            state_gas_before_code
899                + gas_params.create_state_gas()
900                + gas_params.code_deposit_state_gas(1),
901            "set_code(new account, 1 byte) should add CREATE state gas plus 2,300 code deposit state gas"
902        );
903
904        Ok(())
905    }
906
907    /// Tests that state gas (EIP-8037) is deducted from the reservoir first and
908    /// spills into regular gas once the reservoir is exhausted.
909    #[test]
910    fn test_state_gas_spills_from_reservoir_to_regular_gas() -> eyre::Result<()> {
911        let mut evm = TestEvm::new_with_tip1016(TempoHardfork::T4);
912
913        // Reservoir = 500k: enough for 2 full SSTOREs (2 × 230k = 460k)
914        // but the 3rd SSTORE (230k) must spill 190k into regular gas.
915        let gas_limit = 1_000_000u64;
916        let reservoir = 500_000u64;
917        let state_gas_per_sstore = 230_000u64;
918        let mut provider = evm.provider_with_gas_limit(gas_limit, reservoir);
919        let address = Address::random();
920
921        // --- First SSTORE (zero→non-zero): fully covered by reservoir ---
922        provider.sstore(address, U256::from(1), U256::from(42))?;
923
924        let regular_gas_per_sstore = provider.gas_used(); // static + dynamic (regular)
925        assert_eq!(
926            provider.state_gas_used(),
927            state_gas_per_sstore,
928            "first SSTORE should consume 230k state gas"
929        );
930        assert_eq!(
931            provider.reservoir(),
932            reservoir - state_gas_per_sstore,
933            "reservoir should decrease by state gas cost"
934        );
935
936        // --- Second SSTORE: still fits in remaining reservoir (270k left, need 230k) ---
937        provider.sstore(address, U256::from(2), U256::from(43))?;
938
939        assert_eq!(
940            provider.state_gas_used(),
941            2 * state_gas_per_sstore,
942            "two SSTOREs should consume 460k state gas"
943        );
944        assert_eq!(
945            provider.reservoir(),
946            reservoir - 2 * state_gas_per_sstore,
947            "reservoir should have 40k left after 2 SSTOREs"
948        );
949        let remaining_reservoir = provider.reservoir(); // 40k
950        let regular_gas_before_spill = provider.gas_used();
951
952        // --- Third SSTORE: reservoir insufficient, 190k spills to regular gas ---
953        provider.sstore(address, U256::from(3), U256::from(44))?;
954
955        assert_eq!(
956            provider.state_gas_used(),
957            3 * state_gas_per_sstore,
958            "three SSTOREs should consume 690k state gas total"
959        );
960        assert_eq!(
961            provider.reservoir(),
962            0,
963            "reservoir should be fully exhausted"
964        );
965
966        // Regular gas increase = normal sstore cost + spill from reservoir
967        let spill = state_gas_per_sstore - remaining_reservoir; // 230k - 40k = 190k
968        let expected_regular_after = regular_gas_before_spill + regular_gas_per_sstore + spill;
969        assert_eq!(
970            provider.gas_used(),
971            expected_regular_after,
972            "regular gas should include spill of {spill} from exhausted reservoir"
973        );
974
975        Ok(())
976    }
977
978    #[test]
979    fn test_t4_cold_sstore_matches_tip1016_spec() -> eyre::Result<()> {
980        let mut evm = TestEvm::new_with_tip1016(TempoHardfork::T4);
981        let mut provider = evm.provider_with_reservoir(460_000);
982
983        let (address, cold_slot, warm_slot) = (Address::random(), U256::ONE, U256::from(2));
984
985        provider.sstore(address, cold_slot, U256::ONE)?;
986        assert_eq!(
987            provider.gas_used(),
988            22_200,
989            "TIP-1016 cold SSTORE should consume 22,200 regular gas including the retained Berlin cold-slot access charge"
990        );
991        assert_eq!(
992            provider.state_gas_used(),
993            230_000,
994            "TIP-1016 cold SSTORE should consume 230,000 state gas"
995        );
996
997        provider.sload(address, warm_slot)?;
998        let gas_before_warm_sstore = provider.gas_used();
999        let state_gas_before_warm_sstore = provider.state_gas_used();
1000
1001        provider.sstore(address, warm_slot, U256::ONE)?;
1002        assert_eq!(
1003            provider.gas_used() - gas_before_warm_sstore,
1004            20_100,
1005            "TIP-1016 warm zero-to-non-zero SSTORE should consume 20,100 regular gas after the slot is warmed by SLOAD"
1006        );
1007        assert_eq!(
1008            provider.state_gas_used() - state_gas_before_warm_sstore,
1009            230_000,
1010            "TIP-1016 warm zero-to-non-zero SSTORE should still consume 230,000 state gas"
1011        );
1012
1013        Ok(())
1014    }
1015
1016    #[test]
1017    fn test_t4_set_code_new_account_matches_tip1016_success_path() -> eyre::Result<()> {
1018        let mut evm = TestEvm::new_with_tip1016(TempoHardfork::T4);
1019        let gas_params = evm.ctx().cfg.gas_params.clone();
1020
1021        let code = Bytecode::new_raw(vec![0xef].into());
1022        let expected_state_gas =
1023            gas_params.create_state_gas() + gas_params.code_deposit_state_gas(code.len());
1024        let expected_regular_gas = gas_params.create_cost()
1025            + gas_params.code_deposit_cost(code.len())
1026            + gas_params.keccak256_cost(code.len().div_ceil(32));
1027        let mut provider = evm.provider_with_reservoir(expected_state_gas);
1028
1029        provider.set_code(Address::random(), code)?;
1030        assert_eq!(
1031            provider.gas_used(),
1032            expected_regular_gas,
1033            "TIP-1016 CREATE success path should charge CREATE + code deposit"
1034        );
1035        assert_eq!(
1036            provider.state_gas_used(),
1037            expected_state_gas,
1038            "set_code on a new account should charge CREATE state gas plus code deposit state gas"
1039        );
1040
1041        Ok(())
1042    }
1043
1044    #[test]
1045    fn test_sstore_t4_fork_sufficient_gas() -> eyre::Result<()> {
1046        // T4 fork sstore/sload with abundant gas: round-trip the value.
1047        let mut evm = TestEvm::new(TempoHardfork::T4);
1048        let mut provider = evm.provider_max_gas();
1049
1050        let address = Address::random();
1051        let key = U256::from(42);
1052        let value = U256::from(999);
1053
1054        provider.sstore(address, key, value)?;
1055        assert_eq!(provider.sload(address, key)?, value);
1056        Ok(())
1057    }
1058
1059    #[test]
1060    fn test_sload_t4_fork_sufficient_gas() -> eyre::Result<()> {
1061        // T4 fork sload with abundant gas: cold then warm reads return the stored value.
1062        let mut evm = TestEvm::new(TempoHardfork::T4);
1063        let mut provider = evm.provider_max_gas();
1064
1065        let address = Address::random();
1066        let key = U256::from(100);
1067        let value = U256::from(12345);
1068
1069        provider.sstore(address, key, value)?;
1070        assert_eq!(provider.sload(address, key)?, value);
1071        // second access should hit the warm path
1072        assert_eq!(provider.sload(address, key)?, value);
1073        Ok(())
1074    }
1075
1076    #[test]
1077    fn test_with_account_info_t4_fork() -> eyre::Result<()> {
1078        // T4 fork with_account_info on a fresh account: zero balance/nonce.
1079        let mut evm = TestEvm::new(TempoHardfork::T4);
1080        let mut provider = evm.provider_max_gas();
1081
1082        let mut account_nonce = u64::MAX;
1083        provider.with_account_info(Address::random(), &mut |info| {
1084            account_nonce = info.nonce;
1085            assert!(info.balance.is_zero());
1086        })?;
1087
1088        assert_eq!(account_nonce, 0);
1089        Ok(())
1090    }
1091
1092    #[test]
1093    fn test_sstore_sload_cold_storage_t4() -> eyre::Result<()> {
1094        // T4 fork cold/warm handling across multiple addresses.
1095        let mut evm = TestEvm::new(TempoHardfork::T4);
1096        let mut provider = evm.provider_max_gas();
1097
1098        let addr1 = Address::random();
1099        let addr2 = Address::random();
1100        let key1 = U256::from(1);
1101        let key2 = U256::from(2);
1102
1103        // Cold writes
1104        provider.sstore(addr1, key1, U256::from(100))?;
1105        provider.sstore(addr2, key2, U256::from(200))?;
1106
1107        // Warm overwrites
1108        provider.sstore(addr1, key1, U256::from(110))?;
1109        provider.sstore(addr2, key2, U256::from(210))?;
1110
1111        assert_eq!(provider.sload(addr1, key1)?, U256::from(110));
1112        assert_eq!(provider.sload(addr2, key2)?, U256::from(210));
1113        Ok(())
1114    }
1115
1116    #[test]
1117    fn test_sstore_insufficient_gas_for_cold_load_t4() -> eyre::Result<()> {
1118        // T4 fork sstore with a tight gas budget: cold-load cost is skipped when the
1119        // pre-charged static gas leaves the remaining gas below the cold additional cost.
1120        let mut evm = TestEvm::new_with_tip1016(TempoHardfork::T4);
1121        let gas_params = evm.ctx().cfg.gas_params.clone();
1122
1123        let static_gas = gas_params.sstore_static_gas();
1124        let dynamic_gas = 25_000u64;
1125        let gas_limit = static_gas + dynamic_gas;
1126
1127        // Generous reservoir so T4 state-gas (zero->non-zero) doesn't spill into regular gas.
1128        let mut provider = evm.provider_with_gas_limit(gas_limit, u64::MAX);
1129
1130        let initial_gas = provider.gas_used();
1131        let address = Address::random();
1132        let key = U256::from(42);
1133        let value = U256::from(999);
1134
1135        provider.sstore(address, key, value)?;
1136        let gas_after_sstore = provider.gas_used();
1137        assert!(gas_after_sstore > initial_gas, "sstore should consume gas");
1138
1139        assert_eq!(provider.sload(address, key)?, value);
1140        assert!(
1141            provider.gas_used() > gas_after_sstore,
1142            "sload should consume additional gas"
1143        );
1144        Ok(())
1145    }
1146
1147    #[test]
1148    fn test_sload_insufficient_gas_for_cold_load_t4() -> eyre::Result<()> {
1149        // T4 fork sload succeeds even when remaining gas can't cover the cold-load cost.
1150        let mut evm = TestEvm::new(TempoHardfork::T4);
1151        let address = Address::random();
1152        let key = U256::from(100);
1153        let value = U256::from(555);
1154
1155        // Seed storage with abundant gas first.
1156        {
1157            let mut provider = evm.provider_max_gas();
1158            provider.sstore(address, key, value)?;
1159        }
1160
1161        let gas_params = evm.ctx().cfg.gas_params.clone();
1162        let warm_read_gas = gas_params.warm_storage_read_cost();
1163        let dynamic_gas = 2_100u64;
1164        let gas_limit = warm_read_gas + dynamic_gas;
1165
1166        let mut provider = evm.provider_with_gas_limit(gas_limit, 0);
1167        let initial_gas = provider.gas_used();
1168
1169        assert_eq!(provider.sload(address, key)?, value);
1170        assert!(
1171            provider.gas_used() > initial_gas,
1172            "sload should consume gas"
1173        );
1174        Ok(())
1175    }
1176
1177    #[test]
1178    fn test_with_account_info_insufficient_gas_for_cold_load_t4() -> eyre::Result<()> {
1179        // T4 fork with_account_info under a tight gas budget.
1180        let mut evm = TestEvm::new(TempoHardfork::T4);
1181        let gas_params = evm.ctx().cfg.gas_params.clone();
1182
1183        let static_gas = gas_params.sstore_static_gas();
1184        let gas_limit = static_gas + 10_000u64;
1185
1186        let mut provider = evm.provider_with_gas_limit(gas_limit, 0);
1187        let initial_gas = provider.gas_used();
1188
1189        let mut retrieved_nonce = u64::MAX;
1190        provider.with_account_info(Address::random(), &mut |info| {
1191            retrieved_nonce = info.nonce;
1192        })?;
1193
1194        assert_eq!(retrieved_nonce, 0);
1195        assert!(
1196            provider.gas_used() > initial_gas,
1197            "with_account_info should consume gas"
1198        );
1199        Ok(())
1200    }
1201
1202    #[test]
1203    fn test_multiple_sstore_insufficient_gas_scenarios_t4() -> eyre::Result<()> {
1204        // T4 fork multiple sstores under a constrained gas budget.
1205        let mut evm = TestEvm::new_with_tip1016(TempoHardfork::T4);
1206        let gas_params = evm.ctx().cfg.gas_params.clone();
1207
1208        let static_gas = gas_params.sstore_static_gas();
1209        let dynamic_gas = 20_000u64;
1210        let gas_per_sstore = static_gas + dynamic_gas;
1211        let gas_limit = gas_per_sstore * 3;
1212
1213        let mut provider = evm.provider_with_gas_limit(gas_limit, u64::MAX);
1214        let address = Address::random();
1215        let mut prev_gas = provider.gas_used();
1216
1217        for i in 0..3 {
1218            provider.sstore(address, U256::from(i), U256::from(i * 1000))?;
1219            let current_gas = provider.gas_used();
1220            assert!(
1221                current_gas > prev_gas,
1222                "each sstore should increase gas usage"
1223            );
1224            prev_gas = current_gas;
1225        }
1226
1227        for i in 0..3 {
1228            assert_eq!(
1229                provider.sload(address, U256::from(i))?,
1230                U256::from(i * 1000)
1231            );
1232        }
1233        Ok(())
1234    }
1235
1236    #[test]
1237    #[ignore = "TIP-1016 mismatch: 0->X->0 refund math does not net to GAS_WARM_ACCESS (100 gas) yet"]
1238    fn test_t4_sstore_restore_refund_matches_tip1016_spec() -> eyre::Result<()> {
1239        let mut evm = TestEvm::new(TempoHardfork::T4);
1240        let mut provider = evm.provider_with_reservoir(230_000);
1241
1242        let (address, slot) = (Address::random(), U256::ONE);
1243        provider.sstore(address, slot, U256::ONE)?;
1244        provider.sstore(address, slot, U256::ZERO)?;
1245        assert_eq!(provider.gas_refunded(), 247_800);
1246        let net_gas_after_refund =
1247            provider.gas_used() + provider.state_gas_used() - provider.gas_refunded() as u64;
1248        assert_eq!(
1249            net_gas_after_refund, 100,
1250            "TIP-1016 says 0->X->0 should net to GAS_WARM_ACCESS (100)"
1251        );
1252
1253        Ok(())
1254    }
1255}