1use crate::{
3 api::exec::OpContextTr,
4 constants::{BASE_FEE_RECIPIENT, L1_FEE_RECIPIENT, OPERATOR_FEE_RECIPIENT},
5 transaction::{deposit::DEPOSIT_TRANSACTION_TYPE, OpTransactionError, OpTxTr},
6 L1BlockInfo, OpHaltReason, OpSpecId,
7};
8use revm::{
9 context::{journaled_state::JournalCheckpoint, result::InvalidTransaction, LocalContextTr},
10 context_interface::{
11 context::ContextError,
12 result::{EVMError, ExecutionResult, FromStringError},
13 Block, Cfg, ContextTr, JournalTr, Transaction,
14 },
15 handler::{
16 evm::FrameTr,
17 handler::EvmTrError,
18 post_execution::{self, reimburse_caller},
19 pre_execution::{calculate_caller_fee, validate_account_nonce_and_code_with_components},
20 EthFrame, EvmTr, FrameResult, Handler, MainnetHandler,
21 },
22 inspector::{Inspector, InspectorEvmTr, InspectorHandler},
23 interpreter::{interpreter::EthInterpreter, interpreter_action::FrameInit, Gas},
24 primitives::{hardfork::SpecId, U256},
25};
26use std::boxed::Box;
27
28#[derive(Debug, Clone)]
30pub struct OpHandler<EVM, ERROR, FRAME> {
31 pub mainnet: MainnetHandler<EVM, ERROR, FRAME>,
34}
35
36impl<EVM, ERROR, FRAME> OpHandler<EVM, ERROR, FRAME> {
37 pub fn new() -> Self {
39 Self {
40 mainnet: MainnetHandler::default(),
41 }
42 }
43}
44
45impl<EVM, ERROR, FRAME> Default for OpHandler<EVM, ERROR, FRAME> {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51pub trait IsTxError {
55 fn is_tx_error(&self) -> bool;
57}
58
59impl<DB, TX> IsTxError for EVMError<DB, TX> {
60 fn is_tx_error(&self) -> bool {
61 matches!(self, EVMError::Transaction(_))
62 }
63}
64
65impl<EVM, ERROR, FRAME> Handler for OpHandler<EVM, ERROR, FRAME>
66where
67 EVM: EvmTr<Context: OpContextTr, Frame = FRAME>,
68 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
69 FRAME: FrameTr<FrameResult = FrameResult, FrameInit = FrameInit>,
72{
73 type Evm = EVM;
74 type Error = ERROR;
75 type HaltReason = OpHaltReason;
76
77 fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
78 let ctx = evm.ctx();
80 let tx = ctx.tx();
81 let tx_type = tx.tx_type();
82 if tx_type == DEPOSIT_TRANSACTION_TYPE {
83 if tx.is_system_transaction()
85 && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH)
86 {
87 return Err(OpTransactionError::DepositSystemTxPostRegolith.into());
88 }
89 return Ok(());
90 }
91
92 if tx.enveloped_tx().is_none() {
94 return Err(OpTransactionError::MissingEnvelopedTx.into());
95 }
96
97 self.mainnet.validate_env(evm)
98 }
99
100 fn validate_against_state_and_deduct_caller(
101 &self,
102 evm: &mut Self::Evm,
103 ) -> Result<(), Self::Error> {
104 let (block, tx, cfg, journal, chain, _) = evm.ctx().all_mut();
105 let spec = cfg.spec();
106
107 if tx.tx_type() == DEPOSIT_TRANSACTION_TYPE {
108 let basefee = block.basefee() as u128;
109 let blob_price = block.blob_gasprice().unwrap_or_default();
110 let mut caller = journal.load_account_with_code_mut(tx.caller())?.data;
113
114 let effective_balance_spending = tx
115 .effective_balance_spending(basefee, blob_price)
116 .expect("Deposit transaction effective balance spending overflow")
117 - tx.value();
118
119 let mut new_balance = caller
121 .balance()
122 .saturating_add(U256::from(tx.mint().unwrap_or_default()))
123 .saturating_sub(effective_balance_spending);
124
125 if cfg.is_balance_check_disabled() {
126 new_balance = new_balance.max(tx.value());
129 }
130
131 caller.set_balance(new_balance);
133 if tx.kind().is_call() {
134 caller.bump_nonce();
135 }
136
137 return Ok(());
138 }
139
140 if chain.l2_block != Some(block.number()) {
143 *chain = L1BlockInfo::try_fetch(journal.db_mut(), block.number(), spec)?;
144 }
145
146 let mut caller_account = journal.load_account_with_code_mut(tx.caller())?.data;
147
148 validate_account_nonce_and_code_with_components(&caller_account.info, tx, cfg)?;
150
151 let mut balance = caller_account.info.balance;
153
154 if !cfg.is_fee_charge_disabled() {
155 let Some(additional_cost) = chain.tx_cost_with_tx(tx, spec) else {
156 return Err(ERROR::from_string(
157 "[OPTIMISM] Failed to load enveloped transaction.".into(),
158 ));
159 };
160 let Some(new_balance) = balance.checked_sub(additional_cost) else {
161 return Err(InvalidTransaction::LackOfFundForMaxFee {
162 fee: Box::new(additional_cost),
163 balance: Box::new(balance),
164 }
165 .into());
166 };
167 balance = new_balance
168 }
169
170 let balance = calculate_caller_fee(balance, tx, block, cfg)?;
171
172 caller_account.set_balance(balance);
174 if tx.kind().is_call() {
175 caller_account.bump_nonce();
176 }
177
178 Ok(())
179 }
180
181 fn last_frame_result(
182 &mut self,
183 evm: &mut Self::Evm,
184 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
185 ) -> Result<(), Self::Error> {
186 let ctx = evm.ctx();
187 let tx = ctx.tx();
188 let is_deposit = tx.tx_type() == DEPOSIT_TRANSACTION_TYPE;
189 let tx_gas_limit = tx.gas_limit();
190 let is_regolith = ctx.cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
191
192 let instruction_result = frame_result.interpreter_result().result;
193 let gas = frame_result.gas_mut();
194 let remaining = gas.remaining();
195 let refunded = gas.refunded();
196
197 *gas = Gas::new_spent(tx_gas_limit);
199
200 if instruction_result.is_ok() {
201 if !is_deposit || is_regolith {
215 gas.erase_cost(remaining);
218 gas.record_refund(refunded);
219 } else if is_deposit {
220 let tx = ctx.tx();
221 if tx.is_system_transaction() {
222 gas.erase_cost(tx_gas_limit);
225 }
226 }
227 } else if instruction_result.is_revert() {
228 if !is_deposit || is_regolith {
241 gas.erase_cost(remaining);
242 }
243 }
244 Ok(())
245 }
246
247 fn reimburse_caller(
248 &self,
249 evm: &mut Self::Evm,
250 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
251 ) -> Result<(), Self::Error> {
252 let mut additional_refund = U256::ZERO;
253
254 if evm.ctx().tx().tx_type() != DEPOSIT_TRANSACTION_TYPE
255 && !evm.ctx().cfg().is_fee_charge_disabled()
256 {
257 let spec = evm.ctx().cfg().spec();
258 additional_refund = evm
259 .ctx()
260 .chain()
261 .operator_fee_refund(frame_result.gas(), spec);
262 }
263
264 reimburse_caller(evm.ctx(), frame_result.gas(), additional_refund).map_err(From::from)
265 }
266
267 fn refund(
268 &self,
269 evm: &mut Self::Evm,
270 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
271 eip7702_refund: i64,
272 ) {
273 frame_result.gas_mut().record_refund(eip7702_refund);
274
275 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
276 let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
277
278 let is_gas_refund_disabled = is_deposit && !is_regolith;
280 if !is_gas_refund_disabled {
281 frame_result.gas_mut().set_final_refund(
282 evm.ctx()
283 .cfg()
284 .spec()
285 .into_eth_spec()
286 .is_enabled_in(SpecId::LONDON),
287 );
288 }
289 }
290
291 fn reward_beneficiary(
292 &self,
293 evm: &mut Self::Evm,
294 frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
295 ) -> Result<(), Self::Error> {
296 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
297
298 if is_deposit {
300 return Ok(());
301 }
302
303 self.mainnet.reward_beneficiary(evm, frame_result)?;
304 let basefee = evm.ctx().block().basefee() as u128;
305
306 let ctx = evm.ctx();
309 let enveloped = ctx.tx().enveloped_tx().cloned();
310 let spec = ctx.cfg().spec();
311 let l1_block_info = ctx.chain_mut();
312
313 let Some(enveloped_tx) = &enveloped else {
314 return Err(ERROR::from_string(
315 "[OPTIMISM] Failed to load enveloped transaction.".into(),
316 ));
317 };
318
319 let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec);
320 let operator_fee_cost = if spec.is_enabled_in(OpSpecId::ISTHMUS) {
321 l1_block_info.operator_fee_charge(
322 enveloped_tx,
323 U256::from(frame_result.gas().used()),
324 spec,
325 )
326 } else {
327 U256::ZERO
328 };
329 let base_fee_amount = U256::from(basefee.saturating_mul(frame_result.gas().used() as u128));
330
331 for (recipient, amount) in [
333 (L1_FEE_RECIPIENT, l1_cost),
334 (BASE_FEE_RECIPIENT, base_fee_amount),
335 (OPERATOR_FEE_RECIPIENT, operator_fee_cost),
336 ] {
337 ctx.journal_mut().balance_incr(recipient, amount)?;
338 }
339
340 Ok(())
341 }
342
343 fn execution_result(
344 &mut self,
345 evm: &mut Self::Evm,
346 frame_result: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
347 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
348 match core::mem::replace(evm.ctx().error(), Ok(())) {
349 Err(ContextError::Db(e)) => return Err(e.into()),
350 Err(ContextError::Custom(e)) => return Err(Self::Error::from_string(e)),
351 Ok(_) => (),
352 }
353
354 let exec_result =
355 post_execution::output(evm.ctx(), frame_result).map_haltreason(OpHaltReason::Base);
356
357 if exec_result.is_halt() {
358 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
362 if is_deposit && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) {
363 return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith));
364 }
365 }
366 evm.ctx().journal_mut().commit_tx();
367 evm.ctx().chain_mut().clear_tx_l1_cost();
368 evm.ctx().local_mut().clear();
369 evm.frame_stack().clear();
370
371 Ok(exec_result)
372 }
373
374 fn catch_error(
375 &self,
376 evm: &mut Self::Evm,
377 error: Self::Error,
378 ) -> Result<ExecutionResult<Self::HaltReason>, Self::Error> {
379 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
380 let output = if error.is_tx_error() && is_deposit {
381 let ctx = evm.ctx();
382 let spec = ctx.cfg().spec();
383 let tx = ctx.tx();
384 let caller = tx.caller();
385 let mint = tx.mint();
386 let is_system_tx = tx.is_system_transaction();
387 let gas_limit = tx.gas_limit();
388 let journal = evm.ctx().journal_mut();
389
390 journal.checkpoint_revert(JournalCheckpoint::default());
393
394 let mut acc = journal.load_account_mut(caller)?;
404 acc.bump_nonce();
405 acc.incr_balance(U256::from(mint.unwrap_or_default()));
406
407 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 Ok(ExecutionResult::Halt {
421 reason: OpHaltReason::FailedDeposit,
422 gas_used,
423 })
424 } else {
425 Err(error)
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, 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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 .modify_cfg_chained(|cfg| cfg.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 evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK);
1232
1233 assert!(handler.validate_env(&mut evm).is_ok());
1235 }
1236
1237 #[test]
1238 fn test_validate_deposit_tx() {
1239 let ctx = Context::op()
1241 .modify_tx_chained(|tx| {
1242 tx.deposit.source_hash = B256::from([1u8; 32]);
1243 })
1244 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1245
1246 let mut evm = ctx.build_op();
1247 let handler =
1248 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1249
1250 assert!(handler.validate_env(&mut evm).is_ok());
1251 }
1252
1253 #[test]
1254 fn test_validate_tx_against_state_deposit_tx() {
1255 let ctx = Context::op()
1257 .modify_tx_chained(|tx| {
1258 tx.deposit.source_hash = B256::from([1u8; 32]);
1259 })
1260 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1261
1262 let mut evm = ctx.build_op();
1263 let handler =
1264 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1265
1266 assert!(handler.validate_env(&mut evm).is_ok());
1268 }
1269
1270 #[test]
1271 fn test_halted_deposit_tx_post_regolith() {
1272 let ctx = Context::op()
1273 .modify_tx_chained(|tx| {
1274 tx.deposit.source_hash = B256::from([1u8; 32]);
1276 })
1277 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
1278
1279 let mut evm = ctx.build_op();
1280 let mut handler =
1281 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1282
1283 assert_eq!(
1284 handler.execution_result(
1285 &mut evm,
1286 FrameResult::Call(CallOutcome::new(
1287 InterpreterResult {
1288 result: InstructionResult::OutOfGas,
1289 output: Default::default(),
1290 gas: Default::default(),
1291 },
1292 Default::default()
1293 ))
1294 ),
1295 Err(EVMError::Transaction(
1296 OpTransactionError::HaltedDepositPostRegolith
1297 ))
1298 )
1299 }
1300
1301 #[test]
1302 fn test_tx_zero_value_touch_caller() {
1303 let ctx = Context::op();
1304
1305 let mut evm = ctx.build_op();
1306
1307 assert!(!evm
1308 .0
1309 .ctx
1310 .journal_mut()
1311 .load_account(Address::ZERO)
1312 .unwrap()
1313 .is_touched());
1314
1315 let handler =
1316 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1317
1318 handler
1319 .validate_against_state_and_deduct_caller(&mut evm)
1320 .unwrap();
1321
1322 assert!(evm
1323 .0
1324 .ctx
1325 .journal_mut()
1326 .load_account(Address::ZERO)
1327 .unwrap()
1328 .is_touched());
1329 }
1330
1331 #[rstest]
1332 #[case::deposit(true)]
1333 #[case::dyn_fee(false)]
1334 fn test_operator_fee_refund(#[case] is_deposit: bool) {
1335 const SENDER: Address = Address::ZERO;
1336 const GAS_PRICE: u128 = 0xFF;
1337 const OP_FEE_MOCK_PARAM: u128 = 0xFFFF;
1338
1339 let ctx = Context::op()
1340 .with_tx(
1341 OpTransaction::builder()
1342 .base(
1343 TxEnv::builder()
1344 .gas_price(GAS_PRICE)
1345 .gas_priority_fee(None)
1346 .caller(SENDER),
1347 )
1348 .enveloped_tx(if is_deposit {
1349 None
1350 } else {
1351 Some(bytes!("FACADE"))
1352 })
1353 .source_hash(if is_deposit {
1354 B256::from([1u8; 32])
1355 } else {
1356 B256::ZERO
1357 })
1358 .build_fill(),
1359 )
1360 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
1361
1362 let mut evm = ctx.build_op();
1363 let handler =
1364 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1365
1366 evm.ctx().chain.operator_fee_scalar = Some(U256::from(OP_FEE_MOCK_PARAM));
1368 evm.ctx().chain.operator_fee_constant = Some(U256::from(OP_FEE_MOCK_PARAM));
1369
1370 let mut gas = Gas::new(100);
1371 gas.set_spent(10);
1372 let mut exec_result = FrameResult::Call(CallOutcome::new(
1373 InterpreterResult {
1374 result: InstructionResult::Return,
1375 output: Default::default(),
1376 gas,
1377 },
1378 0..0,
1379 ));
1380
1381 handler
1383 .reimburse_caller(&mut evm, &mut exec_result)
1384 .unwrap();
1385
1386 let mut expected_refund =
1389 U256::from(GAS_PRICE * (gas.remaining() + gas.refunded() as u64) as u128);
1390 let op_fee_refund = evm
1391 .ctx()
1392 .chain()
1393 .operator_fee_refund(&gas, OpSpecId::ISTHMUS);
1394 assert!(op_fee_refund > U256::ZERO);
1395
1396 if !is_deposit {
1397 expected_refund += op_fee_refund;
1398 }
1399
1400 let account = evm.ctx().journal_mut().load_account(SENDER).unwrap();
1402 assert_eq!(account.info.balance, expected_refund);
1403 }
1404
1405 #[test]
1406 fn test_tx_low_balance_nonce_unchanged() {
1407 let ctx = Context::op().with_tx(
1408 OpTransaction::builder()
1409 .base(TxEnv::builder().value(U256::from(1000)))
1410 .build_fill(),
1411 );
1412
1413 let mut evm = ctx.build_op();
1414
1415 let handler =
1416 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1417
1418 let result = handler.validate_against_state_and_deduct_caller(&mut evm);
1419
1420 assert!(matches!(
1421 result.err().unwrap(),
1422 EVMError::Transaction(OpTransactionError::Base(
1423 InvalidTransaction::LackOfFundForMaxFee { .. }
1424 ))
1425 ));
1426 assert_eq!(
1427 evm.0
1428 .ctx
1429 .journal_mut()
1430 .load_account(Address::ZERO)
1431 .unwrap()
1432 .info
1433 .nonce,
1434 0
1435 );
1436 }
1437
1438 #[test]
1439 fn test_validate_missing_enveloped_tx() {
1440 use crate::transaction::deposit::DepositTransactionParts;
1441
1442 let ctx = Context::op().with_tx(OpTransaction {
1444 base: TxEnv::builder().build_fill(),
1445 enveloped_tx: None, deposit: DepositTransactionParts::default(), });
1448
1449 let mut evm = ctx.build_op();
1450 let handler =
1451 OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::new();
1452
1453 assert_eq!(
1454 handler.validate_env(&mut evm),
1455 Err(EVMError::Transaction(
1456 OpTransactionError::MissingEnvelopedTx
1457 ))
1458 );
1459 }
1460}