1use crate::{
3 api::exec::OpContextTr,
4 constants::{BASE_FEE_RECIPIENT, L1_FEE_RECIPIENT, OPERATOR_FEE_RECIPIENT},
5 transaction::{deposit::DEPOSIT_TRANSACTION_TYPE, OpTransactionError, OpTxTr},
6 L1BlockInfo, OpHaltReason, OpSpecId,
7};
8use revm::{
9 context::{result::InvalidTransaction, LocalContextTr},
10 context_interface::{
11 context::ContextError,
12 result::{EVMError, ExecutionResult, FromStringError},
13 Block, Cfg, ContextTr, JournalTr, Transaction,
14 },
15 handler::{
16 evm::FrameTr,
17 handler::EvmTrError,
18 post_execution::{self, reimburse_caller},
19 pre_execution::validate_account_nonce_and_code,
20 EthFrame, EvmTr, FrameResult, Handler, MainnetHandler,
21 },
22 inspector::{Inspector, InspectorEvmTr, InspectorHandler},
23 interpreter::{interpreter::EthInterpreter, interpreter_action::FrameInit, Gas},
24 primitives::{hardfork::SpecId, U256},
25};
26use std::boxed::Box;
27
28#[derive(Debug, Clone)]
30pub struct OpHandler<EVM, ERROR, FRAME> {
31 pub mainnet: MainnetHandler<EVM, ERROR, FRAME>,
34}
35
36impl<EVM, ERROR, FRAME> OpHandler<EVM, ERROR, FRAME> {
37 pub fn new() -> Self {
39 Self {
40 mainnet: MainnetHandler::default(),
41 }
42 }
43}
44
45impl<EVM, ERROR, FRAME> Default for OpHandler<EVM, ERROR, FRAME> {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51pub trait IsTxError {
55 fn is_tx_error(&self) -> bool;
57}
58
59impl<DB, TX> IsTxError for EVMError<DB, TX> {
60 fn is_tx_error(&self) -> bool {
61 matches!(self, EVMError::Transaction(_))
62 }
63}
64
65impl<EVM, ERROR, FRAME> Handler for OpHandler<EVM, ERROR, FRAME>
66where
67 EVM: EvmTr<Context: OpContextTr, Frame = FRAME>,
68 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
69 FRAME: FrameTr<FrameResult = FrameResult, FrameInit = FrameInit>,
72{
73 type Evm = EVM;
74 type Error = ERROR;
75 type HaltReason = OpHaltReason;
76
77 fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
78 let ctx = evm.ctx();
80 let tx = ctx.tx();
81 let tx_type = tx.tx_type();
82 if tx_type == DEPOSIT_TRANSACTION_TYPE {
83 if tx.is_system_transaction()
85 && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH)
86 {
87 return Err(OpTransactionError::DepositSystemTxPostRegolith.into());
88 }
89 return Ok(());
90 }
91 self.mainnet.validate_env(evm)
92 }
93
94 fn validate_against_state_and_deduct_caller(
95 &self,
96 evm: &mut Self::Evm,
97 ) -> Result<(), Self::Error> {
98 let ctx = evm.ctx();
99
100 let basefee = ctx.block().basefee() as u128;
101 let blob_price = ctx.block().blob_gasprice().unwrap_or_default();
102 let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
103 let spec = ctx.cfg().spec();
104 let block_number = ctx.block().number();
105 let is_balance_check_disabled = ctx.cfg().is_balance_check_disabled();
106 let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled();
107 let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled();
108
109 let mint = if is_deposit {
110 ctx.tx().mint().unwrap_or_default()
111 } else {
112 0
113 };
114
115 let mut additional_cost = U256::ZERO;
116
117 if !is_deposit {
119 if ctx.chain().l2_block != block_number {
122 *ctx.chain_mut() = L1BlockInfo::try_fetch(ctx.db_mut(), block_number, spec)?;
123 }
124
125 if !ctx.cfg().is_fee_charge_disabled() {
126 let Some(enveloped_tx) = ctx.tx().enveloped_tx().cloned() else {
128 return Err(ERROR::from_string(
129 "[OPTIMISM] Failed to load enveloped transaction.".into(),
130 ));
131 };
132
133 additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec);
135
136 if spec.is_enabled_in(OpSpecId::ISTHMUS) {
138 let gas_limit = U256::from(ctx.tx().gas_limit());
139 let operator_fee_charge =
140 ctx.chain()
141 .operator_fee_charge(&enveloped_tx, gas_limit, spec);
142 additional_cost = additional_cost.saturating_add(operator_fee_charge);
143 }
144 }
145 }
146
147 let (tx, journal) = ctx.tx_journal_mut();
148
149 let caller_account = journal.load_account_code(tx.caller())?.data;
150
151 if !is_deposit {
152 validate_account_nonce_and_code(
154 &mut caller_account.info,
155 tx.nonce(),
156 is_eip3607_disabled,
157 is_nonce_check_disabled,
158 )?;
159 }
160
161 let mut new_balance = caller_account.info.balance.saturating_add(U256::from(mint));
165
166 if !is_deposit && !is_balance_check_disabled {
169 let Some(balance) = new_balance.checked_sub(additional_cost) else {
171 return Err(InvalidTransaction::LackOfFundForMaxFee {
172 fee: Box::new(additional_cost),
173 balance: Box::new(new_balance),
174 }
175 .into());
176 };
177 tx.ensure_enough_balance(balance)?;
178 }
179
180 let gas_balance_spending = tx
182 .gas_balance_spending(basefee, blob_price)
183 .expect("effective balance is always smaller than max balance so it can't overflow");
184
185 let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost);
191
192 new_balance = new_balance.saturating_sub(op_gas_balance_spending);
193
194 if is_balance_check_disabled {
195 new_balance = new_balance.max(tx.value());
198 }
199
200 let old_balance =
201 caller_account.caller_initial_modification(new_balance, tx.kind().is_call());
202
203 journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call());
206
207 Ok(())
208 }
209
210 fn last_frame_result(
211 &mut self,
212 evm: &mut Self::Evm,
213 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
214 ) -> Result<(), Self::Error> {
215 let ctx = evm.ctx();
216 let tx = ctx.tx();
217 let is_deposit = tx.tx_type() == DEPOSIT_TRANSACTION_TYPE;
218 let tx_gas_limit = tx.gas_limit();
219 let is_regolith = ctx.cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
220
221 let instruction_result = frame_result.interpreter_result().result;
222 let gas = frame_result.gas_mut();
223 let remaining = gas.remaining();
224 let refunded = gas.refunded();
225
226 *gas = Gas::new_spent(tx_gas_limit);
228
229 if instruction_result.is_ok() {
230 if !is_deposit || is_regolith {
244 gas.erase_cost(remaining);
247 gas.record_refund(refunded);
248 } else if is_deposit {
249 let tx = ctx.tx();
250 if tx.is_system_transaction() {
251 gas.erase_cost(tx_gas_limit);
254 }
255 }
256 } else if instruction_result.is_revert() {
257 if !is_deposit || is_regolith {
270 gas.erase_cost(remaining);
271 }
272 }
273 Ok(())
274 }
275
276 fn reimburse_caller(
277 &self,
278 evm: &mut Self::Evm,
279 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
280 ) -> Result<(), Self::Error> {
281 let mut additional_refund = U256::ZERO;
282
283 if evm.ctx().tx().tx_type() != DEPOSIT_TRANSACTION_TYPE
284 && !evm.ctx().cfg().is_fee_charge_disabled()
285 {
286 let spec = evm.ctx().cfg().spec();
287 additional_refund = evm
288 .ctx()
289 .chain()
290 .operator_fee_refund(frame_result.gas(), spec);
291 }
292
293 reimburse_caller(evm.ctx(), frame_result.gas(), additional_refund).map_err(From::from)
294 }
295
296 fn refund(
297 &self,
298 evm: &mut Self::Evm,
299 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
300 eip7702_refund: i64,
301 ) {
302 frame_result.gas_mut().record_refund(eip7702_refund);
303
304 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
305 let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
306
307 let is_gas_refund_disabled = is_deposit && !is_regolith;
309 if !is_gas_refund_disabled {
310 frame_result.gas_mut().set_final_refund(
311 evm.ctx()
312 .cfg()
313 .spec()
314 .into_eth_spec()
315 .is_enabled_in(SpecId::LONDON),
316 );
317 }
318 }
319
320 fn reward_beneficiary(
321 &self,
322 evm: &mut Self::Evm,
323 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
324 ) -> Result<(), Self::Error> {
325 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
326
327 if is_deposit {
329 return Ok(());
330 }
331
332 self.mainnet.reward_beneficiary(evm, frame_result)?;
333 let basefee = evm.ctx().block().basefee() as u128;
334
335 let ctx = evm.ctx();
338 let enveloped = ctx.tx().enveloped_tx().cloned();
339 let spec = ctx.cfg().spec();
340 let l1_block_info = ctx.chain_mut();
341
342 let Some(enveloped_tx) = &enveloped else {
343 return Err(ERROR::from_string(
344 "[OPTIMISM] Failed to load enveloped transaction.".into(),
345 ));
346 };
347
348 let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec);
349 let operator_fee_cost = if spec.is_enabled_in(OpSpecId::ISTHMUS) {
350 l1_block_info.operator_fee_charge(
351 enveloped_tx,
352 U256::from(frame_result.gas().used()),
353 spec,
354 )
355 } else {
356 U256::ZERO
357 };
358 let base_fee_amount = U256::from(basefee.saturating_mul(frame_result.gas().used() as u128));
359
360 for (recipient, amount) in [
362 (L1_FEE_RECIPIENT, l1_cost),
363 (BASE_FEE_RECIPIENT, base_fee_amount),
364 (OPERATOR_FEE_RECIPIENT, operator_fee_cost),
365 ] {
366 ctx.journal_mut().balance_incr(recipient, amount)?;
367 }
368
369 Ok(())
370 }
371
372 fn execution_result(
373 &mut self,
374 evm: &mut Self::Evm,
375 frame_result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
376 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
377 match core::mem::replace(evm.ctx().error(), Ok(())) {
378 Err(ContextError::Db(e)) => return Err(e.into()),
379 Err(ContextError::Custom(e)) => return Err(Self::Error::from_string(e)),
380 Ok(_) => (),
381 }
382
383 let exec_result =
384 post_execution::output(evm.ctx(), frame_result).map_haltreason(OpHaltReason::Base);
385
386 if exec_result.is_halt() {
387 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
391 if is_deposit && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) {
392 return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith));
393 }
394 }
395 evm.ctx().journal_mut().commit_tx();
396 evm.ctx().chain_mut().clear_tx_l1_cost();
397 evm.ctx().local_mut().clear();
398 evm.frame_stack().clear();
399
400 Ok(exec_result)
401 }
402
403 fn catch_error(
404 &self,
405 evm: &mut Self::Evm,
406 error: Self::Error,
407 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
408 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
409 let output = if error.is_tx_error() && is_deposit {
410 let ctx = evm.ctx();
411 let spec = ctx.cfg().spec();
412 let tx = ctx.tx();
413 let caller = tx.caller();
414 let mint = tx.mint();
415 let is_system_tx = tx.is_system_transaction();
416 let gas_limit = tx.gas_limit();
417
418 evm.ctx().journal_mut().discard_tx();
420
421 let acc: &mut revm::state::Account = evm.ctx().journal_mut().load_account(caller)?.data;
431
432 let old_balance = acc.info.balance;
433
434 acc.transaction_id -= 1;
436 acc.info.nonce = acc.info.nonce.saturating_add(1);
437 acc.info.balance = acc
438 .info
439 .balance
440 .saturating_add(U256::from(mint.unwrap_or_default()));
441 acc.mark_touch();
442
443 evm.ctx()
445 .journal_mut()
446 .caller_accounting_journal_entry(caller, old_balance, true);
447
448 let gas_used = if spec.is_enabled_in(OpSpecId::REGOLITH) || !is_system_tx {
453 gas_limit
454 } else {
455 0
456 };
457 Ok(ExecutionResult::Halt {
459 reason: OpHaltReason::FailedDeposit,
460 gas_used,
461 })
462 } else {
463 Err(error)
464 };
465 evm.ctx().chain_mut().clear_tx_l1_cost();
467 evm.ctx().local_mut().clear();
468 evm.frame_stack().clear();
469
470 output
471 }
472}
473
474impl<EVM, ERROR> InspectorHandler for OpHandler<EVM, ERROR, EthFrame<EthInterpreter>>
475where
476 EVM: InspectorEvmTr<
477 Context: OpContextTr,
478 Frame = EthFrame<EthInterpreter>,
479 Inspector: Inspector<<<Self as Handler>::Evm as EvmTr>::Context, EthInterpreter>,
480 >,
481 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
482{
483 type IT = EthInterpreter;
484}
485
486#[cfg(test)]
487mod tests {
488 use super::*;
489 use crate::{
490 api::default_ctx::OpContext,
491 constants::{
492 BASE_FEE_SCALAR_OFFSET, DA_FOOTPRINT_GAS_SCALAR_SLOT, ECOTONE_L1_BLOB_BASE_FEE_SLOT,
493 ECOTONE_L1_FEE_SCALARS_SLOT, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT,
494 OPERATOR_FEE_SCALARS_SLOT,
495 },
496 DefaultOp, OpBuilder, OpTransaction,
497 };
498 use alloy_primitives::uint;
499 use revm::{
500 context::{BlockEnv, Context, TxEnv},
501 context_interface::result::InvalidTransaction,
502 database::InMemoryDB,
503 database_interface::EmptyDB,
504 handler::EthFrame,
505 interpreter::{CallOutcome, InstructionResult, InterpreterResult},
506 primitives::{bytes, Address, Bytes, B256},
507 state::AccountInfo,
508 };
509 use rstest::rstest;
510 use std::boxed::Box;
511
512 fn call_last_frame_return(
514 ctx: OpContext<EmptyDB>,
515 instruction_result: InstructionResult,
516 gas: Gas,
517 ) -> Gas {
518 let mut evm = ctx.build_op();
519
520 let mut exec_result = FrameResult::Call(CallOutcome::new(
521 InterpreterResult {
522 result: instruction_result,
523 output: Bytes::new(),
524 gas,
525 },
526 0..0,
527 ));
528
529 let mut handler =
530 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
531
532 handler
533 .last_frame_result(&mut evm, &mut exec_result)
534 .unwrap();
535 handler.refund(&mut evm, &mut exec_result, 0);
536 *exec_result.gas()
537 }
538
539 #[test]
540 fn test_revert_gas() {
541 let ctx = Context::op()
542 .with_tx(
543 OpTransaction::builder()
544 .base(TxEnv::builder().gas_limit(100))
545 .build_fill(),
546 )
547 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
548
549 let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90));
550 assert_eq!(gas.remaining(), 90);
551 assert_eq!(gas.spent(), 10);
552 assert_eq!(gas.refunded(), 0);
553 }
554
555 #[test]
556 fn test_consume_gas() {
557 let ctx = Context::op()
558 .with_tx(
559 OpTransaction::builder()
560 .base(TxEnv::builder().gas_limit(100))
561 .build_fill(),
562 )
563 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
564
565 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
566 assert_eq!(gas.remaining(), 90);
567 assert_eq!(gas.spent(), 10);
568 assert_eq!(gas.refunded(), 0);
569 }
570
571 #[test]
572 fn test_consume_gas_with_refund() {
573 let ctx = Context::op()
574 .with_tx(
575 OpTransaction::builder()
576 .base(TxEnv::builder().gas_limit(100))
577 .source_hash(B256::from([1u8; 32]))
578 .build_fill(),
579 )
580 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
581
582 let mut ret_gas = Gas::new(90);
583 ret_gas.record_refund(20);
584
585 let gas = call_last_frame_return(ctx.clone(), InstructionResult::Stop, ret_gas);
586 assert_eq!(gas.remaining(), 90);
587 assert_eq!(gas.spent(), 10);
588 assert_eq!(gas.refunded(), 2); let gas = call_last_frame_return(ctx, InstructionResult::Revert, ret_gas);
591 assert_eq!(gas.remaining(), 90);
592 assert_eq!(gas.spent(), 10);
593 assert_eq!(gas.refunded(), 0);
594 }
595
596 #[test]
597 fn test_consume_gas_deposit_tx() {
598 let ctx = Context::op()
599 .with_tx(
600 OpTransaction::builder()
601 .base(TxEnv::builder().gas_limit(100))
602 .source_hash(B256::from([1u8; 32]))
603 .build_fill(),
604 )
605 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
606 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
607 assert_eq!(gas.remaining(), 0);
608 assert_eq!(gas.spent(), 100);
609 assert_eq!(gas.refunded(), 0);
610 }
611
612 #[test]
613 fn test_consume_gas_sys_deposit_tx() {
614 let ctx = Context::op()
615 .with_tx(
616 OpTransaction::builder()
617 .base(TxEnv::builder().gas_limit(100))
618 .source_hash(B256::from([1u8; 32]))
619 .is_system_transaction()
620 .build_fill(),
621 )
622 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
623 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
624 assert_eq!(gas.remaining(), 100);
625 assert_eq!(gas.spent(), 0);
626 assert_eq!(gas.refunded(), 0);
627 }
628
629 #[test]
630 fn test_commit_mint_value() {
631 let caller = Address::ZERO;
632 let mut db = InMemoryDB::default();
633 db.insert_account_info(
634 caller,
635 AccountInfo {
636 balance: U256::from(1000),
637 ..Default::default()
638 },
639 );
640
641 let mut ctx = Context::op()
642 .with_db(db)
643 .with_chain(L1BlockInfo {
644 l1_base_fee: U256::from(1_000),
645 l1_fee_overhead: Some(U256::from(1_000)),
646 l1_base_fee_scalar: U256::from(1_000),
647 ..Default::default()
648 })
649 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
650 ctx.modify_tx(|tx| {
651 tx.deposit.source_hash = B256::from([1u8; 32]);
652 tx.deposit.mint = Some(10);
653 });
654
655 let mut evm = ctx.build_op();
656
657 let handler =
658 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
659 handler
660 .validate_against_state_and_deduct_caller(&mut evm)
661 .unwrap();
662
663 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
665 assert_eq!(account.info.balance, U256::from(1010));
666 }
667
668 #[test]
669 fn test_remove_l1_cost_non_deposit() {
670 let caller = Address::ZERO;
671 let mut db = InMemoryDB::default();
672 db.insert_account_info(
673 caller,
674 AccountInfo {
675 balance: U256::from(1058), ..Default::default()
677 },
678 );
679 let ctx = Context::op()
680 .with_db(db)
681 .with_chain(L1BlockInfo {
682 l1_base_fee: U256::from(1_000),
683 l1_fee_overhead: Some(U256::from(1_000)),
684 l1_base_fee_scalar: U256::from(1_000),
685 ..Default::default()
686 })
687 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
688 .with_tx(
689 OpTransaction::builder()
690 .base(TxEnv::builder().gas_limit(100))
691 .enveloped_tx(Some(bytes!("FACADE")))
692 .source_hash(B256::ZERO)
693 .build()
694 .unwrap(),
695 );
696
697 let mut evm = ctx.build_op();
698
699 let handler =
700 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
701 handler
702 .validate_against_state_and_deduct_caller(&mut evm)
703 .unwrap();
704
705 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
707 assert_eq!(account.info.balance, U256::from(10)); }
709
710 #[test]
711 fn test_reload_l1_block_info_isthmus() {
712 const BLOCK_NUM: U256 = uint!(100_U256);
713 const L1_BASE_FEE: U256 = uint!(1_U256);
714 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
715 const L1_BASE_FEE_SCALAR: u64 = 3;
716 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
717 const L1_FEE_SCALARS: U256 = U256::from_limbs([
718 0,
719 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
720 0,
721 0,
722 ]);
723 const OPERATOR_FEE_SCALAR: u64 = 5;
724 const OPERATOR_FEE_CONST: u64 = 6;
725 const OPERATOR_FEE: U256 =
726 U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]);
727
728 let mut db = InMemoryDB::default();
729 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
730 l1_block_contract
731 .storage
732 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
733 l1_block_contract
734 .storage
735 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
736 l1_block_contract
737 .storage
738 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
739 l1_block_contract
740 .storage
741 .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE);
742 db.insert_account_info(
743 Address::ZERO,
744 AccountInfo {
745 balance: U256::from(1000),
746 ..Default::default()
747 },
748 );
749
750 let ctx = Context::op()
751 .with_db(db)
752 .with_chain(L1BlockInfo {
753 l2_block: BLOCK_NUM + U256::from(1), ..Default::default()
755 })
756 .with_block(BlockEnv {
757 number: BLOCK_NUM,
758 ..Default::default()
759 })
760 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
761
762 let mut evm = ctx.build_op();
763
764 assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM);
765
766 let handler =
767 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
768 handler
769 .validate_against_state_and_deduct_caller(&mut evm)
770 .unwrap();
771
772 assert_eq!(
773 *evm.ctx().chain(),
774 L1BlockInfo {
775 l2_block: BLOCK_NUM,
776 l1_base_fee: L1_BASE_FEE,
777 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
778 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
779 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
780 empty_ecotone_scalars: false,
781 l1_fee_overhead: None,
782 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
783 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
784 tx_l1_cost: Some(U256::ZERO),
785 da_footprint_gas_scalar: None
786 }
787 );
788 }
789
790 #[test]
791 fn test_parse_da_footprint_gas_scalar_jovian() {
792 const BLOCK_NUM: U256 = uint!(100_U256);
793 const L1_BASE_FEE: U256 = uint!(1_U256);
794 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
795 const L1_BASE_FEE_SCALAR: u64 = 3;
796 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
797 const L1_FEE_SCALARS: U256 = U256::from_limbs([
798 0,
799 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
800 0,
801 0,
802 ]);
803 const OPERATOR_FEE_SCALAR: u64 = 5;
804 const OPERATOR_FEE_CONST: u64 = 6;
805 const OPERATOR_FEE: U256 =
806 U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]);
807 const DA_FOOTPRINT_GAS_SCALAR: u16 = 7;
808 const DA_FOOTPRINT_GAS_SCALAR_U64: u64 =
809 u64::from_be_bytes([0, DA_FOOTPRINT_GAS_SCALAR as u8, 0, 0, 0, 0, 0, 0]);
810 const DA_FOOTPRINT_GAS_SCALAR_SLOT_VALUE: U256 =
811 U256::from_limbs([0, 0, 0, DA_FOOTPRINT_GAS_SCALAR_U64]);
812
813 let mut db = InMemoryDB::default();
814 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
815 l1_block_contract
816 .storage
817 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
818 l1_block_contract
819 .storage
820 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
821 l1_block_contract
822 .storage
823 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
824 l1_block_contract
825 .storage
826 .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE);
827 l1_block_contract.storage.insert(
828 DA_FOOTPRINT_GAS_SCALAR_SLOT,
829 DA_FOOTPRINT_GAS_SCALAR_SLOT_VALUE,
830 );
831 db.insert_account_info(
832 Address::ZERO,
833 AccountInfo {
834 balance: U256::from(6000),
835 ..Default::default()
836 },
837 );
838
839 let ctx = Context::op()
840 .with_db(db)
841 .with_chain(L1BlockInfo {
842 l2_block: BLOCK_NUM + U256::from(1), operator_fee_scalar: Some(U256::from(2)),
844 operator_fee_constant: Some(U256::from(50)),
845 ..Default::default()
846 })
847 .with_block(BlockEnv {
848 number: BLOCK_NUM,
849 ..Default::default()
850 })
851 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN)
852 .with_tx(
854 OpTransaction::builder()
855 .base(TxEnv::builder().gas_limit(10))
856 .enveloped_tx(Some(bytes!("FACADE")))
857 .build_fill(),
858 );
859
860 let mut evm = ctx.build_op();
861
862 assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM);
863
864 let handler =
865 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
866 handler
867 .validate_against_state_and_deduct_caller(&mut evm)
868 .unwrap();
869
870 assert_eq!(
871 *evm.ctx().chain(),
872 L1BlockInfo {
873 l2_block: BLOCK_NUM,
874 l1_base_fee: L1_BASE_FEE,
875 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
876 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
877 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
878 empty_ecotone_scalars: false,
879 l1_fee_overhead: None,
880 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
881 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
882 tx_l1_cost: Some(U256::ZERO),
883 da_footprint_gas_scalar: Some(DA_FOOTPRINT_GAS_SCALAR),
884 }
885 );
886 }
887
888 #[test]
889 fn test_reload_l1_block_info_regolith() {
890 const BLOCK_NUM: U256 = uint!(200_U256);
891 const L1_BASE_FEE: U256 = uint!(7_U256);
892 const L1_FEE_OVERHEAD: U256 = uint!(9_U256);
893 const L1_BASE_FEE_SCALAR: u64 = 11;
894
895 let mut db = InMemoryDB::default();
896 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
897 l1_block_contract
898 .storage
899 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
900 use crate::constants::{L1_OVERHEAD_SLOT, L1_SCALAR_SLOT};
902 l1_block_contract
903 .storage
904 .insert(L1_OVERHEAD_SLOT, L1_FEE_OVERHEAD);
905 l1_block_contract
906 .storage
907 .insert(L1_SCALAR_SLOT, U256::from(L1_BASE_FEE_SCALAR));
908
909 let ctx = Context::op()
910 .with_db(db)
911 .with_chain(L1BlockInfo {
912 l2_block: BLOCK_NUM + U256::from(1),
913 ..Default::default()
914 })
915 .with_block(BlockEnv {
916 number: BLOCK_NUM,
917 ..Default::default()
918 })
919 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
920
921 let mut evm = ctx.build_op();
922 assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM);
923
924 let handler =
925 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
926 handler
927 .validate_against_state_and_deduct_caller(&mut evm)
928 .unwrap();
929
930 assert_eq!(
931 *evm.ctx().chain(),
932 L1BlockInfo {
933 l2_block: BLOCK_NUM,
934 l1_base_fee: L1_BASE_FEE,
935 l1_fee_overhead: Some(L1_FEE_OVERHEAD),
936 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
937 tx_l1_cost: Some(U256::ZERO),
938 ..Default::default()
939 }
940 );
941 }
942
943 #[test]
944 fn test_reload_l1_block_info_ecotone_pre_isthmus() {
945 const BLOCK_NUM: U256 = uint!(300_U256);
946 const L1_BASE_FEE: U256 = uint!(13_U256);
947 const L1_BLOB_BASE_FEE: U256 = uint!(17_U256);
948 const L1_BASE_FEE_SCALAR: u64 = 19;
949 const L1_BLOB_BASE_FEE_SCALAR: u64 = 23;
950 const L1_FEE_SCALARS: U256 = U256::from_limbs([
951 0,
952 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
953 0,
954 0,
955 ]);
956
957 let mut db = InMemoryDB::default();
958 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
959 l1_block_contract
960 .storage
961 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
962 l1_block_contract
963 .storage
964 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
965 l1_block_contract
966 .storage
967 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
968
969 let ctx = Context::op()
970 .with_db(db)
971 .with_chain(L1BlockInfo {
972 l2_block: BLOCK_NUM + U256::from(1),
973 ..Default::default()
974 })
975 .with_block(BlockEnv {
976 number: BLOCK_NUM,
977 ..Default::default()
978 })
979 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ECOTONE);
980
981 let mut evm = ctx.build_op();
982 assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM);
983
984 let handler =
985 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
986 handler
987 .validate_against_state_and_deduct_caller(&mut evm)
988 .unwrap();
989
990 assert_eq!(
991 *evm.ctx().chain(),
992 L1BlockInfo {
993 l2_block: BLOCK_NUM,
994 l1_base_fee: L1_BASE_FEE,
995 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
996 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
997 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
998 empty_ecotone_scalars: false,
999 l1_fee_overhead: None,
1000 tx_l1_cost: Some(U256::ZERO),
1001 ..Default::default()
1002 }
1003 );
1004 }
1005
1006 #[test]
1007 fn test_remove_l1_cost() {
1008 let caller = Address::ZERO;
1009 let mut db = InMemoryDB::default();
1010 db.insert_account_info(
1011 caller,
1012 AccountInfo {
1013 balance: U256::from(1049),
1014 ..Default::default()
1015 },
1016 );
1017 let ctx = Context::op()
1018 .with_db(db)
1019 .with_chain(L1BlockInfo {
1020 l1_base_fee: U256::from(1_000),
1021 l1_fee_overhead: Some(U256::from(1_000)),
1022 l1_base_fee_scalar: U256::from(1_000),
1023 ..Default::default()
1024 })
1025 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
1026 .with_tx(
1027 OpTransaction::builder()
1028 .base(TxEnv::builder().gas_limit(100))
1029 .source_hash(B256::ZERO)
1030 .enveloped_tx(Some(bytes!("FACADE")))
1031 .build()
1032 .unwrap(),
1033 );
1034
1035 let mut evm = ctx.build_op();
1036 let handler =
1037 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1038
1039 handler
1041 .validate_against_state_and_deduct_caller(&mut evm)
1042 .unwrap();
1043
1044 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
1046 assert_eq!(account.info.balance, U256::from(1));
1047 }
1048
1049 #[test]
1050 fn test_remove_operator_cost_isthmus() {
1051 let caller = Address::ZERO;
1052 let mut db = InMemoryDB::default();
1053 db.insert_account_info(
1054 caller,
1055 AccountInfo {
1056 balance: U256::from(151),
1057 ..Default::default()
1058 },
1059 );
1060 let ctx = Context::op()
1061 .with_db(db)
1062 .with_chain(L1BlockInfo {
1063 operator_fee_scalar: Some(U256::from(10_000_000)),
1064 operator_fee_constant: Some(U256::from(50)),
1065 ..Default::default()
1066 })
1067 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS)
1068 .with_tx(
1069 OpTransaction::builder()
1070 .base(TxEnv::builder().gas_limit(10))
1071 .enveloped_tx(Some(bytes!("FACADE")))
1072 .build_fill(),
1073 );
1074
1075 let mut evm = ctx.build_op();
1076 let handler =
1077 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1078
1079 handler
1082 .validate_against_state_and_deduct_caller(&mut evm)
1083 .unwrap();
1084
1085 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
1087 assert_eq!(account.info.balance, U256::from(1));
1088 }
1089
1090 #[test]
1091 fn test_remove_operator_cost_jovian() {
1092 let caller = Address::ZERO;
1093 let mut db = InMemoryDB::default();
1094 db.insert_account_info(
1095 caller,
1096 AccountInfo {
1097 balance: U256::from(2_051),
1098 ..Default::default()
1099 },
1100 );
1101 let ctx = Context::op()
1102 .with_db(db)
1103 .with_chain(L1BlockInfo {
1104 operator_fee_scalar: Some(U256::from(2)),
1105 operator_fee_constant: Some(U256::from(50)),
1106 ..Default::default()
1107 })
1108 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN)
1109 .with_tx(
1110 OpTransaction::builder()
1111 .base(TxEnv::builder().gas_limit(10))
1112 .enveloped_tx(Some(bytes!("FACADE")))
1113 .build_fill(),
1114 );
1115
1116 let mut evm = ctx.build_op();
1117 let handler =
1118 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1119
1120 handler
1123 .validate_against_state_and_deduct_caller(&mut evm)
1124 .unwrap();
1125
1126 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
1127 assert_eq!(account.info.balance, U256::from(1));
1128 }
1129
1130 #[test]
1131 fn test_remove_l1_cost_lack_of_funds() {
1132 let caller = Address::ZERO;
1133 let mut db = InMemoryDB::default();
1134 db.insert_account_info(
1135 caller,
1136 AccountInfo {
1137 balance: U256::from(48),
1138 ..Default::default()
1139 },
1140 );
1141 let ctx = Context::op()
1142 .with_db(db)
1143 .with_chain(L1BlockInfo {
1144 l1_base_fee: U256::from(1_000),
1145 l1_fee_overhead: Some(U256::from(1_000)),
1146 l1_base_fee_scalar: U256::from(1_000),
1147 ..Default::default()
1148 })
1149 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
1150 .modify_tx_chained(|tx| {
1151 tx.enveloped_tx = Some(bytes!("FACADE"));
1152 });
1153
1154 let mut evm = ctx.build_op();
1156 let handler =
1157 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1158
1159 assert_eq!(
1161 handler.validate_against_state_and_deduct_caller(&mut evm),
1162 Err(EVMError::Transaction(
1163 InvalidTransaction::LackOfFundForMaxFee {
1164 fee: Box::new(U256::from(1048)),
1165 balance: Box::new(U256::from(48)),
1166 }
1167 .into(),
1168 ))
1169 );
1170 }
1171
1172 #[test]
1173 fn test_validate_sys_tx() {
1174 let ctx = Context::op()
1176 .modify_tx_chained(|tx| {
1177 tx.deposit.source_hash = B256::from([1u8; 32]);
1178 tx.deposit.is_system_transaction = true;
1179 })
1180 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1181
1182 let mut evm = ctx.build_op();
1183 let handler =
1184 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1185
1186 assert_eq!(
1187 handler.validate_env(&mut evm),
1188 Err(EVMError::Transaction(
1189 OpTransactionError::DepositSystemTxPostRegolith
1190 ))
1191 );
1192
1193 evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK);
1194
1195 assert!(handler.validate_env(&mut evm).is_ok());
1197 }
1198
1199 #[test]
1200 fn test_validate_deposit_tx() {
1201 let ctx = Context::op()
1203 .modify_tx_chained(|tx| {
1204 tx.deposit.source_hash = B256::from([1u8; 32]);
1205 })
1206 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1207
1208 let mut evm = ctx.build_op();
1209 let handler =
1210 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1211
1212 assert!(handler.validate_env(&mut evm).is_ok());
1213 }
1214
1215 #[test]
1216 fn test_validate_tx_against_state_deposit_tx() {
1217 let ctx = Context::op()
1219 .modify_tx_chained(|tx| {
1220 tx.deposit.source_hash = B256::from([1u8; 32]);
1221 })
1222 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1223
1224 let mut evm = ctx.build_op();
1225 let handler =
1226 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1227
1228 assert!(handler.validate_env(&mut evm).is_ok());
1230 }
1231
1232 #[test]
1233 fn test_halted_deposit_tx_post_regolith() {
1234 let ctx = Context::op()
1235 .modify_tx_chained(|tx| {
1236 tx.deposit.source_hash = B256::from([1u8; 32]);
1238 })
1239 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1240
1241 let mut evm = ctx.build_op();
1242 let mut handler =
1243 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1244
1245 assert_eq!(
1246 handler.execution_result(
1247 &mut evm,
1248 FrameResult::Call(CallOutcome {
1249 result: InterpreterResult {
1250 result: InstructionResult::OutOfGas,
1251 output: Default::default(),
1252 gas: Default::default(),
1253 },
1254 memory_offset: Default::default(),
1255 })
1256 ),
1257 Err(EVMError::Transaction(
1258 OpTransactionError::HaltedDepositPostRegolith
1259 ))
1260 )
1261 }
1262
1263 #[test]
1264 fn test_tx_zero_value_touch_caller() {
1265 let ctx = Context::op();
1266
1267 let mut evm = ctx.build_op();
1268
1269 assert!(!evm
1270 .0
1271 .ctx
1272 .journal_mut()
1273 .load_account(Address::ZERO)
1274 .unwrap()
1275 .is_touched());
1276
1277 let handler =
1278 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1279
1280 handler
1281 .validate_against_state_and_deduct_caller(&mut evm)
1282 .unwrap();
1283
1284 assert!(evm
1285 .0
1286 .ctx
1287 .journal_mut()
1288 .load_account(Address::ZERO)
1289 .unwrap()
1290 .is_touched());
1291 }
1292
1293 #[rstest]
1294 #[case::deposit(true)]
1295 #[case::dyn_fee(false)]
1296 fn test_operator_fee_refund(#[case] is_deposit: bool) {
1297 const SENDER: Address = Address::ZERO;
1298 const GAS_PRICE: u128 = 0xFF;
1299 const OP_FEE_MOCK_PARAM: u128 = 0xFFFF;
1300
1301 let ctx = Context::op()
1302 .with_tx(
1303 OpTransaction::builder()
1304 .base(
1305 TxEnv::builder()
1306 .gas_price(GAS_PRICE)
1307 .gas_priority_fee(None)
1308 .caller(SENDER),
1309 )
1310 .enveloped_tx(if is_deposit {
1311 None
1312 } else {
1313 Some(bytes!("FACADE"))
1314 })
1315 .source_hash(if is_deposit {
1316 B256::from([1u8; 32])
1317 } else {
1318 B256::ZERO
1319 })
1320 .build_fill(),
1321 )
1322 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
1323
1324 let mut evm = ctx.build_op();
1325 let handler =
1326 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1327
1328 evm.ctx().chain.operator_fee_scalar = Some(U256::from(OP_FEE_MOCK_PARAM));
1330 evm.ctx().chain.operator_fee_constant = Some(U256::from(OP_FEE_MOCK_PARAM));
1331
1332 let mut gas = Gas::new(100);
1333 gas.set_spent(10);
1334 let mut exec_result = FrameResult::Call(CallOutcome::new(
1335 InterpreterResult {
1336 result: InstructionResult::Return,
1337 output: Default::default(),
1338 gas,
1339 },
1340 0..0,
1341 ));
1342
1343 handler
1345 .reimburse_caller(&mut evm, &mut exec_result)
1346 .unwrap();
1347
1348 let mut expected_refund =
1351 U256::from(GAS_PRICE * (gas.remaining() + gas.refunded() as u64) as u128);
1352 let op_fee_refund = evm
1353 .ctx()
1354 .chain()
1355 .operator_fee_refund(&gas, OpSpecId::ISTHMUS);
1356 assert!(op_fee_refund > U256::ZERO);
1357
1358 if !is_deposit {
1359 expected_refund += op_fee_refund;
1360 }
1361
1362 let account = evm.ctx().journal_mut().load_account(SENDER).unwrap();
1364 assert_eq!(account.info.balance, expected_refund);
1365 }
1366
1367 #[test]
1368 fn test_tx_low_balance_nonce_unchanged() {
1369 let ctx = Context::op().with_tx(
1370 OpTransaction::builder()
1371 .base(TxEnv::builder().value(U256::from(1000)))
1372 .build_fill(),
1373 );
1374
1375 let mut evm = ctx.build_op();
1376
1377 let handler =
1378 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1379
1380 let result = handler.validate_against_state_and_deduct_caller(&mut evm);
1381
1382 assert!(matches!(
1383 result.err().unwrap(),
1384 EVMError::Transaction(OpTransactionError::Base(
1385 InvalidTransaction::LackOfFundForMaxFee { .. }
1386 ))
1387 ));
1388 assert_eq!(
1389 evm.0
1390 .ctx
1391 .journal_mut()
1392 .load_account(Address::ZERO)
1393 .unwrap()
1394 .info
1395 .nonce,
1396 0
1397 );
1398 }
1399}