Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(EIP-7702): devnet-4 changes #1821

Merged
merged 5 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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