Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
20 changes: 18 additions & 2 deletions crates/context/interface/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,28 @@ pub trait ContextTr: Host {
}
/// Get the error
fn error(&mut self) -> &mut Result<(), ContextError<<Self::Db as Database>::Error>>;

/// Get the transaction and journal. It is used to efficiently load access list
/// into journal without copying them from transaction.
fn tx_journal_mut(&mut self) -> (&Self::Tx, &mut Self::Journal);
fn tx_journal_mut(&mut self) -> (&Self::Tx, &mut Self::Journal) {
let (_, tx, _, journal, _, _) = self.all_mut();
(tx, journal)
}

/// Get the transaction, configuration and mutable journal.
fn tx_block_cfg_journal_mut(
&mut self,
) -> (&Self::Tx, &Self::Block, &Self::Cfg, &mut Self::Journal) {
let (block, tx, cfg, journal, _, _) = self.all_mut();
(tx, block, cfg, journal)
}

/// Get the transaction and local context. It is used to efficiently load initcode
/// into local context without copying them from transaction.
fn tx_local_mut(&mut self) -> (&Self::Tx, &mut Self::Local);
fn tx_local_mut(&mut self) -> (&Self::Tx, &mut Self::Local) {
let (_, tx, _, _, _, local) = self.all_mut();
(tx, local)
}
}

/// Inner Context error used for Interpreter to set error without returning it from instruction
Expand Down
29 changes: 29 additions & 0 deletions crates/context/interface/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use auto_impl::auto_impl;
use core::cmp::min;
use core::fmt::Debug;
use primitives::{eip4844::GAS_PER_BLOB, Address, Bytes, TxKind, B256, U256};
use std::boxed::Box;

/// Transaction validity error types.
pub trait TransactionError: Debug + core::error::Error {}
Expand Down Expand Up @@ -179,6 +180,21 @@ pub trait Transaction {
Ok(max_balance_spending)
}

/// Checks if the caller has enough balance to cover the maximum balance spending of this transaction.
///
/// Internally calls [`Self::max_balance_spending`] and checks if the balance is enough.
#[inline]
fn has_enough_balance(&self, balance: U256) -> Result<(), InvalidTransaction> {
let max_balance_spending = self.max_balance_spending()?;
if max_balance_spending > balance {
return Err(InvalidTransaction::LackOfFundForMaxFee {
fee: Box::new(max_balance_spending),
balance: Box::new(balance),
});
}
Ok(())
}

/// Returns the effective balance that is going to be spent that depends on base_fee
/// Multiplication for gas are done in u128 type (saturated) and value is added as U256 type.
///
Expand All @@ -189,6 +205,7 @@ pub trait Transaction {
/// This is always strictly less than [`Self::max_balance_spending`].
///
/// Return U256 or error if all values overflow U256 number.
#[inline]
fn effective_balance_spending(
&self,
base_fee: u128,
Expand All @@ -210,4 +227,16 @@ pub trait Transaction {

Ok(effective_balance_spending)
}

/// Returns the effective balance calculated with [`Self::effective_balance_spending`] but without the value.
#[inline]
fn effective_balance_spending_without_value(
&self,
base_fee: u128,
blob_price: u128,
) -> Result<U256, InvalidTransaction> {
Ok(self
.effective_balance_spending(base_fee, blob_price)?
.saturating_sub(self.value()))
}
}
10 changes: 0 additions & 10 deletions crates/context/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,6 @@ impl<
fn error(&mut self) -> &mut Result<(), ContextError<<Self::Db as Database>::Error>> {
&mut self.error
}

#[inline]
fn tx_journal_mut(&mut self) -> (&Self::Tx, &mut Self::Journal) {
(&self.tx, &mut self.journaled_state)
}

#[inline]
fn tx_local_mut(&mut self) -> (&Self::Tx, &mut Self::Local) {
(&self.tx, &mut self.local)
}
}

impl<
Expand Down
122 changes: 76 additions & 46 deletions crates/handler/src/pre_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ use context_interface::{
use core::cmp::Ordering;
use primitives::StorageKey;
use primitives::{eip7702, hardfork::SpecId, KECCAK_EMPTY, U256};
use state::AccountInfo;
use std::boxed::Box;
use state::{Account, AccountInfo};

/// Loads and warms accounts for execution, including precompiles and access list.
pub fn load_accounts<
Expand Down Expand Up @@ -66,6 +65,26 @@ pub fn load_accounts<
Ok(())
}

/// Validates caller account nonce and code according to EIP-3607.
///
/// Internally calls [`validate_account_nonce_and_code`] with the components from the configuration.
#[inline]
pub fn validate_account_nonce_and_code_with_components(
caller_info: &mut AccountInfo,
tx: impl Transaction,
cfg: impl Cfg,
) -> Result<(), InvalidTransaction> {
let is_eip3607_disabled = cfg.is_eip3607_disabled();
let is_nonce_check_disabled = cfg.is_nonce_check_disabled();

validate_account_nonce_and_code(
caller_info,
tx.nonce(),
is_eip3607_disabled,
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 @@ -106,73 +125,84 @@ pub fn validate_account_nonce_and_code(
Ok(())
}

/// Validates caller state and deducts transaction costs from the caller's balance.
/// Check maximum possible fee and deduct the effective fee. Returns new balance.
#[inline]
pub fn validate_against_state_and_deduct_caller<
CTX: ContextTr,
ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
>(
context: &mut CTX,
) -> Result<(), ERROR> {
let basefee = context.block().basefee() as u128;
let blob_price = context.block().blob_gasprice().unwrap_or_default();
let is_balance_check_disabled = context.cfg().is_balance_check_disabled();
let is_eip3607_disabled = context.cfg().is_eip3607_disabled();
let is_nonce_check_disabled = context.cfg().is_nonce_check_disabled();

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

// 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(),
is_eip3607_disabled,
is_nonce_check_disabled,
)?;

let max_balance_spending = tx.max_balance_spending()?;

// Check if account has enough balance for `gas_limit * max_fee`` and value transfer.
// Transfer will be done inside `*_inner` functions.
if max_balance_spending > caller_account.info.balance && !is_balance_check_disabled {
return Err(InvalidTransaction::LackOfFundForMaxFee {
fee: Box::new(max_balance_spending),
balance: Box::new(caller_account.info.balance),
}
.into());
pub fn deduct_caller_balance_with_components(
balance: U256,
tx: impl Transaction,
block: impl Block,
cfg: impl Cfg,
) -> Result<U256, InvalidTransaction> {
let basefee = block.basefee() as u128;
let blob_price = block.blob_gasprice().unwrap_or_default();
let is_balance_check_disabled = cfg.is_balance_check_disabled();

if !is_balance_check_disabled {
tx.has_enough_balance(balance)?;
}

let effective_balance_spending = tx
.effective_balance_spending(basefee, blob_price)
.expect("effective balance is always smaller than max balance so it can't overflow");

// subtracting max balance spending with value that is going to be deducted later in the call.
let gas_balance_spending = effective_balance_spending - tx.value();

let mut new_balance = caller_account
.info
.balance
.saturating_sub(gas_balance_spending);
// new balance
let mut new_balance = balance.saturating_sub(gas_balance_spending);

if is_balance_check_disabled {
// Make sure the caller's balance is at least the value of the transaction.
new_balance = new_balance.max(tx.value());
}

let old_balance = caller_account.info.balance;
Ok(new_balance)
}

/// Make changes to the caller account.
///
/// Returns the old balance.
#[inline]
pub fn caller_touch_and_change(
caller_account: &mut Account,
new_balance: U256,
is_call: bool,
) -> U256 {
// Touch account so we know it is changed.
caller_account.mark_touch();
caller_account.info.balance = new_balance;

let old_balance = core::mem::replace(&mut caller_account.info.balance, new_balance);
// Bump the nonce for calls. Nonce for CREATE will be bumped in `make_create_frame`.
if tx.kind().is_call() {
if is_call {
// Nonce is already checked
caller_account.info.nonce = caller_account.info.nonce.saturating_add(1);
}
old_balance
}

/// Validates caller state and deducts transaction costs from the caller's balance.
#[inline]
pub fn validate_against_state_and_deduct_caller<
CTX: ContextTr,
ERROR: From<InvalidTransaction> + From<<CTX::Db as Database>::Error>,
>(
context: &mut CTX,
) -> Result<(), ERROR> {
let (tx, block, cfg, journal) = context.tx_block_cfg_journal_mut();

// Load caller's account.
let caller_account = journal.load_account_code(tx.caller())?.data;

// validate account nonce and code
validate_account_nonce_and_code_with_components(&mut caller_account.info, tx, cfg)?;

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

// make changes to the account
let old_balance = caller_touch_and_change(caller_account, new_balance, tx.kind().is_call());

// journal the change
journal.caller_accounting_journal_entry(tx.caller(), old_balance, tx.kind().is_call());

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@klkvr @mattsse can you take a look? This function is a lot simpler than before as few things got abstracted with functions

Ok(())
}

Expand Down
Loading
Loading