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        Journal,
8        result::{EVMError, ResultAndState},
9    },
10    handler::{EvmTr, FrameResult},
11    inspector::InspectorHandler,
12    interpreter::FrameInput,
13    state::Bytecode,
14};
15use tempo_alloy::TempoNetwork;
16use tempo_evm::{TempoBlockEnv, TempoEvmFactory, evm::TempoEvm};
17use tempo_precompiles::{
18    extend_tempo_precompiles,
19    storage::{StorageActions, StorageCtx},
20};
21use tempo_revm::{
22    TempoInvalidTransaction, TempoTxEnv, evm::TempoContext, gas_params::tempo_gas_params,
23    handler::TempoEvmHandler,
24};
25
26use crate::{
27    FoundryContextExt, FoundryInspectorExt,
28    backend::{DatabaseExt, JournaledState},
29    constants::{CALLER, SYSTEM_PRECOMPILE_STUB, TEST_CONTRACT_ADDRESS},
30    evm::{FoundryEvmFactory, FoundryEvmNetwork, NestedEvm, NestedEvmFor, run_inspected_frame},
31    tempo::{TEMPO_PRECOMPILE_ADDRESSES, TEMPO_TIP20_TOKENS, initialize_tempo_test_genesis_inner},
32};
33
34#[derive(Clone, Copy, Debug, Default)]
35pub struct TempoEvmNetwork;
36impl FoundryEvmNetwork for TempoEvmNetwork {
37    type Network = TempoNetwork;
38    type EvmFactory = TempoEvmFactory;
39}
40
41// Will be removed when the next revm release includes bluealloy/revm#3518.
42pub type TempoRevmEvm<'db, I> = tempo_revm::TempoEvm<&'db mut dyn DatabaseExt<TempoEvmFactory>, I>;
43
44impl FoundryEvmFactory for TempoEvmFactory {
45    type Chain = ();
46    type FoundryContext<'db> = TempoContext<&'db mut dyn DatabaseExt<Self>>;
47
48    type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>> =
49        TempoEvm<&'db mut dyn DatabaseExt<Self>, I>;
50
51    fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
52        &self,
53        db: &'db mut dyn DatabaseExt<Self>,
54        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
55        inspector: I,
56    ) -> Self::FoundryEvm<'db, I> {
57        let is_forked = db.is_forked_mode();
58        let spec = *evm_env.spec_id();
59        let mut tempo_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector);
60        Self::set_execution_spec(tempo_evm.ctx_mut(), spec);
61        tempo_evm.cfg.tx_chain_id_check = true;
62        if tempo_evm.cfg.tx_gas_limit_cap.is_none() {
63            tempo_evm.cfg.tx_gas_limit_cap = spec.tx_gas_limit_cap();
64        }
65
66        // Re-extend Tempo precompiles, preserving shared non-creditable slots.
67        let cfg = tempo_evm.cfg.clone();
68        let non_creditable_slots = tempo_evm.non_creditable_slots();
69        extend_tempo_precompiles(
70            tempo_evm.precompiles_mut(),
71            &cfg,
72            StorageActions::disabled(),
73            non_creditable_slots,
74        );
75
76        initialize_tempo_evm(&mut tempo_evm, is_forked);
77        tempo_evm
78    }
79
80    fn create_nested_evm_with_inspector<'db, I>(
81        &self,
82        db: &'db mut dyn DatabaseExt<Self>,
83        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
84        inspector: I,
85    ) -> NestedEvmFor<'db, Self>
86    where
87        I: FoundryInspectorExt<Self::FoundryContext<'db>> + 'db,
88    {
89        Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_inner())
90    }
91
92    fn set_execution_spec(context: &mut Self::FoundryContext<'_>, spec: Self::Spec) {
93        context.cfg.spec = spec;
94        context.cfg.set_gas_params(tempo_gas_params(spec));
95    }
96}
97
98impl<'db, I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>> NestedEvm
99    for TempoRevmEvm<'db, I>
100{
101    type Spec = TempoHardfork;
102    type Block = TempoBlockEnv;
103    type Tx = TempoTxEnv;
104    type Chain = ();
105    type Journal = Journal<&'db mut dyn DatabaseExt<TempoEvmFactory>>;
106
107    fn tx_mut(&mut self) -> &mut Self::Tx {
108        self.ctx_mut().tx_mut()
109    }
110
111    fn journal_inner_mut(&mut self) -> &mut JournaledState {
112        &mut self.ctx_mut().journaled_state.inner
113    }
114
115    fn chain_mut(&mut self) -> &mut Self::Chain {
116        &mut self.ctx_mut().chain
117    }
118
119    fn precompiles_mut(&mut self) -> &mut alloy_evm::precompiles::PrecompilesMap {
120        &mut self.precompiles
121    }
122
123    fn journal_mut(&mut self) -> &mut Self::Journal {
124        &mut self.ctx_mut().journaled_state
125    }
126
127    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>> {
128        run_inspected_frame(self, TempoEvmHandler::new(), frame).map_err(map_tempo_error)
129    }
130
131    fn transact_raw(&mut self, tx: Self::Tx) -> eyre::Result<ResultAndState> {
132        // The backend seeds nested transactions at outer depth + 1. Tempo AA's batch checkpoint
133        // supplies that increment before inspector callbacks, so avoid counting it twice.
134        if tx.tempo_tx_env.is_some() && self.ctx.journaled_state.inner.depth > 0 {
135            self.ctx.journaled_state.inner.depth -= 1;
136        }
137        self.set_tx(tx);
138
139        let mut handler = TempoEvmHandler::new();
140        let result = handler.inspect_run(self).map_err(map_tempo_error)?;
141
142        Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone()))
143    }
144
145    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
146        self.ctx_ref().evm_clone()
147    }
148}
149
150/// Maps a Tempo [`EVMError`] to the common `EVMError<DatabaseError>` used by [`NestedEvm`].
151///
152/// This exists because [`NestedEvm`] currently uses Eth-typed errors. When `NestedEvm` gains
153/// an associated `Error` type, this mapping can be removed.
154pub(crate) fn map_tempo_error(
155    e: EVMError<DatabaseError, TempoInvalidTransaction>,
156) -> EVMError<DatabaseError> {
157    match e {
158        EVMError::Database(db) => EVMError::Database(db),
159        EVMError::Header(h) => EVMError::Header(h),
160        EVMError::Custom(s) => EVMError::Custom(s),
161        EVMError::CustomAny(custom_any_error) => EVMError::CustomAny(custom_any_error),
162        EVMError::Transaction(t) => match t {
163            TempoInvalidTransaction::EthInvalidTransaction(eth) => EVMError::Transaction(eth),
164            t => EVMError::Custom(format!("tempo transaction error: {t}")),
165        },
166    }
167}
168
169/// Initialize Tempo precompiles and contracts for a newly created EVM.
170///
171/// In non-fork mode, runs full genesis initialization (precompile sentinel bytecode,
172/// TIP20 fee tokens, standard contracts) via [`StorageCtx::enter_evm`].
173///
174/// In fork mode, warms up precompile and TIP20 token addresses with sentinel bytecode
175/// to prevent repeated RPC round-trips for addresses that are Rust-native precompiles
176/// on Tempo nodes (no real EVM bytecode on-chain).
177pub(crate) fn initialize_tempo_evm<
178    'db,
179    I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>,
180>(
181    evm: &mut TempoEvm<&'db mut dyn DatabaseExt<TempoEvmFactory>, I>,
182    is_forked: bool,
183) {
184    let ctx = evm.ctx_mut();
185    StorageCtx::enter_evm(
186        &mut ctx.journaled_state,
187        &ctx.block,
188        &ctx.cfg,
189        &ctx.tx,
190        StorageActions::disabled(),
191        || {
192            if is_forked {
193                // In fork mode, warm up precompile accounts to avoid repeated RPC fetches.
194                // Skip addresses that fail to load. Later accesses fetch them again and
195                // return the database error instead of a panic.
196                let mut sctx = StorageCtx;
197                let sentinel = Bytecode::new_legacy(Bytes::from_static(SYSTEM_PRECOMPILE_STUB));
198                for addr in TEMPO_PRECOMPILE_ADDRESSES
199                    .iter()
200                    .copied()
201                    .chain(TEMPO_TIP20_TOKENS.iter().copied())
202                {
203                    let _ = sctx.set_code(addr, sentinel.clone());
204                }
205            } else {
206                // In non-fork mode, run full genesis initialization.
207                initialize_tempo_test_genesis_inner(TEST_CONTRACT_ADDRESS, CALLER)
208                    .expect("tempo genesis initialization failed");
209            }
210        },
211    );
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use crate::backend::Backend;
218    use revm::{
219        context_interface::cfg::GasParams, inspector::NoOpInspector, primitives::hardfork::SpecId,
220    };
221
222    #[test]
223    fn tempo_execution_spec_preserves_network_gas() {
224        let mut db = Backend::<TempoEvmNetwork>::spawn(None).unwrap();
225        let mut evm = TempoEvmFactory::default().create_foundry_evm_with_inspector(
226            &mut db,
227            EvmEnv::default(),
228            NoOpInspector,
229        );
230        for spec in [TempoHardfork::T3, TempoHardfork::T7, TempoHardfork::T14] {
231            TempoEvmFactory::set_execution_spec(evm.ctx_mut(), spec);
232            assert_eq!(evm.cfg.spec, spec);
233            assert_eq!(evm.cfg.gas_params, tempo_gas_params(spec));
234            assert_ne!(evm.cfg.gas_params, GasParams::new_spec(SpecId::OSAKA));
235        }
236    }
237}