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,
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 } else {
143 validate_account_nonce_and_code(
145 &mut caller_account.info,
146 tx.nonce(),
147 tx.kind().is_call(),
148 is_eip3607_disabled,
149 is_nonce_check_disabled,
150 )?;
151 }
152
153 let max_balance_spending = tx.max_balance_spending()?.saturating_add(additional_cost);
154
155 if is_balance_check_disabled {
158 caller_account.info.balance = caller_account.info.balance.max(tx.value());
161 } else if !is_deposit && max_balance_spending > caller_account.info.balance {
162 return Err(InvalidTransaction::LackOfFundForMaxFee {
165 fee: Box::new(max_balance_spending),
166 balance: Box::new(caller_account.info.balance),
167 }
168 .into());
169 } else {
170 let effective_balance_spending =
171 tx.effective_balance_spending(basefee, blob_price).expect(
172 "effective balance is always smaller than max balance so it can't overflow",
173 );
174
175 let gas_balance_spending = effective_balance_spending - tx.value();
177
178 let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost);
184
185 caller_account.info.balance = caller_account
186 .info
187 .balance
188 .saturating_sub(op_gas_balance_spending);
189 }
190
191 Ok(())
192 }
193
194 fn last_frame_result(
195 &mut self,
196 evm: &mut Self::Evm,
197 frame_result: &mut <Self::Frame as Frame>::FrameResult,
198 ) -> Result<(), Self::Error> {
199 let ctx = evm.ctx();
200 let tx = ctx.tx();
201 let is_deposit = tx.tx_type() == DEPOSIT_TRANSACTION_TYPE;
202 let tx_gas_limit = tx.gas_limit();
203 let is_regolith = ctx.cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
204
205 let instruction_result = frame_result.interpreter_result().result;
206 let gas = frame_result.gas_mut();
207 let remaining = gas.remaining();
208 let refunded = gas.refunded();
209
210 *gas = Gas::new_spent(tx_gas_limit);
212
213 if instruction_result.is_ok() {
214 if !is_deposit || is_regolith {
228 gas.erase_cost(remaining);
231 gas.record_refund(refunded);
232 } else if is_deposit {
233 let tx = ctx.tx();
234 if tx.is_system_transaction() {
235 gas.erase_cost(tx_gas_limit);
238 }
239 }
240 } else if instruction_result.is_revert() {
241 if !is_deposit || is_regolith {
254 gas.erase_cost(remaining);
255 }
256 }
257 Ok(())
258 }
259
260 fn reimburse_caller(
261 &self,
262 evm: &mut Self::Evm,
263 exec_result: &mut <Self::Frame as Frame>::FrameResult,
264 ) -> Result<(), Self::Error> {
265 self.mainnet.reimburse_caller(evm, exec_result)?;
266
267 let context = evm.ctx();
268 if context.tx().tx_type() != DEPOSIT_TRANSACTION_TYPE {
269 let caller = context.tx().caller();
270 let spec = context.cfg().spec();
271 let operator_fee_refund = context.chain().operator_fee_refund(exec_result.gas(), spec);
272
273 let caller_account = context.journal().load_account(caller)?;
274
275 caller_account.data.info.balance = caller_account
278 .data
279 .info
280 .balance
281 .saturating_add(operator_fee_refund);
282 }
283
284 Ok(())
285 }
286
287 fn refund(
288 &self,
289 evm: &mut Self::Evm,
290 exec_result: &mut <Self::Frame as Frame>::FrameResult,
291 eip7702_refund: i64,
292 ) {
293 exec_result.gas_mut().record_refund(eip7702_refund);
294
295 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
296 let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
297
298 let is_gas_refund_disabled = is_deposit && !is_regolith;
300 if !is_gas_refund_disabled {
301 exec_result.gas_mut().set_final_refund(
302 evm.ctx()
303 .cfg()
304 .spec()
305 .into_eth_spec()
306 .is_enabled_in(SpecId::LONDON),
307 );
308 }
309 }
310
311 fn reward_beneficiary(
312 &self,
313 evm: &mut Self::Evm,
314 exec_result: &mut <Self::Frame as Frame>::FrameResult,
315 ) -> Result<(), Self::Error> {
316 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
317
318 if !is_deposit {
320 self.mainnet.reward_beneficiary(evm, exec_result)?;
321 let basefee = evm.ctx().block().basefee() as u128;
322
323 let ctx = evm.ctx();
326 let enveloped = ctx.tx().enveloped_tx().cloned();
327 let spec = ctx.cfg().spec();
328 let l1_block_info = ctx.chain();
329
330 let Some(enveloped_tx) = &enveloped else {
331 return Err(ERROR::from_string(
332 "[OPTIMISM] Failed to load enveloped transaction.".into(),
333 ));
334 };
335
336 let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec);
337 let mut operator_fee_cost = U256::ZERO;
338 if spec.is_enabled_in(OpSpecId::ISTHMUS) {
339 operator_fee_cost = l1_block_info.operator_fee_charge(
340 enveloped_tx,
341 U256::from(exec_result.gas().spent() - exec_result.gas().refunded() as u64),
342 );
343 }
344 let mut l1_fee_vault_account = ctx.journal().load_account(L1_FEE_RECIPIENT)?;
346 l1_fee_vault_account.mark_touch();
347 l1_fee_vault_account.info.balance += l1_cost;
348
349 let mut base_fee_vault_account =
351 evm.ctx().journal().load_account(BASE_FEE_RECIPIENT)?;
352 base_fee_vault_account.mark_touch();
353 base_fee_vault_account.info.balance += U256::from(basefee.saturating_mul(
354 (exec_result.gas().spent() - exec_result.gas().refunded() as u64) as u128,
355 ));
356
357 let mut operator_fee_vault_account =
359 evm.ctx().journal().load_account(OPERATOR_FEE_RECIPIENT)?;
360 operator_fee_vault_account.mark_touch();
361 operator_fee_vault_account.data.info.balance += operator_fee_cost;
362 }
363 Ok(())
364 }
365
366 fn output(
367 &self,
368 evm: &mut Self::Evm,
369 result: <Self::Frame as Frame>::FrameResult,
370 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
371 let result = self.mainnet.output(evm, result)?;
372 let result = result.map_haltreason(OpHaltReason::Base);
373 if result.result.is_halt() {
374 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
378 if is_deposit && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) {
379 return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith));
380 }
381 }
382 evm.ctx().chain().clear_tx_l1_cost();
383 Ok(result)
384 }
385
386 fn catch_error(
387 &self,
388 evm: &mut Self::Evm,
389 error: Self::Error,
390 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
391 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
392 let output = if error.is_tx_error() && is_deposit {
393 let ctx = evm.ctx();
394 let spec = ctx.cfg().spec();
395 let tx = ctx.tx();
396 let caller = tx.caller();
397 let mint = tx.mint();
398 let is_system_tx = tx.is_system_transaction();
399 let gas_limit = tx.gas_limit();
400 let account = {
410 let mut acc = Account::from(
411 evm.ctx()
412 .db()
413 .basic(caller)
414 .unwrap_or_default()
415 .unwrap_or_default(),
416 );
417 acc.info.nonce = acc.info.nonce.saturating_add(1);
418 acc.info.balance = acc
419 .info
420 .balance
421 .saturating_add(U256::from(mint.unwrap_or_default()));
422 acc.mark_touch();
423 acc
424 };
425 let state = HashMap::from_iter([(caller, account)]);
426
427 let gas_used = if spec.is_enabled_in(OpSpecId::REGOLITH) || !is_system_tx {
432 gas_limit
433 } else {
434 0
435 };
436 Ok(ResultAndState {
438 result: ExecutionResult::Halt {
439 reason: OpHaltReason::FailedDeposit,
440 gas_used,
441 },
442 state,
443 })
444 } else {
445 Err(error)
446 };
447 evm.ctx().chain().clear_tx_l1_cost();
449 evm.ctx().journal().clear();
450
451 output
452 }
453}
454
455impl<EVM, ERROR, FRAME> InspectorHandler for OpHandler<EVM, ERROR, FRAME>
456where
457 EVM: InspectorEvmTr<
458 Context: OpContextTr,
459 Inspector: Inspector<<<Self as Handler>::Evm as EvmTr>::Context, EthInterpreter>,
460 >,
461 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
462 FRAME: InspectorFrame<
465 Evm = EVM,
466 Error = ERROR,
467 FrameResult = FrameResult,
468 FrameInit = FrameInput,
469 IT = EthInterpreter,
470 >,
471{
472 type IT = EthInterpreter;
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use crate::{
479 api::default_ctx::OpContext,
480 constants::{
481 BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT,
482 L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT,
483 },
484 DefaultOp, OpBuilder,
485 };
486 use alloy_primitives::uint;
487 use revm::{
488 context::{BlockEnv, Context, TransactionType},
489 context_interface::result::InvalidTransaction,
490 database::InMemoryDB,
491 database_interface::EmptyDB,
492 handler::EthFrame,
493 interpreter::{CallOutcome, InstructionResult, InterpreterResult},
494 primitives::{bytes, Address, Bytes, B256},
495 state::AccountInfo,
496 };
497 use rstest::rstest;
498 use std::boxed::Box;
499
500 fn call_last_frame_return(
502 ctx: OpContext<EmptyDB>,
503 instruction_result: InstructionResult,
504 gas: Gas,
505 ) -> Gas {
506 let mut evm = ctx.build_op();
507
508 let mut exec_result = FrameResult::Call(CallOutcome::new(
509 InterpreterResult {
510 result: instruction_result,
511 output: Bytes::new(),
512 gas,
513 },
514 0..0,
515 ));
516
517 let mut handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
518
519 handler
520 .last_frame_result(&mut evm, &mut exec_result)
521 .unwrap();
522 handler.refund(&mut evm, &mut exec_result, 0);
523 *exec_result.gas()
524 }
525
526 #[test]
527 fn test_revert_gas() {
528 let ctx = Context::op()
529 .modify_tx_chained(|tx| {
530 tx.base.gas_limit = 100;
531 tx.enveloped_tx = None;
532 })
533 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
534
535 let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90));
536 assert_eq!(gas.remaining(), 90);
537 assert_eq!(gas.spent(), 10);
538 assert_eq!(gas.refunded(), 0);
539 }
540
541 #[test]
542 fn test_consume_gas() {
543 let ctx = Context::op()
544 .modify_tx_chained(|tx| {
545 tx.base.gas_limit = 100;
546 tx.deposit.source_hash = B256::ZERO;
547 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
548 })
549 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
550
551 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
552 assert_eq!(gas.remaining(), 90);
553 assert_eq!(gas.spent(), 10);
554 assert_eq!(gas.refunded(), 0);
555 }
556
557 #[test]
558 fn test_consume_gas_with_refund() {
559 let ctx = Context::op()
560 .modify_tx_chained(|tx| {
561 tx.base.gas_limit = 100;
562 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
563 tx.deposit.source_hash = B256::ZERO;
564 })
565 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
566
567 let mut ret_gas = Gas::new(90);
568 ret_gas.record_refund(20);
569
570 let gas = call_last_frame_return(ctx.clone(), InstructionResult::Stop, ret_gas);
571 assert_eq!(gas.remaining(), 90);
572 assert_eq!(gas.spent(), 10);
573 assert_eq!(gas.refunded(), 2); let gas = call_last_frame_return(ctx, InstructionResult::Revert, ret_gas);
576 assert_eq!(gas.remaining(), 90);
577 assert_eq!(gas.spent(), 10);
578 assert_eq!(gas.refunded(), 0);
579 }
580
581 #[test]
582 fn test_consume_gas_deposit_tx() {
583 let ctx = Context::op()
584 .modify_tx_chained(|tx| {
585 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
586 tx.base.gas_limit = 100;
587 tx.deposit.source_hash = B256::ZERO;
588 })
589 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
590 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
591 assert_eq!(gas.remaining(), 0);
592 assert_eq!(gas.spent(), 100);
593 assert_eq!(gas.refunded(), 0);
594 }
595
596 #[test]
597 fn test_consume_gas_sys_deposit_tx() {
598 let ctx = Context::op()
599 .modify_tx_chained(|tx| {
600 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
601 tx.base.gas_limit = 100;
602 tx.deposit.source_hash = B256::ZERO;
603 tx.deposit.is_system_transaction = true;
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(), 100);
608 assert_eq!(gas.spent(), 0);
609 assert_eq!(gas.refunded(), 0);
610 }
611
612 #[test]
613 fn test_commit_mint_value() {
614 let caller = Address::ZERO;
615 let mut db = InMemoryDB::default();
616 db.insert_account_info(
617 caller,
618 AccountInfo {
619 balance: U256::from(1000),
620 ..Default::default()
621 },
622 );
623
624 let mut ctx = Context::op()
625 .with_db(db)
626 .with_chain(L1BlockInfo {
627 l1_base_fee: U256::from(1_000),
628 l1_fee_overhead: Some(U256::from(1_000)),
629 l1_base_fee_scalar: U256::from(1_000),
630 ..Default::default()
631 })
632 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
633 ctx.modify_tx(|tx| {
634 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
635 tx.deposit.source_hash = B256::ZERO;
636 tx.deposit.mint = Some(10);
637 });
638
639 let mut evm = ctx.build_op();
640
641 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
642 handler
643 .validate_against_state_and_deduct_caller(&mut evm)
644 .unwrap();
645
646 let account = evm.ctx().journal().load_account(caller).unwrap();
648 assert_eq!(account.info.balance, U256::from(1010));
649 }
650
651 #[test]
652 fn test_remove_l1_cost_non_deposit() {
653 let caller = Address::ZERO;
654 let mut db = InMemoryDB::default();
655 db.insert_account_info(
656 caller,
657 AccountInfo {
658 balance: U256::from(1000),
659 ..Default::default()
660 },
661 );
662 let ctx = Context::op()
663 .with_db(db)
664 .with_chain(L1BlockInfo {
665 l1_base_fee: U256::from(1_000),
666 l1_fee_overhead: Some(U256::from(1_000)),
667 l1_base_fee_scalar: U256::from(1_000),
668 ..Default::default()
669 })
670 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
671 .modify_tx_chained(|tx| {
672 tx.base.gas_limit = 100;
673 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
674 tx.deposit.mint = Some(10);
675 tx.enveloped_tx = Some(bytes!("FACADE"));
676 tx.deposit.source_hash = B256::ZERO;
677 });
678
679 let mut evm = ctx.build_op();
680
681 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
682 handler
683 .validate_against_state_and_deduct_caller(&mut evm)
684 .unwrap();
685
686 let account = evm.ctx().journal().load_account(caller).unwrap();
688 assert_eq!(account.info.balance, U256::from(1010));
689 }
690
691 #[test]
692 fn test_reload_l1_block_info_isthmus() {
693 const BLOCK_NUM: u64 = 100;
694 const L1_BASE_FEE: U256 = uint!(1_U256);
695 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
696 const L1_BASE_FEE_SCALAR: u64 = 3;
697 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
698 const L1_FEE_SCALARS: U256 = U256::from_limbs([
699 0,
700 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
701 0,
702 0,
703 ]);
704 const OPERATOR_FEE_SCALAR: u64 = 5;
705 const OPERATOR_FEE_CONST: u64 = 6;
706 const OPERATOR_FEE: U256 =
707 U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]);
708
709 let mut db = InMemoryDB::default();
710 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
711 l1_block_contract
712 .storage
713 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
714 l1_block_contract
715 .storage
716 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
717 l1_block_contract
718 .storage
719 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
720 l1_block_contract
721 .storage
722 .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE);
723 db.insert_account_info(
724 Address::ZERO,
725 AccountInfo {
726 balance: U256::from(1000),
727 ..Default::default()
728 },
729 );
730
731 let ctx = Context::op()
732 .with_db(db)
733 .with_chain(L1BlockInfo {
734 l2_block: BLOCK_NUM + 1, ..Default::default()
736 })
737 .with_block(BlockEnv {
738 number: BLOCK_NUM,
739 ..Default::default()
740 })
741 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
742
743 let mut evm = ctx.build_op();
744
745 assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM);
746
747 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
748 handler
749 .validate_against_state_and_deduct_caller(&mut evm)
750 .unwrap();
751
752 assert_eq!(
753 *evm.ctx().chain(),
754 L1BlockInfo {
755 l2_block: BLOCK_NUM,
756 l1_base_fee: L1_BASE_FEE,
757 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
758 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
759 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
760 empty_ecotone_scalars: false,
761 l1_fee_overhead: None,
762 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
763 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
764 tx_l1_cost: Some(U256::ZERO),
765 }
766 );
767 }
768
769 #[test]
770 fn test_remove_l1_cost() {
771 let caller = Address::ZERO;
772 let mut db = InMemoryDB::default();
773 db.insert_account_info(
774 caller,
775 AccountInfo {
776 balance: U256::from(1049),
777 ..Default::default()
778 },
779 );
780 let ctx = Context::op()
781 .with_db(db)
782 .with_chain(L1BlockInfo {
783 l1_base_fee: U256::from(1_000),
784 l1_fee_overhead: Some(U256::from(1_000)),
785 l1_base_fee_scalar: U256::from(1_000),
786 ..Default::default()
787 })
788 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
789 .modify_tx_chained(|tx| {
790 tx.base.gas_limit = 100;
791 tx.deposit.source_hash = B256::ZERO;
792 tx.enveloped_tx = Some(bytes!("FACADE"));
793 });
794
795 let mut evm = ctx.build_op();
796 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
797
798 handler
800 .validate_against_state_and_deduct_caller(&mut evm)
801 .unwrap();
802
803 let account = evm.ctx().journal().load_account(caller).unwrap();
805 assert_eq!(account.info.balance, U256::from(1));
806 }
807
808 #[test]
809 fn test_remove_operator_cost() {
810 let caller = Address::ZERO;
811 let mut db = InMemoryDB::default();
812 db.insert_account_info(
813 caller,
814 AccountInfo {
815 balance: U256::from(151),
816 ..Default::default()
817 },
818 );
819 let ctx = Context::op()
820 .with_db(db)
821 .with_chain(L1BlockInfo {
822 operator_fee_scalar: Some(U256::from(10_000_000)),
823 operator_fee_constant: Some(U256::from(50)),
824 ..Default::default()
825 })
826 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS)
827 .modify_tx_chained(|tx| {
828 tx.base.gas_limit = 10;
829 tx.enveloped_tx = Some(bytes!("FACADE"));
830 });
831
832 let mut evm = ctx.build_op();
833 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
834
835 handler
838 .validate_against_state_and_deduct_caller(&mut evm)
839 .unwrap();
840
841 let account = evm.ctx().journal().load_account(caller).unwrap();
843 assert_eq!(account.info.balance, U256::from(1));
844 }
845
846 #[test]
847 fn test_remove_l1_cost_lack_of_funds() {
848 let caller = Address::ZERO;
849 let mut db = InMemoryDB::default();
850 db.insert_account_info(
851 caller,
852 AccountInfo {
853 balance: U256::from(48),
854 ..Default::default()
855 },
856 );
857 let ctx = Context::op()
858 .with_db(db)
859 .with_chain(L1BlockInfo {
860 l1_base_fee: U256::from(1_000),
861 l1_fee_overhead: Some(U256::from(1_000)),
862 l1_base_fee_scalar: U256::from(1_000),
863 ..Default::default()
864 })
865 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
866 .modify_tx_chained(|tx| {
867 tx.enveloped_tx = Some(bytes!("FACADE"));
868 });
869
870 let mut evm = ctx.build_op();
872 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
873
874 assert_eq!(
876 handler.validate_against_state_and_deduct_caller(&mut evm),
877 Err(EVMError::Transaction(
878 InvalidTransaction::LackOfFundForMaxFee {
879 fee: Box::new(U256::from(1048)),
880 balance: Box::new(U256::from(48)),
881 }
882 .into(),
883 ))
884 );
885 }
886
887 #[test]
888 fn test_validate_sys_tx() {
889 let ctx = Context::op()
891 .modify_tx_chained(|tx| {
892 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
893 tx.deposit.is_system_transaction = true;
894 })
895 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
896
897 let mut evm = ctx.build_op();
898 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
899
900 assert_eq!(
901 handler.validate_env(&mut evm),
902 Err(EVMError::Transaction(
903 OpTransactionError::DepositSystemTxPostRegolith
904 ))
905 );
906
907 evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK);
908
909 assert!(handler.validate_env(&mut evm).is_ok());
911 }
912
913 #[test]
914 fn test_validate_deposit_tx() {
915 let ctx = Context::op()
917 .modify_tx_chained(|tx| {
918 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
919 tx.deposit.source_hash = B256::ZERO;
920 })
921 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
922
923 let mut evm = ctx.build_op();
924 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
925
926 assert!(handler.validate_env(&mut evm).is_ok());
927 }
928
929 #[test]
930 fn test_validate_tx_against_state_deposit_tx() {
931 let ctx = Context::op()
933 .modify_tx_chained(|tx| {
934 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
935 tx.deposit.source_hash = B256::ZERO;
936 })
937 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
938
939 let mut evm = ctx.build_op();
940 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
941
942 assert!(handler.validate_env(&mut evm).is_ok());
944 }
945
946 #[test]
947 fn test_halted_deposit_tx_post_regolith() {
948 let ctx = Context::op()
949 .modify_tx_chained(|tx| {
950 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
951 })
952 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
953
954 let mut evm = ctx.build_op();
955 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
956
957 assert_eq!(
958 handler.output(
959 &mut evm,
960 FrameResult::Call(CallOutcome {
961 result: InterpreterResult {
962 result: InstructionResult::OutOfGas,
963 output: Default::default(),
964 gas: Default::default(),
965 },
966 memory_offset: Default::default(),
967 })
968 ),
969 Err(EVMError::Transaction(
970 OpTransactionError::HaltedDepositPostRegolith
971 ))
972 )
973 }
974
975 #[rstest]
976 #[case::deposit(true)]
977 #[case::dyn_fee(false)]
978 fn test_operator_fee_refund(#[case] is_deposit: bool) {
979 const SENDER: Address = Address::ZERO;
980 const GAS_PRICE: u128 = 0xFF;
981 const OP_FEE_MOCK_PARAM: u128 = 0xFFFF;
982
983 let ctx = Context::op()
984 .modify_tx_chained(|tx| {
985 tx.base.tx_type = if is_deposit {
986 DEPOSIT_TRANSACTION_TYPE
987 } else {
988 TransactionType::Eip1559 as u8
989 };
990 tx.base.gas_price = GAS_PRICE;
991 tx.base.gas_priority_fee = None;
992 tx.base.caller = SENDER;
993 })
994 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
995
996 let mut evm = ctx.build_op();
997 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
998
999 evm.ctx().chain.operator_fee_scalar = Some(U256::from(OP_FEE_MOCK_PARAM));
1001 evm.ctx().chain.operator_fee_constant = Some(U256::from(OP_FEE_MOCK_PARAM));
1002
1003 let mut gas = Gas::new(100);
1004 gas.set_spent(10);
1005 let mut exec_result = FrameResult::Call(CallOutcome::new(
1006 InterpreterResult {
1007 result: InstructionResult::Return,
1008 output: Default::default(),
1009 gas,
1010 },
1011 0..0,
1012 ));
1013
1014 handler
1016 .reimburse_caller(&mut evm, &mut exec_result)
1017 .unwrap();
1018
1019 let mut expected_refund =
1022 U256::from(GAS_PRICE * (gas.remaining() + gas.refunded() as u64) as u128);
1023 let op_fee_refund = evm
1024 .ctx()
1025 .chain()
1026 .operator_fee_refund(&gas, OpSpecId::ISTHMUS);
1027 assert!(op_fee_refund > U256::ZERO);
1028
1029 if !is_deposit {
1030 expected_refund += op_fee_refund;
1031 }
1032
1033 let account = evm.ctx().journal().load_account(SENDER).unwrap();
1035 assert_eq!(account.info.balance, expected_refund);
1036 }
1037}