Skip to content

Commit

Permalink
feat(eip7702): Impl newest version of EIP (#1695)
Browse files Browse the repository at this point in the history
* latest eip7702 wip

* add code loading handler

* WIP adding is_delegate_cold flag

* feat: add StateLoad and Eip7702CodeLoad

* feat: add gas accounting among other things

* clippy,fmt, op test

* path to latest alloy-eips

* comment eip7702 decode tests

* Eip7702 format starts with 0xEF0100

* typo

* fix(eip7702): fix empty or eip7702 code check

* Type Eip7702s to Eip7702

* Corrent comments

* switch new and new_raw Eip7702Bytecode

* propagate last commit

* nit: rename fn

* fix(eip7702): set delegated code on call (#1706)

* type change, return eip7702 raw on Bytecode::bytecode

* eip7702 delegation test

* Cleanup, refactor sstore gas calc

* doc

* chore: add AuthList json format

* fix initial eip7702 gas, fix eip7702 refund on revert

* small refactor

* fix refund cnt

* error handling, EIP-3607 fix, wip on auth validity

* add auth validity check, fix EIP-3607 fix

* switch tests

* missing comment

* fix tests

* rm println

* remove skip of required fields

* docs, test meta dat
  • Loading branch information
rakita authored Aug 29, 2024
1 parent 0ae01d5 commit 8094c45
Show file tree
Hide file tree
Showing 127 changed files with 448,801 additions and 3,837 deletions.
27 changes: 18 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ debug = true
[profile.ethtests]
inherits = "test"
opt-level = 3

[patch.crates-io]
alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "fd159f6" }
4 changes: 2 additions & 2 deletions bins/revme/src/cmd/evmrunner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use revm::{
db::BenchmarkDB,
inspector_handle_register,
inspectors::TracerEip3155,
primitives::{eof::EofDecodeError, Address, Bytecode, TxKind},
primitives::{Address, Bytecode, BytecodeDecodeError, TxKind},
Evm,
};
use std::io::Error as IoError;
Expand All @@ -26,7 +26,7 @@ pub enum Errors {
#[error(transparent)]
Io(#[from] IoError),
#[error(transparent)]
EofError(#[from] EofDecodeError),
BytecodeDecodeError(#[from] BytecodeDecodeError),
}

/// Evm runner command allows running arbitrary evm bytecode.
Expand Down
20 changes: 1 addition & 19 deletions bins/revme/src/cmd/statetest/models/eip7702.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use alloy_rlp::{Decodable, Error as RlpError, Header};
use revm::primitives::{AccessList, Bytes, Signature, SignedAuthorization, TxKind, U256};
use std::vec::Vec;

/// TODO remove it when new tests are generated that has a Authorization json field.
/// [EIP-7702 Set Code Transaction](https://eips.ethereum.org/EIPS/eip-7702)
///
/// Set EOA account code for one transaction
Expand Down Expand Up @@ -112,22 +113,3 @@ impl TxEip7702 {
Ok(tx)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn decode_eip7702_tx() {
let tx_bytes = hex::decode("f8c2018080078398968094a94f5374fce5edbc8e2a8697c15331677e6ebf0b8080c0f85df85b01940000000000000000000000000000000000001000c18001a08171c0ded912d4f458b8115618c18f3f430f414919c73b4daa693c47fd325414a0787741e1621bcb9cb58ece039ad73f41d9422aa259ed53c2b0bd30dc7ff09be780a00e6c8f4d73b175887e1f21cc00bf0f8243af18aed208ec0a4562ee60e7f85736a03f8e8f1b01fcd6d3a988877e80dc17fad16274447f4211ed74b41e8789ae70cd").unwrap();
let tx = TxEip7702::decode(&mut tx_bytes.as_slice()).unwrap();
assert_eq!(tx.authorization_list.len(), 1);
}

#[test]
fn test_eip7702_tx() {
let tx_bytes = hex::decode("f8c2018080078398968094a94f5374fce5edbc8e2a8697c15331677e6ebf0b8080c0f85df85b80940000000000000000000000000000000000001000c10180a09e833a19cf7ac609d713ffeb8d5cd327237ef5cb4ac9524c53195423e348629fa0632893e4b18b32faf56972dc3568c3a3869dcf9eb9c282a637173475d19e8d2f01a05d6eea7691335a6bb066613d5c33a27bd1cbc89feb472b6dd437aca6aec73282a013c492943ea0fce77a20b1d554eac087fee37fa27b0f8294b13fb3162a0fb175").unwrap();
let tx = TxEip7702::decode(&mut tx_bytes.as_slice()).unwrap();
assert_eq!(tx.authorization_list.len(), 1);
}
}
18 changes: 14 additions & 4 deletions bins/revme/src/cmd/statetest/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,25 @@ pub struct TransactionParts {

#[serde(default)]
pub access_lists: Vec<Option<AccessList>>,

//#[serde(default)]
// TODO EIP-7702 when added enable serde `deny_unknown_fields`.
//pub authorization_list: Vec<Option<Vec<TestAuthorization>>>,
#[serde(default)]
pub authorization_list: Vec<Authorization>,
#[serde(default)]
pub blob_versioned_hashes: Vec<B256>,
pub max_fee_per_blob_gas: Option<U256>,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Authorization {
chain_id: U256,
address: Address,
nonce: U256,
v: U256,
r: U256,
s: U256,
signer: Option<Address>,
}

#[cfg(test)]
mod tests {

Expand Down
101 changes: 56 additions & 45 deletions crates/interpreter/src/gas/calc.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use revm_primitives::eip7702;

use super::constants::*;
use crate::{
num_words,
primitives::{AccessListItem, SpecId, U256},
SelfDestructResult,
AccountLoad, Eip7702CodeLoad, SStoreResult, SelfDestructResult, StateLoad,
};

/// `const` Option `?`.
Expand All @@ -18,37 +20,37 @@ macro_rules! tri {
/// `SSTORE` opcode refund calculation.
#[allow(clippy::collapsible_else_if)]
#[inline]
pub fn sstore_refund(spec_id: SpecId, original: U256, current: U256, new: U256) -> i64 {
pub fn sstore_refund(spec_id: SpecId, vals: &SStoreResult) -> i64 {
if spec_id.is_enabled_in(SpecId::ISTANBUL) {
// EIP-3529: Reduction in refunds
let sstore_clears_schedule = if spec_id.is_enabled_in(SpecId::LONDON) {
(SSTORE_RESET - COLD_SLOAD_COST + ACCESS_LIST_STORAGE_KEY) as i64
} else {
REFUND_SSTORE_CLEARS
};
if current == new {
if vals.is_new_eq_present() {
0
} else {
if original == current && new.is_zero() {
if vals.is_original_eq_present() && vals.is_new_zero() {
sstore_clears_schedule
} else {
let mut refund = 0;

if !original.is_zero() {
if current.is_zero() {
if !vals.is_original_zero() {
if vals.is_present_zero() {
refund -= sstore_clears_schedule;
} else if new.is_zero() {
} else if vals.is_new_zero() {
refund += sstore_clears_schedule;
}
}

if original == new {
if vals.is_original_eq_new() {
let (gas_sstore_reset, gas_sload) = if spec_id.is_enabled_in(SpecId::BERLIN) {
(SSTORE_RESET - COLD_SLOAD_COST, WARM_STORAGE_READ_COST)
} else {
(SSTORE_RESET, sload_cost(spec_id, false))
};
if original.is_zero() {
if vals.is_original_zero() {
refund += (SSTORE_SET - gas_sload) as i64;
} else {
refund += (gas_sstore_reset - gas_sload) as i64;
Expand All @@ -59,7 +61,7 @@ pub fn sstore_refund(spec_id: SpecId, original: U256, current: U256, new: U256)
}
}
} else {
if !current.is_zero() && new.is_zero() {
if !vals.is_present_zero() && vals.is_new_zero() {
REFUND_SSTORE_CLEARS
} else {
0
Expand Down Expand Up @@ -123,9 +125,9 @@ pub const fn verylowcopy_cost(len: u64) -> Option<u64> {

/// `EXTCODECOPY` opcode cost calculation.
#[inline]
pub const fn extcodecopy_cost(spec_id: SpecId, len: u64, is_cold: bool) -> Option<u64> {
pub const fn extcodecopy_cost(spec_id: SpecId, len: u64, load: Eip7702CodeLoad<()>) -> Option<u64> {
let base_gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
warm_cold_cost(is_cold)
warm_cold_cost_with_delegation(load)
} else if spec_id.is_enabled_in(SpecId::TANGERINE) {
700
} else {
Expand Down Expand Up @@ -187,24 +189,15 @@ pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 {

/// `SSTORE` opcode cost calculation.
#[inline]
pub fn sstore_cost(
spec_id: SpecId,
original: U256,
current: U256,
new: U256,
gas: u64,
is_cold: bool,
) -> Option<u64> {
pub fn sstore_cost(spec_id: SpecId, vals: &SStoreResult, gas: u64, is_cold: bool) -> Option<u64> {
// EIP-1706 Disable SSTORE with gasleft lower than call stipend
if spec_id.is_enabled_in(SpecId::ISTANBUL) && gas <= CALL_STIPEND {
return None;
}

if spec_id.is_enabled_in(SpecId::BERLIN) {
// Berlin specification logic
let mut gas_cost = istanbul_sstore_cost::<WARM_STORAGE_READ_COST, WARM_SSTORE_RESET>(
original, current, new,
);
let mut gas_cost = istanbul_sstore_cost::<WARM_STORAGE_READ_COST, WARM_SSTORE_RESET>(vals);

if is_cold {
gas_cost += COLD_SLOAD_COST;
Expand All @@ -213,26 +206,24 @@ pub fn sstore_cost(
} else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
// Istanbul logic
Some(istanbul_sstore_cost::<INSTANBUL_SLOAD_GAS, SSTORE_RESET>(
original, current, new,
vals,
))
} else {
// Frontier logic
Some(frontier_sstore_cost(current, new))
Some(frontier_sstore_cost(vals))
}
}

/// EIP-2200: Structured Definitions for Net Gas Metering
#[inline]
fn istanbul_sstore_cost<const SLOAD_GAS: u64, const SSTORE_RESET_GAS: u64>(
original: U256,
current: U256,
new: U256,
vals: &SStoreResult,
) -> u64 {
if new == current {
if vals.is_new_eq_present() {
SLOAD_GAS
} else if original == current && original.is_zero() {
} else if vals.is_original_eq_present() && vals.is_original_zero() {
SSTORE_SET
} else if original == current {
} else if vals.is_original_eq_present() {
SSTORE_RESET_GAS
} else {
SLOAD_GAS
Expand All @@ -241,8 +232,8 @@ fn istanbul_sstore_cost<const SLOAD_GAS: u64, const SSTORE_RESET_GAS: u64>(

/// Frontier sstore cost just had two cases set and reset values.
#[inline]
fn frontier_sstore_cost(current: U256, new: U256) -> u64 {
if current.is_zero() && !new.is_zero() {
fn frontier_sstore_cost(vals: &SStoreResult) -> u64 {
if vals.is_present_zero() && !vals.is_new_zero() {
SSTORE_SET
} else {
SSTORE_RESET
Expand All @@ -251,12 +242,12 @@ fn frontier_sstore_cost(current: U256, new: U256) -> u64 {

/// `SELFDESTRUCT` opcode cost calculation.
#[inline]
pub const fn selfdestruct_cost(spec_id: SpecId, res: SelfDestructResult) -> u64 {
pub const fn selfdestruct_cost(spec_id: SpecId, res: StateLoad<SelfDestructResult>) -> u64 {
// EIP-161: State trie clearing (invariant-preserving alternative)
let should_charge_topup = if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
res.had_value && !res.target_exists
res.data.had_value && !res.data.target_exists
} else {
!res.target_exists
!res.data.target_exists
};

// EIP-150: Gas cost changes for IO-heavy operations
Expand Down Expand Up @@ -288,16 +279,24 @@ pub const fn selfdestruct_cost(spec_id: SpecId, res: SelfDestructResult) -> u64
/// * Transfer value gas. If value is transferred and balance of target account is updated.
/// * If account is not existing and needs to be created. After Spurious dragon
/// this is only accounted if value is transferred.
///
/// account_load.is_empty will be accounted only if hardfork is SPURIOUS_DRAGON and
/// there is transfer value.
///
/// This means that [`crate::OpCode::EXTSTATICCALL`],
/// [`crate::OpCode::EXTDELEGATECALL`] that dont transfer value will not be
/// effected by this field.
///
/// [`crate::OpCode::CALL`], [`crate::OpCode::EXTCALL`] use this field.
///
/// While [`crate::OpCode::STATICCALL`], [`crate::OpCode::DELEGATECALL`],
/// [`crate::OpCode::CALLCODE`] need to have this field hardcoded to false
/// as they were present before SPURIOUS_DRAGON hardfork.
#[inline]
pub const fn call_cost(
spec_id: SpecId,
transfers_value: bool,
is_cold: bool,
new_account_accounting: bool,
) -> u64 {
pub const fn call_cost(spec_id: SpecId, transfers_value: bool, account_load: AccountLoad) -> u64 {
// Account access.
let mut gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
warm_cold_cost(is_cold)
warm_cold_cost_with_delegation(account_load.load)
} else if spec_id.is_enabled_in(SpecId::TANGERINE) {
// EIP-150: Gas cost changes for IO-heavy operations
700
Expand All @@ -311,7 +310,7 @@ pub const fn call_cost(
}

// new account cost
if new_account_accounting {
if account_load.is_empty {
// EIP-161: State trie clearing (invariant-preserving alternative)
if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
// account only if there is value transferred.
Expand All @@ -336,6 +335,18 @@ pub const fn warm_cold_cost(is_cold: bool) -> u64 {
}
}

/// Berlin warm and cold storage access cost for account access.
///
/// If delegation is Some, add additional cost for delegation account load.
#[inline]
pub const fn warm_cold_cost_with_delegation(load: Eip7702CodeLoad<()>) -> u64 {
let mut gas = warm_cold_cost(load.state_load.is_cold);
if let Some(is_cold) = load.is_delegate_account_cold {
gas += warm_cold_cost(is_cold);
}
gas
}

/// Memory expansion cost calculation for a given memory length.
#[inline]
pub const fn memory_gas_for_len(len: usize) -> u64 {
Expand Down Expand Up @@ -400,7 +411,7 @@ pub fn validate_initial_tx_gas(

// EIP-7702
if spec_id.is_enabled_in(SpecId::PRAGUE) {
initial_gas += authorization_list_num * PER_AUTH_BASE_COST;
initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;
}

initial_gas
Expand Down
Loading

0 comments on commit 8094c45

Please sign in to comment.