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        tempo_evm.cfg.gas_params = tempo_gas_params(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
93impl<'db, I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>> NestedEvm
94    for TempoRevmEvm<'db, I>
95{
96    type Spec = TempoHardfork;
97    type Block = TempoBlockEnv;
98    type Tx = TempoTxEnv;
99    type Chain = ();
100    type Journal = Journal<&'db mut dyn DatabaseExt<TempoEvmFactory>>;
101
102    fn tx_mut(&mut self) -> &mut Self::Tx {
103        self.ctx_mut().tx_mut()
104    }
105
106    fn journal_inner_mut(&mut self) -> &mut JournaledState {
107        &mut self.ctx_mut().journaled_state.inner
108    }
109
110    fn chain_mut(&mut self) -> &mut Self::Chain {
111        &mut self.ctx_mut().chain
112    }
113
114    fn precompiles_mut(&mut self) -> &mut alloy_evm::precompiles::PrecompilesMap {
115        &mut self.precompiles
116    }
117
118    fn journal_mut(&mut self) -> &mut Self::Journal {
119        &mut self.ctx_mut().journaled_state
120    }
121
122    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>> {
123        run_inspected_frame(self, TempoEvmHandler::new(), frame).map_err(map_tempo_error)
124    }
125
126    fn transact_raw(&mut self, tx: Self::Tx) -> eyre::Result<ResultAndState> {
127        self.set_tx(tx);
128
129        let mut handler = TempoEvmHandler::new();
130        let result = handler.inspect_run(self).map_err(map_tempo_error)?;
131
132        Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone()))
133    }
134
135    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
136        self.ctx_ref().evm_clone()
137    }
138}
139
140/// Maps a Tempo [`EVMError`] to the common `EVMError<DatabaseError>` used by [`NestedEvm`].
141///
142/// This exists because [`NestedEvm`] currently uses Eth-typed errors. When `NestedEvm` gains
143/// an associated `Error` type, this mapping can be removed.
144pub(crate) fn map_tempo_error(
145    e: EVMError<DatabaseError, TempoInvalidTransaction>,
146) -> EVMError<DatabaseError> {
147    match e {
148        EVMError::Database(db) => EVMError::Database(db),
149        EVMError::Header(h) => EVMError::Header(h),
150        EVMError::Custom(s) => EVMError::Custom(s),
151        EVMError::CustomAny(custom_any_error) => EVMError::CustomAny(custom_any_error),
152        EVMError::Transaction(t) => match t {
153            TempoInvalidTransaction::EthInvalidTransaction(eth) => EVMError::Transaction(eth),
154            t => EVMError::Custom(format!("tempo transaction error: {t}")),
155        },
156    }
157}
158
159/// Initialize Tempo precompiles and contracts for a newly created EVM.
160///
161/// In non-fork mode, runs full genesis initialization (precompile sentinel bytecode,
162/// TIP20 fee tokens, standard contracts) via [`StorageCtx::enter_evm`].
163///
164/// In fork mode, warms up precompile and TIP20 token addresses with sentinel bytecode
165/// to prevent repeated RPC round-trips for addresses that are Rust-native precompiles
166/// on Tempo nodes (no real EVM bytecode on-chain).
167pub(crate) fn initialize_tempo_evm<
168    'db,
169    I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>,
170>(
171    evm: &mut TempoEvm<&'db mut dyn DatabaseExt<TempoEvmFactory>, I>,
172    is_forked: bool,
173) {
174    let ctx = evm.ctx_mut();
175    StorageCtx::enter_evm(
176        &mut ctx.journaled_state,
177        &ctx.block,
178        &ctx.cfg,
179        &ctx.tx,
180        StorageActions::disabled(),
181        || {
182            if is_forked {
183                // In fork mode, warm up precompile accounts to avoid repeated RPC fetches.
184                let mut sctx = StorageCtx;
185                let sentinel = Bytecode::new_legacy(Bytes::from_static(SYSTEM_PRECOMPILE_STUB));
186                for addr in TEMPO_PRECOMPILE_ADDRESSES
187                    .iter()
188                    .copied()
189                    .chain(TEMPO_TIP20_TOKENS.iter().copied())
190                {
191                    sctx.set_code(addr, sentinel.clone())
192                        .expect("failed to warm tempo precompile address");
193                }
194            } else {
195                // In non-fork mode, run full genesis initialization.
196                initialize_tempo_test_genesis_inner(TEST_CONTRACT_ADDRESS, CALLER)
197                    .expect("tempo genesis initialization failed");
198            }
199        },
200    );
201}