Skip to content

Commit

Permalink
Feat: extend EIP-7702 (#73)
Browse files Browse the repository at this point in the history
* Extended EIP-7702 codebase with new EIP changes

* Update EIP-7702 tests

* CI: Added pectra-devnet-4 fixtures for

* Tests pectra-devnet-4

* Update fixtures_pectra-devnet-5 tests

* Added test exceptions fail. Added jsont-tests debug feature

* EXTCOPY, EXTHASH refactoring and tests

* Changes gas for EXTCODECOPY, EXTCODEHASH

* Auth nonce check - fix bounds for verification

* Auth code check for Empty Address case

* Auth list reconfigurations and more tests fixes

* Fixed auth-list cache bug

* Update to Rust 1.84 and fix clippy (#74)

* Added test skip for: set_code_to_non_empty_storage

* Fix clippy

* Remove TODO for CREATE tests

* Added test_ef01_hash
  • Loading branch information
mrLSD authored Jan 14, 2025
1 parent e4ee273 commit 22db6b5
Show file tree
Hide file tree
Showing 18 changed files with 156 additions and 112 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ jobs:

- name: Download Ethereum spec tests fixtures
run: |
wget https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-3%40v1.5.0/fixtures_pectra-devnet-3.tar.gz
wget https://github.com/ethereum/execution-spec-tests/releases/download/pectra-devnet-5%40v1.1.0/fixtures_pectra-devnet-5.tar.gz
mkdir ethereum-spec-tests
tar -xzf fixtures_pectra-devnet-3.tar.gz -C ethereum-spec-tests
tar -xzf fixtures_pectra-devnet-5.tar.gz -C ethereum-spec-tests
tree ethereum-spec-tests/fixtures/state_tests/prague/eip7702_set_code_tx
- name: Run Ethereum state tests
Expand Down
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ keywords.workspace = true
edition.workspace = true

[workspace.dependencies]
evm = { version = "0.46.2", path = "." }
evm-core = { version = "0.46.2", path = "core", default-features = false }
evm-gasometer = { version = "0.46.2", path = "gasometer", default-features = false }
evm-runtime = { version = "0.46.2", path = "runtime", default-features = false }
evm = { version = "0.46.3", path = "." }
evm-core = { version = "0.46.3", path = "core", default-features = false }
evm-gasometer = { version = "0.46.3", path = "gasometer", default-features = false }
evm-runtime = { version = "0.46.3", path = "runtime", default-features = false }
primitive-types = { version = "0.13", default-features = false }
auto_impl = "1.0"
sha3 = { version = "0.10", default-features = false }
Expand Down Expand Up @@ -84,7 +84,7 @@ create-fixed = []
print-debug = ["evm-gasometer/print-debug"]

[workspace.package]
version = "0.46.2"
version = "0.46.3"
license = "Apache-2.0"
authors = ["Aurora Labs <[email protected]>", "Wei Tang <[email protected]>", "Parity Technologies <[email protected]>"]
description = "Portable Ethereum Virtual Machine implementation written in pure Rust."
Expand Down
2 changes: 1 addition & 1 deletion evm-tests/EIP-152/src/portable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use crate::{IV, SIGMA};

/// The G mixing function. See https://tools.ietf.org/html/rfc7693#section-3.1
/// The G mixing function. See <https://tools.ietf.org/html/rfc7693#section-3.1>
#[inline(always)]
fn g(v: &mut [u64], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) {
v[a] = v[a].wrapping_add(v[b]).wrapping_add(x);
Expand Down
2 changes: 1 addition & 1 deletion evm-tests/ethjson/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl<'a> Deserialize<'a> for Bytes {

struct BytesVisitor;

impl<'a> Visitor<'a> for BytesVisitor {
impl Visitor<'_> for BytesVisitor {
type Value = Bytes;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
Expand Down
2 changes: 1 addition & 1 deletion evm-tests/ethjson/src/uint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl<'a> Deserialize<'a> for Uint {

struct UintVisitor;

impl<'a> Visitor<'a> for UintVisitor {
impl Visitor<'_> for UintVisitor {
type Value = Uint;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
Expand Down
1 change: 1 addition & 0 deletions evm-tests/jsontests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ hex-literal = "0.4"

[features]
enable-slow-tests = []
print-debug = ["evm/print-debug"]
12 changes: 12 additions & 0 deletions evm-tests/jsontests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ const SKIPPED_CASES: &[&str] = &[
// NOTE: this tests related to hard forks: London and before London
"stRevertTest/RevertPrecompiledTouch",
"stRevertTest/RevertPrecompiledTouch_storage",
// Wrong json fields `s`, `r` for EIP-7702
"eip7702_set_code_tx/set_code_txs/invalid_tx_invalid_auth_signature",
// Wrong json field `chain_id` for EIP-7702
"eip7702_set_code_tx/set_code_txs/tx_validity_nonce",
// EIP-7702: for non empty storage fails evm state hash check
"eip7702_set_code_tx/set_code_txs/set_code_to_non_empty_storage",
];

#[cfg(not(feature = "enable-slow-tests"))]
Expand All @@ -305,6 +311,12 @@ const SKIPPED_CASES: &[&str] = &[
"stTimeConsuming/static_Call50000_sha256",
"vmPerformance/loopMul",
"stTimeConsuming/CALLBlake2f_MaxRounds",
// Wrong json fields `s`, `r` for EIP-7702
"eip7702_set_code_tx/set_code_txs/invalid_tx_invalid_auth_signature",
// Wrong json field `chain_id` for EIP-7702
"eip7702_set_code_tx/set_code_txs/tx_validity_nonce",
// EIP-7702: for non empty storage fails evm state hash check
"eip7702_set_code_tx/set_code_txs/set_code_to_non_empty_storage",
];

/// Check if a path should be skipped.
Expand Down
13 changes: 10 additions & 3 deletions evm-tests/jsontests/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -942,7 +942,7 @@ fn check_validate_exit_reason(
) -> bool {
expect_exception.as_deref().map_or_else(
|| {
panic!("unexpected validation error reason: {reason:?}");
panic!("unexpected validation error reason: {reason:?} {name}");
},
|exception| {
match reason {
Expand Down Expand Up @@ -1034,6 +1034,13 @@ fn check_validate_exit_reason(
"unexpected exception {exception:?} for BlobVersionedHashesNotSupported for test: [{spec:?}] {name}"
);
},
InvalidTxReason::InvalidAuthorizationChain => {
let check_result = exception == "TransactionException.TYPE_4_INVALID_AUTHORIZATION_FORMAT";
assert!(
check_result,
"unexpected exception {exception:?} for InvalidAuthorizationChain for test: [{spec:?}] {name}"
);
}
InvalidTxReason::InvalidAuthorizationSignature => {
let check_result = exception == "TransactionException.TYPE_4_INVALID_AUTHORITY_SIGNATURE";
assert!(
Expand All @@ -1042,7 +1049,7 @@ fn check_validate_exit_reason(
);
}
InvalidTxReason::AuthorizationListNotExist => {
let check_result = exception == "TransactionException.TYPE_4_EMPTY_AUTHORIZATION_LIST";
let check_result = exception == "TransactionException.TYPE_4_EMPTY_AUTHORIZATION_LIST" || exception == "TransactionException.TYPE_4_TX_CONTRACT_CREATION";
assert!(
check_result,
"unexpected exception {exception:?} for AuthorizationListNotExist for test: [{spec:?}] {name}"
Expand Down Expand Up @@ -1175,7 +1182,7 @@ fn test_run(
// even if `caller_code` is non-empty transaction should be executed.
let is_delegated = original_state
.get(&caller)
.map_or(false, |c| Authorization::is_delegated(&c.code));
.is_some_and(|c| Authorization::is_delegated(&c.code));

for (i, state) in states.iter().enumerate() {
let transaction = test_tx.select(&state.indexes);
Expand Down
25 changes: 10 additions & 15 deletions evm-tests/jsontests/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,35 +447,29 @@ pub mod transaction {
return Err(InvalidTxReason::AuthorizationListNotExist);
}

// The field `to` deviates slightly from the semantics with the exception
// that it MUST NOT be nil and therefore must always represent
// a 20-byte address. This means that blob transactions cannot
// have the form of a create transaction.
let to_address: Option<Address> = test_tx.to.clone().into();
if to_address.is_none() {
return Err(InvalidTxReason::CreateTransaction);
}

// Check EIP-7702 Spec validation steps: 1 and 2
// Other validation step inside EVM transact logic.
for auth in test_tx.authorization_list.iter() {
// 1. Verify the chain id is either 0 or the chain’s current ID.
let mut is_valid =
auth.chain_id.0 == U256::from(0) || auth.chain_id.0 == vicinity.chain_id;
// 2. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]`
let mut is_valid = if auth.chain_id.0 > U256::from(u64::MAX) {
false
} else {
auth.chain_id.0 == U256::from(0) || auth.chain_id.0 == vicinity.chain_id
};
// 3. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]`

// Validate the signature, as in tests it is possible to have invalid signatures values.
let v = auth.v.0 .0;
if !(v[0] < u64::from(u8::MAX) && v[1..4].iter().all(|&elem| elem == 0)) {
return Err(InvalidTxReason::InvalidAuthorizationSignature);
is_valid = false;
}
// Value `v` shouldn't be greater then 1
if v[0] > 1 {
return Err(InvalidTxReason::InvalidAuthorizationSignature);
is_valid = false;
}
// EIP-2 validation
if auth.s.0 > eip7702::SECP256K1N_HALF {
return Err(InvalidTxReason::InvalidAuthorizationSignature);
is_valid = false;
}

let auth_address = eip7702::SignedAuthorization::new(
Expand Down Expand Up @@ -549,6 +543,7 @@ pub mod transaction {
GasPriseEip1559,
AuthorizationListNotExist,
AuthorizationListNotSupported,
InvalidAuthorizationChain,
InvalidAuthorizationSignature,
CreateTransaction,
}
Expand Down
4 changes: 2 additions & 2 deletions fuzzer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use evm_core::Machine;
use std::rc::Rc;

fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
return haystack
haystack
.windows(needle.len())
.position(|window| window == needle);
.position(|window| window == needle)
}

fn split_at_delim(sequence: &[u8], delim: &[u8]) -> (Vec<u8>, Vec<u8>) {
Expand Down
57 changes: 21 additions & 36 deletions gasometer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,16 @@ fn get_and_set_warm<H: Handler>(handler: &mut H, target: H160) -> (bool, Option<
(target_is_cold, delegated_designator_is_cold)
}

/// Get and set warm address if it's not warmed for non-delegated opcodes like `EXT*`.
/// NOTE: Related to EIP-7702
fn get_and_set_non_delegated_warm<H: Handler>(handler: &mut H, target: H160) -> bool {
let target_is_cold = handler.is_cold(target, None);
if target_is_cold {
handler.warm_target((target, None));
}
target_is_cold
}

/// Calculate the opcode cost.
///
/// # Errors
Expand Down Expand Up @@ -674,11 +684,8 @@ pub fn dynamic_opcode_cost<H: Handler>(

Opcode::EXTCODESIZE => {
let target = stack.peek_h256(0)?.into();
let (target_is_cold, delegated_designator_is_cold) = get_and_set_warm(handler, target);
GasCost::ExtCodeSize {
target_is_cold,
delegated_designator_is_cold,
}
let target_is_cold = get_and_set_non_delegated_warm(handler, target);
GasCost::ExtCodeSize { target_is_cold }
}
Opcode::BALANCE => {
let target = stack.peek_h256(0)?.into();
Expand All @@ -692,11 +699,8 @@ pub fn dynamic_opcode_cost<H: Handler>(

Opcode::EXTCODEHASH if config.has_ext_code_hash => {
let target = stack.peek_h256(0)?.into();
let (target_is_cold, delegated_designator_is_cold) = get_and_set_warm(handler, target);
GasCost::ExtCodeHash {
target_is_cold,
delegated_designator_is_cold,
}
let target_is_cold = get_and_set_non_delegated_warm(handler, target);
GasCost::ExtCodeHash { target_is_cold }
}
Opcode::EXTCODEHASH => GasCost::Invalid(opcode),

Expand Down Expand Up @@ -732,10 +736,9 @@ pub fn dynamic_opcode_cost<H: Handler>(
},
Opcode::EXTCODECOPY => {
let target = stack.peek_h256(0)?.into();
let (target_is_cold, delegated_designator_is_cold) = get_and_set_warm(handler, target);
let target_is_cold = get_and_set_non_delegated_warm(handler, target);
GasCost::ExtCodeCopy {
target_is_cold,
delegated_designator_is_cold,
len: stack.peek(3)?,
}
}
Expand Down Expand Up @@ -934,7 +937,7 @@ struct Inner<'config> {
config: &'config Config,
}

impl<'config> Inner<'config> {
impl Inner<'_> {
fn memory_gas(&self, memory: MemoryCost) -> Result<u64, ExitError> {
let from = memory.offset;
let len = memory.len;
Expand Down Expand Up @@ -1051,38 +1054,26 @@ impl<'config> Inner<'config> {
GasCost::Low => u64::from(consts::G_LOW),
GasCost::Invalid(opcode) => return Err(ExitError::InvalidCode(opcode)),

GasCost::ExtCodeSize {
target_is_cold,
delegated_designator_is_cold,
} => costs::address_access_cost(
GasCost::ExtCodeSize { target_is_cold } => costs::address_access_cost(
target_is_cold,
delegated_designator_is_cold,
None,
self.config.gas_ext_code,
self.config,
),
GasCost::ExtCodeCopy {
target_is_cold,
delegated_designator_is_cold,
len,
} => costs::extcodecopy_cost(
len,
target_is_cold,
delegated_designator_is_cold,
self.config,
)?,
} => costs::extcodecopy_cost(len, target_is_cold, None, self.config)?,
GasCost::Balance { target_is_cold } => costs::address_access_cost(
target_is_cold,
None,
self.config.gas_balance,
self.config,
),
GasCost::BlockHash => u64::from(consts::G_BLOCKHASH),
GasCost::ExtCodeHash {
target_is_cold,
delegated_designator_is_cold,
} => costs::address_access_cost(
GasCost::ExtCodeHash { target_is_cold } => costs::address_access_cost(
target_is_cold,
delegated_designator_is_cold,
None,
self.config.gas_ext_code_hash,
self.config,
),
Expand Down Expand Up @@ -1126,8 +1117,6 @@ pub enum GasCost {
ExtCodeSize {
/// True if address has not been previously accessed in this transaction
target_is_cold: bool,
/// True if delegated designator of authority has not been previously accessed in this transaction (EIP-7702)
delegated_designator_is_cold: Option<bool>,
},
/// Gas cost for `BALANCE`.
Balance {
Expand All @@ -1140,8 +1129,6 @@ pub enum GasCost {
ExtCodeHash {
/// True if address has not been previously accessed in this transaction
target_is_cold: bool,
/// True if delegated designator of authority has not been previously accessed in this transaction (EIP-7702)
delegated_designator_is_cold: Option<bool>,
},

/// Gas cost for `CALL`.
Expand Down Expand Up @@ -1230,8 +1217,6 @@ pub enum GasCost {
ExtCodeCopy {
/// True if target has not been previously accessed in this transaction
target_is_cold: bool,
/// True if delegated designator of authority has not been previously accessed in this transaction (EIP-7702)
delegated_designator_is_cold: Option<bool>,
/// Length.
len: U256,
},
Expand Down
7 changes: 5 additions & 2 deletions runtime/src/eval/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,21 +126,24 @@ pub fn blob_hash<H: Handler>(runtime: &mut Runtime, handler: &H) -> Control<H> {
Control::Continue
}

/// NOTE: For EIP-7702 should return 2 (size of `0xEF01`)
pub fn extcodesize<H: Handler>(runtime: &mut Runtime, handler: &mut H) -> Control<H> {
pop_h256!(runtime, address);
push_u256!(runtime, handler.code_size(address.into()));

Control::Continue
}

/// NOTE: For EIP-7702 should return `keccak(0xEF01)`
pub fn extcodehash<H: Handler>(runtime: &mut Runtime, handler: &mut H) -> Control<H> {
pop_h256!(runtime, address);
push_h256!(runtime, handler.code_hash(address.into()));

Control::Continue
}

pub fn extcodecopy<H: Handler>(runtime: &mut Runtime, handler: &mut H) -> Control<H> {
/// NOTE: For EIP-7702 should not copy from designated address
pub fn extcodecopy<H: Handler>(runtime: &mut Runtime, handler: &H) -> Control<H> {
pop_h256!(runtime, address);
pop_u256!(runtime, memory_offset, code_offset, len);

Expand All @@ -160,7 +163,7 @@ pub fn extcodecopy<H: Handler>(runtime: &mut Runtime, handler: &mut H) -> Contro
memory_offset,
code_offset,
len,
&handler.authority_code(address.into()),
&handler.code(address.into()),
) {
Ok(()) => (),
Err(e) => return Control::Exit(e.into()),
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,7 @@ impl DerivedConfigInputs {
let mut config = Self::cancun();
config.has_authorization_list = true;
config.gas_per_empty_account_cost = 25000;
config.gas_per_auth_base_cost = 2500;
config.gas_per_auth_base_cost = 12500;
config
}
}
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[toolchain]
channel = "1.82.0"
channel = "1.84.0"
profile = "minimal"
components = ["rustfmt", "clippy"]
Loading

0 comments on commit 22db6b5

Please sign in to comment.