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::{
10 journaled_state::{account::JournaledAccountTr, JournalCheckpoint},
11 result::InvalidTransaction,
12 LocalContextTr,
13 },
14 context_interface::{
15 context::ContextError,
16 result::{EVMError, ExecutionResult, FromStringError},
17 Block, Cfg, ContextTr, JournalTr, Transaction,
18 },
19 handler::{
20 evm::FrameTr,
21 handler::EvmTrError,
22 post_execution::{self, reimburse_caller},
23 pre_execution::{calculate_caller_fee, validate_account_nonce_and_code_with_components},
24 EthFrame, EvmTr, FrameResult, Handler, MainnetHandler,
25 },
26 inspector::{Inspector, InspectorEvmTr, InspectorHandler},
27 interpreter::{interpreter::EthInterpreter, interpreter_action::FrameInit, Gas},
28 primitives::{hardfork::SpecId, U256},
29};
30use std::boxed::Box;
31
32#[derive(Debug, Clone)]
34pub struct OpHandler<EVM, ERROR, FRAME> {
35 pub mainnet: MainnetHandler<EVM, ERROR, FRAME>,
38}
39
40impl<EVM, ERROR, FRAME> OpHandler<EVM, ERROR, FRAME> {
41 pub fn new() -> Self {
43 Self {
44 mainnet: MainnetHandler::default(),
45 }
46 }
47}
48
49impl<EVM, ERROR, FRAME> Default for OpHandler<EVM, ERROR, FRAME> {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55pub trait IsTxError {
59 fn is_tx_error(&self) -> bool;
61}
62
63impl<DB, TX> IsTxError for EVMError<DB, TX> {
64 fn is_tx_error(&self) -> bool {
65 matches!(self, EVMError::Transaction(_))
66 }
67}
68
69impl<EVM, ERROR, FRAME> Handler for OpHandler<EVM, ERROR, FRAME>
70where
71 EVM: EvmTr<Context: OpContextTr, Frame = FRAME>,
72 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
73 FRAME: FrameTr<FrameResult = FrameResult, FrameInit = FrameInit>,
76{
77 type Evm = EVM;
78 type Error = ERROR;
79 type HaltReason = OpHaltReason;
80
81 fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
82 let ctx = evm.ctx();
84 let tx = ctx.tx();
85 let tx_type = tx.tx_type();
86 if tx_type == DEPOSIT_TRANSACTION_TYPE {
87 if tx.is_system_transaction()
89 && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH)
90 {
91 return Err(OpTransactionError::DepositSystemTxPostRegolith.into());
92 }
93 return Ok(());
94 }
95
96 if tx.enveloped_tx().is_none() {
98 return Err(OpTransactionError::MissingEnvelopedTx.into());
99 }
100
101 self.mainnet.validate_env(evm)
102 }
103
104 fn validate_against_state_and_deduct_caller(
105 &self,
106 evm: &mut Self::Evm,
107 ) -> Result<(), Self::Error> {
108 let (block, tx, cfg, journal, chain, _) = evm.ctx().all_mut();
109 let spec = cfg.spec();
110
111 if tx.tx_type() == DEPOSIT_TRANSACTION_TYPE {
112 let basefee = block.basefee() as u128;
113 let blob_price = block.blob_gasprice().unwrap_or_default();
114 let mut caller = journal.load_account_with_code_mut(tx.caller())?.data;
117
118 let effective_balance_spending = tx
119 .effective_balance_spending(basefee, blob_price)
120 .expect("Deposit transaction effective balance spending overflow")
121 - tx.value();
122
123 let mut new_balance = caller
125 .balance()
126 .saturating_add(U256::from(tx.mint().unwrap_or_default()))
127 .saturating_sub(effective_balance_spending);
128
129 if cfg.is_balance_check_disabled() {
130 new_balance = new_balance.max(tx.value());
133 }
134
135 caller.set_balance(new_balance);
137 if tx.kind().is_call() {
138 caller.bump_nonce();
139 }
140
141 return Ok(());
142 }
143
144 if chain.l2_block != Some(block.number()) {
147 *chain = L1BlockInfo::try_fetch(journal.db_mut(), block.number(), spec)?;
148 }
149
150 let mut caller_account = journal.load_account_with_code_mut(tx.caller())?.data;
151
152 validate_account_nonce_and_code_with_components(&caller_account.account().info, tx, cfg)?;
154
155 let mut balance = caller_account.account().info.balance;
157
158 if !cfg.is_fee_charge_disabled() {
159 let Some(additional_cost) = chain.tx_cost_with_tx(tx, spec) else {
160 return Err(ERROR::from_string(
161 "[OPTIMISM] Failed to load enveloped transaction.".into(),
162 ));
163 };
164 let Some(new_balance) = balance.checked_sub(additional_cost) else {
165 return Err(InvalidTransaction::LackOfFundForMaxFee {
166 fee: Box::new(additional_cost),
167 balance: Box::new(balance),
168 }
169 .into());
170 };
171 balance = new_balance
172 }
173
174 let balance = calculate_caller_fee(balance, tx, block, cfg)?;
175
176 caller_account.set_balance(balance);
178 if tx.kind().is_call() {
179 caller_account.bump_nonce();
180 }
181
182 Ok(())
183 }
184
185 fn last_frame_result(
186 &mut self,
187 evm: &mut Self::Evm,
188 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
189 ) -> Result<(), Self::Error> {
190 let ctx = evm.ctx();
191 let tx = ctx.tx();
192 let is_deposit = tx.tx_type() == DEPOSIT_TRANSACTION_TYPE;
193 let tx_gas_limit = tx.gas_limit();
194 let is_regolith = ctx.cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
195
196 let instruction_result = frame_result.interpreter_result().result;
197 let gas = frame_result.gas_mut();
198 let remaining = gas.remaining();
199 let refunded = gas.refunded();
200
201 *gas = Gas::new_spent(tx_gas_limit);
203
204 if instruction_result.is_ok() {
205 if !is_deposit || is_regolith {
219 gas.erase_cost(remaining);
222 gas.record_refund(refunded);
223 } else if is_deposit {
224 let tx = ctx.tx();
225 if tx.is_system_transaction() {
226 gas.erase_cost(tx_gas_limit);
229 }
230 }
231 } else if instruction_result.is_revert() {
232 if !is_deposit || is_regolith {
245 gas.erase_cost(remaining);
246 }
247 }
248 Ok(())
249 }
250
251 fn reimburse_caller(
252 &self,
253 evm: &mut Self::Evm,
254 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
255 ) -> Result<(), Self::Error> {
256 let mut additional_refund = U256::ZERO;
257
258 if evm.ctx().tx().tx_type() != DEPOSIT_TRANSACTION_TYPE
259 && !evm.ctx().cfg().is_fee_charge_disabled()
260 {
261 let spec = evm.ctx().cfg().spec();
262 additional_refund = evm
263 .ctx()
264 .chain()
265 .operator_fee_refund(frame_result.gas(), spec);
266 }
267
268 reimburse_caller(evm.ctx(), frame_result.gas(), additional_refund).map_err(From::from)
269 }
270
271 fn refund(
272 &self,
273 evm: &mut Self::Evm,
274 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
275 eip7702_refund: i64,
276 ) {
277 frame_result.gas_mut().record_refund(eip7702_refund);
278
279 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
280 let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
281
282 let is_gas_refund_disabled = is_deposit && !is_regolith;
284 if !is_gas_refund_disabled {
285 frame_result.gas_mut().set_final_refund(
286 evm.ctx()
287 .cfg()
288 .spec()
289 .into_eth_spec()
290 .is_enabled_in(SpecId::LONDON),
291 );
292 }
293 }
294
295 fn reward_beneficiary(
296 &self,
297 evm: &mut Self::Evm,
298 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
299 ) -> Result<(), Self::Error> {
300 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
301
302 if is_deposit {
304 return Ok(());
305 }
306
307 self.mainnet.reward_beneficiary(evm, frame_result)?;
308 let basefee = evm.ctx().block().basefee() as u128;
309
310 let ctx = evm.ctx();
313 let enveloped = ctx.tx().enveloped_tx().cloned();
314 let spec = ctx.cfg().spec();
315 let l1_block_info = ctx.chain_mut();
316
317 let Some(enveloped_tx) = &enveloped else {
318 return Err(ERROR::from_string(
319 "[OPTIMISM] Failed to load enveloped transaction.".into(),
320 ));
321 };
322
323 let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec);
324 let operator_fee_cost = if spec.is_enabled_in(OpSpecId::ISTHMUS) {
325 l1_block_info.operator_fee_charge(
326 enveloped_tx,
327 U256::from(frame_result.gas().used()),
328 spec,
329 )
330 } else {
331 U256::ZERO
332 };
333 let base_fee_amount = U256::from(basefee.saturating_mul(frame_result.gas().used() as u128));
334
335 for (recipient, amount) in [
337 (L1_FEE_RECIPIENT, l1_cost),
338 (BASE_FEE_RECIPIENT, base_fee_amount),
339 (OPERATOR_FEE_RECIPIENT, operator_fee_cost),
340 ] {
341 ctx.journal_mut().balance_incr(recipient, amount)?;
342 }
343
344 Ok(())
345 }
346
347 fn execution_result(
348 &mut self,
349 evm: &mut Self::Evm,
350 frame_result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
351 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
352 match core::mem::replace(evm.ctx().error(), Ok(())) {
353 Err(ContextError::Db(e)) => return Err(e.into()),
354 Err(ContextError::Custom(e)) => return Err(Self::Error::from_string(e)),
355 Ok(_) => (),
356 }
357
358 let exec_result =
359 post_execution::output(evm.ctx(), frame_result).map_haltreason(OpHaltReason::Base);
360
361 if exec_result.is_halt() {
362 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
366 if is_deposit && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) {
367 return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith));
368 }
369 }
370 evm.ctx().journal_mut().commit_tx();
371 evm.ctx().chain_mut().clear_tx_l1_cost();
372 evm.ctx().local_mut().clear();
373 evm.frame_stack().clear();
374
375 Ok(exec_result)
376 }
377
378 fn catch_error(
379 &self,
380 evm: &mut Self::Evm,
381 error: Self::Error,
382 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
383 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
384 let output = if error.is_tx_error() && is_deposit {
385 let ctx = evm.ctx();
386 let spec = ctx.cfg().spec();
387 let tx = ctx.tx();
388 let caller = tx.caller();
389 let mint = tx.mint();
390 let is_system_tx = tx.is_system_transaction();
391 let gas_limit = tx.gas_limit();
392 let journal = evm.ctx().journal_mut();
393
394 journal.checkpoint_revert(JournalCheckpoint::default());
397
398 let mut acc = journal.load_account_mut(caller)?;
408 acc.bump_nonce();
409 acc.incr_balance(U256::from(mint.unwrap_or_default()));
410
411 drop(acc); journal.commit_tx();
415
416 let gas_used = if spec.is_enabled_in(OpSpecId::REGOLITH) || !is_system_tx {
421 gas_limit
422 } else {
423 0
424 };
425 Ok(ExecutionResult::Halt {
427 reason: OpHaltReason::FailedDeposit,
428 gas_used,
429 })
430 } else {
431 Err(error)
432 };
433 evm.ctx().chain_mut().clear_tx_l1_cost();
435 evm.ctx().local_mut().clear();
436 evm.frame_stack().clear();
437
438 output
439 }
440}
441
442impl<EVM, ERROR> InspectorHandler for OpHandler<EVM, ERROR, EthFrame<EthInterpreter>>
443where
444 EVM: InspectorEvmTr<
445 Context: OpContextTr,
446 Frame = EthFrame<EthInterpreter>,
447 Inspector: Inspector<<<Self as Handler>::Evm as EvmTr>::Context, EthInterpreter>,
448 >,
449 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
450{
451 type IT = EthInterpreter;
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457 use crate::{
458 api::default_ctx::OpContext,
459 constants::{
460 BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT,
461 L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT,
462 },
463 DefaultOp, OpBuilder, OpTransaction,
464 };
465 use alloy_primitives::uint;
466 use revm::{
467 context::{BlockEnv, Context, TxEnv},
468 context_interface::result::InvalidTransaction,
469 database::InMemoryDB,
470 database_interface::EmptyDB,
471 handler::EthFrame,
472 interpreter::{CallOutcome, InstructionResult, InterpreterResult},
473 primitives::{bytes, Address, Bytes, B256},
474 state::AccountInfo,
475 };
476 use rstest::rstest;
477 use std::boxed::Box;
478
479 fn call_last_frame_return(
481 ctx: OpContext<EmptyDB>,
482 instruction_result: InstructionResult,
483 gas: Gas,
484 ) -> Gas {
485 let mut evm = ctx.build_op();
486
487 let mut exec_result = FrameResult::Call(CallOutcome::new(
488 InterpreterResult {
489 result: instruction_result,
490 output: Bytes::new(),
491 gas,
492 },
493 0..0,
494 ));
495
496 let mut handler =
497 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
498
499 handler
500 .last_frame_result(&mut evm, &mut exec_result)
501 .unwrap();
502 handler.refund(&mut evm, &mut exec_result, 0);
503 *exec_result.gas()
504 }
505
506 #[test]
507 fn test_revert_gas() {
508 let ctx = Context::op()
509 .with_tx(
510 OpTransaction::builder()
511 .base(TxEnv::builder().gas_limit(100))
512 .build_fill(),
513 )
514 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
515
516 let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90));
517 assert_eq!(gas.remaining(), 90);
518 assert_eq!(gas.spent(), 10);
519 assert_eq!(gas.refunded(), 0);
520 }
521
522 #[test]
523 fn test_consume_gas() {
524 let ctx = Context::op()
525 .with_tx(
526 OpTransaction::builder()
527 .base(TxEnv::builder().gas_limit(100))
528 .build_fill(),
529 )
530 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
531
532 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
533 assert_eq!(gas.remaining(), 90);
534 assert_eq!(gas.spent(), 10);
535 assert_eq!(gas.refunded(), 0);
536 }
537
538 #[test]
539 fn test_consume_gas_with_refund() {
540 let ctx = Context::op()
541 .with_tx(
542 OpTransaction::builder()
543 .base(TxEnv::builder().gas_limit(100))
544 .source_hash(B256::from([1u8; 32]))
545 .build_fill(),
546 )
547 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
548
549 let mut ret_gas = Gas::new(90);
550 ret_gas.record_refund(20);
551
552 let gas = call_last_frame_return(ctx.clone(), InstructionResult::Stop, ret_gas);
553 assert_eq!(gas.remaining(), 90);
554 assert_eq!(gas.spent(), 10);
555 assert_eq!(gas.refunded(), 2); let gas = call_last_frame_return(ctx, InstructionResult::Revert, ret_gas);
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_deposit_tx() {
565 let ctx = Context::op()
566 .with_tx(
567 OpTransaction::builder()
568 .base(TxEnv::builder().gas_limit(100))
569 .source_hash(B256::from([1u8; 32]))
570 .build_fill(),
571 )
572 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
573 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
574 assert_eq!(gas.remaining(), 0);
575 assert_eq!(gas.spent(), 100);
576 assert_eq!(gas.refunded(), 0);
577 }
578
579 #[test]
580 fn test_consume_gas_sys_deposit_tx() {
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 .is_system_transaction()
587 .build_fill(),
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(), 100);
592 assert_eq!(gas.spent(), 0);
593 assert_eq!(gas.refunded(), 0);
594 }
595
596 #[test]
597 fn test_commit_mint_value() {
598 let caller = Address::ZERO;
599 let mut db = InMemoryDB::default();
600 db.insert_account_info(
601 caller,
602 AccountInfo {
603 balance: U256::from(1000),
604 ..Default::default()
605 },
606 );
607
608 let mut ctx = Context::op()
609 .with_db(db)
610 .with_chain(L1BlockInfo {
611 l1_base_fee: U256::from(1_000),
612 l1_fee_overhead: Some(U256::from(1_000)),
613 l1_base_fee_scalar: U256::from(1_000),
614 ..Default::default()
615 })
616 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
617 ctx.modify_tx(|tx| {
618 tx.deposit.source_hash = B256::from([1u8; 32]);
619 tx.deposit.mint = Some(10);
620 });
621
622 let mut evm = ctx.build_op();
623
624 let handler =
625 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
626 handler
627 .validate_against_state_and_deduct_caller(&mut evm)
628 .unwrap();
629
630 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
632 assert_eq!(account.info.balance, U256::from(1010));
633 }
634
635 #[test]
636 fn test_remove_l1_cost_non_deposit() {
637 let caller = Address::ZERO;
638 let mut db = InMemoryDB::default();
639 db.insert_account_info(
640 caller,
641 AccountInfo {
642 balance: U256::from(1058), ..Default::default()
644 },
645 );
646 let ctx = Context::op()
647 .with_db(db)
648 .with_chain(L1BlockInfo {
649 l1_base_fee: U256::from(1_000),
650 l1_fee_overhead: Some(U256::from(1_000)),
651 l1_base_fee_scalar: U256::from(1_000),
652 l2_block: Some(U256::from(0)),
653 ..Default::default()
654 })
655 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
656 .with_tx(
657 OpTransaction::builder()
658 .base(TxEnv::builder().gas_limit(100))
659 .enveloped_tx(Some(bytes!("FACADE")))
660 .source_hash(B256::ZERO)
661 .build()
662 .unwrap(),
663 );
664
665 let mut evm = ctx.build_op();
666
667 let handler =
668 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
669 handler
670 .validate_against_state_and_deduct_caller(&mut evm)
671 .unwrap();
672
673 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
675 assert_eq!(account.info.balance, U256::from(10)); }
677
678 #[test]
679 fn test_reload_l1_block_info_isthmus() {
680 const BLOCK_NUM: U256 = uint!(100_U256);
681 const L1_BASE_FEE: U256 = uint!(1_U256);
682 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
683 const L1_BASE_FEE_SCALAR: u64 = 3;
684 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
685 const L1_FEE_SCALARS: U256 = U256::from_limbs([
686 0,
687 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
688 0,
689 0,
690 ]);
691 const OPERATOR_FEE_SCALAR: u64 = 5;
692 const OPERATOR_FEE_CONST: u64 = 6;
693 const OPERATOR_FEE: U256 =
694 U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]);
695
696 let mut db = InMemoryDB::default();
697 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
698 l1_block_contract
699 .storage
700 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
701 l1_block_contract
702 .storage
703 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
704 l1_block_contract
705 .storage
706 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
707 l1_block_contract
708 .storage
709 .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE);
710 db.insert_account_info(
711 Address::ZERO,
712 AccountInfo {
713 balance: U256::from(1000),
714 ..Default::default()
715 },
716 );
717
718 let ctx = Context::op()
719 .with_db(db)
720 .with_chain(L1BlockInfo {
721 l2_block: Some(BLOCK_NUM + U256::from(1)), ..Default::default()
723 })
724 .with_block(BlockEnv {
725 number: BLOCK_NUM,
726 ..Default::default()
727 })
728 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
729
730 let mut evm = ctx.build_op();
731
732 assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM));
733
734 let handler =
735 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
736 handler
737 .validate_against_state_and_deduct_caller(&mut evm)
738 .unwrap();
739
740 assert_eq!(
741 *evm.ctx().chain(),
742 L1BlockInfo {
743 l2_block: Some(BLOCK_NUM),
744 l1_base_fee: L1_BASE_FEE,
745 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
746 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
747 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
748 empty_ecotone_scalars: false,
749 l1_fee_overhead: None,
750 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
751 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
752 tx_l1_cost: Some(U256::ZERO),
753 da_footprint_gas_scalar: None
754 }
755 );
756 }
757
758 #[test]
759 fn test_parse_da_footprint_gas_scalar_jovian() {
760 const BLOCK_NUM: U256 = uint!(100_U256);
761 const L1_BASE_FEE: U256 = uint!(1_U256);
762 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
763 const L1_BASE_FEE_SCALAR: u64 = 3;
764 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
765 const L1_FEE_SCALARS: U256 = U256::from_limbs([
766 0,
767 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
768 0,
769 0,
770 ]);
771 const OPERATOR_FEE_SCALAR: u8 = 5;
772 const OPERATOR_FEE_CONST: u8 = 6;
773 const DA_FOOTPRINT_GAS_SCALAR: u8 = 7;
774 let mut operator_fee_and_da_footprint = [0u8; 32];
775 operator_fee_and_da_footprint[31] = OPERATOR_FEE_CONST;
776 operator_fee_and_da_footprint[23] = OPERATOR_FEE_SCALAR;
777 operator_fee_and_da_footprint[19] = DA_FOOTPRINT_GAS_SCALAR;
778 let operator_fee_and_da_footprint_u256 = U256::from_be_bytes(operator_fee_and_da_footprint);
779
780 let mut db = InMemoryDB::default();
781 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
782 l1_block_contract
783 .storage
784 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
785 l1_block_contract
786 .storage
787 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
788 l1_block_contract
789 .storage
790 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
791 l1_block_contract.storage.insert(
792 OPERATOR_FEE_SCALARS_SLOT,
793 operator_fee_and_da_footprint_u256,
794 );
795 db.insert_account_info(
796 Address::ZERO,
797 AccountInfo {
798 balance: U256::from(6000),
799 ..Default::default()
800 },
801 );
802
803 let ctx = Context::op()
804 .with_db(db)
805 .with_chain(L1BlockInfo {
806 l2_block: Some(BLOCK_NUM + U256::from(1)), operator_fee_scalar: Some(U256::from(2)),
808 operator_fee_constant: Some(U256::from(50)),
809 ..Default::default()
810 })
811 .with_block(BlockEnv {
812 number: BLOCK_NUM,
813 ..Default::default()
814 })
815 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN)
816 .with_tx(
818 OpTransaction::builder()
819 .base(TxEnv::builder().gas_limit(10))
820 .enveloped_tx(Some(bytes!("FACADE")))
821 .build_fill(),
822 );
823
824 let mut evm = ctx.build_op();
825
826 assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM));
827
828 let handler =
829 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
830 handler
831 .validate_against_state_and_deduct_caller(&mut evm)
832 .unwrap();
833
834 assert_eq!(
835 *evm.ctx().chain(),
836 L1BlockInfo {
837 l2_block: Some(BLOCK_NUM),
838 l1_base_fee: L1_BASE_FEE,
839 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
840 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
841 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
842 empty_ecotone_scalars: false,
843 l1_fee_overhead: None,
844 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
845 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
846 tx_l1_cost: Some(U256::ZERO),
847 da_footprint_gas_scalar: Some(DA_FOOTPRINT_GAS_SCALAR as u16),
848 }
849 );
850 }
851
852 #[test]
853 fn test_reload_l1_block_info_regolith() {
854 const BLOCK_NUM: U256 = uint!(200_U256);
855 const L1_BASE_FEE: U256 = uint!(7_U256);
856 const L1_FEE_OVERHEAD: U256 = uint!(9_U256);
857 const L1_BASE_FEE_SCALAR: u64 = 11;
858
859 let mut db = InMemoryDB::default();
860 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
861 l1_block_contract
862 .storage
863 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
864 use crate::constants::{L1_OVERHEAD_SLOT, L1_SCALAR_SLOT};
866 l1_block_contract
867 .storage
868 .insert(L1_OVERHEAD_SLOT, L1_FEE_OVERHEAD);
869 l1_block_contract
870 .storage
871 .insert(L1_SCALAR_SLOT, U256::from(L1_BASE_FEE_SCALAR));
872
873 let ctx = Context::op()
874 .with_db(db)
875 .with_chain(L1BlockInfo {
876 l2_block: Some(BLOCK_NUM + U256::from(1)),
877 ..Default::default()
878 })
879 .with_block(BlockEnv {
880 number: BLOCK_NUM,
881 ..Default::default()
882 })
883 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
884
885 let mut evm = ctx.build_op();
886 assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM));
887
888 let handler =
889 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
890 handler
891 .validate_against_state_and_deduct_caller(&mut evm)
892 .unwrap();
893
894 assert_eq!(
895 *evm.ctx().chain(),
896 L1BlockInfo {
897 l2_block: Some(BLOCK_NUM),
898 l1_base_fee: L1_BASE_FEE,
899 l1_fee_overhead: Some(L1_FEE_OVERHEAD),
900 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
901 tx_l1_cost: Some(U256::ZERO),
902 ..Default::default()
903 }
904 );
905 }
906
907 #[test]
908 fn test_reload_l1_block_info_ecotone_pre_isthmus() {
909 const BLOCK_NUM: U256 = uint!(300_U256);
910 const L1_BASE_FEE: U256 = uint!(13_U256);
911 const L1_BLOB_BASE_FEE: U256 = uint!(17_U256);
912 const L1_BASE_FEE_SCALAR: u64 = 19;
913 const L1_BLOB_BASE_FEE_SCALAR: u64 = 23;
914 const L1_FEE_SCALARS: U256 = U256::from_limbs([
915 0,
916 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
917 0,
918 0,
919 ]);
920
921 let mut db = InMemoryDB::default();
922 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
923 l1_block_contract
924 .storage
925 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
926 l1_block_contract
927 .storage
928 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
929 l1_block_contract
930 .storage
931 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
932
933 let ctx = Context::op()
934 .with_db(db)
935 .with_chain(L1BlockInfo {
936 l2_block: Some(BLOCK_NUM + U256::from(1)),
937 ..Default::default()
938 })
939 .with_block(BlockEnv {
940 number: BLOCK_NUM,
941 ..Default::default()
942 })
943 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ECOTONE);
944
945 let mut evm = ctx.build_op();
946 assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM));
947
948 let handler =
949 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
950 handler
951 .validate_against_state_and_deduct_caller(&mut evm)
952 .unwrap();
953
954 assert_eq!(
955 *evm.ctx().chain(),
956 L1BlockInfo {
957 l2_block: Some(BLOCK_NUM),
958 l1_base_fee: L1_BASE_FEE,
959 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
960 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
961 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
962 empty_ecotone_scalars: false,
963 l1_fee_overhead: None,
964 tx_l1_cost: Some(U256::ZERO),
965 ..Default::default()
966 }
967 );
968 }
969
970 #[test]
971 fn test_load_l1_block_info_isthmus_none() {
972 const BLOCK_NUM: U256 = uint!(100_U256);
973 const L1_BASE_FEE: U256 = uint!(1_U256);
974 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
975 const L1_BASE_FEE_SCALAR: u64 = 3;
976 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
977 const L1_FEE_SCALARS: U256 = U256::from_limbs([
978 0,
979 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
980 0,
981 0,
982 ]);
983 const OPERATOR_FEE_SCALAR: u64 = 5;
984 const OPERATOR_FEE_CONST: u64 = 6;
985 const OPERATOR_FEE: U256 =
986 U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]);
987
988 let mut db = InMemoryDB::default();
989 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
990 l1_block_contract
991 .storage
992 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
993 l1_block_contract
994 .storage
995 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
996 l1_block_contract
997 .storage
998 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
999 l1_block_contract
1000 .storage
1001 .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE);
1002 db.insert_account_info(
1003 Address::ZERO,
1004 AccountInfo {
1005 balance: U256::from(1000),
1006 ..Default::default()
1007 },
1008 );
1009
1010 let ctx = Context::op()
1011 .with_db(db)
1012 .with_block(BlockEnv {
1013 number: BLOCK_NUM,
1014 ..Default::default()
1015 })
1016 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
1017
1018 let mut evm = ctx.build_op();
1019
1020 assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM));
1021
1022 let handler =
1023 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1024 handler
1025 .validate_against_state_and_deduct_caller(&mut evm)
1026 .unwrap();
1027
1028 assert_eq!(
1029 *evm.ctx().chain(),
1030 L1BlockInfo {
1031 l2_block: Some(BLOCK_NUM),
1032 l1_base_fee: L1_BASE_FEE,
1033 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
1034 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
1035 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
1036 empty_ecotone_scalars: false,
1037 l1_fee_overhead: None,
1038 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
1039 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
1040 tx_l1_cost: Some(U256::ZERO),
1041 ..Default::default()
1042 }
1043 );
1044 }
1045
1046 #[test]
1047 fn test_remove_l1_cost() {
1048 let caller = Address::ZERO;
1049 let mut db = InMemoryDB::default();
1050 db.insert_account_info(
1051 caller,
1052 AccountInfo {
1053 balance: U256::from(1049),
1054 ..Default::default()
1055 },
1056 );
1057 let ctx = Context::op()
1058 .with_db(db)
1059 .with_chain(L1BlockInfo {
1060 l1_base_fee: U256::from(1_000),
1061 l1_fee_overhead: Some(U256::from(1_000)),
1062 l1_base_fee_scalar: U256::from(1_000),
1063 l2_block: Some(U256::from(0)),
1064 ..Default::default()
1065 })
1066 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
1067 .with_tx(
1068 OpTransaction::builder()
1069 .base(TxEnv::builder().gas_limit(100))
1070 .source_hash(B256::ZERO)
1071 .enveloped_tx(Some(bytes!("FACADE")))
1072 .build()
1073 .unwrap(),
1074 );
1075
1076 let mut evm = ctx.build_op();
1077 let handler =
1078 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1079
1080 handler
1082 .validate_against_state_and_deduct_caller(&mut evm)
1083 .unwrap();
1084
1085 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
1087 assert_eq!(account.info.balance, U256::from(1));
1088 }
1089
1090 #[test]
1091 fn test_remove_operator_cost_isthmus() {
1092 let caller = Address::ZERO;
1093 let mut db = InMemoryDB::default();
1094 db.insert_account_info(
1095 caller,
1096 AccountInfo {
1097 balance: U256::from(151),
1098 ..Default::default()
1099 },
1100 );
1101 let ctx = Context::op()
1102 .with_db(db)
1103 .with_chain(L1BlockInfo {
1104 operator_fee_scalar: Some(U256::from(10_000_000)),
1105 operator_fee_constant: Some(U256::from(50)),
1106 l2_block: Some(U256::from(0)),
1107 ..Default::default()
1108 })
1109 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS)
1110 .with_tx(
1111 OpTransaction::builder()
1112 .base(TxEnv::builder().gas_limit(10))
1113 .enveloped_tx(Some(bytes!("FACADE")))
1114 .build_fill(),
1115 );
1116
1117 let mut evm = ctx.build_op();
1118 let handler =
1119 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1120
1121 handler
1124 .validate_against_state_and_deduct_caller(&mut evm)
1125 .unwrap();
1126
1127 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
1129 assert_eq!(account.info.balance, U256::from(1));
1130 }
1131
1132 #[test]
1133 fn test_remove_operator_cost_jovian() {
1134 let caller = Address::ZERO;
1135 let mut db = InMemoryDB::default();
1136 db.insert_account_info(
1137 caller,
1138 AccountInfo {
1139 balance: U256::from(2_051),
1140 ..Default::default()
1141 },
1142 );
1143 let ctx = Context::op()
1144 .with_db(db)
1145 .with_chain(L1BlockInfo {
1146 operator_fee_scalar: Some(U256::from(2)),
1147 operator_fee_constant: Some(U256::from(50)),
1148 l2_block: Some(U256::from(0)),
1149 ..Default::default()
1150 })
1151 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN)
1152 .with_tx(
1153 OpTransaction::builder()
1154 .base(TxEnv::builder().gas_limit(10))
1155 .enveloped_tx(Some(bytes!("FACADE")))
1156 .build_fill(),
1157 );
1158
1159 let mut evm = ctx.build_op();
1160 let handler =
1161 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1162
1163 handler
1166 .validate_against_state_and_deduct_caller(&mut evm)
1167 .unwrap();
1168
1169 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
1170 assert_eq!(account.info.balance, U256::from(1));
1171 }
1172
1173 #[test]
1174 fn test_remove_l1_cost_lack_of_funds() {
1175 let caller = Address::ZERO;
1176 let mut db = InMemoryDB::default();
1177 db.insert_account_info(
1178 caller,
1179 AccountInfo {
1180 balance: U256::from(48),
1181 ..Default::default()
1182 },
1183 );
1184 let ctx = Context::op()
1185 .with_db(db)
1186 .with_chain(L1BlockInfo {
1187 l1_base_fee: U256::from(1_000),
1188 l1_fee_overhead: Some(U256::from(1_000)),
1189 l1_base_fee_scalar: U256::from(1_000),
1190 l2_block: Some(U256::from(0)),
1191 ..Default::default()
1192 })
1193 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
1194 .modify_tx_chained(|tx| {
1195 tx.enveloped_tx = Some(bytes!("FACADE"));
1196 });
1197
1198 let mut evm = ctx.build_op();
1200 let handler =
1201 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1202
1203 assert_eq!(
1205 handler.validate_against_state_and_deduct_caller(&mut evm),
1206 Err(EVMError::Transaction(
1207 InvalidTransaction::LackOfFundForMaxFee {
1208 fee: Box::new(U256::from(1048)),
1209 balance: Box::new(U256::from(48)),
1210 }
1211 .into(),
1212 ))
1213 );
1214 }
1215
1216 #[test]
1217 fn test_validate_sys_tx() {
1218 let ctx = Context::op()
1220 .modify_tx_chained(|tx| {
1221 tx.deposit.source_hash = B256::from([1u8; 32]);
1222 tx.deposit.is_system_transaction = true;
1223 })
1224 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1225
1226 let mut evm = ctx.build_op();
1227 let handler =
1228 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1229
1230 assert_eq!(
1231 handler.validate_env(&mut evm),
1232 Err(EVMError::Transaction(
1233 OpTransactionError::DepositSystemTxPostRegolith
1234 ))
1235 );
1236
1237 evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK);
1238
1239 assert!(handler.validate_env(&mut evm).is_ok());
1241 }
1242
1243 #[test]
1244 fn test_validate_deposit_tx() {
1245 let ctx = Context::op()
1247 .modify_tx_chained(|tx| {
1248 tx.deposit.source_hash = B256::from([1u8; 32]);
1249 })
1250 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1251
1252 let mut evm = ctx.build_op();
1253 let handler =
1254 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1255
1256 assert!(handler.validate_env(&mut evm).is_ok());
1257 }
1258
1259 #[test]
1260 fn test_validate_tx_against_state_deposit_tx() {
1261 let ctx = Context::op()
1263 .modify_tx_chained(|tx| {
1264 tx.deposit.source_hash = B256::from([1u8; 32]);
1265 })
1266 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1267
1268 let mut evm = ctx.build_op();
1269 let handler =
1270 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1271
1272 assert!(handler.validate_env(&mut evm).is_ok());
1274 }
1275
1276 #[test]
1277 fn test_halted_deposit_tx_post_regolith() {
1278 let ctx = Context::op()
1279 .modify_tx_chained(|tx| {
1280 tx.deposit.source_hash = B256::from([1u8; 32]);
1282 })
1283 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1284
1285 let mut evm = ctx.build_op();
1286 let mut handler =
1287 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1288
1289 assert_eq!(
1290 handler.execution_result(
1291 &mut evm,
1292 FrameResult::Call(CallOutcome::new(
1293 InterpreterResult {
1294 result: InstructionResult::OutOfGas,
1295 output: Default::default(),
1296 gas: Default::default(),
1297 },
1298 Default::default()
1299 ))
1300 ),
1301 Err(EVMError::Transaction(
1302 OpTransactionError::HaltedDepositPostRegolith
1303 ))
1304 )
1305 }
1306
1307 #[test]
1308 fn test_tx_zero_value_touch_caller() {
1309 let ctx = Context::op();
1310
1311 let mut evm = ctx.build_op();
1312
1313 assert!(!evm
1314 .0
1315 .ctx
1316 .journal_mut()
1317 .load_account(Address::ZERO)
1318 .unwrap()
1319 .is_touched());
1320
1321 let handler =
1322 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1323
1324 handler
1325 .validate_against_state_and_deduct_caller(&mut evm)
1326 .unwrap();
1327
1328 assert!(evm
1329 .0
1330 .ctx
1331 .journal_mut()
1332 .load_account(Address::ZERO)
1333 .unwrap()
1334 .is_touched());
1335 }
1336
1337 #[rstest]
1338 #[case::deposit(true)]
1339 #[case::dyn_fee(false)]
1340 fn test_operator_fee_refund(#[case] is_deposit: bool) {
1341 const SENDER: Address = Address::ZERO;
1342 const GAS_PRICE: u128 = 0xFF;
1343 const OP_FEE_MOCK_PARAM: u128 = 0xFFFF;
1344
1345 let ctx = Context::op()
1346 .with_tx(
1347 OpTransaction::builder()
1348 .base(
1349 TxEnv::builder()
1350 .gas_price(GAS_PRICE)
1351 .gas_priority_fee(None)
1352 .caller(SENDER),
1353 )
1354 .enveloped_tx(if is_deposit {
1355 None
1356 } else {
1357 Some(bytes!("FACADE"))
1358 })
1359 .source_hash(if is_deposit {
1360 B256::from([1u8; 32])
1361 } else {
1362 B256::ZERO
1363 })
1364 .build_fill(),
1365 )
1366 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
1367
1368 let mut evm = ctx.build_op();
1369 let handler =
1370 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1371
1372 evm.ctx().chain.operator_fee_scalar = Some(U256::from(OP_FEE_MOCK_PARAM));
1374 evm.ctx().chain.operator_fee_constant = Some(U256::from(OP_FEE_MOCK_PARAM));
1375
1376 let mut gas = Gas::new(100);
1377 gas.set_spent(10);
1378 let mut exec_result = FrameResult::Call(CallOutcome::new(
1379 InterpreterResult {
1380 result: InstructionResult::Return,
1381 output: Default::default(),
1382 gas,
1383 },
1384 0..0,
1385 ));
1386
1387 handler
1389 .reimburse_caller(&mut evm, &mut exec_result)
1390 .unwrap();
1391
1392 let mut expected_refund =
1395 U256::from(GAS_PRICE * (gas.remaining() + gas.refunded() as u64) as u128);
1396 let op_fee_refund = evm
1397 .ctx()
1398 .chain()
1399 .operator_fee_refund(&gas, OpSpecId::ISTHMUS);
1400 assert!(op_fee_refund > U256::ZERO);
1401
1402 if !is_deposit {
1403 expected_refund += op_fee_refund;
1404 }
1405
1406 let account = evm.ctx().journal_mut().load_account(SENDER).unwrap();
1408 assert_eq!(account.info.balance, expected_refund);
1409 }
1410
1411 #[test]
1412 fn test_tx_low_balance_nonce_unchanged() {
1413 let ctx = Context::op().with_tx(
1414 OpTransaction::builder()
1415 .base(TxEnv::builder().value(U256::from(1000)))
1416 .build_fill(),
1417 );
1418
1419 let mut evm = ctx.build_op();
1420
1421 let handler =
1422 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1423
1424 let result = handler.validate_against_state_and_deduct_caller(&mut evm);
1425
1426 assert!(matches!(
1427 result.err().unwrap(),
1428 EVMError::Transaction(OpTransactionError::Base(
1429 InvalidTransaction::LackOfFundForMaxFee { .. }
1430 ))
1431 ));
1432 assert_eq!(
1433 evm.0
1434 .ctx
1435 .journal_mut()
1436 .load_account(Address::ZERO)
1437 .unwrap()
1438 .info
1439 .nonce,
1440 0
1441 );
1442 }
1443
1444 #[test]
1445 fn test_validate_missing_enveloped_tx() {
1446 use crate::transaction::deposit::DepositTransactionParts;
1447
1448 let ctx = Context::op().with_tx(OpTransaction {
1450 base: TxEnv::builder().build_fill(),
1451 enveloped_tx: None, deposit: DepositTransactionParts::default(), });
1454
1455 let mut evm = ctx.build_op();
1456 let handler =
1457 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1458
1459 assert_eq!(
1460 handler.validate_env(&mut evm),
1461 Err(EVMError::Transaction(
1462 OpTransactionError::MissingEnvelopedTx
1463 ))
1464 );
1465 }
1466}