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::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_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 TEMPO_PRECOMPILE_ADDRESSES.iter().chain(TEMPO_TIP20_TOKENS.iter()) {
55                sctx.set_code(*addr, sentinel.clone())
56                    .expect("failed to warm tempo precompile address");
57            }
58        } else {
59            // In non-fork mode, run full genesis initialization.
60            initialize_tempo_genesis_inner(TEST_CONTRACT_ADDRESS, CALLER)
61                .expect("tempo genesis initialization failed");
62        }
63    });
64}
65
66impl FoundryEvmFactory for TempoEvmFactory {
67    type FoundryContext<'db> = TempoContext<&'db mut dyn DatabaseExt<Self>>;
68
69    type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>> =
70        TempoEvm<&'db mut dyn DatabaseExt<Self>, I>;
71
72    fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
73        &self,
74        db: &'db mut dyn DatabaseExt<Self>,
75        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
76        inspector: I,
77    ) -> Self::FoundryEvm<'db, I> {
78        let is_forked = db.is_forked_mode();
79        let spec = *evm_env.spec_id();
80        let mut tempo_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector);
81        tempo_evm.cfg.gas_params = tempo_gas_params(spec);
82        tempo_evm.cfg.tx_chain_id_check = true;
83        if tempo_evm.cfg.tx_gas_limit_cap.is_none() {
84            tempo_evm.cfg.tx_gas_limit_cap = spec.tx_gas_limit_cap();
85        }
86
87        let networks = tempo_evm.inspector().get_networks();
88        networks.inject_precompiles(tempo_evm.precompiles_mut());
89
90        initialize_tempo_evm(&mut tempo_evm, is_forked);
91        tempo_evm
92    }
93
94    fn create_foundry_nested_evm<'db>(
95        &self,
96        db: &'db mut dyn DatabaseExt<Self>,
97        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
98        inspector: &'db mut dyn FoundryInspectorExt<Self::FoundryContext<'db>>,
99    ) -> Box<dyn NestedEvm<Spec = TempoHardfork, Block = TempoBlockEnv, Tx = TempoTxEnv> + 'db>
100    {
101        Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_inner())
102    }
103}
104
105/// Maps a Tempo [`EVMError`] to the common `EVMError<DatabaseError>` used by [`NestedEvm`].
106///
107/// This exists because [`NestedEvm`] currently uses Eth-typed errors. When `NestedEvm` gains
108/// an associated `Error` type, this mapping can be removed.
109pub(crate) fn map_tempo_error(
110    e: EVMError<DatabaseError, TempoInvalidTransaction>,
111) -> EVMError<DatabaseError> {
112    match e {
113        EVMError::Database(db) => EVMError::Database(db),
114        EVMError::Header(h) => EVMError::Header(h),
115        EVMError::Custom(s) => EVMError::Custom(s),
116        EVMError::CustomAny(custom_any_error) => EVMError::CustomAny(custom_any_error),
117        EVMError::Transaction(t) => match t {
118            TempoInvalidTransaction::EthInvalidTransaction(eth) => EVMError::Transaction(eth),
119            t => EVMError::Custom(format!("tempo transaction error: {t}")),
120        },
121    }
122}
123
124impl<'db, I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>> NestedEvm
125    for TempoRevmEvm<'db, I>
126{
127    type Spec = TempoHardfork;
128    type Block = TempoBlockEnv;
129    type Tx = TempoTxEnv;
130
131    fn journal_inner_mut(&mut self) -> &mut JournaledState {
132        &mut self.ctx_mut().journaled_state.inner
133    }
134
135    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>> {
136        let mut handler = TempoEvmHandler::new();
137
138        let memory =
139            SharedMemory::new_with_buffer(self.ctx_ref().local().shared_memory_buffer().clone());
140        let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame };
141
142        let mut frame_result =
143            handler.inspect_run_exec_loop(self, first_frame_input).map_err(map_tempo_error)?;
144
145        handler.last_frame_result(self, &mut frame_result).map_err(map_tempo_error)?;
146
147        Ok(frame_result)
148    }
149
150    fn transact_raw(&mut self, tx: Self::Tx) -> Result<ResultAndState, EVMError<DatabaseError>> {
151        self.set_tx(tx);
152
153        let mut handler = TempoEvmHandler::new();
154        let result = handler.inspect_run(self).map_err(map_tempo_error)?;
155
156        let result = result.map_haltreason(|h| match h {
157            TempoHaltReason::Ethereum(eth) => eth,
158            _ => HaltReason::PrecompileError,
159        });
160
161        Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone()))
162    }
163
164    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
165        self.ctx_ref().evm_clone()
166    }
167}