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},
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 && tx.is_system_transaction() {
224 gas.erase_cost(tx_gas_limit);
227 }
228 } else if instruction_result.is_revert() {
229 if !is_deposit || is_regolith {
242 gas.erase_cost(remaining);
243 }
244 }
245 Ok(())
246 }
247
248 fn reimburse_caller(
249 &self,
250 evm: &mut Self::Evm,
251 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
252 ) -> Result<(), Self::Error> {
253 let mut additional_refund = U256::ZERO;
254
255 if evm.ctx().tx().tx_type() != DEPOSIT_TRANSACTION_TYPE
256 && !evm.ctx().cfg().is_fee_charge_disabled()
257 {
258 let spec = evm.ctx().cfg().spec();
259 additional_refund = evm
260 .ctx()
261 .chain()
262 .operator_fee_refund(frame_result.gas(), spec);
263 }
264
265 reimburse_caller(evm.ctx(), frame_result.gas(), additional_refund).map_err(From::from)
266 }
267
268 fn refund(
269 &self,
270 evm: &mut Self::Evm,
271 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
272 eip7702_refund: i64,
273 ) {
274 frame_result.gas_mut().record_refund(eip7702_refund);
275
276 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
277 let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
278
279 let is_gas_refund_disabled = is_deposit && !is_regolith;
281 if !is_gas_refund_disabled {
282 frame_result.gas_mut().set_final_refund(
283 evm.ctx()
284 .cfg()
285 .spec()
286 .into_eth_spec()
287 .is_enabled_in(SpecId::LONDON),
288 );
289 }
290 }
291
292 fn reward_beneficiary(
293 &self,
294 evm: &mut Self::Evm,
295 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
296 ) -> Result<(), Self::Error> {
297 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
298
299 if is_deposit {
301 return Ok(());
302 }
303
304 self.mainnet.reward_beneficiary(evm, frame_result)?;
305 let basefee = evm.ctx().block().basefee() as u128;
306
307 let ctx = evm.ctx();
310 let enveloped = ctx.tx().enveloped_tx().cloned();
311 let spec = ctx.cfg().spec();
312 let l1_block_info = ctx.chain_mut();
313
314 let Some(enveloped_tx) = &enveloped else {
315 return Err(ERROR::from_string(
316 "[OPTIMISM] Failed to load enveloped transaction.".into(),
317 ));
318 };
319
320 let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec);
321 let operator_fee_cost = if spec.is_enabled_in(OpSpecId::ISTHMUS) {
322 l1_block_info.operator_fee_charge(
323 enveloped_tx,
324 U256::from(frame_result.gas().used()),
325 spec,
326 )
327 } else {
328 U256::ZERO
329 };
330 let base_fee_amount = U256::from(basefee.saturating_mul(frame_result.gas().used() as u128));
331
332 for (recipient, amount) in [
334 (L1_FEE_RECIPIENT, l1_cost),
335 (BASE_FEE_RECIPIENT, base_fee_amount),
336 (OPERATOR_FEE_RECIPIENT, operator_fee_cost),
337 ] {
338 ctx.journal_mut().balance_incr(recipient, amount)?;
339 }
340
341 Ok(())
342 }
343
344 fn execution_result(
345 &mut self,
346 evm: &mut Self::Evm,
347 frame_result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
348 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
349 take_error::<Self::Error, _>(evm.ctx().error())?;
350
351 let exec_result =
352 post_execution::output(evm.ctx(), frame_result).map_haltreason(OpHaltReason::Base);
353
354 if exec_result.is_halt() {
355 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
359 if is_deposit && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) {
360 return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith));
361 }
362 }
363 evm.ctx().journal_mut().commit_tx();
364 evm.ctx().chain_mut().clear_tx_l1_cost();
365 evm.ctx().local_mut().clear();
366 evm.frame_stack().clear();
367
368 Ok(exec_result)
369 }
370
371 fn catch_error(
372 &self,
373 evm: &mut Self::Evm,
374 error: Self::Error,
375 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
376 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
377 let is_tx_error = error.is_tx_error();
378 let mut output = Err(error);
379
380 if is_tx_error && is_deposit {
382 let ctx = evm.ctx();
383 let spec = ctx.cfg().spec();
384 let tx = ctx.tx();
385 let caller = tx.caller();
386 let mint = tx.mint();
387 let is_system_tx = tx.is_system_transaction();
388 let gas_limit = tx.gas_limit();
389 let journal = evm.ctx().journal_mut();
390
391 journal.checkpoint_revert(JournalCheckpoint::default());
394
395 let mut acc = journal.load_account_mut(caller)?;
405 acc.bump_nonce();
406 acc.incr_balance(U256::from(mint.unwrap_or_default()));
407
408 drop(acc); journal.commit_tx();
412
413 let gas_used = if spec.is_enabled_in(OpSpecId::REGOLITH) || !is_system_tx {
418 gas_limit
419 } else {
420 0
421 };
422 output = Ok(ExecutionResult::Halt {
424 reason: OpHaltReason::FailedDeposit,
425 gas_used,
426 })
427 }
428
429 evm.ctx().chain_mut().clear_tx_l1_cost();
431 evm.ctx().local_mut().clear();
432 evm.frame_stack().clear();
433
434 output
435 }
436}
437
438impl<EVM, ERROR> InspectorHandler for OpHandler<EVM, ERROR, EthFrame<EthInterpreter>>
439where
440 EVM: InspectorEvmTr<
441 Context: OpContextTr,
442 Frame = EthFrame<EthInterpreter>,
443 Inspector: Inspector<<<Self as Handler>::Evm as EvmTr>::Context, EthInterpreter>,
444 >,
445 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
446{
447 type IT = EthInterpreter;
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453 use crate::{
454 api::default_ctx::OpContext,
455 constants::{
456 BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT,
457 L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT,
458 },
459 DefaultOp, OpBuilder, OpTransaction,
460 };
461 use alloy_primitives::uint;
462 use revm::{
463 context::{BlockEnv, CfgEnv, Context, TxEnv},
464 context_interface::result::InvalidTransaction,
465 database::InMemoryDB,
466 database_interface::EmptyDB,
467 handler::EthFrame,
468 interpreter::{CallOutcome, InstructionResult, InterpreterResult},
469 primitives::{bytes, Address, Bytes, B256},
470 state::AccountInfo,
471 };
472 use rstest::rstest;
473 use std::boxed::Box;
474
475 fn call_last_frame_return(
477 ctx: OpContext<EmptyDB>,
478 instruction_result: InstructionResult,
479 gas: Gas,
480 ) -> Gas {
481 let mut evm = ctx.build_op();
482
483 let mut exec_result = FrameResult::Call(CallOutcome::new(
484 InterpreterResult {
485 result: instruction_result,
486 output: Bytes::new(),
487 gas,
488 },
489 0..0,
490 ));
491
492 let mut handler =
493 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
494
495 handler
496 .last_frame_result(&mut evm, &mut exec_result)
497 .unwrap();
498 handler.refund(&mut evm, &mut exec_result, 0);
499 *exec_result.gas()
500 }
501
502 #[test]
503 fn test_revert_gas() {
504 let ctx = Context::op()
505 .with_tx(
506 OpTransaction::builder()
507 .base(TxEnv::builder().gas_limit(100))
508 .build_fill(),
509 )
510 .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK));
511
512 let gas = call_last_frame_return(ctx, InstructionResult::Revert, Gas::new(90));
513 assert_eq!(gas.remaining(), 90);
514 assert_eq!(gas.spent(), 10);
515 assert_eq!(gas.refunded(), 0);
516 }
517
518 #[test]
519 fn test_consume_gas() {
520 let ctx = Context::op()
521 .with_tx(
522 OpTransaction::builder()
523 .base(TxEnv::builder().gas_limit(100))
524 .build_fill(),
525 )
526 .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH));
527
528 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
529 assert_eq!(gas.remaining(), 90);
530 assert_eq!(gas.spent(), 10);
531 assert_eq!(gas.refunded(), 0);
532 }
533
534 #[test]
535 fn test_consume_gas_with_refund() {
536 let ctx = Context::op()
537 .with_tx(
538 OpTransaction::builder()
539 .base(TxEnv::builder().gas_limit(100))
540 .source_hash(B256::from([1u8; 32]))
541 .build_fill(),
542 )
543 .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH));
544
545 let mut ret_gas = Gas::new(90);
546 ret_gas.record_refund(20);
547
548 let gas = call_last_frame_return(ctx.clone(), InstructionResult::Stop, ret_gas);
549 assert_eq!(gas.remaining(), 90);
550 assert_eq!(gas.spent(), 10);
551 assert_eq!(gas.refunded(), 2); let gas = call_last_frame_return(ctx, InstructionResult::Revert, ret_gas);
554 assert_eq!(gas.remaining(), 90);
555 assert_eq!(gas.spent(), 10);
556 assert_eq!(gas.refunded(), 0);
557 }
558
559 #[test]
560 fn test_consume_gas_deposit_tx() {
561 let ctx = Context::op()
562 .with_tx(
563 OpTransaction::builder()
564 .base(TxEnv::builder().gas_limit(100))
565 .source_hash(B256::from([1u8; 32]))
566 .build_fill(),
567 )
568 .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK));
569 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
570 assert_eq!(gas.remaining(), 0);
571 assert_eq!(gas.spent(), 100);
572 assert_eq!(gas.refunded(), 0);
573 }
574
575 #[test]
576 fn test_consume_gas_sys_deposit_tx() {
577 let ctx = Context::op()
578 .with_tx(
579 OpTransaction::builder()
580 .base(TxEnv::builder().gas_limit(100))
581 .source_hash(B256::from([1u8; 32]))
582 .is_system_transaction()
583 .build_fill(),
584 )
585 .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK));
586 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
587 assert_eq!(gas.remaining(), 100);
588 assert_eq!(gas.spent(), 0);
589 assert_eq!(gas.refunded(), 0);
590 }
591
592 #[test]
593 fn test_commit_mint_value() {
594 let caller = Address::ZERO;
595 let mut db = InMemoryDB::default();
596 db.insert_account_info(
597 caller,
598 AccountInfo {
599 balance: U256::from(1000),
600 ..Default::default()
601 },
602 );
603
604 let mut ctx = Context::op()
605 .with_db(db)
606 .with_chain(L1BlockInfo {
607 l1_base_fee: U256::from(1_000),
608 l1_fee_overhead: Some(U256::from(1_000)),
609 l1_base_fee_scalar: U256::from(1_000),
610 ..Default::default()
611 })
612 .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH));
613 ctx.modify_tx(|tx| {
614 tx.deposit.source_hash = B256::from([1u8; 32]);
615 tx.deposit.mint = Some(10);
616 });
617
618 let mut evm = ctx.build_op();
619
620 let handler =
621 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
622 handler
623 .validate_against_state_and_deduct_caller(&mut evm)
624 .unwrap();
625
626 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
628 assert_eq!(account.info.balance, U256::from(1010));
629 }
630
631 #[test]
632 fn test_remove_l1_cost_non_deposit() {
633 let caller = Address::ZERO;
634 let mut db = InMemoryDB::default();
635 db.insert_account_info(
636 caller,
637 AccountInfo {
638 balance: U256::from(1058), ..Default::default()
640 },
641 );
642 let ctx = Context::op()
643 .with_db(db)
644 .with_chain(L1BlockInfo {
645 l1_base_fee: U256::from(1_000),
646 l1_fee_overhead: Some(U256::from(1_000)),
647 l1_base_fee_scalar: U256::from(1_000),
648 l2_block: Some(U256::from(0)),
649 ..Default::default()
650 })
651 .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH))
652 .with_tx(
653 OpTransaction::builder()
654 .base(TxEnv::builder().gas_limit(100))
655 .enveloped_tx(Some(bytes!("FACADE")))
656 .source_hash(B256::ZERO)
657 .build()
658 .unwrap(),
659 );
660
661 let mut evm = ctx.build_op();
662
663 let handler =
664 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
665 handler
666 .validate_against_state_and_deduct_caller(&mut evm)
667 .unwrap();
668
669 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
671 assert_eq!(account.info.balance, U256::from(10)); }
673
674 #[test]
675 fn test_reload_l1_block_info_isthmus() {
676 const BLOCK_NUM: U256 = uint!(100_U256);
677 const L1_BASE_FEE: U256 = uint!(1_U256);
678 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
679 const L1_BASE_FEE_SCALAR: u64 = 3;
680 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
681 const L1_FEE_SCALARS: U256 = U256::from_limbs([
682 0,
683 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
684 0,
685 0,
686 ]);
687 const OPERATOR_FEE_SCALAR: u64 = 5;
688 const OPERATOR_FEE_CONST: u64 = 6;
689 const OPERATOR_FEE: U256 =
690 U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]);
691
692 let mut db = InMemoryDB::default();
693 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
694 l1_block_contract
695 .storage
696 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
697 l1_block_contract
698 .storage
699 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
700 l1_block_contract
701 .storage
702 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
703 l1_block_contract
704 .storage
705 .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE);
706 db.insert_account_info(
707 Address::ZERO,
708 AccountInfo {
709 balance: U256::from(1000),
710 ..Default::default()
711 },
712 );
713
714 let ctx = Context::op()
715 .with_db(db)
716 .with_chain(L1BlockInfo {
717 l2_block: Some(BLOCK_NUM + U256::from(1)), ..Default::default()
719 })
720 .with_block(BlockEnv {
721 number: BLOCK_NUM,
722 ..Default::default()
723 })
724 .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS));
725
726 let mut evm = ctx.build_op();
727
728 assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM));
729
730 let handler =
731 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
732 handler
733 .validate_against_state_and_deduct_caller(&mut evm)
734 .unwrap();
735
736 assert_eq!(
737 *evm.ctx().chain(),
738 L1BlockInfo {
739 l2_block: Some(BLOCK_NUM),
740 l1_base_fee: L1_BASE_FEE,
741 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
742 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
743 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
744 empty_ecotone_scalars: false,
745 l1_fee_overhead: None,
746 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
747 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
748 tx_l1_cost: Some(U256::ZERO),
749 da_footprint_gas_scalar: None
750 }
751 );
752 }
753
754 #[test]
755 fn test_parse_da_footprint_gas_scalar_jovian() {
756 const BLOCK_NUM: U256 = uint!(100_U256);
757 const L1_BASE_FEE: U256 = uint!(1_U256);
758 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
759 const L1_BASE_FEE_SCALAR: u64 = 3;
760 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
761 const L1_FEE_SCALARS: U256 = U256::from_limbs([
762 0,
763 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
764 0,
765 0,
766 ]);
767 const OPERATOR_FEE_SCALAR: u8 = 5;
768 const OPERATOR_FEE_CONST: u8 = 6;
769 const DA_FOOTPRINT_GAS_SCALAR: u8 = 7;
770 let mut operator_fee_and_da_footprint = [0u8; 32];
771 operator_fee_and_da_footprint[31] = OPERATOR_FEE_CONST;
772 operator_fee_and_da_footprint[23] = OPERATOR_FEE_SCALAR;
773 operator_fee_and_da_footprint[19] = DA_FOOTPRINT_GAS_SCALAR;
774 let operator_fee_and_da_footprint_u256 = U256::from_be_bytes(operator_fee_and_da_footprint);
775
776 let mut db = InMemoryDB::default();
777 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
778 l1_block_contract
779 .storage
780 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
781 l1_block_contract
782 .storage
783 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
784 l1_block_contract
785 .storage
786 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
787 l1_block_contract.storage.insert(
788 OPERATOR_FEE_SCALARS_SLOT,
789 operator_fee_and_da_footprint_u256,
790 );
791 db.insert_account_info(
792 Address::ZERO,
793 AccountInfo {
794 balance: U256::from(6000),
795 ..Default::default()
796 },
797 );
798
799 let ctx = Context::op()
800 .with_db(db)
801 .with_chain(L1BlockInfo {
802 l2_block: Some(BLOCK_NUM + U256::from(1)), operator_fee_scalar: Some(U256::from(2)),
804 operator_fee_constant: Some(U256::from(50)),
805 ..Default::default()
806 })
807 .with_block(BlockEnv {
808 number: BLOCK_NUM,
809 ..Default::default()
810 })
811 .with_cfg(CfgEnv::new_with_spec(OpSpecId::JOVIAN))
812 .with_tx(
814 OpTransaction::builder()
815 .base(TxEnv::builder().gas_limit(10))
816 .enveloped_tx(Some(bytes!("FACADE")))
817 .build_fill(),
818 );
819
820 let mut evm = ctx.build_op();
821
822 assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM));
823
824 let handler =
825 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
826 handler
827 .validate_against_state_and_deduct_caller(&mut evm)
828 .unwrap();
829
830 assert_eq!(
831 *evm.ctx().chain(),
832 L1BlockInfo {
833 l2_block: Some(BLOCK_NUM),
834 l1_base_fee: L1_BASE_FEE,
835 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
836 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
837 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
838 empty_ecotone_scalars: false,
839 l1_fee_overhead: None,
840 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
841 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
842 tx_l1_cost: Some(U256::ZERO),
843 da_footprint_gas_scalar: Some(DA_FOOTPRINT_GAS_SCALAR as u16),
844 }
845 );
846 }
847
848 #[test]
849 fn test_reload_l1_block_info_regolith() {
850 const BLOCK_NUM: U256 = uint!(200_U256);
851 const L1_BASE_FEE: U256 = uint!(7_U256);
852 const L1_FEE_OVERHEAD: U256 = uint!(9_U256);
853 const L1_BASE_FEE_SCALAR: u64 = 11;
854
855 let mut db = InMemoryDB::default();
856 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
857 l1_block_contract
858 .storage
859 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
860 use crate::constants::{L1_OVERHEAD_SLOT, L1_SCALAR_SLOT};
862 l1_block_contract
863 .storage
864 .insert(L1_OVERHEAD_SLOT, L1_FEE_OVERHEAD);
865 l1_block_contract
866 .storage
867 .insert(L1_SCALAR_SLOT, U256::from(L1_BASE_FEE_SCALAR));
868
869 let ctx = Context::op()
870 .with_db(db)
871 .with_chain(L1BlockInfo {
872 l2_block: Some(BLOCK_NUM + U256::from(1)),
873 ..Default::default()
874 })
875 .with_block(BlockEnv {
876 number: BLOCK_NUM,
877 ..Default::default()
878 })
879 .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH));
880
881 let mut evm = ctx.build_op();
882 assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM));
883
884 let handler =
885 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
886 handler
887 .validate_against_state_and_deduct_caller(&mut evm)
888 .unwrap();
889
890 assert_eq!(
891 *evm.ctx().chain(),
892 L1BlockInfo {
893 l2_block: Some(BLOCK_NUM),
894 l1_base_fee: L1_BASE_FEE,
895 l1_fee_overhead: Some(L1_FEE_OVERHEAD),
896 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
897 tx_l1_cost: Some(U256::ZERO),
898 ..Default::default()
899 }
900 );
901 }
902
903 #[test]
904 fn test_reload_l1_block_info_ecotone_pre_isthmus() {
905 const BLOCK_NUM: U256 = uint!(300_U256);
906 const L1_BASE_FEE: U256 = uint!(13_U256);
907 const L1_BLOB_BASE_FEE: U256 = uint!(17_U256);
908 const L1_BASE_FEE_SCALAR: u64 = 19;
909 const L1_BLOB_BASE_FEE_SCALAR: u64 = 23;
910 const L1_FEE_SCALARS: U256 = U256::from_limbs([
911 0,
912 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
913 0,
914 0,
915 ]);
916
917 let mut db = InMemoryDB::default();
918 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
919 l1_block_contract
920 .storage
921 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
922 l1_block_contract
923 .storage
924 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
925 l1_block_contract
926 .storage
927 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
928
929 let ctx = Context::op()
930 .with_db(db)
931 .with_chain(L1BlockInfo {
932 l2_block: Some(BLOCK_NUM + U256::from(1)),
933 ..Default::default()
934 })
935 .with_block(BlockEnv {
936 number: BLOCK_NUM,
937 ..Default::default()
938 })
939 .with_cfg(CfgEnv::new_with_spec(OpSpecId::ECOTONE));
940
941 let mut evm = ctx.build_op();
942 assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM));
943
944 let handler =
945 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
946 handler
947 .validate_against_state_and_deduct_caller(&mut evm)
948 .unwrap();
949
950 assert_eq!(
951 *evm.ctx().chain(),
952 L1BlockInfo {
953 l2_block: Some(BLOCK_NUM),
954 l1_base_fee: L1_BASE_FEE,
955 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
956 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
957 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
958 empty_ecotone_scalars: false,
959 l1_fee_overhead: None,
960 tx_l1_cost: Some(U256::ZERO),
961 ..Default::default()
962 }
963 );
964 }
965
966 #[test]
967 fn test_load_l1_block_info_isthmus_none() {
968 const BLOCK_NUM: U256 = uint!(100_U256);
969 const L1_BASE_FEE: U256 = uint!(1_U256);
970 const L1_BLOB_BASE_FEE: U256 = uint!(2_U256);
971 const L1_BASE_FEE_SCALAR: u64 = 3;
972 const L1_BLOB_BASE_FEE_SCALAR: u64 = 4;
973 const L1_FEE_SCALARS: U256 = U256::from_limbs([
974 0,
975 (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR,
976 0,
977 0,
978 ]);
979 const OPERATOR_FEE_SCALAR: u64 = 5;
980 const OPERATOR_FEE_CONST: u64 = 6;
981 const OPERATOR_FEE: U256 =
982 U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]);
983
984 let mut db = InMemoryDB::default();
985 let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap();
986 l1_block_contract
987 .storage
988 .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE);
989 l1_block_contract
990 .storage
991 .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE);
992 l1_block_contract
993 .storage
994 .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS);
995 l1_block_contract
996 .storage
997 .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE);
998 db.insert_account_info(
999 Address::ZERO,
1000 AccountInfo {
1001 balance: U256::from(1000),
1002 ..Default::default()
1003 },
1004 );
1005
1006 let ctx = Context::op()
1007 .with_db(db)
1008 .with_block(BlockEnv {
1009 number: BLOCK_NUM,
1010 ..Default::default()
1011 })
1012 .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS));
1013
1014 let mut evm = ctx.build_op();
1015
1016 assert_ne!(evm.ctx().chain().l2_block, Some(BLOCK_NUM));
1017
1018 let handler =
1019 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1020 handler
1021 .validate_against_state_and_deduct_caller(&mut evm)
1022 .unwrap();
1023
1024 assert_eq!(
1025 *evm.ctx().chain(),
1026 L1BlockInfo {
1027 l2_block: Some(BLOCK_NUM),
1028 l1_base_fee: L1_BASE_FEE,
1029 l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR),
1030 l1_blob_base_fee: Some(L1_BLOB_BASE_FEE),
1031 l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)),
1032 empty_ecotone_scalars: false,
1033 l1_fee_overhead: None,
1034 operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)),
1035 operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)),
1036 tx_l1_cost: Some(U256::ZERO),
1037 ..Default::default()
1038 }
1039 );
1040 }
1041
1042 #[test]
1043 fn test_remove_l1_cost() {
1044 let caller = Address::ZERO;
1045 let mut db = InMemoryDB::default();
1046 db.insert_account_info(
1047 caller,
1048 AccountInfo {
1049 balance: U256::from(1049),
1050 ..Default::default()
1051 },
1052 );
1053 let ctx = Context::op()
1054 .with_db(db)
1055 .with_chain(L1BlockInfo {
1056 l1_base_fee: U256::from(1_000),
1057 l1_fee_overhead: Some(U256::from(1_000)),
1058 l1_base_fee_scalar: U256::from(1_000),
1059 l2_block: Some(U256::from(0)),
1060 ..Default::default()
1061 })
1062 .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH))
1063 .with_tx(
1064 OpTransaction::builder()
1065 .base(TxEnv::builder().gas_limit(100))
1066 .source_hash(B256::ZERO)
1067 .enveloped_tx(Some(bytes!("FACADE")))
1068 .build()
1069 .unwrap(),
1070 );
1071
1072 let mut evm = ctx.build_op();
1073 let handler =
1074 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1075
1076 handler
1078 .validate_against_state_and_deduct_caller(&mut evm)
1079 .unwrap();
1080
1081 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
1083 assert_eq!(account.info.balance, U256::from(1));
1084 }
1085
1086 #[test]
1087 fn test_remove_operator_cost_isthmus() {
1088 let caller = Address::ZERO;
1089 let mut db = InMemoryDB::default();
1090 db.insert_account_info(
1091 caller,
1092 AccountInfo {
1093 balance: U256::from(151),
1094 ..Default::default()
1095 },
1096 );
1097 let ctx = Context::op()
1098 .with_db(db)
1099 .with_chain(L1BlockInfo {
1100 operator_fee_scalar: Some(U256::from(10_000_000)),
1101 operator_fee_constant: Some(U256::from(50)),
1102 l2_block: Some(U256::from(0)),
1103 ..Default::default()
1104 })
1105 .with_cfg(CfgEnv::new_with_spec(OpSpecId::ISTHMUS))
1106 .with_tx(
1107 OpTransaction::builder()
1108 .base(TxEnv::builder().gas_limit(10))
1109 .enveloped_tx(Some(bytes!("FACADE")))
1110 .build_fill(),
1111 );
1112
1113 let mut evm = ctx.build_op();
1114 let handler =
1115 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1116
1117 handler
1120 .validate_against_state_and_deduct_caller(&mut evm)
1121 .unwrap();
1122
1123 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
1125 assert_eq!(account.info.balance, U256::from(1));
1126 }
1127
1128 #[test]
1129 fn test_remove_operator_cost_jovian() {
1130 let caller = Address::ZERO;
1131 let mut db = InMemoryDB::default();
1132 db.insert_account_info(
1133 caller,
1134 AccountInfo {
1135 balance: U256::from(2_051),
1136 ..Default::default()
1137 },
1138 );
1139 let ctx = Context::op()
1140 .with_db(db)
1141 .with_chain(L1BlockInfo {
1142 operator_fee_scalar: Some(U256::from(2)),
1143 operator_fee_constant: Some(U256::from(50)),
1144 l2_block: Some(U256::from(0)),
1145 ..Default::default()
1146 })
1147 .with_cfg(CfgEnv::new_with_spec(OpSpecId::JOVIAN))
1148 .with_tx(
1149 OpTransaction::builder()
1150 .base(TxEnv::builder().gas_limit(10))
1151 .enveloped_tx(Some(bytes!("FACADE")))
1152 .build_fill(),
1153 );
1154
1155 let mut evm = ctx.build_op();
1156 let handler =
1157 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1158
1159 handler
1162 .validate_against_state_and_deduct_caller(&mut evm)
1163 .unwrap();
1164
1165 let account = evm.ctx().journal_mut().load_account(caller).unwrap();
1166 assert_eq!(account.info.balance, U256::from(1));
1167 }
1168
1169 #[test]
1170 fn test_remove_l1_cost_lack_of_funds() {
1171 let caller = Address::ZERO;
1172 let mut db = InMemoryDB::default();
1173 db.insert_account_info(
1174 caller,
1175 AccountInfo {
1176 balance: U256::from(48),
1177 ..Default::default()
1178 },
1179 );
1180 let ctx = Context::op()
1181 .with_db(db)
1182 .with_chain(L1BlockInfo {
1183 l1_base_fee: U256::from(1_000),
1184 l1_fee_overhead: Some(U256::from(1_000)),
1185 l1_base_fee_scalar: U256::from(1_000),
1186 l2_block: Some(U256::from(0)),
1187 ..Default::default()
1188 })
1189 .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH))
1190 .modify_tx_chained(|tx| {
1191 tx.enveloped_tx = Some(bytes!("FACADE"));
1192 });
1193
1194 let mut evm = ctx.build_op();
1196 let handler =
1197 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1198
1199 assert_eq!(
1201 handler.validate_against_state_and_deduct_caller(&mut evm),
1202 Err(EVMError::Transaction(
1203 InvalidTransaction::LackOfFundForMaxFee {
1204 fee: Box::new(U256::from(1048)),
1205 balance: Box::new(U256::from(48)),
1206 }
1207 .into(),
1208 ))
1209 );
1210 }
1211
1212 #[test]
1213 fn test_validate_sys_tx() {
1214 let ctx = Context::op()
1216 .modify_tx_chained(|tx| {
1217 tx.deposit.source_hash = B256::from([1u8; 32]);
1218 tx.deposit.is_system_transaction = true;
1219 })
1220 .with_cfg(CfgEnv::new_with_spec(OpSpecId::REGOLITH));
1221
1222 let mut evm = ctx.build_op();
1223 let handler =
1224 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1225
1226 assert_eq!(
1227 handler.validate_env(&mut evm),
1228 Err(EVMError::Transaction(
1229 OpTransactionError::DepositSystemTxPostRegolith
1230 ))
1231 );
1232
1233 let ctx = evm.into_context();
1235 let mut evm = ctx
1236 .with_cfg(CfgEnv::new_with_spec(OpSpecId::BEDROCK))
1237 .build_op();
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 .with_cfg(CfgEnv::new_with_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 .with_cfg(CfgEnv::new_with_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 .with_cfg(CfgEnv::new_with_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 .with_cfg(CfgEnv::new_with_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}