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