Skip to main content

foundry_evm_core/evm/
tempo.rs

1use super::*;
2
3// Will be removed when the next revm release includes bluealloy/revm#3518.
4pub type TempoRevmEvm<'db, I> = tempo_revm::TempoEvm<&'db mut dyn DatabaseExt<TempoEvmFactory>, I>;
5
6/// Tempo counterpart of [`EthFoundryEvm`]. Wraps `tempo_revm::TempoEvm` and routes execution
7/// through [`TempoEvmHandler`].
8///
9/// Uses [`TempoEvmFactory`] for construction to reuse factory setup logic, then unwraps to the
10/// raw revm EVM via `into_inner()` since the handler operates at the revm level.
11pub struct TempoFoundryEvm<
12    'db,
13    I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>,
14> {
15    pub inner: TempoRevmEvm<'db, I>,
16}
17
18/// Initialize Tempo precompiles and contracts for a newly created [`TempoFoundryEvm`].
19///
20/// In non-fork mode, runs full genesis initialization (precompile sentinel bytecode,
21/// TIP20 fee tokens, standard contracts) via [`StorageCtx::enter_evm`].
22///
23/// In fork mode, warms up precompile and TIP20 token addresses with sentinel bytecode
24/// to prevent repeated RPC round-trips for addresses that are Rust-native precompiles
25/// on Tempo nodes (no real EVM bytecode on-chain).
26pub(crate) fn initialize_tempo_evm<
27    'db,
28    I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>,
29>(
30    evm: &mut TempoFoundryEvm<'db, I>,
31    is_forked: bool,
32) {
33    let ctx = &mut evm.inner.inner.ctx;
34    StorageCtx::enter_evm(&mut ctx.journaled_state, &ctx.block, &ctx.cfg, &ctx.tx, || {
35        if is_forked {
36            // In fork mode, warm up precompile accounts to avoid repeated RPC fetches.
37            let mut sctx = StorageCtx;
38            let sentinel = Bytecode::new_legacy(Bytes::from_static(&[0xef]));
39            for addr in TEMPO_PRECOMPILE_ADDRESSES.iter().chain(TEMPO_TIP20_TOKENS.iter()) {
40                sctx.set_code(*addr, sentinel.clone())
41                    .expect("failed to warm tempo precompile address");
42            }
43        } else {
44            // In non-fork mode, run full genesis initialization.
45            initialize_tempo_genesis_inner(TEST_CONTRACT_ADDRESS, CALLER)
46                .expect("tempo genesis initialization failed");
47        }
48    });
49}
50
51impl FoundryEvmFactory for TempoEvmFactory {
52    type FoundryContext<'db> = TempoContext<&'db mut dyn DatabaseExt<Self>>;
53
54    type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>> =
55        TempoFoundryEvm<'db, I>;
56
57    fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
58        &self,
59        db: &'db mut dyn DatabaseExt<Self>,
60        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
61        inspector: I,
62    ) -> Self::FoundryEvm<'db, I> {
63        let is_forked = db.is_forked_mode();
64        let spec = *evm_env.spec_id();
65        let tempo_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector);
66        let mut inner = tempo_evm.into_inner();
67        inner.ctx.cfg.gas_params = tempo_gas_params(spec);
68        inner.ctx.cfg.tx_chain_id_check = true;
69        if inner.ctx.cfg.tx_gas_limit_cap.is_none() {
70            inner.ctx.cfg.tx_gas_limit_cap = spec.tx_gas_limit_cap();
71        }
72
73        let mut evm = TempoFoundryEvm { inner };
74        let networks = Evm::inspector(&evm).get_networks();
75        networks.inject_precompiles(evm.precompiles_mut());
76
77        initialize_tempo_evm(&mut evm, is_forked);
78        evm
79    }
80
81    fn create_foundry_nested_evm<'db>(
82        &self,
83        db: &'db mut dyn DatabaseExt<Self>,
84        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
85        inspector: &'db mut dyn FoundryInspectorExt<Self::FoundryContext<'db>>,
86    ) -> Box<dyn NestedEvm<Spec = TempoHardfork, Block = TempoBlockEnv, Tx = TempoTxEnv> + 'db>
87    {
88        Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).inner)
89    }
90}
91
92impl<'db, I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>> Evm
93    for TempoFoundryEvm<'db, I>
94{
95    type Precompiles = PrecompilesMap;
96    type Inspector = I;
97    type DB = &'db mut dyn DatabaseExt<TempoEvmFactory>;
98    type Error = EVMError<DatabaseError, TempoInvalidTransaction>;
99    type HaltReason = TempoHaltReason;
100    type Spec = TempoHardfork;
101    type Tx = TempoTxEnv;
102    type BlockEnv = TempoBlockEnv;
103
104    fn block(&self) -> &TempoBlockEnv {
105        &self.inner.block
106    }
107
108    fn chain_id(&self) -> u64 {
109        self.inner.ctx.cfg.chain_id
110    }
111
112    fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) {
113        let evm = &self.inner.inner;
114        (&evm.ctx.journaled_state.database, &evm.inspector, &evm.precompiles)
115    }
116
117    fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) {
118        let evm = &mut self.inner.inner;
119        (&mut evm.ctx.journaled_state.database, &mut evm.inspector, &mut evm.precompiles)
120    }
121
122    fn set_inspector_enabled(&mut self, _enabled: bool) {
123        unimplemented!("TempoFoundryEvm is always inspecting")
124    }
125
126    fn transact_raw(
127        &mut self,
128        tx: Self::Tx,
129    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
130        self.inner.set_tx(tx);
131
132        let mut handler = TempoEvmHandler::new();
133        let result = handler.inspect_run(&mut self.inner)?;
134
135        Ok(ResultAndState::new(result, self.inner.inner.ctx.journaled_state.inner.state.clone()))
136    }
137
138    fn transact_system_call(
139        &mut self,
140        _caller: Address,
141        _contract: Address,
142        _data: Bytes,
143    ) -> Result<ExecResultAndState<ExecutionResult<Self::HaltReason>>, Self::Error> {
144        unimplemented!()
145    }
146
147    fn finish(self) -> (Self::DB, EvmEnv<Self::Spec, Self::BlockEnv>)
148    where
149        Self: Sized,
150    {
151        let revm_evm = self.inner.inner;
152        let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = revm_evm.ctx;
153        (journaled_state.database, EvmEnv { block_env, cfg_env })
154    }
155}
156
157impl<'db, I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>> Deref
158    for TempoFoundryEvm<'db, I>
159{
160    type Target = TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>;
161
162    fn deref(&self) -> &Self::Target {
163        &self.inner.ctx
164    }
165}
166
167impl<'db, I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>> DerefMut
168    for TempoFoundryEvm<'db, I>
169{
170    fn deref_mut(&mut self) -> &mut Self::Target {
171        &mut self.inner.ctx
172    }
173}
174
175/// Maps a Tempo [`EVMError`] to the common `EVMError<DatabaseError>` used by [`NestedEvm`].
176///
177/// This exists because [`NestedEvm`] currently uses Eth-typed errors. When `NestedEvm` gains
178/// an associated `Error` type, this mapping can be removed.
179pub(crate) fn map_tempo_error(
180    e: EVMError<DatabaseError, TempoInvalidTransaction>,
181) -> EVMError<DatabaseError> {
182    match e {
183        EVMError::Database(db) => EVMError::Database(db),
184        EVMError::Header(h) => EVMError::Header(h),
185        EVMError::Custom(s) => EVMError::Custom(s),
186        EVMError::CustomAny(custom_any_error) => EVMError::CustomAny(custom_any_error),
187        EVMError::Transaction(t) => match t {
188            TempoInvalidTransaction::EthInvalidTransaction(eth) => EVMError::Transaction(eth),
189            t => EVMError::Custom(format!("tempo transaction error: {t}")),
190        },
191    }
192}
193
194impl<'db, I: FoundryInspectorExt<TempoContext<&'db mut dyn DatabaseExt<TempoEvmFactory>>>> NestedEvm
195    for TempoRevmEvm<'db, I>
196{
197    type Spec = TempoHardfork;
198    type Block = TempoBlockEnv;
199    type Tx = TempoTxEnv;
200
201    fn journal_inner_mut(&mut self) -> &mut JournaledState {
202        &mut self.ctx_mut().journaled_state.inner
203    }
204
205    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>> {
206        let mut handler = TempoEvmHandler::new();
207
208        let memory =
209            SharedMemory::new_with_buffer(self.ctx().local().shared_memory_buffer().clone());
210        let first_frame_input = FrameInit { depth: 0, memory, frame_input: frame };
211
212        let mut frame_result =
213            handler.inspect_run_exec_loop(self, first_frame_input).map_err(map_tempo_error)?;
214
215        handler.last_frame_result(self, &mut frame_result).map_err(map_tempo_error)?;
216
217        Ok(frame_result)
218    }
219
220    fn transact_raw(
221        &mut self,
222        tx: Self::Tx,
223    ) -> Result<ResultAndState<HaltReason>, EVMError<DatabaseError>> {
224        self.set_tx(tx);
225
226        let mut handler = TempoEvmHandler::new();
227        let result = handler.inspect_run(self).map_err(map_tempo_error)?;
228
229        let result = result.map_haltreason(|h| match h {
230            TempoHaltReason::Ethereum(eth) => eth,
231            _ => HaltReason::PrecompileError,
232        });
233
234        Ok(ResultAndState::new(result, self.ctx.journaled_state.inner.state.clone()))
235    }
236
237    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
238        self.ctx_ref().evm_clone()
239    }
240}