Skip to main content

foundry_evm_core/evm/
tempo.rs

1use alloy_evm::{Evm, EvmEnv, EvmFactory};
2use alloy_primitives::Bytes;
3use foundry_evm_hardforks::TempoHardfork;
4use foundry_fork_db::DatabaseError;
5use revm::{
6    context::{
7        ContextTr, LocalContextTr,
8        result::{EVMError, HaltReason, ResultAndState},
9    },
10    handler::{EvmTr, FrameResult, Handler},
11    inspector::InspectorHandler,
12    interpreter::{FrameInput, SharedMemory, interpreter_action::FrameInit},
13    state::Bytecode,
14};
15use tempo_evm::{TempoBlockEnv, TempoEvmFactory, TempoHaltReason, evm::TempoEvm};
16use tempo_precompiles::{extend_tempo_precompiles, storage::StorageCtx};
17use tempo_revm::{
18    TempoInvalidTransaction, TempoTxEnv, evm::TempoContext, gas_params::tempo_gas_params,
19    handler::TempoEvmHandler,
20};
21
22use crate::{
23    FoundryContextExt, FoundryInspectorExt,
24    backend::{DatabaseExt, JournaledState},
25    constants::{CALLER, TEST_CONTRACT_ADDRESS},
26    evm::{FoundryEvmFactory, NestedEvm},
27    tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS, initialize_tempo_test_genesis_inner},
28};
29
30// Will be removed when the next revm release includes bluealloy/revm#3518.
31pub type TempoRevmEvm<'db, I> = tempo_revm::TempoEvm<&'db mut dyn DatabaseExt<TempoEvmFactory>, I>;
32
33/// Initialize Tempo precompiles and contracts for a newly created EVM.
34///
35/// In non-fork mode, runs full genesis initialization (precompile sentinel bytecode,
36/// TIP20 fee tokens, standard contracts) via [`StorageCtx::enter_evm`].
37///
38/// In fork mode, warms up precompile and TIP20 token addresses with sentinel bytecode
39/// to prevent repeated RPC round-trips for addresses that are Rust-native precompiles
40/// on Tempo nodes (no real EVM bytecode on-chain).
41pub(crate) fn initialize_tempo_evm<
42    'db,
43    I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>,
44>(
45    evm: &mut TempoEvm<&'db mut dyn DatabaseExt<TempoEvmFactory>, I>,
46    is_forked: bool,
47) {
48    let ctx = evm.ctx_mut();
49    StorageCtx::enter_evm(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx, || {
50        if is_forked {
51            // In fork mode, warm up precompile accounts to avoid repeated RPC fetches.
52            let mut sctx = StorageCtx;
53            let sentinel = Bytecode::new_legacy(Bytes::from_static(&[0xef]));
54            for addr in
55                TEMPO_PRECOMPILE_ADDRESSES.iter().copied().chain(TEMPO_TIP20_TOKENS.iter().copied())
56            {
57                sctx.set_code(addr, sentinel.clone())
58                    .expect("failed to warm tempo precompile address");
59            }
60        } else {
61            // In non-fork mode, run full genesis initialization.
62            initialize_tempo_test_genesis_inner(TEST_CONTRACT_ADDRESS, CALLER)
63                .expect("tempo genesis initialization failed");
64        }
65    });
66}
67
68impl FoundryEvmFactory for TempoEvmFactory {
69    type FoundryContext<'db> = TempoContext<&'db mut dyn DatabaseExt<Self>>;
70
71    type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>> =
72        TempoEvm<&'db mut dyn DatabaseExt<Self>, I>;
73
74    fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
75        &self,
76        db: &'db mut dyn DatabaseExt<Self>,
77        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
78        inspector: I,
79    ) -> Self::FoundryEvm<'db, I> {
80        let is_forked = db.is_forked_mode();
81        let spec = *evm_env.spec_id();
82        let mut tempo_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector);
83        tempo_evm.cfg.gas_params = tempo_gas_params(spec);
84        tempo_evm.cfg.tx_chain_id_check = true;
85        if tempo_evm.cfg.tx_gas_limit_cap.is_none() {
86            tempo_evm.cfg.tx_gas_limit_cap = spec.tx_gas_limit_cap();
87        }
88
89        let networks = tempo_evm.inspector().get_networks();
90        networks.inject_precompiles(tempo_evm.precompiles_mut());
91        let cfg = tempo_evm.cfg.clone();
92        extend_tempo_precompiles(tempo_evm.precompiles_mut(), &cfg);
93
94        initialize_tempo_evm(&mut tempo_evm, is_forked);
95        tempo_evm
96    }
97
98    fn create_foundry_nested_evm<'db>(
99        &self,
100        db: &'db mut dyn DatabaseExt<Self>,
101        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
102        inspector: &'db mut dyn FoundryInspectorExt<Self::FoundryContext<'db>>,
103    ) -> Box<dyn NestedEvm<Spec = TempoHardfork, Block = TempoBlockEnv, Tx = TempoTxEnv> + 'db>
104    {
105        Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_inner())
106    }
107}
108
109/// Maps a Tempo [`EVMError`] to the common `EVMError<DatabaseError>` used by [`NestedEvm`].
110///
111/// This exists because [`NestedEvm`] currently uses Eth-typed errors. When `NestedEvm` gains
112/// an associated `Error` type, this mapping can be removed.
113pub(crate) fn map_tempo_error(
114    e: EVMError<DatabaseError, TempoInvalidTransaction>,
115) -> EVMError<DatabaseError> {
116    match e {
117        EVMError::Database(db) => EVMError::Database(db),
118        EVMError::Header(h) => EVMError::Header(h),
119        EVMError::Custom(s) => EVMError::Custom(s),
120        EVMError::CustomAny(custom_any_error) => EVMError::CustomAny(custom_any_error),
121        EVMError::Transaction(t) => match t {
122            TempoInvalidTransaction::EthInvalidTransaction(eth) => EVMError::Transaction(eth),
123            t => EVMError::Custom(format!("tempo transaction error: {t}")),
124        },
125    }
126}
127
128impl<'db, I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>> NestedEvm
129    for TempoRevmEvm<'db, I>
130{
131    type Spec = TempoHardfork;
132    type Block = TempoBlockEnv;
133    type Tx = TempoTxEnv;
134
135    fn journal_inner_mut(&mut self) -> &mut JournaledState {
136        &mut self.ctx_mut().journaled_state.inner
137    }
138
139    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>> {
140        let mut handler = TempoEvmHandler::new();
141        let reservoir = frame.reservoir();
142
143        let memory =
144            SharedMemory::new_with_buffer(self.ctx_ref().local().shared_memory_buffer().clone());
145        let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame };
146
147        let mut frame_result =
148            handler.inspect_run_exec_loop(self, first_frame_input).map_err(map_tempo_error)?;
149
150        handler.last_frame_result(self, reservoir, &mut frame_result).map_err(map_tempo_error)?;
151
152        Ok(frame_result)
153    }
154
155    fn transact_raw(&mut self, tx: Self::Tx) -> Result<ResultAndState, EVMError<DatabaseError>> {
156        self.set_tx(tx);
157
158        let mut handler = TempoEvmHandler::new();
159        let result = handler.inspect_run(self).map_err(map_tempo_error)?;
160
161        let result = result.map_haltreason(|h| match h {
162            TempoHaltReason::Ethereum(eth) => eth,
163            _ => HaltReason::PrecompileError,
164        });
165
166        Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone()))
167    }
168
169    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
170        self.ctx_ref().evm_clone()
171    }
172}