tempo_evm/
evm.rs

1use alloy_evm::{
2    Database, Evm, EvmEnv, EvmFactory,
3    precompiles::PrecompilesMap,
4    revm::{
5        Context, ExecuteEvm, InspectEvm, Inspector, SystemCallEvm,
6        context::result::{EVMError, ResultAndState},
7        inspector::NoOpInspector,
8    },
9};
10use alloy_primitives::{Address, Bytes, Log, TxKind};
11use reth_revm::{InspectSystemCallEvm, MainContext, context::result::ExecutionResult};
12use std::ops::{Deref, DerefMut};
13use tempo_chainspec::hardfork::TempoHardfork;
14use tempo_revm::{TempoHaltReason, TempoInvalidTransaction, TempoTxEnv, evm::TempoContext};
15
16use crate::TempoBlockEnv;
17
18#[derive(Debug, Default, Clone, Copy)]
19#[non_exhaustive]
20pub struct TempoEvmFactory;
21
22impl EvmFactory for TempoEvmFactory {
23    type Evm<DB: Database, I: Inspector<Self::Context<DB>>> = TempoEvm<DB, I>;
24    type Context<DB: Database> = TempoContext<DB>;
25    type Tx = TempoTxEnv;
26    type Error<DBError: std::error::Error + Send + Sync + 'static> =
27        EVMError<DBError, TempoInvalidTransaction>;
28    type HaltReason = TempoHaltReason;
29    type Spec = TempoHardfork;
30    type BlockEnv = TempoBlockEnv;
31    type Precompiles = PrecompilesMap;
32
33    fn create_evm<DB: Database>(
34        &self,
35        db: DB,
36        input: EvmEnv<Self::Spec, Self::BlockEnv>,
37    ) -> Self::Evm<DB, NoOpInspector> {
38        TempoEvm::new(db, input)
39    }
40
41    fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>>>(
42        &self,
43        db: DB,
44        input: EvmEnv<Self::Spec, Self::BlockEnv>,
45        inspector: I,
46    ) -> Self::Evm<DB, I> {
47        TempoEvm::new(db, input).with_inspector(inspector)
48    }
49}
50
51/// Tempo EVM implementation.
52///
53/// This is a wrapper type around the `revm` ethereum evm with optional [`Inspector`] (tracing)
54/// support. [`Inspector`] support is configurable at runtime because it's part of the underlying
55/// `RevmEvm` type.
56#[expect(missing_debug_implementations)]
57pub struct TempoEvm<DB: Database, I = NoOpInspector> {
58    inner: tempo_revm::TempoEvm<DB, I>,
59    inspect: bool,
60}
61
62impl<DB: Database> TempoEvm<DB> {
63    /// Create a new [`TempoEvm`] instance.
64    pub fn new(db: DB, input: EvmEnv<TempoHardfork, TempoBlockEnv>) -> Self {
65        let ctx = Context::mainnet()
66            .with_db(db)
67            .with_block(input.block_env)
68            .with_cfg(input.cfg_env)
69            .with_tx(Default::default());
70
71        Self {
72            inner: tempo_revm::TempoEvm::new(ctx, NoOpInspector {}),
73            inspect: false,
74        }
75    }
76}
77
78impl<DB: Database, I> TempoEvm<DB, I> {
79    /// Provides a reference to the EVM context.
80    pub const fn ctx(&self) -> &TempoContext<DB> {
81        &self.inner.inner.ctx
82    }
83
84    /// Provides a mutable reference to the EVM context.
85    pub fn ctx_mut(&mut self) -> &mut TempoContext<DB> {
86        &mut self.inner.inner.ctx
87    }
88
89    /// Sets the inspector for the EVM.
90    pub fn with_inspector<OINSP>(self, inspector: OINSP) -> TempoEvm<DB, OINSP> {
91        TempoEvm {
92            inner: self.inner.with_inspector(inspector),
93            inspect: true,
94        }
95    }
96
97    /// Takes the inner EVM's revert logs.
98    ///
99    /// This is used as a work around to allow logs to be
100    /// included for reverting transactions.
101    ///
102    /// TODO: remove once revm supports emitting logs for reverted transactions
103    ///
104    /// <https://github.com/tempoxyz/tempo/pull/729>
105    pub fn take_revert_logs(&mut self) -> Vec<Log> {
106        std::mem::take(&mut self.inner.logs)
107    }
108}
109
110impl<DB: Database, I> Deref for TempoEvm<DB, I>
111where
112    DB: Database,
113    I: Inspector<TempoContext<DB>>,
114{
115    type Target = TempoContext<DB>;
116
117    #[inline]
118    fn deref(&self) -> &Self::Target {
119        self.ctx()
120    }
121}
122
123impl<DB: Database, I> DerefMut for TempoEvm<DB, I>
124where
125    DB: Database,
126    I: Inspector<TempoContext<DB>>,
127{
128    #[inline]
129    fn deref_mut(&mut self) -> &mut Self::Target {
130        self.ctx_mut()
131    }
132}
133
134impl<DB, I> Evm for TempoEvm<DB, I>
135where
136    DB: Database,
137    I: Inspector<TempoContext<DB>>,
138{
139    type DB = DB;
140    type Tx = TempoTxEnv;
141    type Error = EVMError<DB::Error, TempoInvalidTransaction>;
142    type HaltReason = TempoHaltReason;
143    type Spec = TempoHardfork;
144    type BlockEnv = TempoBlockEnv;
145    type Precompiles = PrecompilesMap;
146    type Inspector = I;
147
148    fn block(&self) -> &Self::BlockEnv {
149        &self.block
150    }
151
152    fn chain_id(&self) -> u64 {
153        self.cfg.chain_id
154    }
155
156    fn transact_raw(
157        &mut self,
158        tx: Self::Tx,
159    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
160        if tx.is_system_tx {
161            let TxKind::Call(to) = tx.inner.kind else {
162                return Err(TempoInvalidTransaction::SystemTransactionMustBeCall.into());
163            };
164
165            let mut result = if self.inspect {
166                self.inner
167                    .inspect_system_call_with_caller(tx.inner.caller, to, tx.inner.data)?
168            } else {
169                self.inner
170                    .system_call_with_caller(tx.inner.caller, to, tx.inner.data)?
171            };
172
173            // system transactions should not consume any gas
174            let ExecutionResult::Success {
175                gas_used,
176                gas_refunded,
177                ..
178            } = &mut result.result
179            else {
180                return Err(TempoInvalidTransaction::SystemTransactionFailed(result.result).into());
181            };
182
183            *gas_used = 0;
184            *gas_refunded = 0;
185
186            Ok(result)
187        } else if self.inspect {
188            self.inner.inspect_tx(tx)
189        } else {
190            self.inner.transact(tx)
191        }
192    }
193
194    fn transact_system_call(
195        &mut self,
196        caller: Address,
197        contract: Address,
198        data: Bytes,
199    ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
200        self.inner.system_call_with_caller(caller, contract, data)
201    }
202
203    fn finish(self) -> (Self::DB, EvmEnv<Self::Spec, Self::BlockEnv>) {
204        let Context {
205            block: block_env,
206            cfg: cfg_env,
207            journaled_state,
208            ..
209        } = self.inner.inner.ctx;
210
211        (journaled_state.database, EvmEnv { block_env, cfg_env })
212    }
213
214    fn set_inspector_enabled(&mut self, enabled: bool) {
215        self.inspect = enabled;
216    }
217
218    fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) {
219        (
220            &self.inner.inner.ctx.journaled_state.database,
221            &self.inner.inner.inspector,
222            &self.inner.inner.precompiles,
223        )
224    }
225
226    fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) {
227        (
228            &mut self.inner.inner.ctx.journaled_state.database,
229            &mut self.inner.inner.inspector,
230            &mut self.inner.inner.precompiles,
231        )
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use reth_revm::context::BlockEnv;
238    use revm::{context::TxEnv, database::EmptyDB};
239
240    use super::*;
241
242    #[test]
243    fn can_execute_system_tx() {
244        let mut evm = TempoEvm::new(
245            EmptyDB::default(),
246            EvmEnv {
247                block_env: TempoBlockEnv {
248                    inner: BlockEnv {
249                        basefee: 1,
250                        ..Default::default()
251                    },
252                    ..Default::default()
253                },
254                ..Default::default()
255            },
256        );
257        let result = evm
258            .transact(TempoTxEnv {
259                inner: TxEnv {
260                    caller: Address::ZERO,
261                    gas_price: 0,
262                    gas_limit: 21000,
263                    ..Default::default()
264                },
265                is_system_tx: true,
266                ..Default::default()
267            })
268            .unwrap();
269
270        assert!(result.result.is_success());
271    }
272}