From abf286c1e77237cf4053e5056229fe46ed9c2c1f Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 15:01:08 +0200 Subject: [PATCH 01/23] feat: add eip-7702 auth list types --- crates/eips/README.md | 1 + crates/eips/src/eip7702/auth_list.rs | 112 +++++++++++++++++++++++++++ crates/eips/src/eip7702/constants.rs | 18 +++++ crates/eips/src/eip7702/mod.rs | 8 ++ crates/eips/src/lib.rs | 6 +- 5 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 crates/eips/src/eip7702/auth_list.rs create mode 100644 crates/eips/src/eip7702/constants.rs create mode 100644 crates/eips/src/eip7702/mod.rs diff --git a/crates/eips/README.md b/crates/eips/README.md index f516218b417..4956250ee6c 100644 --- a/crates/eips/README.md +++ b/crates/eips/README.md @@ -18,3 +18,4 @@ Contains constants, helpers, and basic data structures for consensus EIPs. - [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) - [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) - [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) +- [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs new file mode 100644 index 00000000000..b6ae4bf80fd --- /dev/null +++ b/crates/eips/src/eip7702/auth_list.rs @@ -0,0 +1,112 @@ +use core::ops::Deref; + +use alloy_primitives::{keccak256, Address, ChainId, Signature, SignatureError, B256}; +use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpEncodable}; + +use super::constants::MAGIC; + +/// An EIP-7702 authorization list. +pub type AuthorizationList = Vec; + +/// An EIP-7702 authorization. +#[derive(Debug, Clone, RlpEncodable)] +pub struct Authorization { + chain_id: ChainId, + address: Address, + nonce: OptionalNonce, + signature: Signature, +} + +impl Authorization { + /// Get the `chain_id` for the authorization. + /// + /// # Note + /// + /// Implementers should check that this matches the current `chain_id` *or* is 0. + pub fn chain_id(&self) -> ChainId { + self.chain_id + } + + /// Get the `address` for the authorization. + pub fn address(&self) -> &Address { + &self.address + } + + /// Get the `nonce` for the authorization. + /// + /// # Note + /// + /// If this is `Some`, implementers should check that the nonce of the authority (see [`Self::recover_authority`]) is equal to this nonce. + pub fn nonce(&self) -> Option { + *self.nonce + } + + /// Get the `signature` for the authorization. + pub fn signature(&self) -> &Signature { + &self.signature + } + + /// Computes the authority prehash used to recover the authority from an authorization list item. + /// + /// The authority prehash is `keccak(MAGIC || rlp([chain_id, [nonce], address]))` + #[inline] + fn authority_prehash(&self) -> B256 { + #[derive(RlpEncodable)] + struct Auth { + chain_id: ChainId, + nonce: OptionalNonce, + address: Address, + } + + let mut buf = Vec::new(); + buf.put_u8(MAGIC); + + Auth { chain_id: self.chain_id, nonce: self.nonce, address: self.address }.encode(&mut buf); + + keccak256(buf) + } + + /// Recover the authority for the authorization. + /// + /// # Note + /// + /// Implementers should check that the authority has no code. + pub fn recover_authority(&self) -> Result { + self.signature.recover_address_from_prehash(&self.authority_prehash()) + } +} + +/// An internal wrapper around an `Option` for optional nonces. +/// +/// In EIP-7702 the nonce is encoded as a list of either 0 or 1 items, where 0 items means that no nonce was specified (i.e. `None`). If there is 1 item, this is the same as `Some`. +/// +/// The wrapper type is used for RLP encoding and decoding. +#[derive(Debug, Copy, Clone)] +struct OptionalNonce(Option); + +impl Encodable for OptionalNonce { + fn encode(&self, out: &mut dyn BufMut) { + match self.0 { + Some(nonce) => { + Header { list: true, payload_length: nonce.length() }.encode(out); + nonce.encode(out); + } + None => Header { list: true, payload_length: 0 }.encode(out), + } + } +} + +impl Decodable for OptionalNonce { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let list: Vec = Vec::decode(buf)?; + Ok(Self(list.first().copied())) + } +} + +impl Deref for OptionalNonce { + type Target = Option; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/crates/eips/src/eip7702/constants.rs b/crates/eips/src/eip7702/constants.rs new file mode 100644 index 00000000000..18b0453f514 --- /dev/null +++ b/crates/eips/src/eip7702/constants.rs @@ -0,0 +1,18 @@ +//! [EIP-7702] constants. +//! +//! [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702 + +/// Identifier for EIP7702's set code transaction. +/// +/// See also [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702). +pub const EIP7702_TX_TYPE_ID: u8 = 4; + +/// Magic number used to calculate an EIP7702 authority. +/// +/// See also [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702). +pub const MAGIC: u8 = 0x05; + +/// An additional gas cost per EIP7702 authorization list item. +/// +/// See also [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702). +pub const PER_AUTH_BASE_COST: u64 = 2500; diff --git a/crates/eips/src/eip7702/mod.rs b/crates/eips/src/eip7702/mod.rs new file mode 100644 index 00000000000..26ad27a883c --- /dev/null +++ b/crates/eips/src/eip7702/mod.rs @@ -0,0 +1,8 @@ +//! [EIP-7702] constants, helpers, and types. +//! +//! [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702 + +mod auth_list; +pub use auth_list::*; + +pub mod constants; diff --git a/crates/eips/src/lib.rs b/crates/eips/src/lib.rs index 0ad80222275..393ba4f343e 100644 --- a/crates/eips/src/lib.rs +++ b/crates/eips/src/lib.rs @@ -27,11 +27,11 @@ pub mod eip2935; pub mod eip4788; -pub mod eip4895; - pub mod eip4844; pub use eip4844::{calc_blob_gasprice, calc_excess_blob_gas}; +pub mod eip4895; + pub mod eip6110; pub mod merge; @@ -40,3 +40,5 @@ pub mod eip7002; pub mod eip7251; pub mod eip7685; + +pub mod eip7702; From e2806d643ebd2fe43b7021a9dcc0f1448c4b8c39 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 15:02:48 +0200 Subject: [PATCH 02/23] chore: fmt --- crates/eips/src/eip7702/auth_list.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index b6ae4bf80fd..230fbf0ac5d 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -36,7 +36,8 @@ impl Authorization { /// /// # Note /// - /// If this is `Some`, implementers should check that the nonce of the authority (see [`Self::recover_authority`]) is equal to this nonce. + /// If this is `Some`, implementers should check that the nonce of the authority (see + /// [`Self::recover_authority`]) is equal to this nonce. pub fn nonce(&self) -> Option { *self.nonce } @@ -46,7 +47,8 @@ impl Authorization { &self.signature } - /// Computes the authority prehash used to recover the authority from an authorization list item. + /// Computes the authority prehash used to recover the authority from an authorization list + /// item. /// /// The authority prehash is `keccak(MAGIC || rlp([chain_id, [nonce], address]))` #[inline] @@ -78,7 +80,8 @@ impl Authorization { /// An internal wrapper around an `Option` for optional nonces. /// -/// In EIP-7702 the nonce is encoded as a list of either 0 or 1 items, where 0 items means that no nonce was specified (i.e. `None`). If there is 1 item, this is the same as `Some`. +/// In EIP-7702 the nonce is encoded as a list of either 0 or 1 items, where 0 items means that no +/// nonce was specified (i.e. `None`). If there is 1 item, this is the same as `Some`. /// /// The wrapper type is used for RLP encoding and decoding. #[derive(Debug, Copy, Clone)] From 1d1fe7ef0da2f3e14823e127c12295b3c880d1cc Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 15:29:30 +0200 Subject: [PATCH 03/23] chore: no_std --- crates/eips/src/eip7702/auth_list.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 230fbf0ac5d..c9b12d5d0a7 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -1,5 +1,7 @@ use core::ops::Deref; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; use alloy_primitives::{keccak256, Address, ChainId, Signature, SignatureError, B256}; use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpEncodable}; From bca4239748122850bfbbb923a57f6afa0ba7ee8f Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 15:56:54 +0200 Subject: [PATCH 04/23] feat: add k256 feature to alloy-eips --- crates/alloy/Cargo.toml | 9 +++++---- crates/consensus/Cargo.toml | 2 +- crates/eips/Cargo.toml | 1 + crates/eips/src/eip7702/auth_list.rs | 2 ++ crates/rpc-types-eth/Cargo.toml | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/alloy/Cargo.toml b/crates/alloy/Cargo.toml index d2da45e5c4a..c925959a88d 100644 --- a/crates/alloy/Cargo.toml +++ b/crates/alloy/Cargo.toml @@ -85,10 +85,10 @@ full = [ "kzg", "network", "provider-http", # includes `providers` - "provider-ws", # includes `providers` - "provider-ipc", # includes `providers` - "rpc-types", # includes `rpc-types-eth` - "signer-local", # includes `signers` + "provider-ws", # includes `providers` + "provider-ipc", # includes `providers` + "rpc-types", # includes `rpc-types-eth` + "signer-local", # includes `signers` ] # configuration @@ -218,6 +218,7 @@ arbitrary = [ k256 = [ "alloy-core/k256", "alloy-consensus?/k256", + "alloy-eips?/k256", "alloy-network?/k256", "alloy-rpc-types?/k256", ] diff --git a/crates/consensus/Cargo.toml b/crates/consensus/Cargo.toml index beeb8adfa9b..1bc962c4911 100644 --- a/crates/consensus/Cargo.toml +++ b/crates/consensus/Cargo.toml @@ -50,7 +50,7 @@ serde_json.workspace = true [features] default = ["std"] std = ["alloy-eips/std", "c-kzg?/std"] -k256 = ["alloy-primitives/k256"] +k256 = ["alloy-primitives/k256", "alloy-eips/k256"] kzg = ["dep:c-kzg", "alloy-eips/kzg", "std"] arbitrary = [ "std", diff --git a/crates/eips/Cargo.toml b/crates/eips/Cargo.toml index 08c9a303233..26f1c293264 100644 --- a/crates/eips/Cargo.toml +++ b/crates/eips/Cargo.toml @@ -70,6 +70,7 @@ serde = [ kzg = ["kzg-sidecar", "sha2", "dep:derive_more", "dep:c-kzg", "dep:once_cell"] kzg-sidecar = ["sha2"] sha2 = ["dep:sha2"] +k256 = ["alloy-primitives/k256"] ssz = [ "std", "dep:ethereum_ssz", diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index c9b12d5d0a7..29a1ead9573 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -54,6 +54,7 @@ impl Authorization { /// /// The authority prehash is `keccak(MAGIC || rlp([chain_id, [nonce], address]))` #[inline] + #[cfg(feature = "k256")] fn authority_prehash(&self) -> B256 { #[derive(RlpEncodable)] struct Auth { @@ -75,6 +76,7 @@ impl Authorization { /// # Note /// /// Implementers should check that the authority has no code. + #[cfg(feature = "k256")] pub fn recover_authority(&self) -> Result { self.signature.recover_address_from_prehash(&self.authority_prehash()) } diff --git a/crates/rpc-types-eth/Cargo.toml b/crates/rpc-types-eth/Cargo.toml index 63bf186f99d..2e2b9a759e6 100644 --- a/crates/rpc-types-eth/Cargo.toml +++ b/crates/rpc-types-eth/Cargo.toml @@ -66,4 +66,4 @@ arbitrary = [ ] jsonrpsee-types = ["dep:jsonrpsee-types"] ssz = ["alloy-primitives/ssz", "alloy-eips/ssz"] -k256 = ["alloy-consensus/k256"] +k256 = ["alloy-consensus/k256", "alloy-eips/k256"] From 12a3a23fa60ce6bb7ae81d26c48ddd98b110398d Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 16:02:09 +0200 Subject: [PATCH 05/23] chore: const fns --- crates/eips/src/eip7702/auth_list.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 29a1ead9573..2d2199c6fb3 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -25,12 +25,12 @@ impl Authorization { /// # Note /// /// Implementers should check that this matches the current `chain_id` *or* is 0. - pub fn chain_id(&self) -> ChainId { + pub const fn chain_id(&self) -> ChainId { self.chain_id } /// Get the `address` for the authorization. - pub fn address(&self) -> &Address { + pub const fn address(&self) -> &Address { &self.address } @@ -45,7 +45,7 @@ impl Authorization { } /// Get the `signature` for the authorization. - pub fn signature(&self) -> &Signature { + pub const fn signature(&self) -> &Signature { &self.signature } From 565b7915180f488671ced0d3586cc4cb3274738c Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 16:24:11 +0200 Subject: [PATCH 06/23] chore: lint --- crates/eips/src/eip7702/auth_list.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 2d2199c6fb3..50fc074badc 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -2,11 +2,11 @@ use core::ops::Deref; #[cfg(not(feature = "std"))] use alloc::vec::Vec; -use alloy_primitives::{keccak256, Address, ChainId, Signature, SignatureError, B256}; +#[cfg(feature = "k256")] +use alloy_primitives::{keccak256, SignatureError, B256}; +use alloy_primitives::{Address, ChainId, Signature}; use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpEncodable}; -use super::constants::MAGIC; - /// An EIP-7702 authorization list. pub type AuthorizationList = Vec; @@ -56,6 +56,8 @@ impl Authorization { #[inline] #[cfg(feature = "k256")] fn authority_prehash(&self) -> B256 { + use super::constants::MAGIC; + #[derive(RlpEncodable)] struct Auth { chain_id: ChainId, From 5620b5938db67c6ac535e625af18bc520c516788 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 16:24:23 +0200 Subject: [PATCH 07/23] chore: rm vec type alias --- crates/eips/src/eip7702/auth_list.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 50fc074badc..186c4aa5627 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -7,9 +7,6 @@ use alloy_primitives::{keccak256, SignatureError, B256}; use alloy_primitives::{Address, ChainId, Signature}; use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpEncodable}; -/// An EIP-7702 authorization list. -pub type AuthorizationList = Vec; - /// An EIP-7702 authorization. #[derive(Debug, Clone, RlpEncodable)] pub struct Authorization { From 66eafe60c855bcf3abf7dd63a6f3f9b174a2ee3c Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 16:36:29 +0200 Subject: [PATCH 08/23] feat: split into signed/unsigned types --- crates/eips/src/eip7702/auth_list.rs | 56 +++++++++++++++++++--------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 186c4aa5627..d3d2a2ec461 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -2,18 +2,17 @@ use core::ops::Deref; #[cfg(not(feature = "std"))] use alloc::vec::Vec; +use alloy_primitives::{keccak256, Address, ChainId, B256}; #[cfg(feature = "k256")] -use alloy_primitives::{keccak256, SignatureError, B256}; -use alloy_primitives::{Address, ChainId, Signature}; -use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpEncodable}; +use alloy_primitives::{Signature, SignatureError}; +use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpDecodable, RlpEncodable}; -/// An EIP-7702 authorization. -#[derive(Debug, Clone, RlpEncodable)] +/// An unsigned EIP-7702 authorization. +#[derive(Debug, Clone, RlpEncodable, RlpDecodable)] pub struct Authorization { chain_id: ChainId, address: Address, nonce: OptionalNonce, - signature: Signature, } impl Authorization { @@ -41,18 +40,12 @@ impl Authorization { *self.nonce } - /// Get the `signature` for the authorization. - pub const fn signature(&self) -> &Signature { - &self.signature - } - - /// Computes the authority prehash used to recover the authority from an authorization list + /// Computes the signature hash used to sign the authorization, or recover the authority from a signed authorization list /// item. /// - /// The authority prehash is `keccak(MAGIC || rlp([chain_id, [nonce], address]))` + /// The signature hash is `keccak(MAGIC || rlp([chain_id, [nonce], address]))` #[inline] - #[cfg(feature = "k256")] - fn authority_prehash(&self) -> B256 { + pub fn signature_hash(&self) -> B256 { use super::constants::MAGIC; #[derive(RlpEncodable)] @@ -70,14 +63,43 @@ impl Authorization { keccak256(buf) } + /// Convert to a signed authorization by adding a signature. + pub fn into_signed(self, signature: S) -> SignedAuthorization { + SignedAuthorization { inner: self, signature } + } +} + +/// A signed EIP-7702 authorization. +#[derive(Debug, Clone, RlpEncodable, RlpDecodable)] +pub struct SignedAuthorization { + inner: Authorization, + signature: S, +} + +impl SignedAuthorization { + /// Get the `signature` for the authorization. + pub const fn signature(&self) -> &S { + &self.signature + } +} + +#[cfg(feature = "k256")] +impl SignedAuthorization { /// Recover the authority for the authorization. /// /// # Note /// /// Implementers should check that the authority has no code. - #[cfg(feature = "k256")] pub fn recover_authority(&self) -> Result { - self.signature.recover_address_from_prehash(&self.authority_prehash()) + self.signature.recover_address_from_prehash(&self.inner.authority_prehash()) + } +} + +impl Deref for SignedAuthorization { + type Target = Authorization; + + fn deref(&self) -> &Self::Target { + &self.inner } } From 60e787efe5b51ed16686087949079ab45edbd2c7 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 16:37:44 +0200 Subject: [PATCH 09/23] fix: use `signature_prehash` --- crates/eips/src/eip7702/auth_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index d3d2a2ec461..d7de9558dda 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -91,7 +91,7 @@ impl SignedAuthorization { /// /// Implementers should check that the authority has no code. pub fn recover_authority(&self) -> Result { - self.signature.recover_address_from_prehash(&self.inner.authority_prehash()) + self.signature.recover_address_from_prehash(&self.inner.signature_hash()) } } From 9b7b806ef990dc5750672a0fb4c6ff4f122e6af6 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 16:38:51 +0200 Subject: [PATCH 10/23] chore: fmt --- crates/eips/src/eip7702/auth_list.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index d7de9558dda..8e0151da7cc 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -40,8 +40,8 @@ impl Authorization { *self.nonce } - /// Computes the signature hash used to sign the authorization, or recover the authority from a signed authorization list - /// item. + /// Computes the signature hash used to sign the authorization, or recover the authority from a + /// signed authorization list item. /// /// The signature hash is `keccak(MAGIC || rlp([chain_id, [nonce], address]))` #[inline] From b73b4ff60f09b3b10f5287c9c9f098bd40e64b57 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 16:39:09 +0200 Subject: [PATCH 11/23] chore: const fn --- crates/eips/src/eip7702/auth_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 8e0151da7cc..728be178b7f 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -64,7 +64,7 @@ impl Authorization { } /// Convert to a signed authorization by adding a signature. - pub fn into_signed(self, signature: S) -> SignedAuthorization { + pub const fn into_signed(self, signature: S) -> SignedAuthorization { SignedAuthorization { inner: self, signature } } } From 1e462697c687692ee2901daae95db4af8e17d286 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 16:41:01 +0200 Subject: [PATCH 12/23] docs: fix doc link --- crates/eips/src/eip7702/auth_list.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 728be178b7f..82c95fee207 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -34,8 +34,7 @@ impl Authorization { /// /// # Note /// - /// If this is `Some`, implementers should check that the nonce of the authority (see - /// [`Self::recover_authority`]) is equal to this nonce. + /// If this is `Some`, implementers should check that the nonce of the authority is equal to this nonce. pub fn nonce(&self) -> Option { *self.nonce } From f4962d93a10a43eeb0311478f4a8f995d129b377 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 16:46:30 +0200 Subject: [PATCH 13/23] chore: fmt -.- --- crates/eips/src/eip7702/auth_list.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 82c95fee207..71eef034480 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -34,7 +34,8 @@ impl Authorization { /// /// # Note /// - /// If this is `Some`, implementers should check that the nonce of the authority is equal to this nonce. + /// If this is `Some`, implementers should check that the nonce of the authority is equal to + /// this nonce. pub fn nonce(&self) -> Option { *self.nonce } From 0140aa1918a3ad1fe4ddd7adf72330e1e88daa12 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 20:14:46 +0200 Subject: [PATCH 14/23] chore: make pub --- crates/eips/src/eip7702/auth_list.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 71eef034480..853bb1031db 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -10,9 +10,9 @@ use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpDecodable, RlpEncodable /// An unsigned EIP-7702 authorization. #[derive(Debug, Clone, RlpEncodable, RlpDecodable)] pub struct Authorization { - chain_id: ChainId, - address: Address, - nonce: OptionalNonce, + pub chain_id: ChainId, + pub address: Address, + pub nonce: OptionalNonce, } impl Authorization { @@ -110,7 +110,20 @@ impl Deref for SignedAuthorization { /// /// The wrapper type is used for RLP encoding and decoding. #[derive(Debug, Copy, Clone)] -struct OptionalNonce(Option); +pub struct OptionalNonce(Option); + +impl OptionalNonce { + /// Create a new [`OptionalNonce`] + pub fn new(nonce: Option) -> Self { + Self(nonce) + } +} + +impl From> for OptionalNonce { + fn from(value: Option) -> Self { + Self::new(value) + } +} impl Encodable for OptionalNonce { fn encode(&self, out: &mut dyn BufMut) { From 8512e8c0ff5905c3731698259e835d0a87bfbe00 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 20:15:49 +0200 Subject: [PATCH 15/23] chore: cleanup --- crates/eips/src/eip7702/auth_list.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 853bb1031db..608c980887e 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -3,15 +3,16 @@ use core::ops::Deref; #[cfg(not(feature = "std"))] use alloc::vec::Vec; use alloy_primitives::{keccak256, Address, ChainId, B256}; -#[cfg(feature = "k256")] -use alloy_primitives::{Signature, SignatureError}; use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpDecodable, RlpEncodable}; /// An unsigned EIP-7702 authorization. #[derive(Debug, Clone, RlpEncodable, RlpDecodable)] pub struct Authorization { + /// The chain ID of the authorization. pub chain_id: ChainId, + /// The address of the authorization. pub address: Address, + /// The nonce for the authorization. pub nonce: OptionalNonce, } @@ -84,13 +85,13 @@ impl SignedAuthorization { } #[cfg(feature = "k256")] -impl SignedAuthorization { +impl SignedAuthorization { /// Recover the authority for the authorization. /// /// # Note /// /// Implementers should check that the authority has no code. - pub fn recover_authority(&self) -> Result { + pub fn recover_authority(&self) -> Result { self.signature.recover_address_from_prehash(&self.inner.signature_hash()) } } From a21ddb7e55eac50f87d98ae549126319e4adc5f0 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 20:16:48 +0200 Subject: [PATCH 16/23] fix: error on list longer than 1 for nonce --- crates/eips/src/eip7702/auth_list.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 608c980887e..1c946435f12 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -141,7 +141,11 @@ impl Encodable for OptionalNonce { impl Decodable for OptionalNonce { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { let list: Vec = Vec::decode(buf)?; - Ok(Self(list.first().copied())) + if list.len() > 1 { + Err(alloy_rlp::Error::UnexpectedLength) + } else { + Ok(Self(list.first().copied())) + } } } From 44fcdb2ae50f8165fa037018da51fc7ceeff02a8 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 20:21:02 +0200 Subject: [PATCH 17/23] chore: const fn -.- --- crates/eips/src/eip7702/auth_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 1c946435f12..622174a994e 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -115,7 +115,7 @@ pub struct OptionalNonce(Option); impl OptionalNonce { /// Create a new [`OptionalNonce`] - pub fn new(nonce: Option) -> Self { + pub const fn new(nonce: Option) -> Self { Self(nonce) } } From 58fef6db8cc6f5d0d1ebe4a2426389d7a94b8573 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 22:36:37 +0200 Subject: [PATCH 18/23] feat: default derive --- crates/eips/src/eip7702/auth_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 622174a994e..600f3d7c808 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -110,7 +110,7 @@ impl Deref for SignedAuthorization { /// nonce was specified (i.e. `None`). If there is 1 item, this is the same as `Some`. /// /// The wrapper type is used for RLP encoding and decoding. -#[derive(Debug, Copy, Clone)] +#[derive(Default, Debug, Copy, Clone)] pub struct OptionalNonce(Option); impl OptionalNonce { From 5fd3860af064454858da4fbb89635ad9c1199f52 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Thu, 20 Jun 2024 22:45:14 +0200 Subject: [PATCH 19/23] chore: manual decode impl for `OptionalNonce` --- crates/eips/src/eip7702/auth_list.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 600f3d7c808..26ae68d1470 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -140,11 +140,18 @@ impl Encodable for OptionalNonce { impl Decodable for OptionalNonce { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let list: Vec = Vec::decode(buf)?; - if list.len() > 1 { + let mut bytes = Header::decode_bytes(buf, true)?; + if bytes.is_empty() { + return Ok(Self(None)); + } + + let payload_view = &mut bytes; + let nonce = u64::decode(payload_view)?; + if !payload_view.is_empty() { + // if there's more than 1 item in the nonce list we error Err(alloy_rlp::Error::UnexpectedLength) } else { - Ok(Self(list.first().copied())) + Ok(Self(Some(nonce))) } } } From 7186591643cd7f1680622627dcf896b2a021b415 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 21 Jun 2024 03:06:45 +0200 Subject: [PATCH 20/23] docs: small driveby link nits --- crates/eips/src/eip6110.rs | 2 +- crates/eips/src/eip7251.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/eips/src/eip6110.rs b/crates/eips/src/eip6110.rs index 0f39db8ad10..f04c02d1897 100644 --- a/crates/eips/src/eip6110.rs +++ b/crates/eips/src/eip6110.rs @@ -1,4 +1,4 @@ -//! Contains Deposit types, first introduced in the Prague hardfork: +//! Contains Deposit types, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md). //! //! See also [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110): Supply validator deposits on chain //! diff --git a/crates/eips/src/eip7251.rs b/crates/eips/src/eip7251.rs index 27c51210345..c62c2c641c4 100644 --- a/crates/eips/src/eip7251.rs +++ b/crates/eips/src/eip7251.rs @@ -1,4 +1,4 @@ -//! Contains consolidtion types, first introduced in the Prague hardfork: +//! Contains consolidtion types, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md). //! //! See also [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251): Increase the MAX_EFFECTIVE_BALANCE From 3172967488088f415ca40606089735b297d67377 Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Fri, 21 Jun 2024 10:32:25 +0200 Subject: [PATCH 21/23] test: add small rt ser/de tests --- crates/eips/src/eip7702/auth_list.rs | 45 ++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 26ae68d1470..b4af680a9c6 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -6,7 +6,7 @@ use alloy_primitives::{keccak256, Address, ChainId, B256}; use alloy_rlp::{BufMut, Decodable, Encodable, Header, RlpDecodable, RlpEncodable}; /// An unsigned EIP-7702 authorization. -#[derive(Debug, Clone, RlpEncodable, RlpDecodable)] +#[derive(Debug, Clone, RlpEncodable, RlpDecodable, Eq, PartialEq)] pub struct Authorization { /// The chain ID of the authorization. pub chain_id: ChainId, @@ -110,7 +110,7 @@ impl Deref for SignedAuthorization { /// nonce was specified (i.e. `None`). If there is 1 item, this is the same as `Some`. /// /// The wrapper type is used for RLP encoding and decoding. -#[derive(Default, Debug, Copy, Clone)] +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] pub struct OptionalNonce(Option); impl OptionalNonce { @@ -163,3 +163,44 @@ impl Deref for OptionalNonce { &self.0 } } + +#[cfg(test)] +mod tests { + use super::*; + + fn test_encode_decode_roundtrip(auth: Authorization) { + let mut buf = Vec::new(); + auth.encode(&mut buf); + let decoded = Authorization::decode(&mut buf.as_ref()).unwrap(); + assert_eq!(buf.len(), auth.length()); + assert_eq!(decoded, auth); + } + + #[test] + fn test_encode_decode_auth() { + // fully filled + test_encode_decode_roundtrip(Authorization { + chain_id: 1u64, + address: Address::left_padding_from(&[6]), + nonce: Some(1u64).into(), + }); + + // no nonce + test_encode_decode_roundtrip(Authorization { + chain_id: 1u64, + address: Address::left_padding_from(&[6]), + nonce: None.into(), + }); + } + + #[test] + fn opt_nonce_too_many_elements() { + let mut buf = Vec::new(); + vec![1u64, 2u64].encode(&mut buf); + + assert_eq!( + OptionalNonce::decode(&mut buf.as_ref()), + Err(alloy_rlp::Error::UnexpectedLength) + ) + } +} From b091519af2c64f5c5848b9b220151a3c966d586d Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 24 Jun 2024 13:00:59 +0200 Subject: [PATCH 22/23] feat: add `RecoveredAuthorization` --- crates/eips/src/eip7702/auth_list.rs | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index b4af680a9c6..38c06832627 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -94,6 +94,12 @@ impl SignedAuthorization { pub fn recover_authority(&self) -> Result { self.signature.recover_address_from_prehash(&self.inner.signature_hash()) } + + /// Recover the authority and transform the signed authorization into a + /// [`RecoveredAuthorization`]. + pub fn into_recovered(self) -> RecoveredAuthorization { + RecoveredAuthorization { inner: self.inner, authority: self.recover_authority().ok() } + } } impl Deref for SignedAuthorization { @@ -104,6 +110,30 @@ impl Deref for SignedAuthorization { } } +/// A recovered authorization. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct RecoveredAuthorization { + inner: Authorization, + authority: Option
, +} + +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
{ + self.authority + } +} + +impl Deref for RecoveredAuthorization { + type Target = Authorization; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + /// An internal wrapper around an `Option` for optional nonces. /// /// In EIP-7702 the nonce is encoded as a list of either 0 or 1 items, where 0 items means that no From 43e85003ec5b3763191f407ae79e76c07c4090cd Mon Sep 17 00:00:00 2001 From: Oliver Nordbjerg Date: Mon, 24 Jun 2024 13:02:46 +0200 Subject: [PATCH 23/23] fix: partial move --- crates/eips/src/eip7702/auth_list.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/eips/src/eip7702/auth_list.rs b/crates/eips/src/eip7702/auth_list.rs index 38c06832627..c6c05ce81d0 100644 --- a/crates/eips/src/eip7702/auth_list.rs +++ b/crates/eips/src/eip7702/auth_list.rs @@ -98,7 +98,8 @@ impl SignedAuthorization { /// Recover the authority and transform the signed authorization into a /// [`RecoveredAuthorization`]. pub fn into_recovered(self) -> RecoveredAuthorization { - RecoveredAuthorization { inner: self.inner, authority: self.recover_authority().ok() } + let authority = self.recover_authority().ok(); + RecoveredAuthorization { inner: self.inner, authority } } }