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