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