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 let enveloped_tx = ctx
127 .tx()
128 .enveloped_tx()
129 .expect("all not deposit tx have enveloped tx")
130 .clone();
131
132 additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec);
134
135 if spec.is_enabled_in(OpSpecId::ISTHMUS) {
137 let gas_limit = U256::from(ctx.tx().gas_limit());
138 let operator_fee_charge = ctx.chain().operator_fee_charge(&enveloped_tx, gas_limit);
139 additional_cost = additional_cost.saturating_add(operator_fee_charge);
140 }
141 }
142
143 let (tx, journal) = ctx.tx_journal_mut();
144
145 let caller_account = journal.load_account_code(tx.caller())?.data;
146
147 if !is_deposit {
148 validate_account_nonce_and_code(
150 &mut caller_account.info,
151 tx.nonce(),
152 is_eip3607_disabled,
153 is_nonce_check_disabled,
154 )?;
155 }
156
157 let max_balance_spending = tx.max_balance_spending()?.saturating_add(additional_cost);
158
159 let old_balance = caller_account.info.balance;
161
162 let mut new_balance = caller_account.info.balance.saturating_add(U256::from(mint));
166
167 if !is_deposit && max_balance_spending > new_balance && !is_balance_check_disabled {
170 return Err(InvalidTransaction::LackOfFundForMaxFee {
173 fee: Box::new(max_balance_spending),
174 balance: Box::new(new_balance),
175 }
176 .into());
177 }
178
179 let effective_balance_spending = tx
180 .effective_balance_spending(basefee, blob_price)
181 .expect("effective balance is always smaller than max balance so it can't overflow");
182
183 let gas_balance_spending = effective_balance_spending - tx.value();
185
186 let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost);
192
193 new_balance = new_balance.saturating_sub(op_gas_balance_spending);
194
195 if is_balance_check_disabled {
196 new_balance = new_balance.max(tx.value());
199 }
200
201 caller_account.mark_touch();
203 caller_account.info.balance = new_balance;
204
205 if tx.kind().is_call() {
207 caller_account.info.nonce = caller_account.info.nonce.saturating_add(1);
208 }
209
210 journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call());
213
214 Ok(())
215 }
216
217 fn last_frame_result(
218 &mut self,
219 evm: &mut Self::Evm,
220 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
221 ) -> Result<(), Self::Error> {
222 let ctx = evm.ctx();
223 let tx = ctx.tx();
224 let is_deposit = tx.tx_type() == DEPOSIT_TRANSACTION_TYPE;
225 let tx_gas_limit = tx.gas_limit();
226 let is_regolith = ctx.cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
227
228 let instruction_result = frame_result.interpreter_result().result;
229 let gas = frame_result.gas_mut();
230 let remaining = gas.remaining();
231 let refunded = gas.refunded();
232
233 *gas = Gas::new_spent(tx_gas_limit);
235
236 if instruction_result.is_ok() {
237 if !is_deposit || is_regolith {
251 gas.erase_cost(remaining);
254 gas.record_refund(refunded);
255 } else if is_deposit {
256 let tx = ctx.tx();
257 if tx.is_system_transaction() {
258 gas.erase_cost(tx_gas_limit);
261 }
262 }
263 } else if instruction_result.is_revert() {
264 if !is_deposit || is_regolith {
277 gas.erase_cost(remaining);
278 }
279 }
280 Ok(())
281 }
282
283 fn reimburse_caller(
284 &self,
285 evm: &mut Self::Evm,
286 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
287 ) -> Result<(), Self::Error> {
288 let mut additional_refund = U256::ZERO;
289
290 if evm.ctx().tx().tx_type() != DEPOSIT_TRANSACTION_TYPE {
291 let spec = evm.ctx().cfg().spec();
292 additional_refund = evm
293 .ctx()
294 .chain()
295 .operator_fee_refund(frame_result.gas(), spec);
296 }
297
298 reimburse_caller(evm.ctx(), frame_result.gas(), additional_refund).map_err(From::from)
299 }
300
301 fn refund(
302 &self,
303 evm: &mut Self::Evm,
304 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
305 eip7702_refund: i64,
306 ) {
307 frame_result.gas_mut().record_refund(eip7702_refund);
308
309 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
310 let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
311
312 let is_gas_refund_disabled = is_deposit && !is_regolith;
314 if !is_gas_refund_disabled {
315 frame_result.gas_mut().set_final_refund(
316 evm.ctx()
317 .cfg()
318 .spec()
319 .into_eth_spec()
320 .is_enabled_in(SpecId::LONDON),
321 );
322 }
323 }
324
325 fn reward_beneficiary(
326 &self,
327 evm: &mut Self::Evm,
328 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
329 ) -> Result<(), Self::Error> {
330 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
331
332 if is_deposit {
334 return Ok(());
335 }
336
337 self.mainnet.reward_beneficiary(evm, frame_result)?;
338 let basefee = evm.ctx().block().basefee() as u128;
339
340 let ctx = evm.ctx();
343 let enveloped = ctx.tx().enveloped_tx().cloned();
344 let spec = ctx.cfg().spec();
345 let l1_block_info = ctx.chain_mut();
346
347 let Some(enveloped_tx) = &enveloped else {
348 return Err(ERROR::from_string(
349 "[OPTIMISM] Failed to load enveloped transaction.".into(),
350 ));
351 };
352
353 let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec);
354 let operator_fee_cost = if spec.is_enabled_in(OpSpecId::ISTHMUS) {
355 l1_block_info.operator_fee_charge(enveloped_tx, U256::from(frame_result.gas().used()))
356 } else {
357 U256::ZERO
358 };
359 let base_fee_amount = U256::from(basefee.saturating_mul(frame_result.gas().used() as u128));
360
361 for (recipient, amount) in [
363 (L1_FEE_RECIPIENT, l1_cost),
364 (BASE_FEE_RECIPIENT, base_fee_amount),
365 (OPERATOR_FEE_RECIPIENT, operator_fee_cost),
366 ] {
367 ctx.journal_mut().balance_incr(recipient, amount)?;
368 }
369
370 Ok(())
371 }
372
373 fn execution_result(
374 &mut self,
375 evm: &mut Self::Evm,
376 frame_result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
377 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
378 match core::mem::replace(evm.ctx().error(), Ok(())) {
379 Err(ContextError::Db(e)) => return Err(e.into()),
380 Err(ContextError::Custom(e)) => return Err(Self::Error::from_string(e)),
381 Ok(_) => (),
382 }
383
384 let exec_result =
385 post_execution::output(evm.ctx(), frame_result).map_haltreason(OpHaltReason::Base);
386
387 if exec_result.is_halt() {
388 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
392 if is_deposit && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) {
393 return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith));
394 }
395 }
396 evm.ctx().journal_mut().commit_tx();
397 evm.ctx().chain_mut().clear_tx_l1_cost();
398 evm.ctx().local_mut().clear();
399 evm.frame_stack().clear();
400
401 Ok(exec_result)
402 }
403
404 fn catch_error(
405 &self,
406 evm: &mut Self::Evm,
407 error: Self::Error,
408 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
409 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
410 let output = if error.is_tx_error() && is_deposit {
411 let ctx = evm.ctx();
412 let spec = ctx.cfg().spec();
413 let tx = ctx.tx();
414 let caller = tx.caller();
415 let mint = tx.mint();
416 let is_system_tx = tx.is_system_transaction();
417 let gas_limit = tx.gas_limit();
418
419 evm.ctx().journal_mut().discard_tx();
421
422 let acc: &mut revm::state::Account = evm.ctx().journal_mut().load_account(caller)?.data;
432
433 let old_balance = acc.info.balance;
434
435 acc.transaction_id -= 1;
437 acc.info.nonce = acc.info.nonce.saturating_add(1);
438 acc.info.balance = acc
439 .info
440 .balance
441 .saturating_add(U256::from(mint.unwrap_or_default()));
442 acc.mark_touch();
443
444 evm.ctx()
446 .journal_mut()
447 .caller_accounting_journal_entry(caller, old_balance, true);
448
449 let gas_used = if spec.is_enabled_in(OpSpecId::REGOLITH) || !is_system_tx {
454 gas_limit
455 } else {
456 0
457 };
458 Ok(ExecutionResult::Halt {
460 reason: OpHaltReason::FailedDeposit,
461 gas_used,
462 })
463 } else {
464 Err(error)
465 };
466 evm.ctx().chain_mut().clear_tx_l1_cost();
468 evm.ctx().local_mut().clear();
469 evm.frame_stack().clear();
470
471 output
472 }
473}
474
475impl<EVM, ERROR> InspectorHandler for OpHandler<EVM, ERROR, EthFrame<EthInterpreter>>
476where
477 EVM: InspectorEvmTr<
478 Context: OpContextTr,
479 Frame = EthFrame<EthInterpreter>,
480 Inspector: Inspector<<<Self as Handler>::Evm as EvmTr>::Context, EthInterpreter>,
481 >,
482 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
483{
484 type IT = EthInterpreter;
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use crate::{
491 api::default_ctx::OpContext,
492 constants::{
493 BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT,
494 L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, 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 }
786 );
787 }
788
789 #[test]
790 fn test_remove_l1_cost() {
791 let caller = Address::ZERO;
792 let mut db = InMemoryDB::default();
793 db.insert_account_info(
794 caller,
795 AccountInfo {
796 balance: U256::from(1049),
797 ..Default::default()
798 },
799 );
800 let ctx = Context::op()
801 .with_db(db)
802 .with_chain(L1BlockInfo {
803 l1_base_fee: U256::from(1_000),
804 l1_fee_overhead: Some(U256::from(1_000)),
805 l1_base_fee_scalar: U256::from(1_000),
806 ..Default::default()
807 })
808 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
809 .with_tx(
810 OpTransaction::builder()
811 .base(TxEnv::builder().gas_limit(100))
812 .source_hash(B256::ZERO)
813 .enveloped_tx(Some(bytes!("FACADE")))
814 .build()
815 .unwrap(),
816 );
817
818 let mut evm = ctx.build_op();
819 let handler =
820 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
821
822 handler
824 .validate_against_state_and_deduct_caller(&mut evm)
825 .unwrap();
826
827 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
829 assert_eq!(account.info.balance, U256::from(1));
830 }
831
832 #[test]
833 fn test_remove_operator_cost() {
834 let caller = Address::ZERO;
835 let mut db = InMemoryDB::default();
836 db.insert_account_info(
837 caller,
838 AccountInfo {
839 balance: U256::from(151),
840 ..Default::default()
841 },
842 );
843 let ctx = Context::op()
844 .with_db(db)
845 .with_chain(L1BlockInfo {
846 operator_fee_scalar: Some(U256::from(10_000_000)),
847 operator_fee_constant: Some(U256::from(50)),
848 ..Default::default()
849 })
850 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS)
851 .with_tx(
852 OpTransaction::builder()
853 .base(TxEnv::builder().gas_limit(10))
854 .enveloped_tx(Some(bytes!("FACADE")))
855 .build_fill(),
856 );
857
858 let mut evm = ctx.build_op();
859 let handler =
860 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
861
862 handler
865 .validate_against_state_and_deduct_caller(&mut evm)
866 .unwrap();
867
868 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
870 assert_eq!(account.info.balance, U256::from(1));
871 }
872
873 #[test]
874 fn test_remove_l1_cost_lack_of_funds() {
875 let caller = Address::ZERO;
876 let mut db = InMemoryDB::default();
877 db.insert_account_info(
878 caller,
879 AccountInfo {
880 balance: U256::from(48),
881 ..Default::default()
882 },
883 );
884 let ctx = Context::op()
885 .with_db(db)
886 .with_chain(L1BlockInfo {
887 l1_base_fee: U256::from(1_000),
888 l1_fee_overhead: Some(U256::from(1_000)),
889 l1_base_fee_scalar: U256::from(1_000),
890 ..Default::default()
891 })
892 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
893 .modify_tx_chained(|tx| {
894 tx.enveloped_tx = Some(bytes!("FACADE"));
895 });
896
897 let mut evm = ctx.build_op();
899 let handler =
900 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
901
902 assert_eq!(
904 handler.validate_against_state_and_deduct_caller(&mut evm),
905 Err(EVMError::Transaction(
906 InvalidTransaction::LackOfFundForMaxFee {
907 fee: Box::new(U256::from(1048)),
908 balance: Box::new(U256::from(48)),
909 }
910 .into(),
911 ))
912 );
913 }
914
915 #[test]
916 fn test_validate_sys_tx() {
917 let ctx = Context::op()
919 .modify_tx_chained(|tx| {
920 tx.deposit.source_hash = B256::from([1u8; 32]);
921 tx.deposit.is_system_transaction = true;
922 })
923 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
924
925 let mut evm = ctx.build_op();
926 let handler =
927 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
928
929 assert_eq!(
930 handler.validate_env(&mut evm),
931 Err(EVMError::Transaction(
932 OpTransactionError::DepositSystemTxPostRegolith
933 ))
934 );
935
936 evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK);
937
938 assert!(handler.validate_env(&mut evm).is_ok());
940 }
941
942 #[test]
943 fn test_validate_deposit_tx() {
944 let ctx = Context::op()
946 .modify_tx_chained(|tx| {
947 tx.deposit.source_hash = B256::from([1u8; 32]);
948 })
949 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
950
951 let mut evm = ctx.build_op();
952 let handler =
953 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
954
955 assert!(handler.validate_env(&mut evm).is_ok());
956 }
957
958 #[test]
959 fn test_validate_tx_against_state_deposit_tx() {
960 let ctx = Context::op()
962 .modify_tx_chained(|tx| {
963 tx.deposit.source_hash = B256::from([1u8; 32]);
964 })
965 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
966
967 let mut evm = ctx.build_op();
968 let handler =
969 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
970
971 assert!(handler.validate_env(&mut evm).is_ok());
973 }
974
975 #[test]
976 fn test_halted_deposit_tx_post_regolith() {
977 let ctx = Context::op()
978 .modify_tx_chained(|tx| {
979 tx.deposit.source_hash = B256::from([1u8; 32]);
981 })
982 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
983
984 let mut evm = ctx.build_op();
985 let mut handler =
986 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
987
988 assert_eq!(
989 handler.execution_result(
990 &mut evm,
991 FrameResult::Call(CallOutcome {
992 result: InterpreterResult {
993 result: InstructionResult::OutOfGas,
994 output: Default::default(),
995 gas: Default::default(),
996 },
997 memory_offset: Default::default(),
998 })
999 ),
1000 Err(EVMError::Transaction(
1001 OpTransactionError::HaltedDepositPostRegolith
1002 ))
1003 )
1004 }
1005
1006 #[test]
1007 fn test_tx_zero_value_touch_caller() {
1008 let ctx = Context::op();
1009
1010 let mut evm = ctx.build_op();
1011
1012 assert!(!evm
1013 .0
1014 .ctx
1015 .journal_mut()
1016 .load_account(Address::ZERO)
1017 .unwrap()
1018 .is_touched());
1019
1020 let handler =
1021 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1022
1023 handler
1024 .validate_against_state_and_deduct_caller(&mut evm)
1025 .unwrap();
1026
1027 assert!(evm
1028 .0
1029 .ctx
1030 .journal_mut()
1031 .load_account(Address::ZERO)
1032 .unwrap()
1033 .is_touched());
1034 }
1035
1036 #[rstest]
1037 #[case::deposit(true)]
1038 #[case::dyn_fee(false)]
1039 fn test_operator_fee_refund(#[case] is_deposit: bool) {
1040 const SENDER: Address = Address::ZERO;
1041 const GAS_PRICE: u128 = 0xFF;
1042 const OP_FEE_MOCK_PARAM: u128 = 0xFFFF;
1043
1044 let ctx = Context::op()
1045 .with_tx(
1046 OpTransaction::builder()
1047 .base(
1048 TxEnv::builder()
1049 .gas_price(GAS_PRICE)
1050 .gas_priority_fee(None)
1051 .caller(SENDER),
1052 )
1053 .enveloped_tx(if is_deposit {
1054 None
1055 } else {
1056 Some(bytes!("FACADE"))
1057 })
1058 .source_hash(if is_deposit {
1059 B256::from([1u8; 32])
1060 } else {
1061 B256::ZERO
1062 })
1063 .build_fill(),
1064 )
1065 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
1066
1067 let mut evm = ctx.build_op();
1068 let handler =
1069 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1070
1071 evm.ctx().chain.operator_fee_scalar = Some(U256::from(OP_FEE_MOCK_PARAM));
1073 evm.ctx().chain.operator_fee_constant = Some(U256::from(OP_FEE_MOCK_PARAM));
1074
1075 let mut gas = Gas::new(100);
1076 gas.set_spent(10);
1077 let mut exec_result = FrameResult::Call(CallOutcome::new(
1078 InterpreterResult {
1079 result: InstructionResult::Return,
1080 output: Default::default(),
1081 gas,
1082 },
1083 0..0,
1084 ));
1085
1086 handler
1088 .reimburse_caller(&mut evm, &mut exec_result)
1089 .unwrap();
1090
1091 let mut expected_refund =
1094 U256::from(GAS_PRICE * (gas.remaining() + gas.refunded() as u64) as u128);
1095 let op_fee_refund = evm
1096 .ctx()
1097 .chain()
1098 .operator_fee_refund(&gas, OpSpecId::ISTHMUS);
1099 assert!(op_fee_refund > U256::ZERO);
1100
1101 if !is_deposit {
1102 expected_refund += op_fee_refund;
1103 }
1104
1105 let account = evm.ctx().journal_mut().load_account(SENDER).unwrap();
1107 assert_eq!(account.info.balance, expected_refund);
1108 }
1109
1110 #[test]
1111 fn test_tx_low_balance_nonce_unchanged() {
1112 let ctx = Context::op().with_tx(
1113 OpTransaction::builder()
1114 .base(TxEnv::builder().value(U256::from(1000)))
1115 .build_fill(),
1116 );
1117
1118 let mut evm = ctx.build_op();
1119
1120 let handler =
1121 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1122
1123 let result = handler.validate_against_state_and_deduct_caller(&mut evm);
1124
1125 assert!(matches!(
1126 result.err().unwrap(),
1127 EVMError::Transaction(OpTransactionError::Base(
1128 InvalidTransaction::LackOfFundForMaxFee { .. }
1129 ))
1130 ));
1131 assert_eq!(
1132 evm.0
1133 .ctx
1134 .journal_mut()
1135 .load_account(Address::ZERO)
1136 .unwrap()
1137 .info
1138 .nonce,
1139 0
1140 );
1141 }
1142}