Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions crates/handler/src/pre_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ pub fn load_accounts<
Ok(())
}

/// Validates caller account nonce and code according to EIP-3607.
#[inline]
pub fn validate_account_nonce_and_code_with_components(
caller_info: &mut AccountInfo,
tx: impl Transaction,
cfg: impl Cfg,
) -> Result<(), InvalidTransaction> {
validate_account_nonce_and_code(
caller_info,
tx.nonce(),
cfg.is_eip3607_disabled(),
cfg.is_nonce_check_disabled(),
)
}

/// Validates caller account nonce and code according to EIP-3607.
#[inline]
pub fn validate_account_nonce_and_code(
Expand Down Expand Up @@ -153,12 +168,7 @@ pub fn validate_against_state_and_deduct_caller<
// Load caller's account.
let caller_account = journal.load_account_code(tx.caller())?.data;

validate_account_nonce_and_code(
&mut caller_account.info,
tx.nonce(),
cfg.is_eip3607_disabled(),
cfg.is_nonce_check_disabled(),
)?;
validate_account_nonce_and_code_with_components(&mut caller_account.info, tx, cfg)?;

let new_balance = calculate_caller_fee(caller_account.info.balance, tx, block, cfg)?;

Expand Down
97 changes: 18 additions & 79 deletions crates/op-revm/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use revm::{
evm::FrameTr,
handler::EvmTrError,
post_execution::{self, reimburse_caller},
pre_execution::validate_account_nonce_and_code,
pre_execution::{calculate_caller_fee, validate_account_nonce_and_code_with_components},
EthFrame, EvmTr, FrameResult, Handler, MainnetHandler,
},
inspector::{Inspector, InspectorEvmTr, InspectorHandler},
Expand Down Expand Up @@ -101,19 +101,10 @@ where
&self,
evm: &mut Self::Evm,
) -> Result<(), Self::Error> {
let ctx = evm.ctx();

let basefee = ctx.block().basefee() as u128;
let blob_price = ctx.block().blob_gasprice().unwrap_or_default();
let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE;
let spec = ctx.cfg().spec();
let block_number = ctx.block().number();
let is_balance_check_disabled = ctx.cfg().is_balance_check_disabled();
let is_eip3607_disabled = ctx.cfg().is_eip3607_disabled();
let is_nonce_check_disabled = ctx.cfg().is_nonce_check_disabled();
let (block, tx, cfg, journal, chain, _) = evm.ctx().all_mut();
let spec = cfg.spec();

if is_deposit {
let (block, tx, cfg, journal, _, _) = evm.ctx().all_mut();
if tx.tx_type() == DEPOSIT_TRANSACTION_TYPE {
let basefee = block.basefee() as u128;
let blob_price = block.blob_gasprice().unwrap_or_default();
// deposit skips max fee check and just deducts the effective balance spending.
Expand Down Expand Up @@ -148,87 +139,35 @@ where
return Ok(());
}

let mut additional_cost = U256::ZERO;

// L1 block info is stored in the context for later use.
// and it will be reloaded from the database if it is not for the current block.
if ctx.chain().l2_block != Some(block_number) {
*ctx.chain_mut() = L1BlockInfo::try_fetch(ctx.db_mut(), block_number, spec)?;
if chain.l2_block != Some(block.number()) {
*chain = L1BlockInfo::try_fetch(journal.db_mut(), block.number(), spec)?;
}

if !ctx.cfg().is_fee_charge_disabled() {
// account for additional cost of l1 fee and operator fee
// account for additional cost of l1 fee and operator fee
let Some(enveloped_tx) = ctx.tx().enveloped_tx().cloned() else {
return Err(ERROR::from_string(
"[OPTIMISM] Failed to load enveloped transaction.".into(),
));
};

// compute L1 cost
additional_cost = ctx.chain_mut().calculate_tx_l1_cost(&enveloped_tx, spec);

// compute operator fee
if spec.is_enabled_in(OpSpecId::ISTHMUS) {
let gas_limit = U256::from(ctx.tx().gas_limit());
let operator_fee_charge =
ctx.chain()
.operator_fee_charge(&enveloped_tx, gas_limit, spec);
additional_cost = additional_cost.saturating_add(operator_fee_charge);
}
}

let (tx, journal) = ctx.tx_journal_mut();

let caller_account = journal.load_account_code(tx.caller())?.data;

// validates account nonce and code
validate_account_nonce_and_code(
&mut caller_account.info,
tx.nonce(),
is_eip3607_disabled,
is_nonce_check_disabled,
)?;

let mut new_balance = caller_account.info.balance;

// Check if account has enough balance for `gas_limit * max_fee`` and value transfer.
// Transfer will be done inside `*_inner` functions.
if !is_balance_check_disabled {
// check additional cost and deduct it from the caller's balances
let Some(balance) = new_balance.checked_sub(additional_cost) else {
validate_account_nonce_and_code_with_components(&mut caller_account.info, tx, cfg)?;

// check additional cost and deduct it from the caller's balances
let mut balance = caller_account.info.balance;

if !cfg.is_fee_charge_disabled() {
let additional_cost = chain.tx_cost_with_tx(tx, spec);
let Some(new_balance) = balance.checked_sub(additional_cost) else {
return Err(InvalidTransaction::LackOfFundForMaxFee {
fee: Box::new(additional_cost),
balance: Box::new(new_balance),
balance: Box::new(balance),
}
.into());
};
tx.ensure_enough_balance(balance)?;
}

// subtracting max balance spending with value that is going to be deducted later in the call.
let gas_balance_spending = tx
.gas_balance_spending(basefee, blob_price)
.expect("effective balance is always smaller than max balance so it can't overflow");

// If the transaction is not a deposit transaction, subtract the L1 data fee from the
// caller's balance directly after minting the requested amount of ETH.
// Additionally deduct the operator fee from the caller's account.
//
// In case of deposit additional cost will be zero.
let op_gas_balance_spending = gas_balance_spending.saturating_add(additional_cost);

new_balance = new_balance.saturating_sub(op_gas_balance_spending);

if is_balance_check_disabled {
// Make sure the caller's balance is at least the value of the transaction.
// this is not consensus critical, and it is used in testing.
new_balance = new_balance.max(tx.value());
balance = new_balance
}

let old_balance =
caller_account.caller_initial_modification(new_balance, tx.kind().is_call());
let balance = calculate_caller_fee(balance, tx, block, cfg)?;

let old_balance = caller_account.caller_initial_modification(balance, tx.kind().is_call());
// NOTE: all changes to the caller account should journaled so in case of error
// we can revert the changes.
journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call());
Expand Down
31 changes: 30 additions & 1 deletion crates/op-revm/src/l1block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET, OPERATOR_FEE_JOVIAN_MULTIPLIER,
OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET,
},
transaction::estimate_tx_compressed_size,
transaction::{estimate_tx_compressed_size, OpTxTr},
OpSpecId,
};
use revm::{
Expand Down Expand Up @@ -256,6 +256,35 @@ impl L1BlockInfo {
self.tx_l1_cost = None;
}

/// Calculate additional transaction cost with OpTxTr.
///
/// Internally calls [`L1BlockInfo::tx_cost`].
#[track_caller]
pub fn tx_cost_with_tx(&mut self, tx: impl OpTxTr, spec: OpSpecId) -> U256 {
// account for additional cost of l1 fee and operator fee
let enveloped_tx = tx
.enveloped_tx()
.expect("all not deposit tx have enveloped tx")
.clone();
let gas_limit = U256::from(tx.gas_limit());
self.tx_cost(&enveloped_tx, gas_limit, spec)
}

/// Calculate additional transaction cost.
#[inline]
pub fn tx_cost(&mut self, enveloped_tx: &[u8], gas_limit: U256, spec: OpSpecId) -> U256 {
// compute L1 cost
let mut additional_cost = self.calculate_tx_l1_cost(enveloped_tx, spec);

// compute operator fee
if spec.is_enabled_in(OpSpecId::ISTHMUS) {
let operator_fee_charge = self.operator_fee_charge(enveloped_tx, gas_limit, spec);
additional_cost = additional_cost.saturating_add(operator_fee_charge);
}

additional_cost
}

/// Calculate the gas cost of a transaction based on L1 block data posted on L2, depending on the [OpSpecId] passed.
pub fn calculate_tx_l1_cost(&mut self, input: &[u8], spec_id: OpSpecId) -> U256 {
if let Some(tx_l1_cost) = self.tx_l1_cost {
Expand Down
9 changes: 2 additions & 7 deletions examples/erc20_gas/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use revm::{
context::Cfg,
context_interface::{result::HaltReason, Block, ContextTr, JournalTr, Transaction},
handler::{
pre_execution::{calculate_caller_fee, validate_account_nonce_and_code},
pre_execution::{calculate_caller_fee, validate_account_nonce_and_code_with_components},
EvmTr, EvmTrError, FrameResult, FrameTr, Handler,
},
interpreter::interpreter_action::FrameInit,
Expand Down Expand Up @@ -54,12 +54,7 @@ where
// Load caller's account.
let caller_account = journal.load_account_code(tx.caller())?.data;

validate_account_nonce_and_code(
&mut caller_account.info,
tx.nonce(),
cfg.is_eip3607_disabled(),
cfg.is_nonce_check_disabled(),
)?;
validate_account_nonce_and_code_with_components(&mut caller_account.info, tx, cfg)?;

// make changes to the account. Account balance stays the same
caller_account
Expand Down
Loading