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