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