Skip to content

Commit

Permalink
feat(EIP-7702): devnet-4 changes (#1821)
Browse files Browse the repository at this point in the history
* feat(EIP-7702): devnet-4 changes

* add new test suite

* fix test

* check parity in revme, remove invalid auth tx tests

* fix clippy
  • Loading branch information
rakita authored Oct 14, 2024
1 parent 4f09399 commit f551baf
Show file tree
Hide file tree
Showing 95 changed files with 4,284 additions and 1,857 deletions.
9 changes: 8 additions & 1 deletion bins/revme/src/cmd/statetest/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use deserializer::*;
pub use eip7702::TxEip7702;
pub use spec::SpecName;

use revm::primitives::{AccessList, Address, AuthorizationList, Bytes, HashMap, B256, U256};
use revm::primitives::{
alloy_primitives::Parity, AccessList, Address, AuthorizationList, Bytes, HashMap, B256, U256,
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

Expand Down Expand Up @@ -60,6 +62,11 @@ impl Test {
if txbytes.first() == Some(&0x04) {
let mut txbytes = &txbytes[1..];
let tx = TxEip7702::decode(&mut txbytes)?;
if let Parity::Eip155(parity) = tx.signature.v() {
if parity < u8::MAX as u64 {
return Err(alloy_rlp::Error::Custom("Invalid parity value"));
}
}
return Ok(Some(
AuthorizationList::Signed(tx.authorization_list).into_recovered(),
));
Expand Down
4 changes: 2 additions & 2 deletions crates/primitives/src/eip7702.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub use bytecode::{
};

// Base cost of updating authorized account.
pub const PER_AUTH_BASE_COST: u64 = 2500;
pub const PER_AUTH_BASE_COST: u64 = 12500;

/// Cost of creating authorized account that was previously empty.
pub const PER_EMPTY_ACCOUNT_COST: u64 = 25000;
Expand All @@ -20,7 +20,7 @@ pub const PER_EMPTY_ACCOUNT_COST: u64 = 25000;
/// to EIP-2 should have an S value less than or equal to this.
///
/// `57896044618658097711785492504343953926418782139537452191302581570759080747168`
const SECP256K1N_HALF: U256 = U256::from_be_bytes([
pub const SECP256K1N_HALF: U256 = U256::from_be_bytes([
0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x5D, 0x57, 0x6E, 0x73, 0x57, 0xA4, 0x50, 0x1D, 0xDF, 0xE9, 0x2F, 0x46, 0x68, 0x1B, 0x20, 0xA0,
]);
50 changes: 14 additions & 36 deletions crates/primitives/src/eip7702/authorization_list.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
pub use alloy_eip7702::{Authorization, SignedAuthorization};
pub use alloy_primitives::{Parity, Signature};

use super::SECP256K1N_HALF;
use crate::Address;
use core::{fmt, ops::Deref};
use std::{boxed::Box, vec::Vec};

use super::SECP256K1N_HALF;

/// Authorization list for EIP-7702 transaction type.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand Down Expand Up @@ -35,40 +36,6 @@ impl AuthorizationList {
}
}

/// Returns true if the authorization list is valid.
pub fn is_valid(&self, _chain_id: u64) -> Result<(), InvalidAuthorization> {
let validate = |auth: &SignedAuthorization| -> Result<(), InvalidAuthorization> {
// TODO Eip7702. Check chain_id
// Pending: https://github.com/ethereum/EIPs/pull/8833/files
// let auth_chain_id: u64 = auth.chain_id().try_into().unwrap_or(u64::MAX);
// if auth_chain_id != 0 && auth_chain_id != chain_id {
// return Err(InvalidAuthorization::InvalidChainId);
// }

// Check y_parity, Parity::Parity means that it was 0 or 1.
if !matches!(auth.signature().v(), Parity::Parity(_)) {
return Err(InvalidAuthorization::InvalidYParity);
}

// Check s-value
if auth.signature().s() > SECP256K1N_HALF {
return Err(InvalidAuthorization::Eip2InvalidSValue);
}

Ok(())
};

match self {
Self::Signed(signed) => signed.iter().try_for_each(validate)?,
Self::Recovered(recovered) => recovered
.iter()
.map(|recovered| &recovered.inner)
.try_for_each(validate)?,
};

Ok(())
}

/// Return empty authorization list.
pub fn empty() -> Self {
Self::Recovered(Vec::new())
Expand Down Expand Up @@ -114,7 +81,18 @@ impl RecoveredAuthorization {
/// Get the `authority` for the authorization.
///
/// If this is `None`, then the authority could not be recovered.
pub const fn authority(&self) -> Option<Address> {
pub fn authority(&self) -> Option<Address> {
let signature = self.inner.signature();

// Check s-value
if signature.s() > SECP256K1N_HALF {
return None;
}

// Check y_parity, Parity::Parity means that it was 0 or 1.
if !matches!(signature.v(), Parity::Parity(_)) {
return None;
}
self.authority
}

Expand Down
3 changes: 0 additions & 3 deletions crates/primitives/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,6 @@ impl Env {
return Err(InvalidTransaction::EmptyAuthorizationList);
}

// Check validity of authorization_list
auth_list.is_valid(self.cfg.chain_id)?;

// Check if other fields are unset.
if self.tx.max_fee_per_blob_gas.is_some() || !self.tx.blob_hashes.is_empty() {
return Err(InvalidTransaction::AuthorizationListInvalidFields);
Expand Down
4 changes: 2 additions & 2 deletions crates/revm/benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ fn transfer(c: &mut Criterion) {
}

fn bench_transact<EXT>(g: &mut BenchmarkGroup<'_, WallTime>, evm: &mut Evm<'_, EXT, BenchmarkDB>) {
let state = match evm.context.evm.db.0 {
let state = match evm.context.evm.db.bytecode {
Bytecode::LegacyRaw(_) => "raw",
Bytecode::LegacyAnalyzed(_) => "analysed",
Bytecode::Eof(_) => "eof",
Expand All @@ -96,7 +96,7 @@ fn bench_eval(g: &mut BenchmarkGroup<'_, WallTime>, evm: &mut Evm<'static, (), B
g.bench_function("eval", |b| {
let contract = Contract {
input: evm.context.evm.env.tx.data.clone(),
bytecode: to_analysed(evm.context.evm.db.0.clone()),
bytecode: to_analysed(evm.context.evm.db.bytecode.clone()),
..Default::default()
};
let mut shared_memory = SharedMemory::new();
Expand Down
33 changes: 27 additions & 6 deletions crates/revm/src/db/in_memory_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,28 +360,49 @@ impl AccountState {
///
/// Any other address will return an empty account.
#[derive(Debug, Default, Clone)]
pub struct BenchmarkDB(pub Bytecode, B256);
pub struct BenchmarkDB {
pub bytecode: Bytecode,
pub hash: B256,
pub target: Address,
pub caller: Address,
}

impl BenchmarkDB {
/// Create a new benchmark database with the given bytecode.
pub fn new_bytecode(bytecode: Bytecode) -> Self {
let hash = bytecode.hash_slow();
Self(bytecode, hash)
Self {
bytecode,
hash,
target: Address::ZERO,
caller: Address::with_last_byte(1),
}
}

/// Change the caller address for the benchmark.
pub fn with_caller(self, caller: Address) -> Self {
Self { caller, ..self }
}

/// Change the target address for the benchmark.
pub fn with_target(self, target: Address) -> Self {
Self { target, ..self }
}
}

impl Database for BenchmarkDB {
type Error = Infallible;
/// Get basic account information.
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
if address == Address::ZERO {
if address == self.target {
return Ok(Some(AccountInfo {
nonce: 1,
balance: U256::from(10000000),
code: Some(self.0.clone()),
code_hash: self.1,
code: Some(self.bytecode.clone()),
code_hash: self.hash,
}));
}
if address == Address::with_last_byte(1) {
if address == self.caller {
return Ok(Some(AccountInfo {
nonce: 0,
balance: U256::from(10000000),
Expand Down
4 changes: 2 additions & 2 deletions crates/revm/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,15 +402,15 @@ mod tests {

#[test]
fn sanity_eip7702_tx() {
let delegate = address!("0000000000000000000000000000000000000000");
let caller = address!("0000000000000000000000000000000000000001");
let delegate = address!("0000000000000000000000000000000000000002");
let auth = address!("0000000000000000000000000000000000000100");

let bytecode = Bytecode::new_legacy([PUSH1, 0x01, PUSH1, 0x01, SSTORE].into());

let mut evm = Evm::builder()
.with_spec_id(SpecId::PRAGUE)
.with_db(BenchmarkDB::new_bytecode(bytecode))
.with_db(BenchmarkDB::new_bytecode(bytecode).with_target(delegate))
.modify_tx_env(|tx| {
tx.authorization_list = Some(
vec![RecoveredAuthorization::new_unchecked(
Expand Down
44 changes: 28 additions & 16 deletions crates/revm/src/handler/mainnet/pre_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
db::Database,
eip7702, Account, Bytecode, EVMError, Env, Spec,
SpecId::{CANCUN, PRAGUE, SHANGHAI},
TxKind, BLOCKHASH_STORAGE_ADDRESS, U256,
TxKind, BLOCKHASH_STORAGE_ADDRESS, KECCAK_EMPTY, U256,
},
Context, ContextPrecompiles,
};
Expand Down Expand Up @@ -114,51 +114,63 @@ pub fn apply_eip7702_auth_list<SPEC: Spec, EXT, DB: Database>(

let mut refunded_accounts = 0;
for authorization in authorization_list.recovered_iter() {
// 1. recover authority and authorized addresses.
// authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]
let Some(authority) = authorization.authority() else {
continue;
};

// 2. Verify the chain id is either 0 or the chain's current ID.
// 1. Verify the chain id is either 0 or the chain's current ID.
if !authorization.chain_id().is_zero()
&& authorization.chain_id() != U256::from(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.
// 3. Add authority to accessed_addresses (as defined in EIP-2929.)
// 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)?;

// 4. Verify the code of authority is either empty or already delegated.
// 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;
}
}

// 5. Verify the nonce of authority is equal to nonce.
// 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;
}

// 6. Refund the sender PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST gas if authority exists in the trie.
// 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;
}

// 7. Set the code of authority to be 0xef0100 || address. This is a delegation designation.
let bytecode = Bytecode::new_eip7702(authorization.address);
authority_acc.info.code_hash = bytecode.hash_slow();
// 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);

// 8. Increase the nonce of authority by one.
// 9. Increase the nonce of `authority` by one.
authority_acc.info.nonce = authority_acc.info.nonce.saturating_add(1);
authority_acc.mark_touch();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"comment": "`execution-spec-tests` generated test",
"filling-transition-tool": "ethereumjs t8n v1",
"description": "Test function documentation:\n\n Test the BLS12_G1ADD precompile using different call types.",
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-3@v1.4.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-4@v1.0.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
"reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md",
"reference-spec-version": "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e"
}
Expand Down Expand Up @@ -124,7 +124,7 @@
"comment": "`execution-spec-tests` generated test",
"filling-transition-tool": "ethereumjs t8n v1",
"description": "Test function documentation:\n\n Test the BLS12_G1ADD precompile using different call types.",
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-3@v1.4.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-4@v1.0.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
"reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md",
"reference-spec-version": "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e"
}
Expand Down Expand Up @@ -189,7 +189,7 @@
"comment": "`execution-spec-tests` generated test",
"filling-transition-tool": "ethereumjs t8n v1",
"description": "Test function documentation:\n\n Test the BLS12_G1ADD precompile using different call types.",
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-3@v1.4.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-4@v1.0.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
"reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md",
"reference-spec-version": "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"comment": "`execution-spec-tests` generated test",
"filling-transition-tool": "ethereumjs t8n v1",
"description": "Test function documentation:\n\n Test the BLS12_G1ADD precompile gas requirements.",
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-3@v1.4.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L170",
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-4@v1.0.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L170",
"reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md",
"reference-spec-version": "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e"
}
Expand Down Expand Up @@ -124,7 +124,7 @@
"comment": "`execution-spec-tests` generated test",
"filling-transition-tool": "ethereumjs t8n v1",
"description": "Test function documentation:\n\n Test the BLS12_G1ADD precompile gas requirements.",
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-3@v1.4.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L170",
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-4@v1.0.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L170",
"reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md",
"reference-spec-version": "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e"
}
Expand Down
Loading

0 comments on commit f551baf

Please sign in to comment.