1use alloy_evm::{
2 Database, Evm, EvmEnv, EvmFactory,
3 precompiles::PrecompilesMap,
4 revm::{
5 Context, ExecuteEvm, InspectEvm, Inspector, SystemCallEvm,
6 context::result::{EVMError, ResultAndState, ResultGas},
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#[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 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 pub const fn ctx(&self) -> &TempoContext<DB> {
81 &self.inner.inner.ctx
82 }
83
84 pub fn ctx_mut(&mut self) -> &mut TempoContext<DB> {
86 &mut self.inner.inner.ctx
87 }
88
89 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 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 let ExecutionResult::Success { gas, .. } = &mut result.result else {
175 return Err(
176 TempoInvalidTransaction::SystemTransactionFailed(result.result.into()).into(),
177 );
178 };
179
180 *gas = ResultGas::default().with_limit(tx.inner.gas_limit);
181
182 Ok(result)
183 } else if self.inspect {
184 self.inner.inspect_tx(tx)
185 } else {
186 self.inner.transact(tx)
187 }
188 }
189
190 fn transact_system_call(
191 &mut self,
192 caller: Address,
193 contract: Address,
194 data: Bytes,
195 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
196 self.inner.system_call_with_caller(caller, contract, data)
197 }
198
199 fn finish(self) -> (Self::DB, EvmEnv<Self::Spec, Self::BlockEnv>) {
200 let Context {
201 block: block_env,
202 cfg: cfg_env,
203 journaled_state,
204 ..
205 } = self.inner.inner.ctx;
206
207 (journaled_state.database, EvmEnv { block_env, cfg_env })
208 }
209
210 fn set_inspector_enabled(&mut self, enabled: bool) {
211 self.inspect = enabled;
212 }
213
214 fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) {
215 (
216 &self.inner.inner.ctx.journaled_state.database,
217 &self.inner.inner.inspector,
218 &self.inner.inner.precompiles,
219 )
220 }
221
222 fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) {
223 (
224 &mut self.inner.inner.ctx.journaled_state.database,
225 &mut self.inner.inner.inspector,
226 &mut self.inner.inner.precompiles,
227 )
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use crate::test_utils::{test_evm, test_evm_with_basefee};
234 use revm::{
235 context::{CfgEnv, TxEnv},
236 database::{EmptyDB, in_memory_db::CacheDB},
237 };
238 use tempo_chainspec::hardfork::TempoHardfork;
239 use tempo_revm::gas_params::tempo_gas_params;
240
241 use super::*;
242
243 #[test]
244 fn can_execute_system_tx() {
245 let mut evm = test_evm(EmptyDB::default());
246 let result = evm
247 .transact(TempoTxEnv {
248 inner: TxEnv {
249 caller: Address::ZERO,
250 gas_price: 0,
251 gas_limit: 21000,
252 ..Default::default()
253 },
254 is_system_tx: true,
255 ..Default::default()
256 })
257 .unwrap();
258
259 assert!(result.result.is_success());
260 }
261
262 #[test]
263 fn test_transact_raw() {
264 let mut evm = test_evm_with_basefee(EmptyDB::default(), 0);
265
266 let tx = TempoTxEnv {
267 inner: TxEnv {
268 caller: Address::repeat_byte(0x01),
269 gas_price: 0,
270 gas_limit: 21000,
271 kind: TxKind::Call(Address::repeat_byte(0x02)),
272 ..Default::default()
273 },
274 is_system_tx: false,
275 fee_token: None,
276 ..Default::default()
277 };
278
279 let result = evm.transact_raw(tx);
280 assert!(result.is_ok());
281
282 let result = result.unwrap();
283 assert!(result.result.is_success());
284 assert_eq!(result.result.gas_used(), 21000);
285 }
286
287 #[test]
288 fn test_transact_raw_system_tx() {
289 let mut evm = test_evm(EmptyDB::default());
290
291 let tx = TempoTxEnv {
293 inner: TxEnv {
294 caller: Address::ZERO,
295 gas_price: 0,
296 gas_limit: 21000,
297 kind: TxKind::Call(Address::repeat_byte(0x01)),
298 ..Default::default()
299 },
300 is_system_tx: true,
301 ..Default::default()
302 };
303
304 let result = evm.transact_raw(tx);
305 assert!(result.is_ok());
306
307 let result = result.unwrap();
308 assert!(result.result.is_success());
309 assert_eq!(result.result.gas_used(), 0);
311 }
312
313 #[test]
314 fn test_transact_raw_system_tx_must_be_call() {
315 let mut evm = test_evm(EmptyDB::default());
316
317 let tx = TempoTxEnv {
319 inner: TxEnv {
320 caller: Address::ZERO,
321 gas_price: 0,
322 gas_limit: 21000,
323 kind: TxKind::Create,
324 ..Default::default()
325 },
326 is_system_tx: true,
327 ..Default::default()
328 };
329
330 let result = evm.transact_raw(tx);
331 assert!(result.is_err());
332
333 let err = result.unwrap_err();
334 assert!(matches!(
335 err,
336 EVMError::Transaction(TempoInvalidTransaction::SystemTransactionMustBeCall)
337 ));
338 }
339
340 #[test]
341 fn test_transact_raw_system_tx_failed() {
342 let mut cache_db = CacheDB::new(EmptyDB::default());
343 let revert_code = Bytes::from_static(&[0x60, 0x00, 0x60, 0x00, 0xfd]);
345 let contract_addr = Address::repeat_byte(0xaa);
346
347 cache_db.insert_account_info(
348 contract_addr,
349 revm::state::AccountInfo {
350 code_hash: alloy_primitives::keccak256(&revert_code),
351 code: Some(revm::bytecode::Bytecode::new_raw(revert_code)),
352 ..Default::default()
353 },
354 );
355
356 let mut evm = test_evm(cache_db);
357
358 let tx = TempoTxEnv {
360 inner: TxEnv {
361 caller: Address::ZERO,
362 gas_price: 0,
363 gas_limit: 1_000_000,
364 kind: TxKind::Call(contract_addr),
365 ..Default::default()
366 },
367 is_system_tx: true,
368 ..Default::default()
369 };
370
371 let result = evm.transact_raw(tx);
372 assert!(result.is_err());
373
374 let err = result.unwrap_err();
375 assert!(matches!(
376 err,
377 EVMError::Transaction(TempoInvalidTransaction::SystemTransactionFailed(_))
378 ));
379 }
380
381 #[test]
382 fn test_transact_system_call() {
383 let mut evm = test_evm(EmptyDB::default());
384
385 let caller = Address::repeat_byte(0x01);
386 let contract = Address::repeat_byte(0x02);
387 let data = Bytes::from_static(&[0x01, 0x02, 0x03]);
388
389 let result = evm.transact_system_call(caller, contract, data);
390 assert!(result.is_ok());
391
392 let result = result.unwrap();
393 assert!(result.result.is_success());
394 }
395
396 #[test]
397 fn test_take_revert_logs() {
398 let mut evm = test_evm(EmptyDB::default());
399
400 assert!(evm.take_revert_logs().is_empty());
401
402 let log1 = Log::new_unchecked(
403 Address::repeat_byte(0x01),
404 vec![alloy_primitives::B256::repeat_byte(0xaa)],
405 Bytes::from_static(&[0x01, 0x02]),
406 );
407 let log2 = Log::new_unchecked(
408 Address::repeat_byte(0x02),
409 vec![],
410 Bytes::from_static(&[0x03, 0x04]),
411 );
412 evm.inner.logs.push(log1);
413 evm.inner.logs.push(log2);
414
415 let logs = evm.take_revert_logs();
416 assert_eq!(logs.len(), 2);
417 assert_eq!(logs[0].address, Address::repeat_byte(0x01));
418 assert_eq!(logs[1].address, Address::repeat_byte(0x02));
419
420 assert!(evm.take_revert_logs().is_empty());
421 }
422
423 fn evm_env_with_spec(
427 spec: tempo_chainspec::hardfork::TempoHardfork,
428 ) -> EvmEnv<tempo_chainspec::hardfork::TempoHardfork, TempoBlockEnv> {
429 EvmEnv::<tempo_chainspec::hardfork::TempoHardfork, TempoBlockEnv>::new(
430 CfgEnv::new_with_spec_and_gas_params(spec, tempo_gas_params(spec)),
431 TempoBlockEnv::default(),
432 )
433 }
434
435 #[test]
440 fn test_tempo_evm_applies_gas_params() {
441 let evm = TempoEvm::new(EmptyDB::default(), evm_env_with_spec(TempoHardfork::T1));
443
444 let gas_params = &evm.ctx().cfg.gas_params;
447 assert_eq!(
448 gas_params.tx_eip7702_per_empty_account_cost(),
449 12_500,
450 "T1 should have EIP-7702 per empty account cost of 12,500"
451 );
452 }
453
454 #[test]
460 fn test_tempo_evm_respects_gas_cap() {
461 let mut env = evm_env_with_spec(TempoHardfork::T1);
462 env.cfg_env.tx_gas_limit_cap = TempoHardfork::T1.tx_gas_limit_cap();
463
464 let evm = TempoEvm::new(EmptyDB::default(), env);
465
466 assert_eq!(
468 evm.ctx().cfg.tx_gas_limit_cap,
469 TempoHardfork::T1.tx_gas_limit_cap(),
470 "TempoEvm should preserve the gas limit cap from input"
471 );
472 }
473
474 #[test]
476 fn test_tempo_evm_gas_params_differ_t0_vs_t1() {
477 let t0_evm = TempoEvm::new(EmptyDB::default(), evm_env_with_spec(TempoHardfork::T0));
479 let t1_evm = TempoEvm::new(EmptyDB::default(), evm_env_with_spec(TempoHardfork::T1));
480
481 let t0_eip7702_cost = t0_evm
484 .ctx()
485 .cfg
486 .gas_params
487 .tx_eip7702_per_empty_account_cost();
488 let t1_eip7702_cost = t1_evm
489 .ctx()
490 .cfg
491 .gas_params
492 .tx_eip7702_per_empty_account_cost();
493
494 assert_eq!(t0_eip7702_cost, 25_000, "T0 should have default 25,000");
495 assert_eq!(t1_eip7702_cost, 12_500, "T1 should have reduced 12,500");
496 assert_ne!(
497 t0_eip7702_cost, t1_eip7702_cost,
498 "Gas params should differ between T0 and T1"
499 );
500 }
501
502 #[test]
504 fn test_tempo_evm_t1_state_creation_costs() {
505 use revm::context_interface::cfg::GasId;
506
507 let evm = TempoEvm::new(EmptyDB::default(), evm_env_with_spec(TempoHardfork::T1));
508 let gas_params = &evm.ctx().cfg.gas_params;
509
510 assert_eq!(
512 gas_params.get(GasId::sstore_set_without_load_cost()),
513 250_000,
514 "T1 SSTORE set cost should be 250,000"
515 );
516 assert_eq!(
517 gas_params.get(GasId::tx_create_cost()),
518 500_000,
519 "T1 TX create cost should be 500,000"
520 );
521 assert_eq!(
522 gas_params.get(GasId::create()),
523 500_000,
524 "T1 CREATE opcode cost should be 500,000"
525 );
526 assert_eq!(
527 gas_params.get(GasId::new_account_cost()),
528 250_000,
529 "T1 new account cost should be 250,000"
530 );
531 assert_eq!(
532 gas_params.get(GasId::code_deposit_cost()),
533 1_000,
534 "T1 code deposit cost should be 1,000 per byte"
535 );
536 }
537}