Skip to main content

tempo_precompiles/storage/
evm.rs

1use alloy::primitives::{Address, Log, LogData, U256};
2use alloy_evm::{EvmInternals, EvmInternalsError};
3use revm::{
4    context::{Block, CfgEnv, journaled_state::JournalCheckpoint},
5    context_interface::cfg::{GasParams, gas},
6    state::{AccountInfo, Bytecode},
7};
8use tempo_chainspec::hardfork::TempoHardfork;
9
10use crate::{error::TempoPrecompileError, storage::PrecompileStorageProvider};
11
12/// Production [`PrecompileStorageProvider`] backed by the live EVM journal.
13///
14/// Wraps `EvmInternals` and tracks gas consumption for storage operations.
15pub struct EvmPrecompileStorageProvider<'a> {
16    internals: EvmInternals<'a>,
17    gas_remaining: u64,
18    gas_refunded: i64,
19    gas_limit: u64,
20    spec: TempoHardfork,
21    is_static: bool,
22    gas_params: GasParams,
23    /// Debug-only LIFO checkpoint validator. See [`Self::assert_lifo`].
24    #[cfg(debug_assertions)]
25    checkpoint_stack: Vec<(usize, usize)>,
26}
27
28impl<'a> EvmPrecompileStorageProvider<'a> {
29    /// Creates a new storage provider with the given gas limit, hardfork, and static flag.
30    pub fn new(
31        internals: EvmInternals<'a>,
32        gas_limit: u64,
33        spec: TempoHardfork,
34        is_static: bool,
35        gas_params: GasParams,
36    ) -> Self {
37        Self {
38            internals,
39            gas_remaining: gas_limit,
40            gas_refunded: 0,
41            gas_limit,
42            spec,
43            is_static,
44            gas_params,
45            #[cfg(debug_assertions)]
46            checkpoint_stack: Vec::new(),
47        }
48    }
49
50    /// Creates a new storage provider with maximum gas limit and non-static context.
51    pub fn new_max_gas(internals: EvmInternals<'a>, cfg: &CfgEnv<TempoHardfork>) -> Self {
52        Self::new(internals, u64::MAX, cfg.spec, false, cfg.gas_params.clone())
53    }
54
55    /// Creates a new storage provider with the given gas limit, deriving spec from `cfg`.
56    pub fn new_with_gas_limit(
57        internals: EvmInternals<'a>,
58        cfg: &CfgEnv<TempoHardfork>,
59        gas_limit: u64,
60    ) -> Self {
61        Self::new(
62            internals,
63            gas_limit,
64            cfg.spec,
65            false,
66            cfg.gas_params.clone(),
67        )
68    }
69}
70
71impl<'a> PrecompileStorageProvider for EvmPrecompileStorageProvider<'a> {
72    fn chain_id(&self) -> u64 {
73        self.internals.chain_id()
74    }
75
76    fn timestamp(&self) -> U256 {
77        self.internals.block_timestamp()
78    }
79
80    fn beneficiary(&self) -> Address {
81        self.internals.block_env().beneficiary()
82    }
83
84    fn block_number(&self) -> u64 {
85        self.internals.block_env().number().to::<u64>()
86    }
87
88    #[inline]
89    fn set_code(&mut self, address: Address, code: Bytecode) -> Result<(), TempoPrecompileError> {
90        self.deduct_gas(self.gas_params.code_deposit_cost(code.len()))?;
91
92        self.internals
93            .load_account_mut(address)?
94            .set_code_and_hash_slow(code);
95
96        Ok(())
97    }
98
99    #[inline]
100    fn with_account_info(
101        &mut self,
102        address: Address,
103        f: &mut dyn FnMut(&AccountInfo),
104    ) -> Result<(), TempoPrecompileError> {
105        let additional_cost = self.gas_params.cold_account_additional_cost();
106
107        let mut account = self
108            .internals
109            .load_account_mut_skip_cold_load(address, false)?;
110
111        // TODO(rakita) can be moved to the beginning of the function. Requires fork.
112        deduct_gas(
113            &mut self.gas_remaining,
114            self.gas_params.warm_storage_read_cost(),
115        )?;
116
117        // dynamic gas
118        if account.is_cold {
119            deduct_gas(&mut self.gas_remaining, additional_cost)?;
120        }
121
122        account.load_code()?;
123
124        f(&account.data.account().info);
125        Ok(())
126    }
127
128    #[inline]
129    fn sstore(
130        &mut self,
131        address: Address,
132        key: U256,
133        value: U256,
134    ) -> Result<(), TempoPrecompileError> {
135        let result = self
136            .internals
137            .load_account_mut(address)?
138            .sstore(key, value, false)?;
139
140        // TODO(rakita) can be moved to the beginning of the function. Requires fork.
141        self.deduct_gas(self.gas_params.sstore_static_gas())?;
142
143        // dynamic gas
144        self.deduct_gas(
145            self.gas_params
146                .sstore_dynamic_gas(true, &result.data, result.is_cold),
147        )?;
148
149        // refund gas.
150        self.refund_gas(self.gas_params.sstore_refund(true, &result.data));
151
152        Ok(())
153    }
154
155    #[inline]
156    fn tstore(
157        &mut self,
158        address: Address,
159        key: U256,
160        value: U256,
161    ) -> Result<(), TempoPrecompileError> {
162        self.deduct_gas(self.gas_params.warm_storage_read_cost())?;
163        self.internals.tstore(address, key, value);
164        Ok(())
165    }
166
167    #[inline]
168    fn emit_event(&mut self, address: Address, event: LogData) -> Result<(), TempoPrecompileError> {
169        self.deduct_gas(
170            gas::LOG
171                + self
172                    .gas_params
173                    .log_cost(event.topics().len() as u8, event.data.len() as u64),
174        )?;
175
176        self.internals.log(Log {
177            address,
178            data: event,
179        });
180
181        Ok(())
182    }
183
184    #[inline]
185    fn sload(&mut self, address: Address, key: U256) -> Result<U256, TempoPrecompileError> {
186        let additional_cost = self.gas_params.cold_storage_additional_cost();
187
188        let value;
189        let is_cold;
190        {
191            let mut account = self.internals.load_account_mut(address)?;
192            let val = account.sload(key, false)?;
193
194            value = val.present_value;
195            is_cold = val.is_cold;
196        };
197
198        // TODO(rakita) can be moved to the beginning of the function. Requires fork.
199        self.deduct_gas(self.gas_params.warm_storage_read_cost())?;
200
201        if is_cold {
202            self.deduct_gas(additional_cost)?;
203        }
204
205        Ok(value)
206    }
207
208    #[inline]
209    fn tload(&mut self, address: Address, key: U256) -> Result<U256, TempoPrecompileError> {
210        self.deduct_gas(self.gas_params.warm_storage_read_cost())?;
211
212        Ok(self.internals.tload(address, key))
213    }
214
215    #[inline]
216    fn deduct_gas(&mut self, gas: u64) -> Result<(), TempoPrecompileError> {
217        self.gas_remaining = self
218            .gas_remaining
219            .checked_sub(gas)
220            .ok_or(TempoPrecompileError::OutOfGas)?;
221        Ok(())
222    }
223
224    #[inline]
225    fn refund_gas(&mut self, gas: i64) {
226        self.gas_refunded = self.gas_refunded.saturating_add(gas);
227    }
228
229    #[inline]
230    fn gas_used(&self) -> u64 {
231        self.gas_limit - self.gas_remaining
232    }
233
234    #[inline]
235    fn gas_refunded(&self) -> i64 {
236        self.gas_refunded
237    }
238
239    #[inline]
240    fn spec(&self) -> TempoHardfork {
241        self.spec
242    }
243
244    #[inline]
245    fn is_static(&self) -> bool {
246        self.is_static
247    }
248
249    #[inline]
250    fn checkpoint(&mut self) -> JournalCheckpoint {
251        let cp = self.internals.checkpoint();
252        #[cfg(debug_assertions)]
253        self.track_checkpoint(&cp);
254        cp
255    }
256
257    #[inline]
258    fn checkpoint_commit(&mut self, _checkpoint: JournalCheckpoint) {
259        #[cfg(debug_assertions)]
260        self.assert_lifo(&_checkpoint, "commit");
261        self.internals.checkpoint_commit()
262    }
263
264    #[inline]
265    fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) {
266        #[cfg(debug_assertions)]
267        self.assert_lifo(&checkpoint, "revert");
268        self.internals.checkpoint_revert(checkpoint)
269    }
270}
271
272/// LIFO checkpoint validation (debug builds only).
273///
274/// Since `EvmInternals` doesn't expose revm's journal depth, we mirror it by
275/// recording each checkpoint's (`journal_i`, `log_i`) on creation and asserting
276/// that commits/reverts always resolve the most recent checkpoint first.
277#[cfg(debug_assertions)]
278impl EvmPrecompileStorageProvider<'_> {
279    /// Records a newly created checkpoint for later LIFO validation.
280    fn track_checkpoint(&mut self, cp: &JournalCheckpoint) {
281        self.checkpoint_stack.push((cp.journal_i, cp.log_i));
282    }
283
284    /// Panics if `cp` is not the most recently created checkpoint.
285    fn assert_lifo(&mut self, cp: &JournalCheckpoint, op: &str) {
286        let top = self
287            .checkpoint_stack
288            .pop()
289            .unwrap_or_else(|| panic!("checkpoint_{op}: no active checkpoint"));
290
291        assert_eq!(
292            (cp.journal_i, cp.log_i),
293            top,
294            "out-of-order checkpoint {op} (expected top of stack)"
295        );
296    }
297}
298
299impl From<EvmInternalsError> for TempoPrecompileError {
300    fn from(value: EvmInternalsError) -> Self {
301        Self::Fatal(value.to_string())
302    }
303}
304
305/// Deducts gas from the remaining gas and returns an error if insufficient.
306#[inline]
307pub fn deduct_gas(gas: &mut u64, additional_cost: u64) -> Result<(), TempoPrecompileError> {
308    *gas = gas
309        .checked_sub(additional_cost)
310        .ok_or(TempoPrecompileError::OutOfGas)?;
311    Ok(())
312}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317    use alloy::primitives::{B256, address, b256, bytes, keccak256};
318    use alloy_evm::{EvmEnv, EvmFactory, EvmInternals, revm::context::Host};
319    use alloy_signer::SignerSync;
320    use alloy_signer_local::PrivateKeySigner;
321    use revm::{
322        database::{CacheDB, EmptyDB},
323        interpreter::StateLoad,
324    };
325    use tempo_evm::TempoEvmFactory;
326
327    #[test]
328    fn test_sstore_sload() -> eyre::Result<()> {
329        let db = CacheDB::new(EmptyDB::new());
330        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
331        let ctx = evm.ctx_mut();
332        let evm_internals =
333            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
334        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
335
336        let addr = Address::random();
337        let key = U256::random();
338
339        let value = U256::random();
340
341        provider.sstore(addr, key, value)?;
342        let sload_val = provider.sload(addr, key)?;
343
344        assert_eq!(sload_val, value);
345        Ok(())
346    }
347
348    #[test]
349    fn test_set_code() -> eyre::Result<()> {
350        let db = CacheDB::new(EmptyDB::new());
351        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
352        let ctx = evm.ctx_mut();
353        let evm_internals =
354            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
355        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
356
357        let addr = Address::random();
358        let code = Bytecode::new_raw(vec![0xff].into());
359        provider.set_code(addr, code.clone())?;
360        drop(provider);
361
362        let Some(StateLoad { data, is_cold: _ }) = evm.load_account_code(addr) else {
363            panic!("Failed to load account code")
364        };
365
366        assert_eq!(data, *code.original_bytes());
367        Ok(())
368    }
369
370    #[test]
371    fn test_get_account_info() -> eyre::Result<()> {
372        let db = CacheDB::new(EmptyDB::new());
373        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
374        let ctx = evm.ctx_mut();
375        let evm_internals =
376            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
377        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
378
379        let address = address!("3000000000000000000000000000000000000003");
380
381        // Get account info for a new account
382        provider.with_account_info(address, &mut |info| {
383            // Should be an empty account
384            assert!(info.balance.is_zero());
385            assert_eq!(info.nonce, 0);
386            // Note: load_account_code may return empty bytecode as Some(empty) for new accounts
387            if let Some(ref code) = info.code {
388                assert!(code.is_empty(), "New account should have empty code");
389            }
390        })?;
391
392        Ok(())
393    }
394
395    #[test]
396    fn test_emit_event() -> eyre::Result<()> {
397        let db = CacheDB::new(EmptyDB::new());
398        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
399        let ctx = evm.ctx_mut();
400        let evm_internals =
401            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
402        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
403
404        let address = address!("4000000000000000000000000000000000000004");
405        let topic = b256!("0000000000000000000000000000000000000000000000000000000000000001");
406        let data = bytes!(
407            "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001"
408        );
409
410        let log_data = LogData::new_unchecked(vec![topic], data);
411
412        // Should not error even though events can't be emitted from handlers
413        provider.emit_event(address, log_data)?;
414
415        Ok(())
416    }
417
418    #[test]
419    fn test_multiple_storage_operations() -> eyre::Result<()> {
420        let db = CacheDB::new(EmptyDB::new());
421        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
422        let ctx = evm.ctx_mut();
423        let evm_internals =
424            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
425        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
426
427        let address = address!("5000000000000000000000000000000000000005");
428
429        // Store multiple values
430        for i in 0..10 {
431            let key = U256::from(i);
432            let value = U256::from(i * 100);
433            provider.sstore(address, key, value)?;
434        }
435
436        // Verify all values
437        for i in 0..10 {
438            let key = U256::from(i);
439            let expected_value = U256::from(i * 100);
440            let loaded_value = provider.sload(address, key)?;
441            assert_eq!(loaded_value, expected_value);
442        }
443
444        Ok(())
445    }
446
447    #[test]
448    fn test_overwrite_storage() -> eyre::Result<()> {
449        let db = CacheDB::new(EmptyDB::new());
450        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
451        let ctx = evm.ctx_mut();
452        let evm_internals =
453            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
454        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
455
456        let address = address!("6000000000000000000000000000000000000006");
457        let key = U256::from(99);
458
459        // Store initial value
460        let initial_value = U256::from(111);
461        provider.sstore(address, key, initial_value)?;
462        assert_eq!(provider.sload(address, key)?, initial_value);
463
464        // Overwrite with new value
465        let new_value = U256::from(999);
466        provider.sstore(address, key, new_value)?;
467        assert_eq!(provider.sload(address, key)?, new_value);
468
469        Ok(())
470    }
471
472    #[test]
473    fn test_different_addresses() -> eyre::Result<()> {
474        let db = CacheDB::new(EmptyDB::new());
475        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
476        let ctx = evm.ctx_mut();
477        let evm_internals =
478            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
479        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
480
481        let address1 = address!("7000000000000000000000000000000000000001");
482        let address2 = address!("7000000000000000000000000000000000000002");
483        let key = U256::from(42);
484
485        // Store different values at the same key for different addresses
486        let value1 = U256::from(100);
487        let value2 = U256::from(200);
488
489        provider.sstore(address1, key, value1)?;
490        provider.sstore(address2, key, value2)?;
491
492        // Verify values are independent
493        assert_eq!(provider.sload(address1, key)?, value1);
494        assert_eq!(provider.sload(address2, key)?, value2);
495
496        Ok(())
497    }
498
499    #[test]
500    fn test_multiple_transient_storage_operations() -> eyre::Result<()> {
501        let db = CacheDB::new(EmptyDB::new());
502        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
503        let ctx = evm.ctx_mut();
504        let evm_internals =
505            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
506        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
507
508        let address = address!("8000000000000000000000000000000000000001");
509
510        // Store multiple values
511        for i in 0..10 {
512            let key = U256::from(i);
513            let value = U256::from(i * 100);
514            provider.tstore(address, key, value)?;
515        }
516
517        // Verify all values
518        for i in 0..10 {
519            let key = U256::from(i);
520            let expected_value = U256::from(i * 100);
521            let loaded_value = provider.tload(address, key)?;
522            assert_eq!(loaded_value, expected_value);
523        }
524
525        Ok(())
526    }
527
528    #[test]
529    fn test_overwrite_transient_storage() -> eyre::Result<()> {
530        let db = CacheDB::new(EmptyDB::new());
531        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
532        let ctx = evm.ctx_mut();
533        let evm_internals =
534            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
535        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
536
537        let address = address!("9000000000000000000000000000000000000001");
538        let key = U256::from(99);
539
540        // Store initial value
541        let initial_value = U256::from(111);
542        provider.tstore(address, key, initial_value)?;
543        assert_eq!(provider.tload(address, key)?, initial_value);
544
545        // Overwrite with new value
546        let new_value = U256::from(999);
547        provider.tstore(address, key, new_value)?;
548        assert_eq!(provider.tload(address, key)?, new_value);
549
550        Ok(())
551    }
552
553    #[test]
554    fn test_transient_storage_different_addresses() -> eyre::Result<()> {
555        let db = CacheDB::new(EmptyDB::new());
556        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
557        let ctx = evm.ctx_mut();
558        let evm_internals =
559            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
560        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
561
562        let address1 = address!("a000000000000000000000000000000000000001");
563        let address2 = address!("a000000000000000000000000000000000000002");
564        let key = U256::from(42);
565
566        // Store different values at the same key for different addresses
567        let value1 = U256::from(100);
568        let value2 = U256::from(200);
569
570        provider.tstore(address1, key, value1)?;
571        provider.tstore(address2, key, value2)?;
572
573        // Verify values are independent
574        assert_eq!(provider.tload(address1, key)?, value1);
575        assert_eq!(provider.tload(address2, key)?, value2);
576
577        Ok(())
578    }
579
580    #[test]
581    fn test_transient_storage_isolation_from_persistent() -> eyre::Result<()> {
582        let db = CacheDB::new(EmptyDB::new());
583        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
584        let ctx = evm.ctx_mut();
585        let evm_internals =
586            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
587        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
588
589        let address = address!("b000000000000000000000000000000000000001");
590        let key = U256::from(123);
591        let persistent_value = U256::from(456);
592        let transient_value = U256::from(789);
593
594        // Store in persistent storage
595        provider.sstore(address, key, persistent_value)?;
596
597        // Store in transient storage with same key
598        provider.tstore(address, key, transient_value)?;
599
600        // Verify they are independent
601        assert_eq!(provider.sload(address, key)?, persistent_value);
602        assert_eq!(provider.tload(address, key)?, transient_value);
603
604        Ok(())
605    }
606
607    #[test]
608    fn test_keccak256_gas() -> eyre::Result<()> {
609        let db = CacheDB::new(EmptyDB::new());
610        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
611        let ctx = evm.ctx_mut();
612        let evm_internals =
613            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
614        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
615
616        // 1 word: KECCAK256(30) + KECCAK256WORD(6) * ceil(11/32) = 36
617        let data = b"hello world";
618        assert_eq!(provider.keccak256(data)?, keccak256(data));
619        assert_eq!(provider.gas_used(), 36);
620
621        // 2 words: 30 + 6*2 = 42, cumulative = 78
622        provider.keccak256(&[0u8; 64])?;
623        assert_eq!(provider.gas_used(), 78);
624        std::mem::drop(provider);
625
626        // OOG: 30 gas is not enough (needs 36 for 1 word)
627        let evm_internals =
628            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
629        let mut provider =
630            EvmPrecompileStorageProvider::new_with_gas_limit(evm_internals, &ctx.cfg, 30);
631        assert!(matches!(
632            provider.keccak256(b"hello"),
633            Err(TempoPrecompileError::OutOfGas)
634        ));
635
636        Ok(())
637    }
638
639    #[test]
640    fn test_recover_signer_gas() -> eyre::Result<()> {
641        let db = CacheDB::new(EmptyDB::new());
642        let mut evm = TempoEvmFactory::default().create_evm(db, EvmEnv::default());
643        let ctx = evm.ctx_mut();
644        let evm_internals =
645            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
646        let mut provider = EvmPrecompileStorageProvider::new_max_gas(evm_internals, &ctx.cfg);
647
648        // Invalid v → None, gas still charged
649        assert!(
650            provider
651                .recover_signer(B256::ZERO, 0, B256::ZERO, B256::ZERO)?
652                .is_none()
653        );
654        assert_eq!(provider.gas_used(), crate::ECRECOVER_GAS);
655
656        // Valid signature → correct recovery
657        let signer = PrivateKeySigner::random();
658        let digest = keccak256(b"test message");
659        let sig = signer.sign_hash_sync(&digest).unwrap();
660        let v = sig.v() as u8 + 27;
661        let r: B256 = sig.r().into();
662        let s: B256 = sig.s().into();
663        assert_eq!(
664            provider.recover_signer(digest, v, r, s)?,
665            Some(signer.address())
666        );
667        assert_eq!(provider.gas_used(), crate::ECRECOVER_GAS * 2);
668        std::mem::drop(provider);
669
670        // OOG: 100 gas is not enough (needs 3000)
671        let evm_internals =
672            EvmInternals::new(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx);
673        let mut provider =
674            EvmPrecompileStorageProvider::new_with_gas_limit(evm_internals, &ctx.cfg, 100);
675        assert!(matches!(
676            provider.recover_signer(digest, v, r, s),
677            Err(TempoPrecompileError::OutOfGas)
678        ));
679
680        Ok(())
681    }
682}