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        self.set_tx(tx);
133
134        let mut handler = TempoEvmHandler::new();
135        let result = handler.inspect_run(self).map_err(map_tempo_error)?;
136
137        Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone()))
138    }
139
140    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
141        self.ctx_ref().evm_clone()
142    }
143}
144
145/// Maps a Tempo [`EVMError`] to the common `EVMError<DatabaseError>` used by [`NestedEvm`].
146///
147/// This exists because [`NestedEvm`] currently uses Eth-typed errors. When `NestedEvm` gains
148/// an associated `Error` type, this mapping can be removed.
149pub(crate) fn map_tempo_error(
150    e: EVMError<DatabaseError, TempoInvalidTransaction>,
151) -> EVMError<DatabaseError> {
152    match e {
153        EVMError::Database(db) => EVMError::Database(db),
154        EVMError::Header(h) => EVMError::Header(h),
155        EVMError::Custom(s) => EVMError::Custom(s),
156        EVMError::CustomAny(custom_any_error) => EVMError::CustomAny(custom_any_error),
157        EVMError::Transaction(t) => match t {
158            TempoInvalidTransaction::EthInvalidTransaction(eth) => EVMError::Transaction(eth),
159            t => EVMError::Custom(format!("tempo transaction error: {t}")),
160        },
161    }
162}
163
164/// Initialize Tempo precompiles and contracts for a newly created EVM.
165///
166/// In non-fork mode, runs full genesis initialization (precompile sentinel bytecode,
167/// TIP20 fee tokens, standard contracts) via [`StorageCtx::enter_evm`].
168///
169/// In fork mode, warms up precompile and TIP20 token addresses with sentinel bytecode
170/// to prevent repeated RPC round-trips for addresses that are Rust-native precompiles
171/// on Tempo nodes (no real EVM bytecode on-chain).
172pub(crate) fn initialize_tempo_evm<
173    'db,
174    I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>,
175>(
176    evm: &mut TempoEvm<&'db mut dyn DatabaseExt<TempoEvmFactory>, I>,
177    is_forked: bool,
178) {
179    let ctx = evm.ctx_mut();
180    StorageCtx::enter_evm(
181        &mut ctx.journaled_state,
182        &ctx.block,
183        &ctx.cfg,
184        &ctx.tx,
185        StorageActions::disabled(),
186        || {
187            if is_forked {
188                // In fork mode, warm up precompile accounts to avoid repeated RPC fetches.
189                let mut sctx = StorageCtx;
190                let sentinel = Bytecode::new_legacy(Bytes::from_static(SYSTEM_PRECOMPILE_STUB));
191                for addr in TEMPO_PRECOMPILE_ADDRESSES
192                    .iter()
193                    .copied()
194                    .chain(TEMPO_TIP20_TOKENS.iter().copied())
195                {
196                    sctx.set_code(addr, sentinel.clone())
197                        .expect("failed to warm tempo precompile address");
198                }
199            } else {
200                // In non-fork mode, run full genesis initialization.
201                initialize_tempo_test_genesis_inner(TEST_CONTRACT_ADDRESS, CALLER)
202                    .expect("tempo genesis initialization failed");
203            }
204        },
205    );
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211    use crate::backend::Backend;
212    use revm::{
213        context_interface::cfg::GasParams, inspector::NoOpInspector, primitives::hardfork::SpecId,
214    };
215
216    #[test]
217    fn tempo_execution_spec_preserves_network_gas() {
218        let mut db = Backend::<TempoEvmNetwork>::spawn(None).unwrap();
219        let mut evm = TempoEvmFactory::default().create_foundry_evm_with_inspector(
220            &mut db,
221            EvmEnv::default(),
222            NoOpInspector,
223        );
224        for spec in [TempoHardfork::T3, TempoHardfork::T7, TempoHardfork::T14] {
225            TempoEvmFactory::set_execution_spec(evm.ctx_mut(), spec);
226            assert_eq!(evm.cfg.spec, spec);
227            assert_eq!(evm.cfg.gas_params, tempo_gas_params(spec));
228            assert_ne!(evm.cfg.gas_params, GasParams::new_spec(SpecId::OSAKA));
229        }
230    }
231}