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_interface::{
10 result::{EVMError, ExecutionResult, FromStringError, ResultAndState},
11 Block, Cfg, ContextTr, JournalTr, Transaction,
12 },
13 handler::{
14 handler::EvmTrError, validation::validate_tx_against_account, EvmTr, Frame, FrameResult,
15 Handler, MainnetHandler,
16 },
17 inspector::{Inspector, InspectorEvmTr, InspectorFrame, InspectorHandler},
18 interpreter::{interpreter::EthInterpreter, FrameInput, Gas},
19 primitives::hardfork::SpecId,
20 primitives::{HashMap, U256},
21 state::Account,
22 Database,
23};
24
25pub struct OpHandler<EVM, ERROR, FRAME> {
26 pub mainnet: MainnetHandler<EVM, ERROR, FRAME>,
27 pub _phantom: core::marker::PhantomData<(EVM, ERROR, FRAME)>,
28}
29
30impl<EVM, ERROR, FRAME> OpHandler<EVM, ERROR, FRAME> {
31 pub fn new() -> Self {
32 Self {
33 mainnet: MainnetHandler::default(),
34 _phantom: core::marker::PhantomData,
35 }
36 }
37}
38
39impl<EVM, ERROR, FRAME> Default for OpHandler<EVM, ERROR, FRAME> {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45pub trait IsTxError {
46 fn is_tx_error(&self) -> bool;
47}
48
49impl<DB, TX> IsTxError for EVMError<DB, TX> {
50 fn is_tx_error(&self) -> bool {
51 matches!(self, EVMError::Transaction(_))
52 }
53}
54
55impl<EVM, ERROR, FRAME> Handler for OpHandler<EVM, ERROR, FRAME>
56where
57 EVM: EvmTr<Context: OpContextTr>,
58 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
59 FRAME: Frame<Evm = EVM, Error = ERROR, FrameResult = FrameResult, FrameInit = FrameInput>,
62{
63 type Evm = EVM;
64 type Error = ERROR;
65 type Frame = FRAME;
66 type HaltReason = OpHaltReason;
67
68 fn validate_env(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
69 let ctx = evm.ctx();
71 let tx = ctx.tx();
72 let tx_type = tx.tx_type();
73 if tx_type == DEPOSIT_TRANSACTION_TYPE {
74 if tx.is_system_transaction()
76 && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH)
77 {
78 return Err(OpTransactionError::DepositSystemTxPostRegolith.into());
79 }
80 return Ok(());
81 }
82 self.mainnet.validate_env(evm)
83 }
84
85 fn validate_tx_against_state(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
86 let context = evm.ctx();
87 let spec = context.cfg().spec();
88 let block_number = context.block().number();
89 if context.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE {
90 return Ok(());
91 } else {
92 if context.chain().l2_block != block_number {
94 *context.chain() = L1BlockInfo::try_fetch(context.db(), block_number, spec)?;
97 }
98 }
99
100 let enveloped_tx = context
101 .tx()
102 .enveloped_tx()
103 .expect("all not deposit tx have enveloped tx")
104 .clone();
105
106 let mut additional_cost = context.chain().calculate_tx_l1_cost(&enveloped_tx, spec);
108
109 if spec.is_enabled_in(OpSpecId::ISTHMUS) {
110 let gas_limit = U256::from(context.tx().gas_limit());
111 let operator_fee_charge = context
112 .chain()
113 .operator_fee_charge(&enveloped_tx, gas_limit);
114
115 additional_cost = additional_cost.saturating_add(operator_fee_charge);
116 }
117
118 let tx_caller = context.tx().caller();
119
120 let account = context.journal().load_account_code(tx_caller)?;
122 let account = account.data.info.clone();
123
124 validate_tx_against_account(&account, context, additional_cost)?;
125 Ok(())
126 }
127
128 fn deduct_caller(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
129 let ctx = evm.ctx();
130 let spec = ctx.cfg().spec();
131 let caller = ctx.tx().caller();
132 let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
133
134 let mut tx_l1_cost = U256::ZERO;
138 if is_deposit {
139 let tx = ctx.tx();
140 if let Some(mint) = tx.mint() {
141 let mut caller_account = ctx.journal().load_account(caller)?;
142 caller_account.info.balance += U256::from(mint);
143 }
144 } else {
145 let enveloped_tx = ctx
146 .tx()
147 .enveloped_tx()
148 .expect("all not deposit tx have enveloped tx")
149 .clone();
150 tx_l1_cost = ctx.chain().calculate_tx_l1_cost(&enveloped_tx, spec);
151 }
152
153 self.mainnet.deduct_caller(evm)?;
156
157 if !is_deposit {
161 let ctx = evm.ctx();
162
163 let gas_limit = U256::from(ctx.tx().gas_limit());
165 let enveloped_tx = ctx
166 .tx()
167 .enveloped_tx()
168 .expect("all not deposit tx have enveloped tx")
169 .clone();
170
171 let mut operator_fee_charge = U256::ZERO;
172 if spec.is_enabled_in(OpSpecId::ISTHMUS) {
173 operator_fee_charge = ctx.chain().operator_fee_charge(&enveloped_tx, gas_limit);
174 }
175
176 let mut caller_account = ctx.journal().load_account(caller)?;
177 caller_account.info.balance = caller_account
178 .info
179 .balance
180 .saturating_sub(tx_l1_cost.saturating_add(operator_fee_charge));
181 }
182 Ok(())
183 }
184
185 fn last_frame_result(
186 &self,
187 evm: &mut Self::Evm,
188 frame_result: &mut <Self::Frame as Frame>::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 {
224 let tx = ctx.tx();
225 if tx.is_system_transaction() {
226 gas.erase_cost(tx_gas_limit);
229 }
230 }
231 } else if instruction_result.is_revert() {
232 if !is_deposit || is_regolith {
245 gas.erase_cost(remaining);
246 }
247 }
248 Ok(())
249 }
250
251 fn reimburse_caller(
252 &self,
253 evm: &mut Self::Evm,
254 exec_result: &mut <Self::Frame as Frame>::FrameResult,
255 ) -> Result<(), Self::Error> {
256 self.mainnet.reimburse_caller(evm, exec_result)?;
257
258 let context = evm.ctx();
259 if context.tx().tx_type() != DEPOSIT_TRANSACTION_TYPE {
260 let caller = context.tx().caller();
261 let spec = context.cfg().spec();
262 let operator_fee_refund = context.chain().operator_fee_refund(exec_result.gas(), spec);
263
264 let caller_account = context.journal().load_account(caller)?;
265
266 caller_account.data.info.balance = caller_account
269 .data
270 .info
271 .balance
272 .saturating_add(operator_fee_refund);
273 }
274
275 Ok(())
276 }
277
278 fn refund(
279 &self,
280 evm: &mut Self::Evm,
281 exec_result: &mut <Self::Frame as Frame>::FrameResult,
282 eip7702_refund: i64,
283 ) {
284 exec_result.gas_mut().record_refund(eip7702_refund);
285
286 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
287 let is_regolith = evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH);
288
289 let is_gas_refund_disabled = is_deposit && !is_regolith;
291 if !is_gas_refund_disabled {
292 exec_result.gas_mut().set_final_refund(
293 evm.ctx()
294 .cfg()
295 .spec()
296 .into_eth_spec()
297 .is_enabled_in(SpecId::LONDON),
298 );
299 }
300 }
301
302 fn reward_beneficiary(
303 &self,
304 evm: &mut Self::Evm,
305 exec_result: &mut <Self::Frame as Frame>::FrameResult,
306 ) -> Result<(), Self::Error> {
307 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
308
309 if !is_deposit {
311 self.mainnet.reward_beneficiary(evm, exec_result)?;
312 let basefee = evm.ctx().block().basefee() as u128;
313
314 let ctx = evm.ctx();
317 let enveloped = ctx.tx().enveloped_tx().cloned();
318 let spec = ctx.cfg().spec();
319 let l1_block_info = ctx.chain();
320
321 let Some(enveloped_tx) = &enveloped else {
322 return Err(ERROR::from_string(
323 "[OPTIMISM] Failed to load enveloped transaction.".into(),
324 ));
325 };
326
327 let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec);
328 let mut operator_fee_cost = U256::ZERO;
329 if spec.is_enabled_in(OpSpecId::ISTHMUS) {
330 operator_fee_cost = l1_block_info.operator_fee_charge(
331 enveloped_tx,
332 U256::from(exec_result.gas().spent() - exec_result.gas().refunded() as u64),
333 );
334 }
335 let mut l1_fee_vault_account = ctx.journal().load_account(L1_FEE_RECIPIENT)?;
337 l1_fee_vault_account.mark_touch();
338 l1_fee_vault_account.info.balance += l1_cost;
339
340 let mut base_fee_vault_account =
342 evm.ctx().journal().load_account(BASE_FEE_RECIPIENT)?;
343 base_fee_vault_account.mark_touch();
344 base_fee_vault_account.info.balance += U256::from(basefee.saturating_mul(
345 (exec_result.gas().spent() - exec_result.gas().refunded() as u64) as u128,
346 ));
347
348 let mut operator_fee_vault_account =
350 evm.ctx().journal().load_account(OPERATOR_FEE_RECIPIENT)?;
351 operator_fee_vault_account.mark_touch();
352 operator_fee_vault_account.data.info.balance += operator_fee_cost;
353 }
354 Ok(())
355 }
356
357 fn output(
358 &self,
359 evm: &mut Self::Evm,
360 result: <Self::Frame as Frame>::FrameResult,
361 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
362 let result = self.mainnet.output(evm, result)?;
363 let result = result.map_haltreason(OpHaltReason::Base);
364 if result.result.is_halt() {
365 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
369 if is_deposit && evm.ctx().cfg().spec().is_enabled_in(OpSpecId::REGOLITH) {
370 return Err(ERROR::from(OpTransactionError::HaltedDepositPostRegolith));
371 }
372 }
373 evm.ctx().chain().clear_tx_l1_cost();
374 Ok(result)
375 }
376
377 fn catch_error(
378 &self,
379 evm: &mut Self::Evm,
380 error: Self::Error,
381 ) -> Result<ResultAndState<Self::HaltReason>, Self::Error> {
382 let is_deposit = evm.ctx().tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
383 let output = if error.is_tx_error() && is_deposit {
384 let ctx = evm.ctx();
385 let spec = ctx.cfg().spec();
386 let tx = ctx.tx();
387 let caller = tx.caller();
388 let mint = tx.mint();
389 let is_system_tx = tx.is_system_transaction();
390 let gas_limit = tx.gas_limit();
391 let account = {
401 let mut acc = Account::from(
402 evm.ctx()
403 .db()
404 .basic(caller)
405 .unwrap_or_default()
406 .unwrap_or_default(),
407 );
408 acc.info.nonce = acc.info.nonce.saturating_add(1);
409 acc.info.balance = acc
410 .info
411 .balance
412 .saturating_add(U256::from(mint.unwrap_or_default()));
413 acc.mark_touch();
414 acc
415 };
416 let state = HashMap::from_iter([(caller, account)]);
417
418 let gas_used = if spec.is_enabled_in(OpSpecId::REGOLITH) || !is_system_tx {
423 gas_limit
424 } else {
425 0
426 };
427 Ok(ResultAndState {
429 result: ExecutionResult::Halt {
430 reason: OpHaltReason::FailedDeposit,
431 gas_used,
432 },
433 state,
434 })
435 } else {
436 Err(error)
437 };
438 evm.ctx().chain().clear_tx_l1_cost();
440 evm.ctx().journal().clear();
441
442 output
443 }
444}
445
446impl<EVM, ERROR, FRAME> InspectorHandler for OpHandler<EVM, ERROR, FRAME>
447where
448 EVM: InspectorEvmTr<
449 Context: OpContextTr,
450 Inspector: Inspector<<<Self as Handler>::Evm as EvmTr>::Context, EthInterpreter>,
451 >,
452 ERROR: EvmTrError<EVM> + From<OpTransactionError> + FromStringError + IsTxError,
453 FRAME: InspectorFrame<
456 Evm = EVM,
457 Error = ERROR,
458 FrameResult = FrameResult,
459 FrameInit = FrameInput,
460 IT = EthInterpreter,
461 >,
462{
463 type IT = EthInterpreter;
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469 use crate::{api::default_ctx::OpContext, DefaultOp, OpBuilder};
470 use revm::{
471 context::{Context, TransactionType},
472 context_interface::result::InvalidTransaction,
473 database::InMemoryDB,
474 database_interface::EmptyDB,
475 handler::EthFrame,
476 interpreter::{CallOutcome, InstructionResult, InterpreterResult},
477 primitives::{bytes, Address, Bytes, B256},
478 state::AccountInfo,
479 };
480 use rstest::rstest;
481 use std::boxed::Box;
482
483 fn call_last_frame_return(
485 ctx: OpContext<EmptyDB>,
486 instruction_result: InstructionResult,
487 gas: Gas,
488 ) -> Gas {
489 let mut evm = ctx.build_op();
490
491 let mut exec_result = FrameResult::Call(CallOutcome::new(
492 InterpreterResult {
493 result: instruction_result,
494 output: Bytes::new(),
495 gas,
496 },
497 0..0,
498 ));
499
500 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
501
502 handler
503 .last_frame_result(&mut evm, &mut exec_result)
504 .unwrap();
505 handler.refund(&mut evm, &mut exec_result, 0);
506 *exec_result.gas()
507 }
508
509 #[test]
510 fn test_revert_gas() {
511 let ctx = Context::op()
512 .modify_tx_chained(|tx| {
513 tx.base.gas_limit = 100;
514 tx.enveloped_tx = None;
515 })
516 .modify_cfg_chained(|cfg| cfg.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.spent(), 10);
521 assert_eq!(gas.refunded(), 0);
522 }
523
524 #[test]
525 fn test_consume_gas() {
526 let ctx = Context::op()
527 .modify_tx_chained(|tx| {
528 tx.base.gas_limit = 100;
529 tx.deposit.source_hash = B256::ZERO;
530 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
531 })
532 .modify_cfg_chained(|cfg| cfg.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.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 .modify_tx_chained(|tx| {
544 tx.base.gas_limit = 100;
545 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
546 tx.deposit.source_hash = B256::ZERO;
547 })
548 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
549
550 let mut ret_gas = Gas::new(90);
551 ret_gas.record_refund(20);
552
553 let gas = call_last_frame_return(ctx.clone(), InstructionResult::Stop, ret_gas);
554 assert_eq!(gas.remaining(), 90);
555 assert_eq!(gas.spent(), 10);
556 assert_eq!(gas.refunded(), 2); let gas = call_last_frame_return(ctx, InstructionResult::Revert, ret_gas);
559 assert_eq!(gas.remaining(), 90);
560 assert_eq!(gas.spent(), 10);
561 assert_eq!(gas.refunded(), 0);
562 }
563
564 #[test]
565 fn test_consume_gas_deposit_tx() {
566 let ctx = Context::op()
567 .modify_tx_chained(|tx| {
568 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
569 tx.base.gas_limit = 100;
570 tx.deposit.source_hash = B256::ZERO;
571 })
572 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
573 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
574 assert_eq!(gas.remaining(), 0);
575 assert_eq!(gas.spent(), 100);
576 assert_eq!(gas.refunded(), 0);
577 }
578
579 #[test]
580 fn test_consume_gas_sys_deposit_tx() {
581 let ctx = Context::op()
582 .modify_tx_chained(|tx| {
583 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
584 tx.base.gas_limit = 100;
585 tx.deposit.source_hash = B256::ZERO;
586 tx.deposit.is_system_transaction = true;
587 })
588 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::BEDROCK);
589 let gas = call_last_frame_return(ctx, InstructionResult::Stop, Gas::new(90));
590 assert_eq!(gas.remaining(), 100);
591 assert_eq!(gas.spent(), 0);
592 assert_eq!(gas.refunded(), 0);
593 }
594
595 #[test]
596 fn test_commit_mint_value() {
597 let caller = Address::ZERO;
598 let mut db = InMemoryDB::default();
599 db.insert_account_info(
600 caller,
601 AccountInfo {
602 balance: U256::from(1000),
603 ..Default::default()
604 },
605 );
606
607 let mut ctx = Context::op()
608 .with_db(db)
609 .with_chain(L1BlockInfo {
610 l1_base_fee: U256::from(1_000),
611 l1_fee_overhead: Some(U256::from(1_000)),
612 l1_base_fee_scalar: U256::from(1_000),
613 ..Default::default()
614 })
615 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
616 ctx.modify_tx(|tx| {
617 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
618 tx.deposit.source_hash = B256::ZERO;
619 tx.deposit.mint = Some(10);
620 });
621
622 let mut evm = ctx.build_op();
623
624 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
625 handler.deduct_caller(&mut evm).unwrap();
626
627 let account = evm.ctx().journal().load_account(caller).unwrap();
629 assert_eq!(account.info.balance, U256::from(1010));
630 }
631
632 #[test]
633 fn test_remove_l1_cost_non_deposit() {
634 let caller = Address::ZERO;
635 let mut db = InMemoryDB::default();
636 db.insert_account_info(
637 caller,
638 AccountInfo {
639 balance: U256::from(1000),
640 ..Default::default()
641 },
642 );
643 let ctx = Context::op()
644 .with_db(db)
645 .with_chain(L1BlockInfo {
646 l1_base_fee: U256::from(1_000),
647 l1_fee_overhead: Some(U256::from(1_000)),
648 l1_base_fee_scalar: U256::from(1_000),
649 ..Default::default()
650 })
651 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
652 .modify_tx_chained(|tx| {
653 tx.base.gas_limit = 100;
654 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
655 tx.deposit.mint = Some(10);
656 tx.enveloped_tx = Some(bytes!("FACADE"));
657 tx.deposit.source_hash = B256::ZERO;
658 });
659
660 let mut evm = ctx.build_op();
661
662 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
663 handler.deduct_caller(&mut evm).unwrap();
664
665 let account = evm.ctx().journal().load_account(caller).unwrap();
667 assert_eq!(account.info.balance, U256::from(1010));
668 }
669
670 #[test]
671 fn test_remove_l1_cost() {
672 let caller = Address::ZERO;
673 let mut db = InMemoryDB::default();
674 db.insert_account_info(
675 caller,
676 AccountInfo {
677 balance: U256::from(1049),
678 ..Default::default()
679 },
680 );
681 let ctx = Context::op()
682 .with_db(db)
683 .with_chain(L1BlockInfo {
684 l1_base_fee: U256::from(1_000),
685 l1_fee_overhead: Some(U256::from(1_000)),
686 l1_base_fee_scalar: U256::from(1_000),
687 ..Default::default()
688 })
689 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
690 .modify_tx_chained(|tx| {
691 tx.base.gas_limit = 100;
692 tx.deposit.source_hash = B256::ZERO;
693 tx.enveloped_tx = Some(bytes!("FACADE"));
694 });
695
696 let mut evm = ctx.build_op();
697 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
698
699 handler.deduct_caller(&mut evm).unwrap();
701
702 let account = evm.ctx().journal().load_account(caller).unwrap();
704 assert_eq!(account.info.balance, U256::from(1));
705 }
706
707 #[test]
708 fn test_remove_operator_cost() {
709 let caller = Address::ZERO;
710 let mut db = InMemoryDB::default();
711 db.insert_account_info(
712 caller,
713 AccountInfo {
714 balance: U256::from(151),
715 ..Default::default()
716 },
717 );
718 let ctx = Context::op()
719 .with_db(db)
720 .with_chain(L1BlockInfo {
721 operator_fee_scalar: Some(U256::from(10_000_000)),
722 operator_fee_constant: Some(U256::from(50)),
723 ..Default::default()
724 })
725 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS)
726 .modify_tx_chained(|tx| {
727 tx.base.gas_limit = 10;
728 tx.enveloped_tx = Some(bytes!("FACADE"));
729 });
730
731 let mut evm = ctx.build_op();
732 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
733
734 handler.deduct_caller(&mut evm).unwrap();
737
738 let account = evm.ctx().journal().load_account(caller).unwrap();
740 assert_eq!(account.info.balance, U256::from(1));
741 }
742
743 #[test]
744 fn test_remove_l1_cost_lack_of_funds() {
745 let caller = Address::ZERO;
746 let mut db = InMemoryDB::default();
747 db.insert_account_info(
748 caller,
749 AccountInfo {
750 balance: U256::from(48),
751 ..Default::default()
752 },
753 );
754 let ctx = Context::op()
755 .with_db(db)
756 .with_chain(L1BlockInfo {
757 l1_base_fee: U256::from(1_000),
758 l1_fee_overhead: Some(U256::from(1_000)),
759 l1_base_fee_scalar: U256::from(1_000),
760 ..Default::default()
761 })
762 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH)
763 .modify_tx_chained(|tx| {
764 tx.enveloped_tx = Some(bytes!("FACADE"));
765 });
766
767 let mut evm = ctx.build_op();
769 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
770
771 assert_eq!(
773 handler.validate_tx_against_state(&mut evm),
774 Err(EVMError::Transaction(
775 InvalidTransaction::LackOfFundForMaxFee {
776 fee: Box::new(U256::from(1048)),
777 balance: Box::new(U256::from(48)),
778 }
779 .into(),
780 ))
781 );
782 }
783
784 #[test]
785 fn test_validate_sys_tx() {
786 let ctx = Context::op()
788 .modify_tx_chained(|tx| {
789 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
790 tx.deposit.is_system_transaction = true;
791 })
792 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
793
794 let mut evm = ctx.build_op();
795 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
796
797 assert_eq!(
798 handler.validate_env(&mut evm),
799 Err(EVMError::Transaction(
800 OpTransactionError::DepositSystemTxPostRegolith
801 ))
802 );
803
804 evm.ctx().modify_cfg(|cfg| cfg.spec = OpSpecId::BEDROCK);
805
806 assert!(handler.validate_env(&mut evm).is_ok());
808 }
809
810 #[test]
811 fn test_validate_deposit_tx() {
812 let ctx = Context::op()
814 .modify_tx_chained(|tx| {
815 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
816 tx.deposit.source_hash = B256::ZERO;
817 })
818 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
819
820 let mut evm = ctx.build_op();
821 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
822
823 assert!(handler.validate_env(&mut evm).is_ok());
824 }
825
826 #[test]
827 fn test_validate_tx_against_state_deposit_tx() {
828 let ctx = Context::op()
830 .modify_tx_chained(|tx| {
831 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
832 tx.deposit.source_hash = B256::ZERO;
833 })
834 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
835
836 let mut evm = ctx.build_op();
837 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
838
839 assert!(handler.validate_env(&mut evm).is_ok());
841 }
842
843 #[test]
844 fn test_halted_deposit_tx_post_regolith() {
845 let ctx = Context::op()
846 .modify_tx_chained(|tx| {
847 tx.base.tx_type = DEPOSIT_TRANSACTION_TYPE;
848 })
849 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::REGOLITH);
850
851 let mut evm = ctx.build_op();
852 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
853
854 assert_eq!(
855 handler.output(
856 &mut evm,
857 FrameResult::Call(CallOutcome {
858 result: InterpreterResult {
859 result: InstructionResult::OutOfGas,
860 output: Default::default(),
861 gas: Default::default(),
862 },
863 memory_offset: Default::default(),
864 })
865 ),
866 Err(EVMError::Transaction(
867 OpTransactionError::HaltedDepositPostRegolith
868 ))
869 )
870 }
871
872 #[rstest]
873 #[case::deposit(true)]
874 #[case::dyn_fee(false)]
875 fn test_operator_fee_refund(#[case] is_deposit: bool) {
876 const SENDER: Address = Address::ZERO;
877 const GAS_PRICE: u128 = 0xFF;
878 const OP_FEE_MOCK_PARAM: u128 = 0xFFFF;
879
880 let ctx = Context::op()
881 .modify_tx_chained(|tx| {
882 tx.base.tx_type = if is_deposit {
883 DEPOSIT_TRANSACTION_TYPE
884 } else {
885 TransactionType::Eip1559 as u8
886 };
887 tx.base.gas_price = GAS_PRICE;
888 tx.base.gas_priority_fee = None;
889 tx.base.caller = SENDER;
890 })
891 .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS);
892
893 let mut evm = ctx.build_op();
894 let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new();
895
896 evm.ctx().chain.operator_fee_scalar = Some(U256::from(OP_FEE_MOCK_PARAM));
898 evm.ctx().chain.operator_fee_constant = Some(U256::from(OP_FEE_MOCK_PARAM));
899
900 let mut gas = Gas::new(100);
901 gas.set_spent(10);
902 let mut exec_result = FrameResult::Call(CallOutcome::new(
903 InterpreterResult {
904 result: InstructionResult::Return,
905 output: Default::default(),
906 gas,
907 },
908 0..0,
909 ));
910
911 handler
913 .reimburse_caller(&mut evm, &mut exec_result)
914 .unwrap();
915
916 let mut expected_refund =
919 U256::from(GAS_PRICE * (gas.remaining() + gas.refunded() as u64) as u128);
920 let op_fee_refund = evm
921 .ctx()
922 .chain()
923 .operator_fee_refund(&gas, OpSpecId::ISTHMUS);
924 assert!(op_fee_refund > U256::ZERO);
925
926 if !is_deposit {
927 expected_refund += op_fee_refund;
928 }
929
930 let account = evm.ctx().journal().load_account(SENDER).unwrap();
932 assert_eq!(account.info.balance, expected_refund);
933 }
934}