1use alloy_evm::{
2 Database, Evm, EvmEnv, EvmFactory, IntoTxEnv,
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, TxKind};
11use reth_revm::{
12 InspectSystemCallEvm, MainContext,
13 context::{CfgEnv, result::ExecutionResult},
14};
15use std::ops::{Deref, DerefMut};
16use tempo_chainspec::hardfork::TempoHardfork;
17use tempo_revm::{
18 TempoHaltReason, TempoInvalidTransaction, TempoTxEnv, ValidationContext, evm::TempoContext,
19 handler::TempoEvmHandler,
20};
21
22use crate::TempoBlockEnv;
23
24#[derive(Debug, Default, Clone, Copy)]
25#[non_exhaustive]
26pub struct TempoEvmFactory;
27
28impl EvmFactory for TempoEvmFactory {
29 type Evm<DB: Database, I: Inspector<Self::Context<DB>>> = TempoEvm<DB, I>;
30 type Context<DB: Database> = TempoContext<DB>;
31 type Tx = TempoTxEnv;
32 type Error<DBError: std::error::Error + Send + Sync + 'static> =
33 EVMError<DBError, TempoInvalidTransaction>;
34 type HaltReason = TempoHaltReason;
35 type Spec = TempoHardfork;
36 type BlockEnv = TempoBlockEnv;
37 type Precompiles = PrecompilesMap;
38
39 fn create_evm<DB: Database>(
40 &self,
41 db: DB,
42 input: EvmEnv<Self::Spec, Self::BlockEnv>,
43 ) -> Self::Evm<DB, NoOpInspector> {
44 TempoEvm::new(db, input)
45 }
46
47 fn create_evm_with_inspector<DB: Database, I: Inspector<Self::Context<DB>>>(
48 &self,
49 db: DB,
50 input: EvmEnv<Self::Spec, Self::BlockEnv>,
51 inspector: I,
52 ) -> Self::Evm<DB, I> {
53 TempoEvm::new(db, input).with_inspector(inspector)
54 }
55}
56
57#[expect(missing_debug_implementations)]
63pub struct TempoEvm<DB: Database, I = NoOpInspector> {
64 inner: tempo_revm::TempoEvm<DB, I>,
65 inspect: bool,
66}
67
68impl<DB: Database> TempoEvm<DB> {
69 pub fn new(db: DB, input: EvmEnv<TempoHardfork, TempoBlockEnv>) -> Self {
71 let ctx = Context::mainnet()
75 .with_db(db)
76 .with_block(input.block_env)
77 .with_cfg(input.cfg_env)
78 .with_tx(Default::default());
79
80 Self {
81 inner: tempo_revm::TempoEvm::new(ctx, NoOpInspector {}),
82 inspect: false,
83 }
84 }
85}
86
87impl<DB: Database, I> TempoEvm<DB, I> {
88 pub fn into_inner(self) -> tempo_revm::TempoEvm<DB, I> {
90 self.inner
91 }
92
93 pub const fn ctx(&self) -> &TempoContext<DB> {
95 &self.inner.inner.ctx
96 }
97
98 pub fn ctx_mut(&mut self) -> &mut TempoContext<DB> {
100 &mut self.inner.inner.ctx
101 }
102
103 pub fn inner_mut(&mut self) -> &mut tempo_revm::TempoEvm<DB, I> {
105 &mut self.inner
106 }
107
108 pub fn with_inspector<OINSP>(self, inspector: OINSP) -> TempoEvm<DB, OINSP> {
110 TempoEvm {
111 inner: self.inner.with_inspector(inspector),
112 inspect: true,
113 }
114 }
115
116 pub fn validate_transaction(
120 &mut self,
121 tx: impl IntoTxEnv<TempoTxEnv>,
122 ) -> Result<ValidationContext, EVMError<DB::Error, TempoInvalidTransaction>> {
123 self.inner.inner.ctx.tx = tx.into_tx_env();
124 let mut handler = TempoEvmHandler::new();
125 handler.validate_transaction(&mut self.inner)
126 }
127}
128
129impl<DB: Database, I> Deref for TempoEvm<DB, I>
130where
131 DB: Database,
132 I: Inspector<TempoContext<DB>>,
133{
134 type Target = TempoContext<DB>;
135
136 #[inline]
137 fn deref(&self) -> &Self::Target {
138 self.ctx()
139 }
140}
141
142impl<DB: Database, I> DerefMut for TempoEvm<DB, I>
143where
144 DB: Database,
145 I: Inspector<TempoContext<DB>>,
146{
147 #[inline]
148 fn deref_mut(&mut self) -> &mut Self::Target {
149 self.ctx_mut()
150 }
151}
152
153impl<DB, I> Evm for TempoEvm<DB, I>
154where
155 DB: Database,
156 I: Inspector<TempoContext<DB>>,
157{
158 type DB = DB;
159 type Tx = TempoTxEnv;
160 type Error = EVMError<DB::Error, TempoInvalidTransaction>;
161 type HaltReason = TempoHaltReason;
162 type Spec = TempoHardfork;
163 type BlockEnv = TempoBlockEnv;
164 type Precompiles = PrecompilesMap;
165 type Inspector = I;
166
167 fn block(&self) -> &Self::BlockEnv {
168 &self.block
169 }
170
171 fn cfg_env(&self) -> &CfgEnv<Self::Spec> {
172 &self.cfg
173 }
174
175 fn chain_id(&self) -> u64 {
176 self.cfg.chain_id
177 }
178
179 fn transact_raw(
180 &mut self,
181 tx: Self::Tx,
182 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
183 if tx.is_system_tx {
184 let TxKind::Call(to) = tx.inner.kind else {
185 return Err(TempoInvalidTransaction::SystemTransactionMustBeCall.into());
186 };
187
188 let mut result = if self.inspect {
189 self.inner
190 .inspect_system_call_with_caller(tx.inner.caller, to, tx.inner.data)?
191 } else {
192 self.inner
193 .system_call_with_caller(tx.inner.caller, to, tx.inner.data)?
194 };
195
196 let ExecutionResult::Success { gas, .. } = &mut result.result else {
198 return Err(
199 TempoInvalidTransaction::SystemTransactionFailed(result.result.into()).into(),
200 );
201 };
202
203 *gas = ResultGas::default();
204
205 Ok(result)
206 } else if self.inspect {
207 self.inner.inspect_tx(tx)
208 } else {
209 self.inner.transact(tx)
210 }
211 }
212
213 fn transact_system_call(
214 &mut self,
215 caller: Address,
216 contract: Address,
217 data: Bytes,
218 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
219 self.inner.system_call_with_caller(caller, contract, data)
220 }
221
222 fn finish(self) -> (Self::DB, EvmEnv<Self::Spec, Self::BlockEnv>) {
223 let Context {
224 block: block_env,
225 cfg: cfg_env,
226 journaled_state,
227 ..
228 } = self.inner.inner.ctx;
229
230 (journaled_state.database, EvmEnv { block_env, cfg_env })
231 }
232
233 fn set_inspector_enabled(&mut self, enabled: bool) {
234 self.inspect = enabled;
235 }
236
237 fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) {
238 (
239 &self.inner.inner.ctx.journaled_state.database,
240 &self.inner.inner.inspector,
241 &self.inner.inner.precompiles,
242 )
243 }
244
245 fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) {
246 (
247 &mut self.inner.inner.ctx.journaled_state.database,
248 &mut self.inner.inner.inspector,
249 &mut self.inner.inner.precompiles,
250 )
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use crate::test_utils::{test_evm, test_evm_with_basefee};
257 use revm::{
258 context::{CfgEnv, TxEnv},
259 database::{EmptyDB, in_memory_db::CacheDB},
260 };
261 use tempo_chainspec::hardfork::TempoHardfork;
262 use tempo_revm::gas_params::tempo_gas_params_with_amsterdam;
263
264 use super::*;
265
266 #[test]
267 fn can_execute_system_tx() {
268 let mut evm = test_evm(EmptyDB::default());
269 let result = evm
270 .transact(TempoTxEnv {
271 inner: TxEnv {
272 caller: Address::ZERO,
273 gas_price: 0,
274 gas_limit: 21000,
275 ..Default::default()
276 },
277 is_system_tx: true,
278 ..Default::default()
279 })
280 .unwrap();
281
282 assert!(result.result.is_success());
283 }
284
285 #[test]
286 fn test_transact_raw() {
287 let mut evm = test_evm_with_basefee(EmptyDB::default(), 0);
288
289 let tx = TempoTxEnv {
290 inner: TxEnv {
291 caller: Address::repeat_byte(0x01),
292 gas_price: 0,
293 gas_limit: 21000,
294 kind: TxKind::Call(Address::repeat_byte(0x02)),
295 ..Default::default()
296 },
297 is_system_tx: false,
298 fee_token: None,
299 ..Default::default()
300 };
301
302 let result = evm.transact_raw(tx);
303 assert!(result.is_ok());
304
305 let result = result.unwrap();
306 assert!(result.result.is_success());
307 assert_eq!(result.result.tx_gas_used(), 21000);
308 }
309
310 #[test]
311 fn test_transact_raw_system_tx() {
312 let mut evm = test_evm(EmptyDB::default());
313
314 let tx = TempoTxEnv {
316 inner: TxEnv {
317 caller: Address::ZERO,
318 gas_price: 0,
319 gas_limit: 21000,
320 kind: TxKind::Call(Address::repeat_byte(0x01)),
321 ..Default::default()
322 },
323 is_system_tx: true,
324 ..Default::default()
325 };
326
327 let result = evm.transact_raw(tx);
328 assert!(result.is_ok());
329
330 let result = result.unwrap();
331 assert!(result.result.is_success());
332 assert_eq!(result.result.tx_gas_used(), 0);
334 }
335
336 #[test]
337 fn test_transact_raw_system_tx_must_be_call() {
338 let mut evm = test_evm(EmptyDB::default());
339
340 let tx = TempoTxEnv {
342 inner: TxEnv {
343 caller: Address::ZERO,
344 gas_price: 0,
345 gas_limit: 21000,
346 kind: TxKind::Create,
347 ..Default::default()
348 },
349 is_system_tx: true,
350 ..Default::default()
351 };
352
353 let result = evm.transact_raw(tx);
354 assert!(result.is_err());
355
356 let err = result.unwrap_err();
357 assert!(matches!(
358 err,
359 EVMError::Transaction(TempoInvalidTransaction::SystemTransactionMustBeCall)
360 ));
361 }
362
363 #[test]
364 fn test_transact_raw_system_tx_failed() {
365 let mut cache_db = CacheDB::new(EmptyDB::default());
366 let revert_code = Bytes::from_static(&[0x60, 0x00, 0x60, 0x00, 0xfd]);
368 let contract_addr = Address::repeat_byte(0xaa);
369
370 cache_db.insert_account_info(
371 contract_addr,
372 revm::state::AccountInfo {
373 code_hash: alloy_primitives::keccak256(&revert_code),
374 code: Some(revm::bytecode::Bytecode::new_raw(revert_code)),
375 ..Default::default()
376 },
377 );
378
379 let mut evm = test_evm(cache_db);
380
381 let tx = TempoTxEnv {
383 inner: TxEnv {
384 caller: Address::ZERO,
385 gas_price: 0,
386 gas_limit: 1_000_000,
387 kind: TxKind::Call(contract_addr),
388 ..Default::default()
389 },
390 is_system_tx: true,
391 ..Default::default()
392 };
393
394 let result = evm.transact_raw(tx);
395 assert!(result.is_err());
396
397 let err = result.unwrap_err();
398 assert!(matches!(
399 err,
400 EVMError::Transaction(TempoInvalidTransaction::SystemTransactionFailed(_))
401 ));
402 }
403
404 #[test]
405 fn test_transact_system_call() {
406 let mut evm = test_evm(EmptyDB::default());
407
408 let caller = Address::repeat_byte(0x01);
409 let contract = Address::repeat_byte(0x02);
410 let data = Bytes::from_static(&[0x01, 0x02, 0x03]);
411
412 let result = evm.transact_system_call(caller, contract, data);
413 assert!(result.is_ok());
414
415 let result = result.unwrap();
416 assert!(result.result.is_success());
417 }
418
419 fn evm_env_with_spec(
423 spec: tempo_chainspec::hardfork::TempoHardfork,
424 ) -> EvmEnv<tempo_chainspec::hardfork::TempoHardfork, TempoBlockEnv> {
425 EvmEnv::<tempo_chainspec::hardfork::TempoHardfork, TempoBlockEnv>::new(
426 CfgEnv::new_with_spec_and_gas_params(
427 spec,
428 tempo_gas_params_with_amsterdam(spec, false),
429 ),
430 TempoBlockEnv::default(),
431 )
432 }
433
434 #[test]
439 fn test_tempo_evm_applies_gas_params() {
440 let evm = TempoEvm::new(EmptyDB::default(), evm_env_with_spec(TempoHardfork::T1));
442
443 let gas_params = &evm.ctx().cfg.gas_params;
446 assert_eq!(
447 gas_params.tx_eip7702_per_empty_account_cost(),
448 12_500,
449 "T1 should have EIP-7702 per empty account cost of 12,500"
450 );
451 }
452
453 #[test]
459 fn test_tempo_evm_respects_gas_cap() {
460 let mut env = evm_env_with_spec(TempoHardfork::T1);
461 env.cfg_env.tx_gas_limit_cap = TempoHardfork::T1.tx_gas_limit_cap();
462
463 let evm = TempoEvm::new(EmptyDB::default(), env);
464
465 assert_eq!(
467 evm.ctx().cfg.tx_gas_limit_cap,
468 TempoHardfork::T1.tx_gas_limit_cap(),
469 "TempoEvm should preserve the gas limit cap from input"
470 );
471 }
472
473 #[test]
475 fn test_tempo_evm_gas_params_differ_t0_vs_t1() {
476 let t0_evm = TempoEvm::new(EmptyDB::default(), evm_env_with_spec(TempoHardfork::T0));
478 let t1_evm = TempoEvm::new(EmptyDB::default(), evm_env_with_spec(TempoHardfork::T1));
479
480 let t0_eip7702_cost = t0_evm
483 .ctx()
484 .cfg
485 .gas_params
486 .tx_eip7702_per_empty_account_cost();
487 let t1_eip7702_cost = t1_evm
488 .ctx()
489 .cfg
490 .gas_params
491 .tx_eip7702_per_empty_account_cost();
492
493 assert_eq!(t0_eip7702_cost, 25_000, "T0 should have default 25,000");
494 assert_eq!(t1_eip7702_cost, 12_500, "T1 should have reduced 12,500");
495 assert_ne!(
496 t0_eip7702_cost, t1_eip7702_cost,
497 "Gas params should differ between T0 and T1"
498 );
499 }
500
501 #[test]
503 fn test_tempo_evm_t1_state_creation_costs() {
504 use revm::context_interface::cfg::GasId;
505
506 let evm = TempoEvm::new(EmptyDB::default(), evm_env_with_spec(TempoHardfork::T1));
507 let gas_params = &evm.ctx().cfg.gas_params;
508
509 assert_eq!(
511 gas_params.get(GasId::sstore_set_without_load_cost()),
512 250_000,
513 "T1 SSTORE set cost should be 250,000"
514 );
515 assert_eq!(
516 gas_params.get(GasId::tx_create_cost()),
517 500_000,
518 "T1 TX create cost should be 500,000"
519 );
520 assert_eq!(
521 gas_params.get(GasId::create()),
522 500_000,
523 "T1 CREATE opcode cost should be 500,000"
524 );
525 assert_eq!(
526 gas_params.get(GasId::new_account_cost()),
527 250_000,
528 "T1 new account cost should be 250,000"
529 );
530 assert_eq!(
531 gas_params.get(GasId::code_deposit_cost()),
532 1_000,
533 "T1 code deposit cost should be 1,000 per byte"
534 );
535 }
536}