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 result::{EVMError, ExecutionResult, FromStringError, ResultAndState},
12 Block, Cfg, ContextTr, JournalTr, Transaction,
13 },
14 handler::{
15 handler::EvmTrError, pre_execution::validate_account_nonce_and_code, EvmTr, Frame,
16 FrameResult, Handler, MainnetHandler,
17 },
18 inspector::{Inspector, InspectorEvmTr, InspectorFrame, InspectorHandler},
19 interpreter::{interpreter::EthInterpreter, FrameInput, Gas},
20 primitives::{hardfork::SpecId, HashMap, U256},
21 state::Account,
22 Database,
23};
24use std::boxed::Box;
25
26pub struct OpHandler<EVM, ERROR, FRAME> {
27 pub mainnet: MainnetHandler<EVM, ERROR, FRAME>,
28 pub _phantom: core::marker::PhantomData<(EVM, ERROR, FRAME)>,
29}
30
31impl<EVM, ERROR, FRAME> OpHandler<EVM, ERROR, FRAME> {
32 pub fn new() -> Self {
33 Self {
34 mainnet: MainnetHandler::default(),
35 _phantom: core::marker::PhantomData,
36 }
37 }
38}
39
40impl<EVM, ERROR, FRAME> Default for OpHandler<EVM, ERROR, FRAME> {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46pub trait IsTxError {
47 fn is_tx_error(&self) -> bool;
48}
49
50impl<DB, TX> IsTxError for EVMError<DB, TX> {
51 fn is_tx_error(&self) -> bool {
52 matches!(self, EVMError::Transaction(_))
53 }
54}
55
56impl<EVM, ERROR, FRAME> Handler for OpHandler<EVM, ERROR, FRAME>
57where
58 EVM: EvmTr<Context: OpContextTr>,
59 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
60 FRAME: Frame<Evm = EVM, Error = ERROR, FrameResult = FrameResult, FrameInit = FrameInput>,
63{
64 type Evm = EVM;
65 type Error = ERROR;
66 type Frame = FRAME;
67 type HaltReason = OpHaltReason;
68
69 fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
70 let ctx = evm.ctx();
72 let tx = ctx.tx();
73 let tx_type = tx.tx_type();
74 if tx_type == DEPOSIT_TRANSACTION_TYPE {
75 if tx.is_system_transaction()
77 && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH)
78 {
79 return Err(OpTransactionError::DepositSystemTxPostRegolith.into());
80 }
81 return Ok(());
82 }
83 self.mainnet.validate_env(evm)
84 }
85
86 fn validate_against_state_and_deduct_caller(
87 &self,
88 evm: &mut Self::Evm,
89 ) -> Result<(), Self::Error> {
90 let ctx = evm.ctx();
91
92 let basefee = ctx.block().basefee() as u128;
93 let blob_price = ctx.block().blob_gasprice().unwrap_or_default();
94 let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
95 let spec = ctx.cfg().spec();
96 let block_number = ctx.block().number();
97 let is_balance_check_disabled = ctx.cfg().is_balance_check_disabled();
98 let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled();
99 let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled();
100 let mint = ctx.tx().mint();
101
102 let mut additional_cost = U256::ZERO;
103
104 if !is_deposit {
106 if ctx.chain().l2_block != block_number {
109 *ctx.chain() = L1BlockInfo::try_fetch(ctx.db(), block_number, spec)?;
110 }
111
112 let enveloped_tx = ctx
114 .tx()
115 .enveloped_tx()
116 .expect("all not deposit tx have enveloped tx")
117 .clone();
118
119 additional_cost = ctx.chain().calculate_tx_l1_cost(&enveloped_tx, spec);
121
122 if spec.is_enabled_in(OpSpecId::ISTHMUS) {
124 let gas_limit = U256::from(ctx.tx().gas_limit());
125 let operator_fee_charge = ctx.chain().operator_fee_charge(&enveloped_tx, gas_limit);
126 additional_cost = additional_cost.saturating_add(operator_fee_charge);
127 }
128 }
129
130 let (tx, journal) = ctx.tx_journal();
131
132 let caller_account = journal.load_account_code(tx.caller())?.data;
133
134 if is_deposit {
138 if let Some(mint) = mint {
139 caller_account.info.balance =
140 caller_account.info.balance.saturating_add(U256::from(mint));
141 }
142 if tx.kind().is_call() {
143 caller_account.info.nonce = caller_account.info.nonce.saturating_add(1);
144 }
145 } else {
146 validate_account_nonce_and_code(
148 &mut caller_account.info,
149 tx.nonce(),
150 tx.kind().is_call(),
151 is_eip3607_disabled,
152 is_nonce_check_disabled,
153 )?;
154 }
155
156 let max_balance_spending = tx.max_balance_spending()?.saturating_add(additional_cost);
157
158 if is_balance_check_disabled {
161 caller_account.info.balance = caller_account.info.balance.max(tx.value());
164 } else if !is_deposit && max_balance_spending > caller_account.info.balance {
165 return Err(InvalidTransaction::LackOfFundForMaxFee {
168 fee: Box::new(max_balance_spending),
169 balance: Box::new(caller_account.info.balance),
170 }
171 .into());
172 } else {
173 let effective_balance_spending =
174 tx.effective_balance_spending(basefee, blob_price).expect(
175 "effective balance is always smaller than max balance so it can't overflow",
176 );
177
178 let gas_balance_spending = effective_balance_spending - tx.value();
180
181 let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost);
187
188 caller_account.info.balance = caller_account
189 .info
190 .balance
191 .saturating_sub(op_gas_balance_spending);
192 }
193
194 caller_account.mark_touch();
196 Ok(())
197 }
198
199 fn last_frame_result(
200 &mut self,
201 evm: &mut Self::Evm,
202 frame_result: &mut <Self::Frame as Frame>::FrameResult,
203 ) -> Result<(), Self::Error> {
204 let ctx = evm.ctx();
205 let tx = ctx.tx();
206 let is_deposit = tx.tx_type() == DEPOSIT_TRANSACTION_TYPE;
207 let tx_gas_limit = tx.gas_limit();
208 let is_regolith = ctx.cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
209
210 let instruction_result = frame_result.interpreter_result().result;
211 let gas = frame_result.gas_mut();
212 let remaining = gas.remaining();
213 let refunded = gas.refunded();
214
215 *gas = Gas::new_spent(tx_gas_limit);
217
218 if instruction_result.is_ok() {
219 if !is_deposit || is_regolith {
233 gas.erase_cost(remaining);
236 gas.record_refund(refunded);
237 } else if is_deposit {
238 let tx = ctx.tx();
239 if tx.is_system_transaction() {
240 gas.erase_cost(tx_gas_limit);
243 }
244 }
245 } else if instruction_result.is_revert() {
246 if !is_deposit || is_regolith {
259 gas.erase_cost(remaining);
260 }
261 }
262 Ok(())
263 }
264
265 fn reimburse_caller(
266 &self,
267 evm: &mut Self::Evm,
268 exec_result: &mut <Self::Frame as Frame>::FrameResult,
269 ) -> Result<(), Self::Error> {
270 self.mainnet.reimburse_caller(evm, exec_result)?;
271
272 let context = evm.ctx();
273 if context.tx().tx_type() != DEPOSIT_TRANSACTION_TYPE {
274 let caller = context.tx().caller();
275 let spec = context.cfg().spec();
276 let operator_fee_refund = context.chain().operator_fee_refund(exec_result.gas(), spec);
277
278 let caller_account = context.journal().load_account(caller)?;
279
280 caller_account.data.info.balance = caller_account
283 .data
284 .info
285 .balance
286 .saturating_add(operator_fee_refund);
287 }
288
289 Ok(())
290 }
291
292 fn refund(
293 &self,
294 evm: &mut Self::Evm,
295 exec_result: &mut <Self::Frame as Frame>::FrameResult,
296 eip7702_refund: i64,
297 ) {
298 exec_result.gas_mut().record_refund(eip7702_refund);
299
300 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
301 let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
302
303 let is_gas_refund_disabled = is_deposit && !is_regolith;
305 if !is_gas_refund_disabled {
306 exec_result.gas_mut().set_final_refund(
307 evm.ctx()
308 .cfg()
309 .spec()
310 .into_eth_spec()
311 .is_enabled_in(SpecId::LONDON),
312 );
313 }
314 }
315
316 fn reward_beneficiary(
317 &self,
318 evm: &mut Self::Evm,
319 exec_result: &mut <Self::Frame as Frame>::FrameResult,
320 ) -> Result<(), Self::Error> {
321 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
322
323 if !is_deposit {
325 self.mainnet.reward_beneficiary(evm, exec_result)?;
326 let basefee = evm.ctx().block().basefee() as u128;
327
328 let ctx = evm.ctx();
331 let enveloped = ctx.tx().enveloped_tx().cloned();
332 let spec = ctx.cfg().spec();
333 let l1_block_info = ctx.chain();
334
335 let Some(enveloped_tx) = &enveloped else {
336 return Err(ERROR::from_string(
337 "[OPTIMISM] Failed to load enveloped transaction.".into(),
338 ));
339 };
340
341 let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec);
342 let mut operator_fee_cost = U256::ZERO;
343 if spec.is_enabled_in(OpSpecId::ISTHMUS) {
344 operator_fee_cost = l1_block_info.operator_fee_charge(
345 enveloped_tx,
346 U256::from(exec_result.gas().spent() - exec_result.gas().refunded() as u64),
347 );
348 }
349 let mut l1_fee_vault_account = ctx.journal().load_account(L1_FEE_RECIPIENT)?;
351 l1_fee_vault_account.mark_touch();
352 l1_fee_vault_account.info.balance += l1_cost;
353
354 let mut base_fee_vault_account =
356 evm.ctx().journal().load_account(BASE_FEE_RECIPIENT)?;
357 base_fee_vault_account.mark_touch();
358 base_fee_vault_account.info.balance += U256::from(basefee.saturating_mul(
359 (exec_result.gas().spent() - exec_result.gas().refunded() as u64) as u128,
360 ));
361
362 let mut operator_fee_vault_account =
364 evm.ctx().journal().load_account(OPERATOR_FEE_RECIPIENT)?;
365 operator_fee_vault_account.mark_touch();
366 operator_fee_vault_account.data.info.balance += operator_fee_cost;
367 }
368 Ok(())
369 }
370
371 fn output(
372 &self,
373 evm: &mut Self::Evm,
374 result: <Self::Frame as Frame>::FrameResult,
375 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
376 let result = self.mainnet.output(evm, result)?;
377 let result = result.map_haltreason(OpHaltReason::Base);
378 if result.result.is_halt() {
379 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
383 if is_deposit && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) {
384 return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith));
385 }
386 }
387 evm.ctx().chain().clear_tx_l1_cost();
388 Ok(result)
389 }
390
391 fn catch_error(
392 &self,
393 evm: &mut Self::Evm,
394 error: Self::Error,
395 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
396 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
397 let output = if error.is_tx_error() && is_deposit {
398 let ctx = evm.ctx();
399 let spec = ctx.cfg().spec();
400 let tx = ctx.tx();
401 let caller = tx.caller();
402 let mint = tx.mint();
403 let is_system_tx = tx.is_system_transaction();
404 let gas_limit = tx.gas_limit();
405 let account = {
415 let mut acc = Account::from(
416 evm.ctx()
417 .db()
418 .basic(caller)
419 .unwrap_or_default()
420 .unwrap_or_default(),
421 );
422 acc.info.nonce = acc.info.nonce.saturating_add(1);
423 acc.info.balance = acc
424 .info
425 .balance
426 .saturating_add(U256::from(mint.unwrap_or_default()));
427 acc.mark_touch();
428 acc
429 };
430 let state = HashMap::from_iter([(caller, account)]);
431
432 let gas_used = if spec.is_enabled_in(OpSpecId::REGOLITH) || !is_system_tx {
437 gas_limit
438 } else {
439 0
440 };
441 Ok(ResultAndState {
443 result: ExecutionResult::Halt {
444 reason: OpHaltReason::FailedDeposit,
445 gas_used,
446 },
447 state,
448 })
449 } else {
450 Err(error)
451 };
452 evm.ctx().chain().clear_tx_l1_cost();
454 evm.ctx().journal().clear();
455 evm.ctx().local().clear();
456
457 output
458 }
459}
460
461impl<EVM, ERROR, FRAME> InspectorHandler for OpHandler<EVM, ERROR, FRAME>
462where
463 EVM: InspectorEvmTr<
464 Context: OpContextTr,
465 Inspector: Inspector<<<Self as Handler>::Evm as EvmTr>::Context, EthInterpreter>,
466 >,
467 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
468 FRAME: InspectorFrame<
471 Evm = EVM,
472 Error = ERROR,
473 FrameResult = FrameResult,
474 FrameInit = FrameInput,
475 IT = EthInterpreter,
476 >,
477{
478 type IT = EthInterpreter;
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484 use crate::{
485 api::default_ctx::OpContext,
486 constants::{
487 BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT,
488 L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT,
489 },
490 DefaultOp, OpBuilder,
491 };
492 use alloy_primitives::uint;
493 use revm::{
494 context::{BlockEnv, Context, TransactionType},
495 context_interface::result::InvalidTransaction,
496 database::InMemoryDB,
497 database_interface::EmptyDB,
498 handler::EthFrame,
499 interpreter::{CallOutcome, InstructionResult, InterpreterResult},
500 primitives::{bytes, Address, Bytes, B256},
501 state::AccountInfo,
502 };
503 use rstest::rstest;
504 use std::boxed::Box;
505
506 fn call_last_frame_return(
508 ctx: OpContext<EmptyDB>,
509 instruction_result: InstructionResult,
510 gas: Gas,
511 ) -> Gas {
512 let mut evm = ctx.build_op();
513
514 let mut exec_result = FrameResult::Call(CallOutcome::new(
515 InterpreterResult {
516 result: instruction_result,
517 output: Bytes::new(),
518 gas,
519 },
520 0..0,
521 ));
522
523 let mut handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
524
525 handler
526 .last_frame_result(&mut evm, &mut exec_result)
527 .unwrap();
528 handler.refund(&mut evm, &mut exec_result, 0);
529 *exec_result.gas()
530 }
531
532 #[test]
533 fn test_revert_gas() {
534 let ctx = Context::op()
535 .modify_tx_chained(|tx| {
536 tx.base.gas_limit = 100;
537 tx.enveloped_tx = None;
538 })
539 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
540
541 let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90));
542 assert_eq!(gas.remaining(), 90);
543 assert_eq!(gas.spent(), 10);
544 assert_eq!(gas.refunded(), 0);
545 }
546
547 #[test]
548 fn test_consume_gas() {
549 let ctx = Context::op()
550 .modify_tx_chained(|tx| {
551 tx.base.gas_limit = 100;
552 tx.deposit.source_hash = B256::ZERO;
553 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
554 })
555 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
556
557 let gas = call_last_frame_return(ctx, InstructionResult::Stop, 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_with_refund() {
565 let ctx = Context::op()
566 .modify_tx_chained(|tx| {
567 tx.base.gas_limit = 100;
568 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
569 tx.deposit.source_hash = B256::ZERO;
570 })
571 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
572
573 let mut ret_gas = Gas::new(90);
574 ret_gas.record_refund(20);
575
576 let gas = call_last_frame_return(ctx.clone(), InstructionResult::Stop, ret_gas);
577 assert_eq!(gas.remaining(), 90);
578 assert_eq!(gas.spent(), 10);
579 assert_eq!(gas.refunded(), 2); let gas = call_last_frame_return(ctx, InstructionResult::Revert, ret_gas);
582 assert_eq!(gas.remaining(), 90);
583 assert_eq!(gas.spent(), 10);
584 assert_eq!(gas.refunded(), 0);
585 }
586
587 #[test]
588 fn test_consume_gas_deposit_tx() {
589 let ctx = Context::op()
590 .modify_tx_chained(|tx| {
591 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
592 tx.base.gas_limit = 100;
593 tx.deposit.source_hash = B256::ZERO;
594 })
595 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
596 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
597 assert_eq!(gas.remaining(), 0);
598 assert_eq!(gas.spent(), 100);
599 assert_eq!(gas.refunded(), 0);
600 }
601
602 #[test]
603 fn test_consume_gas_sys_deposit_tx() {
604 let ctx = Context::op()
605 .modify_tx_chained(|tx| {
606 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
607 tx.base.gas_limit = 100;
608 tx.deposit.source_hash = B256::ZERO;
609 tx.deposit.is_system_transaction = true;
610 })
611 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
612 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
613 assert_eq!(gas.remaining(), 100);
614 assert_eq!(gas.spent(), 0);
615 assert_eq!(gas.refunded(), 0);
616 }
617
618 #[test]
619 fn test_commit_mint_value() {
620 let caller = Address::ZERO;
621 let mut db = InMemoryDB::default();
622 db.insert_account_info(
623 caller,
624 AccountInfo {
625 balance: U256::from(1000),
626 ..Default::default()
627 },
628 );
629
630 let mut ctx = Context::op()
631 .with_db(db)
632 .with_chain(L1BlockInfo {
633 l1_base_fee: U256::from(1_000),
634 l1_fee_overhead: Some(U256::from(1_000)),
635 l1_base_fee_scalar: U256::from(1_000),
636 ..Default::default()
637 })
638 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
639 ctx.modify_tx(|tx| {
640 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
641 tx.deposit.source_hash = B256::ZERO;
642 tx.deposit.mint = Some(10);
643 });
644
645 let mut evm = ctx.build_op();
646
647 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
648 handler
649 .validate_against_state_and_deduct_caller(&mut evm)
650 .unwrap();
651
652 let account = evm.ctx().journal().load_account(caller).unwrap();
654 assert_eq!(account.info.balance, U256::from(1010));
655 }
656
657 #[test]
658 fn test_remove_l1_cost_non_deposit() {
659 let caller = Address::ZERO;
660 let mut db = InMemoryDB::default();
661 db.insert_account_info(
662 caller,
663 AccountInfo {
664 balance: U256::from(1000),
665 ..Default::default()
666 },
667 );
668 let ctx = Context::op()
669 .with_db(db)
670 .with_chain(L1BlockInfo {
671 l1_base_fee: U256::from(1_000),
672 l1_fee_overhead: Some(U256::from(1_000)),
673 l1_base_fee_scalar: U256::from(1_000),
674 ..Default::default()
675 })
676 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
677 .modify_tx_chained(|tx| {
678 tx.base.gas_limit = 100;
679 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
680 tx.deposit.mint = Some(10);
681 tx.enveloped_tx = Some(bytes!("FACADE"));
682 tx.deposit.source_hash = B256::ZERO;
683 });
684
685 let mut evm = ctx.build_op();
686
687 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
688 handler
689 .validate_against_state_and_deduct_caller(&mut evm)
690 .unwrap();
691
692 let account = evm.ctx().journal().load_account(caller).unwrap();
694 assert_eq!(account.info.balance, U256::from(1010));
695 }
696
697 #[test]
698 fn test_reload_l1_block_info_isthmus() {
699 const BLOCK_NUM: u64 = 100;
700 const L1_BASE_FEE: U256 = uint!(1_U256);
701 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
702 const L1_BASE_FEE_SCALAR: u64 = 3;
703 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
704 const L1_FEE_SCALARS: U256 = U256::from_limbs([
705 0,
706 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
707 0,
708 0,
709 ]);
710 const OPERATOR_FEE_SCALAR: u64 = 5;
711 const OPERATOR_FEE_CONST: u64 = 6;
712 const OPERATOR_FEE: U256 =
713 U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]);
714
715 let mut db = InMemoryDB::default();
716 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
717 l1_block_contract
718 .storage
719 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
720 l1_block_contract
721 .storage
722 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
723 l1_block_contract
724 .storage
725 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
726 l1_block_contract
727 .storage
728 .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE);
729 db.insert_account_info(
730 Address::ZERO,
731 AccountInfo {
732 balance: U256::from(1000),
733 ..Default::default()
734 },
735 );
736
737 let ctx = Context::op()
738 .with_db(db)
739 .with_chain(L1BlockInfo {
740 l2_block: BLOCK_NUM + 1, ..Default::default()
742 })
743 .with_block(BlockEnv {
744 number: BLOCK_NUM,
745 ..Default::default()
746 })
747 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
748
749 let mut evm = ctx.build_op();
750
751 assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM);
752
753 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
754 handler
755 .validate_against_state_and_deduct_caller(&mut evm)
756 .unwrap();
757
758 assert_eq!(
759 *evm.ctx().chain(),
760 L1BlockInfo {
761 l2_block: BLOCK_NUM,
762 l1_base_fee: L1_BASE_FEE,
763 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
764 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
765 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
766 empty_ecotone_scalars: false,
767 l1_fee_overhead: None,
768 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
769 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
770 tx_l1_cost: Some(U256::ZERO),
771 }
772 );
773 }
774
775 #[test]
776 fn test_remove_l1_cost() {
777 let caller = Address::ZERO;
778 let mut db = InMemoryDB::default();
779 db.insert_account_info(
780 caller,
781 AccountInfo {
782 balance: U256::from(1049),
783 ..Default::default()
784 },
785 );
786 let ctx = Context::op()
787 .with_db(db)
788 .with_chain(L1BlockInfo {
789 l1_base_fee: U256::from(1_000),
790 l1_fee_overhead: Some(U256::from(1_000)),
791 l1_base_fee_scalar: U256::from(1_000),
792 ..Default::default()
793 })
794 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
795 .modify_tx_chained(|tx| {
796 tx.base.gas_limit = 100;
797 tx.deposit.source_hash = B256::ZERO;
798 tx.enveloped_tx = Some(bytes!("FACADE"));
799 });
800
801 let mut evm = ctx.build_op();
802 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
803
804 handler
806 .validate_against_state_and_deduct_caller(&mut evm)
807 .unwrap();
808
809 let account = evm.ctx().journal().load_account(caller).unwrap();
811 assert_eq!(account.info.balance, U256::from(1));
812 }
813
814 #[test]
815 fn test_remove_operator_cost() {
816 let caller = Address::ZERO;
817 let mut db = InMemoryDB::default();
818 db.insert_account_info(
819 caller,
820 AccountInfo {
821 balance: U256::from(151),
822 ..Default::default()
823 },
824 );
825 let ctx = Context::op()
826 .with_db(db)
827 .with_chain(L1BlockInfo {
828 operator_fee_scalar: Some(U256::from(10_000_000)),
829 operator_fee_constant: Some(U256::from(50)),
830 ..Default::default()
831 })
832 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS)
833 .modify_tx_chained(|tx| {
834 tx.base.gas_limit = 10;
835 tx.enveloped_tx = Some(bytes!("FACADE"));
836 });
837
838 let mut evm = ctx.build_op();
839 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
840
841 handler
844 .validate_against_state_and_deduct_caller(&mut evm)
845 .unwrap();
846
847 let account = evm.ctx().journal().load_account(caller).unwrap();
849 assert_eq!(account.info.balance, U256::from(1));
850 }
851
852 #[test]
853 fn test_remove_l1_cost_lack_of_funds() {
854 let caller = Address::ZERO;
855 let mut db = InMemoryDB::default();
856 db.insert_account_info(
857 caller,
858 AccountInfo {
859 balance: U256::from(48),
860 ..Default::default()
861 },
862 );
863 let ctx = Context::op()
864 .with_db(db)
865 .with_chain(L1BlockInfo {
866 l1_base_fee: U256::from(1_000),
867 l1_fee_overhead: Some(U256::from(1_000)),
868 l1_base_fee_scalar: U256::from(1_000),
869 ..Default::default()
870 })
871 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
872 .modify_tx_chained(|tx| {
873 tx.enveloped_tx = Some(bytes!("FACADE"));
874 });
875
876 let mut evm = ctx.build_op();
878 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
879
880 assert_eq!(
882 handler.validate_against_state_and_deduct_caller(&mut evm),
883 Err(EVMError::Transaction(
884 InvalidTransaction::LackOfFundForMaxFee {
885 fee: Box::new(U256::from(1048)),
886 balance: Box::new(U256::from(48)),
887 }
888 .into(),
889 ))
890 );
891 }
892
893 #[test]
894 fn test_validate_sys_tx() {
895 let ctx = Context::op()
897 .modify_tx_chained(|tx| {
898 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
899 tx.deposit.is_system_transaction = true;
900 })
901 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
902
903 let mut evm = ctx.build_op();
904 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
905
906 assert_eq!(
907 handler.validate_env(&mut evm),
908 Err(EVMError::Transaction(
909 OpTransactionError::DepositSystemTxPostRegolith
910 ))
911 );
912
913 evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK);
914
915 assert!(handler.validate_env(&mut evm).is_ok());
917 }
918
919 #[test]
920 fn test_validate_deposit_tx() {
921 let ctx = Context::op()
923 .modify_tx_chained(|tx| {
924 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
925 tx.deposit.source_hash = B256::ZERO;
926 })
927 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
928
929 let mut evm = ctx.build_op();
930 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
931
932 assert!(handler.validate_env(&mut evm).is_ok());
933 }
934
935 #[test]
936 fn test_validate_tx_against_state_deposit_tx() {
937 let ctx = Context::op()
939 .modify_tx_chained(|tx| {
940 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
941 tx.deposit.source_hash = B256::ZERO;
942 })
943 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
944
945 let mut evm = ctx.build_op();
946 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
947
948 assert!(handler.validate_env(&mut evm).is_ok());
950 }
951
952 #[test]
953 fn test_halted_deposit_tx_post_regolith() {
954 let ctx = Context::op()
955 .modify_tx_chained(|tx| {
956 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
957 })
958 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
959
960 let mut evm = ctx.build_op();
961 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
962
963 assert_eq!(
964 handler.output(
965 &mut evm,
966 FrameResult::Call(CallOutcome {
967 result: InterpreterResult {
968 result: InstructionResult::OutOfGas,
969 output: Default::default(),
970 gas: Default::default(),
971 },
972 memory_offset: Default::default(),
973 })
974 ),
975 Err(EVMError::Transaction(
976 OpTransactionError::HaltedDepositPostRegolith
977 ))
978 )
979 }
980
981 #[rstest]
982 #[case::deposit(true)]
983 #[case::dyn_fee(false)]
984 fn test_operator_fee_refund(#[case] is_deposit: bool) {
985 const SENDER: Address = Address::ZERO;
986 const GAS_PRICE: u128 = 0xFF;
987 const OP_FEE_MOCK_PARAM: u128 = 0xFFFF;
988
989 let ctx = Context::op()
990 .modify_tx_chained(|tx| {
991 tx.base.tx_type = if is_deposit {
992 DEPOSIT_TRANSACTION_TYPE
993 } else {
994 TransactionType::Eip1559 as u8
995 };
996 tx.base.gas_price = GAS_PRICE;
997 tx.base.gas_priority_fee = None;
998 tx.base.caller = SENDER;
999 })
1000 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
1001
1002 let mut evm = ctx.build_op();
1003 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
1004
1005 evm.ctx().chain.operator_fee_scalar = Some(U256::from(OP_FEE_MOCK_PARAM));
1007 evm.ctx().chain.operator_fee_constant = Some(U256::from(OP_FEE_MOCK_PARAM));
1008
1009 let mut gas = Gas::new(100);
1010 gas.set_spent(10);
1011 let mut exec_result = FrameResult::Call(CallOutcome::new(
1012 InterpreterResult {
1013 result: InstructionResult::Return,
1014 output: Default::default(),
1015 gas,
1016 },
1017 0..0,
1018 ));
1019
1020 handler
1022 .reimburse_caller(&mut evm, &mut exec_result)
1023 .unwrap();
1024
1025 let mut expected_refund =
1028 U256::from(GAS_PRICE * (gas.remaining() + gas.refunded() as u64) as u128);
1029 let op_fee_refund = evm
1030 .ctx()
1031 .chain()
1032 .operator_fee_refund(&gas, OpSpecId::ISTHMUS);
1033 assert!(op_fee_refund > U256::ZERO);
1034
1035 if !is_deposit {
1036 expected_refund += op_fee_refund;
1037 }
1038
1039 let account = evm.ctx().journal().load_account(SENDER).unwrap();
1041 assert_eq!(account.info.balance, expected_refund);
1042 }
1043}