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: add eip-7702 helpers #950

Merged
merged 23 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 17 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: 5 additions & 4 deletions crates/alloy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -218,6 +218,7 @@ arbitrary = [
k256 = [
"alloy-core/k256",
"alloy-consensus?/k256",
"alloy-eips?/k256",
"alloy-network?/k256",
"alloy-rpc-types?/k256",
]
Expand Down
2 changes: 1 addition & 1 deletion crates/consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions crates/eips/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have seck256k1 lib/feature flag as it is faster. But looking at it as a low priority.

ssz = [
"std",
"dep:ethereum_ssz",
Expand Down
1 change: 1 addition & 0 deletions crates/eips/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
158 changes: 158 additions & 0 deletions crates/eips/src/eip7702/auth_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use core::ops::Deref;

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
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)]
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,
}

impl Authorization {
/// Get the `chain_id` for the authorization.
///
/// # Note
///
/// Implementers should check that this matches the current `chain_id` *or* is 0.
pub const fn chain_id(&self) -> ChainId {
self.chain_id
}

/// Get the `address` for the authorization.
pub const 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 is equal to
/// this nonce.
pub fn nonce(&self) -> Option<u64> {
*self.nonce
}

/// 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]
pub fn signature_hash(&self) -> B256 {
use super::constants::MAGIC;

#[derive(RlpEncodable)]
struct Auth {
chain_id: ChainId,
nonce: OptionalNonce,
DaniPopes marked this conversation as resolved.
Show resolved Hide resolved
onbjerg marked this conversation as resolved.
Show resolved Hide resolved
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)
}

/// Convert to a signed authorization by adding a signature.
pub const fn into_signed<S>(self, signature: S) -> SignedAuthorization<S> {
SignedAuthorization { inner: self, signature }
}
}

/// A signed EIP-7702 authorization.
#[derive(Debug, Clone, RlpEncodable, RlpDecodable)]
pub struct SignedAuthorization<S> {
Copy link
Contributor

@rakita rakita Jun 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add RecoveredAuthorization? Where we will cache the recovered account as Option<Address (Option as it can fail).

Recovery can be expensive and can be done before revm gets called. We are okay to do that as it is covered by gas.

The idea that I have is to have an enum in Revm as input:

AuthorisationList {
   Recovered(Vec<RecoveredAuthorization>),
   Signed(Vec<SignedAuthorization>),
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

inner: Authorization,
signature: S,
}

impl<S> SignedAuthorization<S> {
/// Get the `signature` for the authorization.
pub const fn signature(&self) -> &S {
&self.signature
}
}

#[cfg(feature = "k256")]
impl SignedAuthorization<alloy_primitives::Signature> {
/// Recover the authority for the authorization.
///
/// # Note
///
/// Implementers should check that the authority has no code.
pub fn recover_authority(&self) -> Result<Address, alloy_primitives::SignatureError> {
self.signature.recover_address_from_prehash(&self.inner.signature_hash())
}
}

impl<S> Deref for SignedAuthorization<S> {
type Target = Authorization;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

/// An internal wrapper around an `Option<u64>` 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)]
onbjerg marked this conversation as resolved.
Show resolved Hide resolved
pub struct OptionalNonce(Option<u64>);

impl OptionalNonce {
/// Create a new [`OptionalNonce`]
pub const fn new(nonce: Option<u64>) -> Self {
Self(nonce)
}
}

impl From<Option<u64>> for OptionalNonce {
fn from(value: Option<u64>) -> Self {
Self::new(value)
}
}

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<Self> {
let list: Vec<u64> = Vec::decode(buf)?;
onbjerg marked this conversation as resolved.
Show resolved Hide resolved
if list.len() > 1 {
Err(alloy_rlp::Error::UnexpectedLength)
} else {
Ok(Self(list.first().copied()))
}
}
}

impl Deref for OptionalNonce {
type Target = Option<u64>;

fn deref(&self) -> &Self::Target {
&self.0
}
}
18 changes: 18 additions & 0 deletions crates/eips/src/eip7702/constants.rs
Original file line number Diff line number Diff line change
@@ -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;
8 changes: 8 additions & 0 deletions crates/eips/src/eip7702/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
6 changes: 4 additions & 2 deletions crates/eips/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -40,3 +40,5 @@ pub mod eip7002;
pub mod eip7251;

pub mod eip7685;

pub mod eip7702;
2 changes: 1 addition & 1 deletion crates/rpc-types-eth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]