Skip to content

Commit

Permalink
feat: add EIP-7702 support
Browse files Browse the repository at this point in the history
  • Loading branch information
lattejed committed Jun 17, 2024
1 parent a04c7cd commit c0e1e36
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions crates/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ all = "warn"
alloy-primitives = { version = "0.7.2", default-features = false, features = [
"rlp",
] }
alloy-rlp = { version = "0.3", default-features = false }
hashbrown = "0.14"
auto_impl = "1.2"
bitvec = { version = "1", default-features = false, features = ["alloc"] }
bitflags = { version = "2.5.0", default-features = false }
k256 = { version = "0.13.3", default-features = false, features = ["ecdsa"] }

# For setting the CfgEnv KZGSettings. Enabled by c-kzg flag.
c-kzg = { version = "1.0.2", default-features = false, optional = true }
Expand Down
77 changes: 77 additions & 0 deletions crates/primitives/src/authorization.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) Set EOA account code for one transaction
//!
//! These transactions include a list of [`Authorization`]
use alloy_primitives::{Address, ChainId, Keccak256, U256};
use alloy_rlp::{BufMut, Encodable, Header};
use k256::{
ecdsa::{RecoveryId, Signature, VerifyingKey},
FieldBytes,
};

use crate::AUTHORIZATION_MAGIC_BYTE;

#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Authorization {
/// Chain ID, will not be checked if set to zero
pub chain_id: ChainId,
/// The address of the code that will get set to the signer's address
pub address: Address,
/// Optional nonce
pub nonce: Option<u64>,
/// yParity: Signature Y parity
pub y_parity: bool,
/// The R field of the signature
pub r: U256,
/// The S field of the signature
pub s: U256,
}

impl Authorization {
/// Recover the address of the signer for EIP-7702 transactions
/// Since these authorizations are fallible, we will ignore errors and optionally
/// return the signer's address
pub fn recovered_authority(&self) -> Option<Address> {
Signature::from_scalars(
*FieldBytes::from_slice(&self.r.to_be_bytes::<32>()),
*FieldBytes::from_slice(&self.s.to_be_bytes::<32>()),
)
.ok()
.and_then(|sig| RecoveryId::from_byte(self.y_parity as u8).map(|recid| (sig, recid)))
.and_then(|(sig, recid)| {
let nonce = self.nonce.map(|n| vec![n]).unwrap_or(vec![]);

let mut length = 0;
length += self.chain_id.length();
length += self.address.length();
length += nonce.length();

let mut buffer = Vec::new();

buffer.put_u8(AUTHORIZATION_MAGIC_BYTE);

Header {
list: true,
payload_length: length,
}
.encode(&mut buffer);
self.chain_id.encode(&mut buffer);
self.address.encode(&mut buffer);
nonce.encode(&mut buffer);

let mut hasher = Keccak256::new();
hasher.update(buffer);
let hash = hasher.finalize();

let recovered_key =
VerifyingKey::recover_from_prehash(&hash.as_slice(), &sig, recid).ok()?;
let encoded_point = recovered_key.to_encoded_point(false);

let mut hasher = Keccak256::new();
hasher.update(&encoded_point.as_bytes()[1..]);
let address = &hasher.finalize()[12..];
Some(Address::from_slice(address))
})
}
}
5 changes: 5 additions & 0 deletions crates/primitives/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,8 @@ pub const BLOB_GASPRICE_UPDATE_FRACTION: u64 = 3338477;

/// First version of the blob.
pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01;

// === EIP-7702 constants ===

// Magic byte prepended to the rlp-encoded payload of authorizations
pub const AUTHORIZATION_MAGIC_BYTE: u8 = 0x05;
10 changes: 7 additions & 3 deletions crates/primitives/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ pub mod handler_cfg;
pub use handler_cfg::{CfgEnvWithHandlerCfg, EnvWithHandlerCfg, HandlerCfg};

use crate::{
calc_blob_gasprice, Account, Address, Bytes, HashMap, InvalidHeader, InvalidTransaction, Spec,
SpecId, B256, GAS_PER_BLOB, KECCAK_EMPTY, MAX_BLOB_NUMBER_PER_BLOCK, MAX_INITCODE_SIZE, U256,
VERSIONED_HASH_VERSION_KZG,
calc_blob_gasprice, Account, Address, Authorization, Bytes, HashMap, InvalidHeader,
InvalidTransaction, Spec, SpecId, B256, GAS_PER_BLOB, KECCAK_EMPTY, MAX_BLOB_NUMBER_PER_BLOCK,
MAX_INITCODE_SIZE, U256, VERSIONED_HASH_VERSION_KZG,
};
use core::cmp::{min, Ordering};
use core::hash::Hash;
Expand Down Expand Up @@ -584,6 +584,9 @@ pub struct TxEnv {
/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
pub max_fee_per_blob_gas: Option<U256>,

/// TODO(eip7702): fix me
pub authorization_list: Vec<Authorization>,

/// EOF Initcodes for EOF CREATE transaction
///
/// Incorporated as part of the Prague upgrade via [EOF]
Expand Down Expand Up @@ -642,6 +645,7 @@ impl Default for TxEnv {
access_list: Vec::new(),
blob_hashes: Vec::new(),
max_fee_per_blob_gas: None,
authorization_list: Vec::new(),
eof_initcodes: Vec::new(),
eof_initcodes_hashed: HashMap::new(),
#[cfg(feature = "optimism")]
Expand Down
4 changes: 4 additions & 0 deletions crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ extern crate alloc as std;

mod bytecode;
mod constants;

pub mod authorization;
pub mod db;
pub mod env;

Expand All @@ -19,10 +21,12 @@ pub mod result;
pub mod specification;
pub mod state;
pub mod utilities;

pub use alloy_primitives::{
self, address, b256, bytes, fixed_bytes, hex, hex_literal, ruint, uint, Address, Bytes,
FixedBytes, Log, LogData, B256, I256, U256,
};
pub use authorization::*;
pub use bitvec;
pub use bytecode::*;
pub use constants::*;
Expand Down
3 changes: 3 additions & 0 deletions crates/revm/src/handler/mainnet/post_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub fn end<EXT, DB: Database>(
/// Clear handle clears error and journal state.
#[inline]
pub fn clear<EXT, DB: Database>(context: &mut Context<EXT, DB>) {
// TODO(eip7702): teardown
// 'set the code of each authority back to empty'

// clear error and journaled state.
let _ = context.evm.take_error();
context.evm.inner.journaled_state.clear();
Expand Down
47 changes: 47 additions & 0 deletions crates/revm/src/handler/mainnet/pre_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,53 @@ pub fn load_accounts<SPEC: Spec, EXT, DB: Database>(
)?;
}

// Load code into EOAs
// EIP-7702: Set EOA account code for one transaction
if SPEC::enabled(PRAGUE) {
// These authorizations are fallible, so if we encounter an invalid authorization
// or an error, skip it and continue
for authorization in context.evm.inner.env.tx.authorization_list.iter() {
// Recover the signer address if possible
let Some(authority) = authorization.recovered_authority() else {
continue;
};

// Optionally match the chain id
if authorization.chain_id != 0
&& authorization.chain_id != context.evm.inner.env.cfg.chain_id
{
continue;
}

// Verify that the code of authority is empty
let Ok(authority_account) = context.evm.inner.journaled_state.initial_account_load(
authority,
&[],
&mut context.evm.inner.db,
) else {
continue;
};
if authority_account.info.code.is_some() {
continue;
};

// Check nonce if present
if let Some(nonce) = authorization.nonce {
if authority_account.info.nonce != nonce {
continue;
}
}

// Load the code into the account
// This touches the access list for `address` but not for the `from_address`
_ = context.evm.inner.journaled_state.load_code_into(
authority,
authorization.address,
&mut context.evm.inner.db,
);
}
}

context.evm.load_access_list()?;
Ok(())
}
Expand Down
31 changes: 31 additions & 0 deletions crates/revm/src/journaled_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,37 @@ impl JournaledState {
Ok((acc, is_cold))
}

/// Load code from one account to another
///
/// For EIP-7702: Set EOA account code for one transaction
/// This touches the access list for `address` but not for the `from_address`
#[inline]
pub fn load_code_into<DB: Database>(
&mut self,
address: Address,
from_address: Address,
db: &mut DB,
) -> Result<(&mut Account, bool), EVMError<DB::Error>> {
let from_acc = db
.basic(from_address)
.map_err(EVMError::Database)?
.map(|i| i.into())
.unwrap_or(Account::new_not_existing());
let (acc, is_cold) = self.load_account(address, db)?;
if acc.info.code.is_none() {
if from_acc.info.code_hash == KECCAK_EMPTY {
let empty = Bytecode::default();
acc.info.code = Some(empty);
} else {
let code = db
.code_by_hash(from_acc.info.code_hash)
.map_err(EVMError::Database)?;
acc.info.code = Some(code);
}
}
Ok((acc, is_cold))
}

/// Load storage slot
///
/// # Panics
Expand Down

0 comments on commit c0e1e36

Please # to comment.