Skip to content

Commit

Permalink
fix(eip7702): Add tests and fix some bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
rakita committed Jul 11, 2024
1 parent 313146f commit 2de2c61
Show file tree
Hide file tree
Showing 318 changed files with 293,275 additions and 447 deletions.
609 changes: 187 additions & 422 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ debug = true
inherits = "test"
opt-level = 3

# [patch.crates-io]
# alloy-eips = { git = "https://github.com/alloy-rs/alloy.git", rev = "41d4c7c" }
# alloy-provider = { git = "https://github.com/alloy-rs/alloy.git", rev = "41d4c7c" }
# alloy-transport = { git = "https://github.com/alloy-rs/alloy.git", rev = "41d4c7c" }
[patch.crates-io]
alloy-eips = { git = "https://github.com/alloy-rs/alloy.git", rev = "678617e" }
alloy-provider = { git = "https://github.com/alloy-rs/alloy.git", rev = "678617e" }
alloy-transport = { git = "https://github.com/alloy-rs/alloy.git", rev = "678617e" }
8 changes: 7 additions & 1 deletion bins/revme/src/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod bytecode;
pub mod eofvalidation;
pub mod evmrunner;
pub mod format_kzg_setup;
pub mod statetest;
Expand All @@ -9,8 +10,10 @@ use structopt::{clap::AppSettings, StructOpt};
#[structopt(setting = AppSettings::InferSubcommands)]
#[allow(clippy::large_enum_variant)]
pub enum MainCmd {
#[structopt(about = "Launch Ethereum state tests")]
#[structopt(about = "Execute Ethereum state tests")]
Statetest(statetest::Cmd),
#[structopt(about = "Execute eof validation tests")]
EofValidation(eofvalidation::Cmd),
#[structopt(
about = "Format kzg settings from a trusted setup file (.txt) into binary format (.bin)"
)]
Expand All @@ -31,12 +34,15 @@ pub enum Error {
KzgErrors(#[from] format_kzg_setup::KzgErrors),
#[error(transparent)]
EvmRunnerErrors(#[from] evmrunner::Errors),
#[error("Custom error: {0}")]
Custom(&'static str),
}

impl MainCmd {
pub fn run(&self) -> Result<(), Error> {
match self {
Self::Statetest(cmd) => cmd.run().map_err(Into::into),
Self::EofValidation(cmd) => cmd.run().map_err(Into::into),
Self::FormatKzgSetup(cmd) => cmd.run().map_err(Into::into),
Self::Evm(cmd) => cmd.run().map_err(Into::into),
Self::Bytecode(cmd) => {
Expand Down
22 changes: 22 additions & 0 deletions bins/revme/src/cmd/eofvalidation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::cmd::Error;
use std::path::PathBuf;
use structopt::StructOpt;

/// Statetest command
#[derive(StructOpt, Debug)]
pub struct Cmd {
/// Input path to the kzg trusted setup file.
#[structopt(required = true)]
path: PathBuf,
}

impl Cmd {
/// Run statetest command.
pub fn run(&self) -> Result<(), Error> {
// check if path exists.
if !self.path.exists() {
return Err(Error::Custom("The specified path does not exist"));
}
Ok(())
}
}
125 changes: 125 additions & 0 deletions bins/revme/src/cmd/statetest/models/eip7702.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use alloy_rlp::{Decodable, Error as RlpError, Header};
use revm::primitives::{AccessList, Bytes, Signature, SignedAuthorization, TxKind, U256};

/// [EIP-7702 Set Code Transaction](https://eips.ethereum.org/EIPS/eip-7702)
///
/// Set EOA account code for one transaction
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TxEip7702 {
/// Added as EIP-155: Simple replay attack protection
pub chain_id: u64,
/// A scalar value equal to the number of transactions sent by the sender; formally Tn.
pub nonce: u64,
/// A scalar value equal to the number of
/// Wei to be paid per unit of gas for all computation
/// costs incurred as a result of the execution of this transaction; formally Tp.
///
/// As ethereum circulation is around 120mil eth as of 2022 that is around
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
/// 340282366920938463463374607431768211455
pub gas_limit: u64,
/// A scalar value equal to the maximum
/// amount of gas that should be used in executing
/// this transaction. This is paid up-front, before any
/// computation is done and may not be increased
/// later; formally Tg.
///
/// As ethereum circulation is around 120mil eth as of 2022 that is around
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
/// 340282366920938463463374607431768211455
///
/// This is also known as `GasFeeCap`
pub max_fee_per_gas: u128,
/// Max Priority fee that transaction is paying
///
/// As ethereum circulation is around 120mil eth as of 2022 that is around
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
/// 340282366920938463463374607431768211455
///
/// This is also known as `GasTipCap`
pub max_priority_fee_per_gas: u128,
/// The 160-bit address of the message call’s recipient or, for a contract creation
/// transaction, ∅, used here to denote the only member of B0 ; formally Tt.
pub to: TxKind,
/// A scalar value equal to the number of Wei to
/// be transferred to the message call’s recipient or,
/// in the case of contract creation, as an endowment
/// to the newly created account; formally Tv.
pub value: U256,
/// The accessList specifies a list of addresses and storage keys;
/// these addresses and storage keys are added into the `accessed_addresses`
/// and `accessed_storage_keys` global sets (introduced in EIP-2929).
/// A gas cost is charged, though at a discount relative to the cost of
/// accessing outside the list.
pub access_list: AccessList,
/// Authorizations are used to temporarily set the code of its signer to
/// the code referenced by `address`. These also include a `chain_id` (which
/// can be set to zero and not evaluated) as well as an optional `nonce`.
pub authorization_list: Vec<SignedAuthorization>,
/// Input has two uses depending if the transaction `to` field is [`TxKind::Create`] or
/// [`TxKind::Call`].
///
/// Input as init code, or if `to` is [`TxKind::Create`]: An unlimited size byte array
/// specifying the EVM-code for the account initialisation procedure `CREATE`
///
/// Input as data, or if `to` is [`TxKind::Call`]: An unlimited size byte array specifying the
/// input data of the message call, formally Td.
pub input: Bytes,
pub signature: Signature,
}

impl TxEip7702 {
/// Decodes the inner [`TxEip7702`] fields from RLP bytes.
///
/// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following
/// RLP fields in the following order:
///
/// - `chain_id`
/// - `nonce`
/// - `gas_price`
/// - `gas_limit`
/// - `to`
/// - `value`
/// - `data` (`input`)
/// - `access_list`
/// - `authorization_list`
fn decode_inner(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(Self {
chain_id: Decodable::decode(buf)?,
nonce: Decodable::decode(buf)?,
max_priority_fee_per_gas: Decodable::decode(buf)?,
max_fee_per_gas: Decodable::decode(buf)?,
gas_limit: Decodable::decode(buf)?,
to: Decodable::decode(buf)?,
value: Decodable::decode(buf)?,
input: Decodable::decode(buf)?,
access_list: Decodable::decode(buf)?,
authorization_list: Decodable::decode(buf)?,
signature: Signature::decode_rlp_vrs(buf)?,
})
}

pub fn decode(data: &mut &[u8]) -> alloy_rlp::Result<Self> {
// decode the list header for the rest of the transaction
let header = Header::decode(data)?;
if !header.list {
return Err(RlpError::Custom(
"typed tx fields must be encoded as a list",
));
}
let tx = TxEip7702::decode_inner(data)?;
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);
}
}
28 changes: 22 additions & 6 deletions bins/revme/src/cmd/statetest/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use revm::primitives::{AccessList, Address, Bytes, HashMap, B256, U256};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

mod deserializer;
mod eip7702;
mod spec;

use deserializer::*;
use eip7702::TxEip7702;
pub use spec::SpecName;

mod spec;
pub use self::spec::SpecName;
use revm::primitives::{AccessList, Address, AuthorizationList, Bytes, HashMap, B256, U256};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

#[derive(Debug, PartialEq, Eq, Deserialize)]
pub struct TestSuite(pub BTreeMap<String, TestUnit>);
Expand Down Expand Up @@ -47,6 +49,20 @@ pub struct Test {
pub txbytes: Option<Bytes>,
}

impl Test {
pub fn eip7702_authorization_list(&self) -> Option<AuthorizationList> {
let txbytes = self.txbytes.as_ref()?;

if txbytes.get(0) == Some(&0x04) {
let mut txbytes = &txbytes[1..];
let tx = TxEip7702::decode(&mut txbytes).expect("Expect EIP-7702 tx bytes to decode");
return Some(AuthorizationList::Signed(tx.authorization_list).into_recovered());
}

None
}
}

#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TxPartIndices {
Expand Down
10 changes: 6 additions & 4 deletions bins/revme/src/cmd/statetest/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ pub fn execute_test_suite(
.and_then(Option::as_deref)
.cloned()
.unwrap_or_default();
env.tx.authorization_list = test.eip7702_authorization_list();

let to = match unit.transaction.to {
Some(add) => TxKind::Call(add),
Expand All @@ -360,10 +361,11 @@ pub fn execute_test_suite(
env.tx.transact_to = to;

let mut cache = cache_state.clone();
cache.set_state_clear_flag(SpecId::enabled(
spec_id,
revm::primitives::SpecId::SPURIOUS_DRAGON,
));
let is_spurious_dragon =
SpecId::enabled(spec_id, revm::primitives::SpecId::SPURIOUS_DRAGON);
let is_prague = SpecId::enabled(spec_id, revm::primitives::SpecId::PRAGUE);
// Turn state clear after prague
cache.set_state_clear_flag(is_spurious_dragon && !is_prague);
let mut state = revm::db::State::builder()
.with_cached_prestate(cache)
.with_bundle_update()
Expand Down
2 changes: 1 addition & 1 deletion crates/interpreter/src/gas/calc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ pub fn validate_initial_tx_gas(

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

initial_gas
Expand Down
2 changes: 1 addition & 1 deletion crates/interpreter/src/gas/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub const WARM_STORAGE_READ_COST: u64 = 100;
pub const WARM_SSTORE_RESET: u64 = SSTORE_RESET - COLD_SLOAD_COST;

/// EIP-7702
pub const PER_CONTRACT_CODE_BASE_COST: u64 = 2400;
pub const PER_AUTH_BASE_COST: u64 = 2500;

/// EIP-3860 : Limit and meter initcode
pub const INITCODE_WORD_COST: u64 = 2;
Expand Down
2 changes: 1 addition & 1 deletion crates/primitives/src/env.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod eip7702;
pub mod handler_cfg;

pub use eip7702::AuthorizationList;
pub use eip7702::{AuthorizationList, RecoveredAuthorization, Signature, SignedAuthorization};
pub use handler_cfg::{CfgEnvWithHandlerCfg, EnvWithHandlerCfg, HandlerCfg};

use crate::{
Expand Down
20 changes: 17 additions & 3 deletions crates/primitives/src/env/eip7702.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use alloy_eips::eip7702::{RecoveredAuthorization, SignedAuthorization};
use alloy_primitives::Signature;
pub use alloy_eips::eip7702::{RecoveredAuthorization, SignedAuthorization};
pub use alloy_primitives::Signature;

use std::{boxed::Box, vec::Vec};

/// Authorization list for EIP-7702 transaction type.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AuthorizationList {
Signed(Vec<SignedAuthorization<Signature>>),
Signed(Vec<SignedAuthorization>),
Recovered(Vec<RecoveredAuthorization>),
}

Expand All @@ -33,4 +34,17 @@ impl AuthorizationList {
Self::Recovered(recovered) => Box::new(recovered.clone().into_iter()),
}
}

/// Returns recovered authorizations list.
pub fn into_recovered(self) -> Self {
match self {
Self::Signed(signed) => Self::Recovered(
signed
.into_iter()
.map(|signed| signed.into_recovered())
.collect(),
),
a @ Self::Recovered(_) => a,
}
}
}
2 changes: 2 additions & 0 deletions crates/primitives/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ bitflags! {
/// used only for pre spurious dragon hardforks where existing and empty were two separate states.
/// it became same state after EIP-161: State trie clearing
const LoadedAsNotExisting = 0b0001000;
/// EIP-7702 authorized account.
const Authorized = 0b00100000;
/// used to mark account as cold
const Cold = 0b0010000;
}
Expand Down
1 change: 1 addition & 0 deletions crates/revm/src/db/states/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ impl CacheState {
/// Used inside tests to generate merkle tree.
pub fn trie_account(&self) -> impl IntoIterator<Item = (Address, &PlainAccount)> {
self.accounts.iter().filter_map(|(address, account)| {
println!("{:?} : {:?}", address, account);
account
.account
.as_ref()
Expand Down
5 changes: 2 additions & 3 deletions crates/revm/src/handler/mainnet/post_execution.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
interpreter::{Gas, SuccessOrHalt},
primitives::{
db::Database, EVMError, ExecutionResult, ResultAndState, Spec, SpecId::LONDON,
db::Database, Bytecode, EVMError, ExecutionResult, ResultAndState, Spec, SpecId::LONDON,
KECCAK_EMPTY, U256,
},
Context, FrameResult,
Expand Down Expand Up @@ -103,9 +103,8 @@ pub fn output<EXT, DB: Database>(
let account = state
.get_mut(&authorized)
.expect("Authorized account must exist");
account.info.code = None;
account.info.code = Some(Bytecode::default());
account.info.code_hash = KECCAK_EMPTY;
account.storage.clear();
}

let result = match instruction_result.result.into() {
Expand Down
2 changes: 1 addition & 1 deletion crates/revm/src/handler/mainnet/pre_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pub fn load_accounts<SPEC: Spec, EXT, DB: Database>(
.evm
.inner
.journaled_state
.load_code(authority, &mut context.evm.inner.db)?;
.load_code(authorization.address, &mut context.evm.inner.db)?;
let code = account.info.code.clone();
let code_hash = account.info.code_hash;

Expand Down
Loading

0 comments on commit 2de2c61

Please sign in to comment.