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 pub _phantom: core::marker::PhantomData<(EVM, ERROR, FRAME)>,
36}
37
38impl<EVM, ERROR, FRAME> OpHandler<EVM, ERROR, FRAME> {
39 pub fn new() -> Self {
41 Self {
42 mainnet: MainnetHandler::default(),
43 _phantom: core::marker::PhantomData,
44 }
45 }
46}
47
48impl<EVM, ERROR, FRAME> Default for OpHandler<EVM, ERROR, FRAME> {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54pub trait IsTxError {
58 fn is_tx_error(&self) -> bool;
60}
61
62impl<DB, TX> IsTxError for EVMError<DB, TX> {
63 fn is_tx_error(&self) -> bool {
64 matches!(self, EVMError::Transaction(_))
65 }
66}
67
68impl<EVM, ERROR, FRAME> Handler for OpHandler<EVM, ERROR, FRAME>
69where
70 EVM: EvmTr<Context: OpContextTr, Frame = FRAME>,
71 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
72 FRAME: FrameTr<FrameResult = FrameResult, FrameInit = FrameInit>,
75{
76 type Evm = EVM;
77 type Error = ERROR;
78 type HaltReason = OpHaltReason;
79
80 fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
81 let ctx = evm.ctx();
83 let tx = ctx.tx();
84 let tx_type = tx.tx_type();
85 if tx_type == DEPOSIT_TRANSACTION_TYPE {
86 if tx.is_system_transaction()
88 && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH)
89 {
90 return Err(OpTransactionError::DepositSystemTxPostRegolith.into());
91 }
92 return Ok(());
93 }
94 self.mainnet.validate_env(evm)
95 }
96
97 fn validate_against_state_and_deduct_caller(
98 &self,
99 evm: &mut Self::Evm,
100 ) -> Result<(), Self::Error> {
101 let ctx = evm.ctx();
102
103 let basefee = ctx.block().basefee() as u128;
104 let blob_price = ctx.block().blob_gasprice().unwrap_or_default();
105 let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
106 let spec = ctx.cfg().spec();
107 let block_number = ctx.block().number();
108 let is_balance_check_disabled = ctx.cfg().is_balance_check_disabled();
109 let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled();
110 let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled();
111
112 let mint = if is_deposit {
113 ctx.tx().mint().unwrap_or_default()
114 } else {
115 0
116 };
117
118 let mut additional_cost = U256::ZERO;
119
120 if !is_deposit {
122 if ctx.chain().l2_block != block_number {
125 *ctx.chain_mut() = L1BlockInfo::try_fetch(ctx.db_mut(), block_number, spec)?;
126 }
127
128 let enveloped_tx = ctx
130 .tx()
131 .enveloped_tx()
132 .expect("all not deposit tx have enveloped tx")
133 .clone();
134
135 additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec);
137
138 if spec.is_enabled_in(OpSpecId::ISTHMUS) {
140 let gas_limit = U256::from(ctx.tx().gas_limit());
141 let operator_fee_charge = ctx.chain().operator_fee_charge(&enveloped_tx, gas_limit);
142 additional_cost = additional_cost.saturating_add(operator_fee_charge);
143 }
144 }
145
146 let (tx, journal) = ctx.tx_journal_mut();
147
148 let caller_account = journal.load_account_code(tx.caller())?.data;
149
150 if !is_deposit {
151 validate_account_nonce_and_code(
153 &mut caller_account.info,
154 tx.nonce(),
155 is_eip3607_disabled,
156 is_nonce_check_disabled,
157 )?;
158 }
159
160 let max_balance_spending = tx.max_balance_spending()?.saturating_add(additional_cost);
161
162 let old_balance = caller_account.info.balance;
164
165 let mut new_balance = caller_account.info.balance.saturating_add(U256::from(mint));
169
170 if !is_deposit && max_balance_spending > new_balance && !is_balance_check_disabled {
173 return Err(InvalidTransaction::LackOfFundForMaxFee {
176 fee: Box::new(max_balance_spending),
177 balance: Box::new(new_balance),
178 }
179 .into());
180 }
181
182 let effective_balance_spending = tx
183 .effective_balance_spending(basefee, blob_price)
184 .expect("effective balance is always smaller than max balance so it can't overflow");
185
186 let gas_balance_spending = effective_balance_spending - tx.value();
188
189 let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost);
195
196 new_balance = new_balance.saturating_sub(op_gas_balance_spending);
197
198 if is_balance_check_disabled {
199 new_balance = new_balance.max(tx.value());
202 }
203
204 caller_account.mark_touch();
206 caller_account.info.balance = new_balance;
207
208 if tx.kind().is_call() {
210 caller_account.info.nonce = caller_account.info.nonce.saturating_add(1);
211 }
212
213 journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call());
216
217 Ok(())
218 }
219
220 fn last_frame_result(
221 &mut self,
222 evm: &mut Self::Evm,
223 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
224 ) -> Result<(), Self::Error> {
225 let ctx = evm.ctx();
226 let tx = ctx.tx();
227 let is_deposit = tx.tx_type() == DEPOSIT_TRANSACTION_TYPE;
228 let tx_gas_limit = tx.gas_limit();
229 let is_regolith = ctx.cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
230
231 let instruction_result = frame_result.interpreter_result().result;
232 let gas = frame_result.gas_mut();
233 let remaining = gas.remaining();
234 let refunded = gas.refunded();
235
236 *gas = Gas::new_spent(tx_gas_limit);
238
239 if instruction_result.is_ok() {
240 if !is_deposit || is_regolith {
254 gas.erase_cost(remaining);
257 gas.record_refund(refunded);
258 } else if is_deposit {
259 let tx = ctx.tx();
260 if tx.is_system_transaction() {
261 gas.erase_cost(tx_gas_limit);
264 }
265 }
266 } else if instruction_result.is_revert() {
267 if !is_deposit || is_regolith {
280 gas.erase_cost(remaining);
281 }
282 }
283 Ok(())
284 }
285
286 fn reimburse_caller(
287 &self,
288 evm: &mut Self::Evm,
289 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
290 ) -> Result<(), Self::Error> {
291 let mut additional_refund = U256::ZERO;
292
293 if evm.ctx().tx().tx_type() != DEPOSIT_TRANSACTION_TYPE {
294 let spec = evm.ctx().cfg().spec();
295 additional_refund = evm
296 .ctx()
297 .chain()
298 .operator_fee_refund(frame_result.gas(), spec);
299 }
300
301 reimburse_caller(evm.ctx(), frame_result.gas(), additional_refund).map_err(From::from)
302 }
303
304 fn refund(
305 &self,
306 evm: &mut Self::Evm,
307 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
308 eip7702_refund: i64,
309 ) {
310 frame_result.gas_mut().record_refund(eip7702_refund);
311
312 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
313 let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
314
315 let is_gas_refund_disabled = is_deposit && !is_regolith;
317 if !is_gas_refund_disabled {
318 frame_result.gas_mut().set_final_refund(
319 evm.ctx()
320 .cfg()
321 .spec()
322 .into_eth_spec()
323 .is_enabled_in(SpecId::LONDON),
324 );
325 }
326 }
327
328 fn reward_beneficiary(
329 &self,
330 evm: &mut Self::Evm,
331 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
332 ) -> Result<(), Self::Error> {
333 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
334
335 if is_deposit {
337 return Ok(());
338 }
339
340 self.mainnet.reward_beneficiary(evm, frame_result)?;
341 let basefee = evm.ctx().block().basefee() as u128;
342
343 let ctx = evm.ctx();
346 let enveloped = ctx.tx().enveloped_tx().cloned();
347 let spec = ctx.cfg().spec();
348 let l1_block_info = ctx.chain_mut();
349
350 let Some(enveloped_tx) = &enveloped else {
351 return Err(ERROR::from_string(
352 "[OPTIMISM] Failed to load enveloped transaction.".into(),
353 ));
354 };
355
356 let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec);
357 let operator_fee_cost = if spec.is_enabled_in(OpSpecId::ISTHMUS) {
358 l1_block_info.operator_fee_charge(enveloped_tx, U256::from(frame_result.gas().used()))
359 } else {
360 U256::ZERO
361 };
362 let base_fee_amount = U256::from(basefee.saturating_mul(frame_result.gas().used() as u128));
363
364 for (recipient, amount) in [
366 (L1_FEE_RECIPIENT, l1_cost),
367 (BASE_FEE_RECIPIENT, base_fee_amount),
368 (OPERATOR_FEE_RECIPIENT, operator_fee_cost),
369 ] {
370 ctx.journal_mut().balance_incr(recipient, amount)?;
371 }
372
373 Ok(())
374 }
375
376 fn execution_result(
377 &mut self,
378 evm: &mut Self::Evm,
379 frame_result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
380 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
381 match core::mem::replace(evm.ctx().error(), Ok(())) {
382 Err(ContextError::Db(e)) => return Err(e.into()),
383 Err(ContextError::Custom(e)) => return Err(Self::Error::from_string(e)),
384 Ok(_) => (),
385 }
386
387 let exec_result =
388 post_execution::output(evm.ctx(), frame_result).map_haltreason(OpHaltReason::Base);
389
390 if exec_result.is_halt() {
391 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
395 if is_deposit && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) {
396 return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith));
397 }
398 }
399 evm.ctx().journal_mut().commit_tx();
400 evm.ctx().chain_mut().clear_tx_l1_cost();
401 evm.ctx().local_mut().clear();
402 evm.frame_stack().clear();
403
404 Ok(exec_result)
405 }
406
407 fn catch_error(
408 &self,
409 evm: &mut Self::Evm,
410 error: Self::Error,
411 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
412 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
413 let output = if error.is_tx_error() && is_deposit {
414 let ctx = evm.ctx();
415 let spec = ctx.cfg().spec();
416 let tx = ctx.tx();
417 let caller = tx.caller();
418 let mint = tx.mint();
419 let is_system_tx = tx.is_system_transaction();
420 let gas_limit = tx.gas_limit();
421
422 evm.ctx().journal_mut().discard_tx();
424
425 let acc: &mut revm::state::Account = evm.ctx().journal_mut().load_account(caller)?.data;
435
436 let old_balance = acc.info.balance;
437
438 acc.transaction_id -= acc.transaction_id;
440 acc.info.nonce = acc.info.nonce.saturating_add(1);
441 acc.info.balance = acc
442 .info
443 .balance
444 .saturating_add(U256::from(mint.unwrap_or_default()));
445 acc.mark_touch();
446
447 evm.ctx()
449 .journal_mut()
450 .caller_accounting_journal_entry(caller, old_balance, true);
451
452 let gas_used = if spec.is_enabled_in(OpSpecId::REGOLITH) || !is_system_tx {
457 gas_limit
458 } else {
459 0
460 };
461 Ok(ExecutionResult::Halt {
463 reason: OpHaltReason::FailedDeposit,
464 gas_used,
465 })
466 } else {
467 Err(error)
468 };
469 evm.ctx().chain_mut().clear_tx_l1_cost();
471 evm.ctx().local_mut().clear();
472 evm.frame_stack().clear();
473
474 output
475 }
476}
477
478impl<EVM, ERROR> InspectorHandler for OpHandler<EVM, ERROR, EthFrame<EthInterpreter>>
479where
480 EVM: InspectorEvmTr<
481 Context: OpContextTr,
482 Frame = EthFrame<EthInterpreter>,
483 Inspector: Inspector<<<Self as Handler>::Evm as EvmTr>::Context, EthInterpreter>,
484 >,
485 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
486{
487 type IT = EthInterpreter;
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493 use crate::{
494 api::default_ctx::OpContext,
495 constants::{
496 BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT,
497 L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT,
498 },
499 DefaultOp, OpBuilder, OpTransaction,
500 };
501 use alloy_primitives::uint;
502 use revm::{
503 context::{BlockEnv, Context, TxEnv},
504 context_interface::result::InvalidTransaction,
505 database::InMemoryDB,
506 database_interface::EmptyDB,
507 handler::EthFrame,
508 interpreter::{CallOutcome, InstructionResult, InterpreterResult},
509 primitives::{bytes, Address, Bytes, B256},
510 state::AccountInfo,
511 };
512 use rstest::rstest;
513 use std::boxed::Box;
514
515 fn call_last_frame_return(
517 ctx: OpContext<EmptyDB>,
518 instruction_result: InstructionResult,
519 gas: Gas,
520 ) -> Gas {
521 let mut evm = ctx.build_op();
522
523 let mut exec_result = FrameResult::Call(CallOutcome::new(
524 InterpreterResult {
525 result: instruction_result,
526 output: Bytes::new(),
527 gas,
528 },
529 0..0,
530 ));
531
532 let mut handler =
533 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
534
535 handler
536 .last_frame_result(&mut evm, &mut exec_result)
537 .unwrap();
538 handler.refund(&mut evm, &mut exec_result, 0);
539 *exec_result.gas()
540 }
541
542 #[test]
543 fn test_revert_gas() {
544 let ctx = Context::op()
545 .with_tx(
546 OpTransaction::builder()
547 .base(TxEnv::builder().gas_limit(100))
548 .build_fill(),
549 )
550 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
551
552 let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90));
553 assert_eq!(gas.remaining(), 90);
554 assert_eq!(gas.spent(), 10);
555 assert_eq!(gas.refunded(), 0);
556 }
557
558 #[test]
559 fn test_consume_gas() {
560 let ctx = Context::op()
561 .with_tx(
562 OpTransaction::builder()
563 .base(TxEnv::builder().gas_limit(100))
564 .build_fill(),
565 )
566 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
567
568 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
569 assert_eq!(gas.remaining(), 90);
570 assert_eq!(gas.spent(), 10);
571 assert_eq!(gas.refunded(), 0);
572 }
573
574 #[test]
575 fn test_consume_gas_with_refund() {
576 let ctx = Context::op()
577 .with_tx(
578 OpTransaction::builder()
579 .base(TxEnv::builder().gas_limit(100))
580 .source_hash(B256::from([1u8; 32]))
581 .build_fill(),
582 )
583 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
584
585 let mut ret_gas = Gas::new(90);
586 ret_gas.record_refund(20);
587
588 let gas = call_last_frame_return(ctx.clone(), InstructionResult::Stop, ret_gas);
589 assert_eq!(gas.remaining(), 90);
590 assert_eq!(gas.spent(), 10);
591 assert_eq!(gas.refunded(), 2); let gas = call_last_frame_return(ctx, InstructionResult::Revert, ret_gas);
594 assert_eq!(gas.remaining(), 90);
595 assert_eq!(gas.spent(), 10);
596 assert_eq!(gas.refunded(), 0);
597 }
598
599 #[test]
600 fn test_consume_gas_deposit_tx() {
601 let ctx = Context::op()
602 .with_tx(
603 OpTransaction::builder()
604 .base(TxEnv::builder().gas_limit(100))
605 .source_hash(B256::from([1u8; 32]))
606 .build_fill(),
607 )
608 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
609 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
610 assert_eq!(gas.remaining(), 0);
611 assert_eq!(gas.spent(), 100);
612 assert_eq!(gas.refunded(), 0);
613 }
614
615 #[test]
616 fn test_consume_gas_sys_deposit_tx() {
617 let ctx = Context::op()
618 .with_tx(
619 OpTransaction::builder()
620 .base(TxEnv::builder().gas_limit(100))
621 .source_hash(B256::from([1u8; 32]))
622 .is_system_transaction()
623 .build_fill(),
624 )
625 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
626 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
627 assert_eq!(gas.remaining(), 100);
628 assert_eq!(gas.spent(), 0);
629 assert_eq!(gas.refunded(), 0);
630 }
631
632 #[test]
633 fn test_commit_mint_value() {
634 let caller = Address::ZERO;
635 let mut db = InMemoryDB::default();
636 db.insert_account_info(
637 caller,
638 AccountInfo {
639 balance: U256::from(1000),
640 ..Default::default()
641 },
642 );
643
644 let mut ctx = Context::op()
645 .with_db(db)
646 .with_chain(L1BlockInfo {
647 l1_base_fee: U256::from(1_000),
648 l1_fee_overhead: Some(U256::from(1_000)),
649 l1_base_fee_scalar: U256::from(1_000),
650 ..Default::default()
651 })
652 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
653 ctx.modify_tx(|tx| {
654 tx.deposit.source_hash = B256::from([1u8; 32]);
655 tx.deposit.mint = Some(10);
656 });
657
658 let mut evm = ctx.build_op();
659
660 let handler =
661 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
662 handler
663 .validate_against_state_and_deduct_caller(&mut evm)
664 .unwrap();
665
666 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
668 assert_eq!(account.info.balance, U256::from(1010));
669 }
670
671 #[test]
672 fn test_remove_l1_cost_non_deposit() {
673 let caller = Address::ZERO;
674 let mut db = InMemoryDB::default();
675 db.insert_account_info(
676 caller,
677 AccountInfo {
678 balance: U256::from(1058), ..Default::default()
680 },
681 );
682 let ctx = Context::op()
683 .with_db(db)
684 .with_chain(L1BlockInfo {
685 l1_base_fee: U256::from(1_000),
686 l1_fee_overhead: Some(U256::from(1_000)),
687 l1_base_fee_scalar: U256::from(1_000),
688 ..Default::default()
689 })
690 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
691 .with_tx(
692 OpTransaction::builder()
693 .base(TxEnv::builder().gas_limit(100))
694 .enveloped_tx(Some(bytes!("FACADE")))
695 .source_hash(B256::ZERO)
696 .build()
697 .unwrap(),
698 );
699
700 let mut evm = ctx.build_op();
701
702 let handler =
703 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
704 handler
705 .validate_against_state_and_deduct_caller(&mut evm)
706 .unwrap();
707
708 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
710 assert_eq!(account.info.balance, U256::from(10)); }
712
713 #[test]
714 fn test_reload_l1_block_info_isthmus() {
715 const BLOCK_NUM: U256 = uint!(100_U256);
716 const L1_BASE_FEE: U256 = uint!(1_U256);
717 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
718 const L1_BASE_FEE_SCALAR: u64 = 3;
719 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
720 const L1_FEE_SCALARS: U256 = U256::from_limbs([
721 0,
722 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
723 0,
724 0,
725 ]);
726 const OPERATOR_FEE_SCALAR: u64 = 5;
727 const OPERATOR_FEE_CONST: u64 = 6;
728 const OPERATOR_FEE: U256 =
729 U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]);
730
731 let mut db = InMemoryDB::default();
732 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
733 l1_block_contract
734 .storage
735 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
736 l1_block_contract
737 .storage
738 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
739 l1_block_contract
740 .storage
741 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
742 l1_block_contract
743 .storage
744 .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE);
745 db.insert_account_info(
746 Address::ZERO,
747 AccountInfo {
748 balance: U256::from(1000),
749 ..Default::default()
750 },
751 );
752
753 let ctx = Context::op()
754 .with_db(db)
755 .with_chain(L1BlockInfo {
756 l2_block: BLOCK_NUM + U256::from(1), ..Default::default()
758 })
759 .with_block(BlockEnv {
760 number: BLOCK_NUM,
761 ..Default::default()
762 })
763 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
764
765 let mut evm = ctx.build_op();
766
767 assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM);
768
769 let handler =
770 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
771 handler
772 .validate_against_state_and_deduct_caller(&mut evm)
773 .unwrap();
774
775 assert_eq!(
776 *evm.ctx().chain(),
777 L1BlockInfo {
778 l2_block: BLOCK_NUM,
779 l1_base_fee: L1_BASE_FEE,
780 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
781 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
782 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
783 empty_ecotone_scalars: false,
784 l1_fee_overhead: None,
785 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
786 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
787 tx_l1_cost: Some(U256::ZERO),
788 }
789 );
790 }
791
792 #[test]
793 fn test_remove_l1_cost() {
794 let caller = Address::ZERO;
795 let mut db = InMemoryDB::default();
796 db.insert_account_info(
797 caller,
798 AccountInfo {
799 balance: U256::from(1049),
800 ..Default::default()
801 },
802 );
803 let ctx = Context::op()
804 .with_db(db)
805 .with_chain(L1BlockInfo {
806 l1_base_fee: U256::from(1_000),
807 l1_fee_overhead: Some(U256::from(1_000)),
808 l1_base_fee_scalar: U256::from(1_000),
809 ..Default::default()
810 })
811 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
812 .with_tx(
813 OpTransaction::builder()
814 .base(TxEnv::builder().gas_limit(100))
815 .source_hash(B256::ZERO)
816 .enveloped_tx(Some(bytes!("FACADE")))
817 .build()
818 .unwrap(),
819 );
820
821 let mut evm = ctx.build_op();
822 let handler =
823 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
824
825 handler
827 .validate_against_state_and_deduct_caller(&mut evm)
828 .unwrap();
829
830 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
832 assert_eq!(account.info.balance, U256::from(1));
833 }
834
835 #[test]
836 fn test_remove_operator_cost() {
837 let caller = Address::ZERO;
838 let mut db = InMemoryDB::default();
839 db.insert_account_info(
840 caller,
841 AccountInfo {
842 balance: U256::from(151),
843 ..Default::default()
844 },
845 );
846 let ctx = Context::op()
847 .with_db(db)
848 .with_chain(L1BlockInfo {
849 operator_fee_scalar: Some(U256::from(10_000_000)),
850 operator_fee_constant: Some(U256::from(50)),
851 ..Default::default()
852 })
853 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS)
854 .with_tx(
855 OpTransaction::builder()
856 .base(TxEnv::builder().gas_limit(10))
857 .enveloped_tx(Some(bytes!("FACADE")))
858 .build_fill(),
859 );
860
861 let mut evm = ctx.build_op();
862 let handler =
863 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
864
865 handler
868 .validate_against_state_and_deduct_caller(&mut evm)
869 .unwrap();
870
871 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
873 assert_eq!(account.info.balance, U256::from(1));
874 }
875
876 #[test]
877 fn test_remove_l1_cost_lack_of_funds() {
878 let caller = Address::ZERO;
879 let mut db = InMemoryDB::default();
880 db.insert_account_info(
881 caller,
882 AccountInfo {
883 balance: U256::from(48),
884 ..Default::default()
885 },
886 );
887 let ctx = Context::op()
888 .with_db(db)
889 .with_chain(L1BlockInfo {
890 l1_base_fee: U256::from(1_000),
891 l1_fee_overhead: Some(U256::from(1_000)),
892 l1_base_fee_scalar: U256::from(1_000),
893 ..Default::default()
894 })
895 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
896 .modify_tx_chained(|tx| {
897 tx.enveloped_tx = Some(bytes!("FACADE"));
898 });
899
900 let mut evm = ctx.build_op();
902 let handler =
903 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
904
905 assert_eq!(
907 handler.validate_against_state_and_deduct_caller(&mut evm),
908 Err(EVMError::Transaction(
909 InvalidTransaction::LackOfFundForMaxFee {
910 fee: Box::new(U256::from(1048)),
911 balance: Box::new(U256::from(48)),
912 }
913 .into(),
914 ))
915 );
916 }
917
918 #[test]
919 fn test_validate_sys_tx() {
920 let ctx = Context::op()
922 .modify_tx_chained(|tx| {
923 tx.deposit.source_hash = B256::from([1u8; 32]);
924 tx.deposit.is_system_transaction = true;
925 })
926 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
927
928 let mut evm = ctx.build_op();
929 let handler =
930 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
931
932 assert_eq!(
933 handler.validate_env(&mut evm),
934 Err(EVMError::Transaction(
935 OpTransactionError::DepositSystemTxPostRegolith
936 ))
937 );
938
939 evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK);
940
941 assert!(handler.validate_env(&mut evm).is_ok());
943 }
944
945 #[test]
946 fn test_validate_deposit_tx() {
947 let ctx = Context::op()
949 .modify_tx_chained(|tx| {
950 tx.deposit.source_hash = B256::from([1u8; 32]);
951 })
952 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
953
954 let mut evm = ctx.build_op();
955 let handler =
956 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
957
958 assert!(handler.validate_env(&mut evm).is_ok());
959 }
960
961 #[test]
962 fn test_validate_tx_against_state_deposit_tx() {
963 let ctx = Context::op()
965 .modify_tx_chained(|tx| {
966 tx.deposit.source_hash = B256::from([1u8; 32]);
967 })
968 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
969
970 let mut evm = ctx.build_op();
971 let handler =
972 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
973
974 assert!(handler.validate_env(&mut evm).is_ok());
976 }
977
978 #[test]
979 fn test_halted_deposit_tx_post_regolith() {
980 let ctx = Context::op()
981 .modify_tx_chained(|tx| {
982 tx.deposit.source_hash = B256::from([1u8; 32]);
984 })
985 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
986
987 let mut evm = ctx.build_op();
988 let mut handler =
989 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
990
991 assert_eq!(
992 handler.execution_result(
993 &mut evm,
994 FrameResult::Call(CallOutcome {
995 result: InterpreterResult {
996 result: InstructionResult::OutOfGas,
997 output: Default::default(),
998 gas: Default::default(),
999 },
1000 memory_offset: Default::default(),
1001 })
1002 ),
1003 Err(EVMError::Transaction(
1004 OpTransactionError::HaltedDepositPostRegolith
1005 ))
1006 )
1007 }
1008
1009 #[test]
1010 fn test_tx_zero_value_touch_caller() {
1011 let ctx = Context::op();
1012
1013 let mut evm = ctx.build_op();
1014
1015 assert!(!evm
1016 .0
1017 .ctx
1018 .journal_mut()
1019 .load_account(Address::ZERO)
1020 .unwrap()
1021 .is_touched());
1022
1023 let handler =
1024 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1025
1026 handler
1027 .validate_against_state_and_deduct_caller(&mut evm)
1028 .unwrap();
1029
1030 assert!(evm
1031 .0
1032 .ctx
1033 .journal_mut()
1034 .load_account(Address::ZERO)
1035 .unwrap()
1036 .is_touched());
1037 }
1038
1039 #[rstest]
1040 #[case::deposit(true)]
1041 #[case::dyn_fee(false)]
1042 fn test_operator_fee_refund(#[case] is_deposit: bool) {
1043 const SENDER: Address = Address::ZERO;
1044 const GAS_PRICE: u128 = 0xFF;
1045 const OP_FEE_MOCK_PARAM: u128 = 0xFFFF;
1046
1047 let ctx = Context::op()
1048 .with_tx(
1049 OpTransaction::builder()
1050 .base(
1051 TxEnv::builder()
1052 .gas_price(GAS_PRICE)
1053 .gas_priority_fee(None)
1054 .caller(SENDER),
1055 )
1056 .enveloped_tx(if is_deposit {
1057 None
1058 } else {
1059 Some(bytes!("FACADE"))
1060 })
1061 .source_hash(if is_deposit {
1062 B256::from([1u8; 32])
1063 } else {
1064 B256::ZERO
1065 })
1066 .build_fill(),
1067 )
1068 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
1069
1070 let mut evm = ctx.build_op();
1071 let handler =
1072 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1073
1074 evm.ctx().chain.operator_fee_scalar = Some(U256::from(OP_FEE_MOCK_PARAM));
1076 evm.ctx().chain.operator_fee_constant = Some(U256::from(OP_FEE_MOCK_PARAM));
1077
1078 let mut gas = Gas::new(100);
1079 gas.set_spent(10);
1080 let mut exec_result = FrameResult::Call(CallOutcome::new(
1081 InterpreterResult {
1082 result: InstructionResult::Return,
1083 output: Default::default(),
1084 gas,
1085 },
1086 0..0,
1087 ));
1088
1089 handler
1091 .reimburse_caller(&mut evm, &mut exec_result)
1092 .unwrap();
1093
1094 let mut expected_refund =
1097 U256::from(GAS_PRICE * (gas.remaining() + gas.refunded() as u64) as u128);
1098 let op_fee_refund = evm
1099 .ctx()
1100 .chain()
1101 .operator_fee_refund(&gas, OpSpecId::ISTHMUS);
1102 assert!(op_fee_refund > U256::ZERO);
1103
1104 if !is_deposit {
1105 expected_refund += op_fee_refund;
1106 }
1107
1108 let account = evm.ctx().journal_mut().load_account(SENDER).unwrap();
1110 assert_eq!(account.info.balance, expected_refund);
1111 }
1112
1113 #[test]
1114 fn test_tx_low_balance_nonce_unchanged() {
1115 let ctx = Context::op().with_tx(
1116 OpTransaction::builder()
1117 .base(TxEnv::builder().value(U256::from(1000)))
1118 .build_fill(),
1119 );
1120
1121 let mut evm = ctx.build_op();
1122
1123 let handler =
1124 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1125
1126 let result = handler.validate_against_state_and_deduct_caller(&mut evm);
1127
1128 assert!(matches!(
1129 result.err().unwrap(),
1130 EVMError::Transaction(OpTransactionError::Base(
1131 InvalidTransaction::LackOfFundForMaxFee { .. }
1132 ))
1133 ));
1134 assert_eq!(
1135 evm.0
1136 .ctx
1137 .journal_mut()
1138 .load_account(Address::ZERO)
1139 .unwrap()
1140 .info
1141 .nonce,
1142 0
1143 );
1144 }
1145}