tempo_revm/
evm.rs

1use crate::{TempoBlockEnv, TempoTxEnv, instructions};
2use alloy_evm::{Database, precompiles::PrecompilesMap};
3use alloy_primitives::{Log, U256};
4use revm::{
5    Context, Inspector,
6    context::{CfgEnv, ContextError, Evm, FrameStack},
7    handler::{
8        EthFrame, EthPrecompiles, EvmTr, FrameInitOrResult, FrameTr, ItemOrResult,
9        instructions::EthInstructions,
10    },
11    inspector::InspectorEvmTr,
12    interpreter::interpreter::EthInterpreter,
13};
14use tempo_chainspec::hardfork::TempoHardfork;
15use tempo_precompiles::extend_tempo_precompiles;
16
17/// The Tempo EVM context type.
18pub type TempoContext<DB> = Context<TempoBlockEnv, TempoTxEnv, CfgEnv<TempoHardfork>, DB>;
19
20/// TempoEvm extends the Evm with Tempo specific types and logic.
21#[derive(Debug, derive_more::Deref, derive_more::DerefMut)]
22#[expect(clippy::type_complexity)]
23pub struct TempoEvm<DB: Database, I> {
24    /// Inner EVM type.
25    #[deref]
26    #[deref_mut]
27    pub inner: Evm<
28        TempoContext<DB>,
29        I,
30        EthInstructions<EthInterpreter, TempoContext<DB>>,
31        PrecompilesMap,
32        EthFrame<EthInterpreter>,
33    >,
34    /// Preserved logs from the last transaction
35    pub logs: Vec<Log>,
36    /// The fee collected in `collectFeePreTx` call.
37    pub(crate) collected_fee: U256,
38}
39
40impl<DB: Database, I> TempoEvm<DB, I> {
41    /// Create a new Tempo EVM.
42    pub fn new(ctx: TempoContext<DB>, inspector: I) -> Self {
43        let mut precompiles = PrecompilesMap::from_static(EthPrecompiles::default().precompiles);
44        extend_tempo_precompiles(&mut precompiles, &ctx.cfg);
45
46        Self::new_inner(Evm {
47            ctx,
48            inspector,
49            instruction: instructions::tempo_instructions(),
50            precompiles,
51            frame_stack: FrameStack::new(),
52        })
53    }
54
55    /// Inner helper function to create a new Tempo EVM with empty logs.
56    #[inline]
57    #[expect(clippy::type_complexity)]
58    fn new_inner(
59        inner: Evm<
60            TempoContext<DB>,
61            I,
62            EthInstructions<EthInterpreter, TempoContext<DB>>,
63            PrecompilesMap,
64            EthFrame<EthInterpreter>,
65        >,
66    ) -> Self {
67        Self {
68            inner,
69            logs: Vec::new(),
70            collected_fee: U256::ZERO,
71        }
72    }
73}
74
75impl<DB: Database, I> TempoEvm<DB, I> {
76    /// Consumed self and returns a new Evm type with given Inspector.
77    pub fn with_inspector<OINSP>(self, inspector: OINSP) -> TempoEvm<DB, OINSP> {
78        TempoEvm::new_inner(self.inner.with_inspector(inspector))
79    }
80
81    /// Consumes self and returns a new Evm type with given Precompiles.
82    pub fn with_precompiles(self, precompiles: PrecompilesMap) -> Self {
83        Self::new_inner(self.inner.with_precompiles(precompiles))
84    }
85
86    /// Consumes self and returns the inner Inspector.
87    pub fn into_inspector(self) -> I {
88        self.inner.into_inspector()
89    }
90
91    /// Take logs from the EVM.
92    #[inline]
93    pub fn take_logs(&mut self) -> Vec<Log> {
94        std::mem::take(&mut self.logs)
95    }
96}
97
98impl<DB, I> EvmTr for TempoEvm<DB, I>
99where
100    DB: Database,
101{
102    type Context = TempoContext<DB>;
103    type Instructions = EthInstructions<EthInterpreter, TempoContext<DB>>;
104    type Precompiles = PrecompilesMap;
105    type Frame = EthFrame<EthInterpreter>;
106
107    fn all(
108        &self,
109    ) -> (
110        &Self::Context,
111        &Self::Instructions,
112        &Self::Precompiles,
113        &FrameStack<Self::Frame>,
114    ) {
115        self.inner.all()
116    }
117
118    fn all_mut(
119        &mut self,
120    ) -> (
121        &mut Self::Context,
122        &mut Self::Instructions,
123        &mut Self::Precompiles,
124        &mut FrameStack<Self::Frame>,
125    ) {
126        self.inner.all_mut()
127    }
128
129    fn frame_stack(&mut self) -> &mut FrameStack<Self::Frame> {
130        &mut self.inner.frame_stack
131    }
132
133    fn frame_init(
134        &mut self,
135        frame_input: <Self::Frame as FrameTr>::FrameInit,
136    ) -> Result<
137        ItemOrResult<&mut Self::Frame, <Self::Frame as FrameTr>::FrameResult>,
138        ContextError<DB::Error>,
139    > {
140        self.inner.frame_init(frame_input)
141    }
142
143    fn frame_run(&mut self) -> Result<FrameInitOrResult<Self::Frame>, ContextError<DB::Error>> {
144        self.inner.frame_run()
145    }
146
147    #[doc = " Returns the result of the frame to the caller. Frame is popped from the frame stack."]
148    #[doc = " Consumes the frame result or returns it if there is more frames to run."]
149    fn frame_return_result(
150        &mut self,
151        result: <Self::Frame as FrameTr>::FrameResult,
152    ) -> Result<Option<<Self::Frame as FrameTr>::FrameResult>, ContextError<DB::Error>> {
153        self.inner.frame_return_result(result)
154    }
155}
156
157impl<DB, I> InspectorEvmTr for TempoEvm<DB, I>
158where
159    DB: Database,
160    I: Inspector<TempoContext<DB>>,
161{
162    type Inspector = I;
163
164    fn all_inspector(
165        &self,
166    ) -> (
167        &Self::Context,
168        &Self::Instructions,
169        &Self::Precompiles,
170        &FrameStack<Self::Frame>,
171        &Self::Inspector,
172    ) {
173        self.inner.all_inspector()
174    }
175
176    fn all_mut_inspector(
177        &mut self,
178    ) -> (
179        &mut Self::Context,
180        &mut Self::Instructions,
181        &mut Self::Precompiles,
182        &mut FrameStack<Self::Frame>,
183        &mut Self::Inspector,
184    ) {
185        self.inner.all_mut_inspector()
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use alloy_evm::{Evm, EvmFactory};
192    use alloy_primitives::{Address, U256, bytes};
193    use reth_evm::EvmInternals;
194    use revm::{
195        context::{ContextTr, TxEnv},
196        database::{CacheDB, EmptyDB},
197        state::{AccountInfo, Bytecode},
198    };
199    use tempo_contracts::DEFAULT_7702_DELEGATE_ADDRESS;
200    use tempo_evm::TempoEvmFactory;
201    use tempo_precompiles::{
202        storage::{StorageCtx, evm::EvmPrecompileStorageProvider},
203        test_util::TIP20Setup,
204    };
205
206    #[test]
207    fn test_auto_7702_delegation() -> eyre::Result<()> {
208        let db = CacheDB::new(EmptyDB::new());
209        let mut tempo_evm = TempoEvmFactory::default().create_evm(db, Default::default());
210
211        // HACK: initialize default fee token and pathUSD so that fee token validation passes
212        let ctx = tempo_evm.ctx_mut();
213        let mut storage = EvmPrecompileStorageProvider::new_max_gas(
214            EvmInternals::new(&mut ctx.journaled_state, &ctx.block),
215            &ctx.cfg,
216        );
217        StorageCtx::enter(&mut storage, || {
218            TIP20Setup::create("USD", "USD", Address::ZERO)
219                .apply()
220                .unwrap();
221        });
222        drop(storage);
223
224        let caller_0 = Address::random();
225        let tx_env = TxEnv {
226            caller: caller_0,
227            nonce: 0,
228            ..Default::default()
229        };
230        let mut res = tempo_evm.transact_raw(tx_env.into())?;
231        assert!(res.result.is_success());
232
233        let account = res.state.remove(&caller_0).unwrap();
234        assert_eq!(
235            account.info.code.unwrap(),
236            Bytecode::new_eip7702(DEFAULT_7702_DELEGATE_ADDRESS),
237        );
238
239        Ok(())
240    }
241
242    #[test]
243    fn test_access_millis_timestamp() -> eyre::Result<()> {
244        let db = CacheDB::new(EmptyDB::new());
245        let mut tempo_evm = TempoEvmFactory::default().create_evm(db, Default::default());
246        let ctx = tempo_evm.ctx_mut();
247        ctx.block.timestamp = U256::from(1000);
248        ctx.block.timestamp_millis_part = 100;
249        let mut storage = EvmPrecompileStorageProvider::new_max_gas(
250            EvmInternals::new(&mut ctx.journaled_state, &ctx.block),
251            &ctx.cfg,
252        );
253        StorageCtx::enter(&mut storage, || {
254            TIP20Setup::create("USD", "USD", Address::ZERO)
255                .apply()
256                .unwrap();
257        });
258        drop(storage);
259
260        let contract = Address::random();
261
262        // Create a simple contract that returns output of the opcode.
263        ctx.db_mut().insert_account_info(
264            contract,
265            AccountInfo {
266                // MILLISTIMESTAMP PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN
267                code: Some(Bytecode::new_raw(bytes!("0x4F5F5260205FF3"))),
268                ..Default::default()
269            },
270        );
271
272        let tx_env = TxEnv {
273            kind: contract.into(),
274            ..Default::default()
275        };
276        let res = tempo_evm.transact_raw(tx_env.into())?;
277        assert!(res.result.is_success());
278        assert_eq!(
279            U256::from_be_slice(res.result.output().unwrap()),
280            U256::from(1000100)
281        );
282
283        Ok(())
284    }
285}