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