revm/handler/mainnet/pre_execution.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
//! Handles related to the main function of the EVM.
//!
//! They handle initial setup of the EVM, call loop and the final return of the EVM
use crate::{
precompile::PrecompileSpecId,
primitives::{
db::Database,
eip7702, Account, Bytecode, EVMError, Env, Spec,
SpecId::{CANCUN, PRAGUE, SHANGHAI},
TxKind, BLOCKHASH_STORAGE_ADDRESS, KECCAK_EMPTY, U256,
},
Context, ContextPrecompiles,
};
/// Main precompile load
#[inline]
pub fn load_precompiles<SPEC: Spec, DB: Database>() -> ContextPrecompiles<DB> {
ContextPrecompiles::new(PrecompileSpecId::from_spec_id(SPEC::SPEC_ID))
}
/// Main load handle
#[inline]
pub fn load_accounts<SPEC: Spec, EXT, DB: Database>(
context: &mut Context<EXT, DB>,
) -> Result<(), EVMError<DB::Error>> {
// set journaling state flag.
context.evm.journaled_state.set_spec_id(SPEC::SPEC_ID);
// load coinbase
// EIP-3651: Warm COINBASE. Starts the `COINBASE` address warm
if SPEC::enabled(SHANGHAI) {
let coinbase = context.evm.inner.env.block.coinbase;
context
.evm
.journaled_state
.warm_preloaded_addresses
.insert(coinbase);
}
// Load blockhash storage address
// EIP-2935: Serve historical block hashes from state
if SPEC::enabled(PRAGUE) {
context
.evm
.journaled_state
.warm_preloaded_addresses
.insert(BLOCKHASH_STORAGE_ADDRESS);
}
// Load access list
context.evm.load_access_list()?;
Ok(())
}
/// Helper function that deducts the caller balance.
#[inline]
pub fn deduct_caller_inner<SPEC: Spec>(caller_account: &mut Account, env: &Env) {
// Subtract gas costs from the caller's account.
// We need to saturate the gas cost to prevent underflow in case that `disable_balance_check` is enabled.
let mut gas_cost = U256::from(env.tx.gas_limit).saturating_mul(env.effective_gas_price());
// EIP-4844
if SPEC::enabled(CANCUN) {
let data_fee = env.calc_data_fee().expect("already checked");
gas_cost = gas_cost.saturating_add(data_fee);
}
// set new caller account balance.
caller_account.info.balance = caller_account.info.balance.saturating_sub(gas_cost);
// bump the nonce for calls. Nonce for CREATE will be bumped in `handle_create`.
if matches!(env.tx.transact_to, TxKind::Call(_)) {
// Nonce is already checked
caller_account.info.nonce = caller_account.info.nonce.saturating_add(1);
}
// touch account so we know it is changed.
caller_account.mark_touch();
}
/// Deducts the caller balance to the transaction limit.
#[inline]
pub fn deduct_caller<SPEC: Spec, EXT, DB: Database>(
context: &mut Context<EXT, DB>,
) -> Result<(), EVMError<DB::Error>> {
// load caller's account.
let caller_account = context
.evm
.inner
.journaled_state
.load_account(context.evm.inner.env.tx.caller, &mut context.evm.inner.db)?;
// deduct gas cost from caller's account.
deduct_caller_inner::<SPEC>(caller_account.data, &context.evm.inner.env);
Ok(())
}
/// Apply EIP-7702 auth list and return number gas refund on already created accounts.
#[inline]
pub fn apply_eip7702_auth_list<SPEC: Spec, EXT, DB: Database>(
context: &mut Context<EXT, DB>,
) -> Result<u64, EVMError<DB::Error>> {
// EIP-7702. Load bytecode to authorized accounts.
if !SPEC::enabled(PRAGUE) {
return Ok(0);
}
// return if there is no auth list.
let Some(authorization_list) = context.evm.inner.env.tx.authorization_list.as_ref() else {
return Ok(0);
};
let mut refunded_accounts = 0;
for authorization in authorization_list.recovered_iter() {
// 1. Verify the chain id is either 0 or the chain's current ID.
let chain_id = authorization.chain_id();
if chain_id != 0 && chain_id != context.evm.inner.env.cfg.chain_id {
continue;
}
// 2. Verify the `nonce` is less than `2**64 - 1`.
if authorization.nonce() == u64::MAX {
continue;
}
// recover authority and authorized addresses.
// 3. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]`
let Some(authority) = authorization.authority() else {
continue;
};
// warm authority account and check nonce.
// 4. Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).)
let mut authority_acc = context
.evm
.inner
.journaled_state
.load_code(authority, &mut context.evm.inner.db)?;
// 5. Verify the code of `authority` is either empty or already delegated.
if let Some(bytecode) = &authority_acc.info.code {
// if it is not empty and it is not eip7702
if !bytecode.is_empty() && !bytecode.is_eip7702() {
continue;
}
}
// 6. Verify the nonce of `authority` is equal to `nonce`. In case `authority` does not exist in the trie, verify that `nonce` is equal to `0`.
if authorization.nonce() != authority_acc.info.nonce {
continue;
}
// 7. Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if `authority` exists in the trie.
if !authority_acc.is_empty() {
refunded_accounts += 1;
}
// 8. Set the code of `authority` to be `0xef0100 || address`. This is a delegation designation.
// * As a special case, if `address` is `0x0000000000000000000000000000000000000000` do not write the designation. Clear the accounts code and reset the account's code hash to the empty hash `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`.
let (bytecode, hash) = if authorization.address.is_zero() {
(Bytecode::default(), KECCAK_EMPTY)
} else {
let bytecode = Bytecode::new_eip7702(authorization.address);
let hash = bytecode.hash_slow();
(bytecode, hash)
};
authority_acc.info.code_hash = hash;
authority_acc.info.code = Some(bytecode);
// 9. Increase the nonce of `authority` by one.
authority_acc.info.nonce = authority_acc.info.nonce.saturating_add(1);
authority_acc.mark_touch();
}
let refunded_gas =
refunded_accounts * (eip7702::PER_EMPTY_ACCOUNT_COST - eip7702::PER_AUTH_BASE_COST);
Ok(refunded_gas)
}