Skip to content

Commit

Permalink
feat(relay): ExecutionRequestsV4 with eip7685::Requests conversion (#…
Browse files Browse the repository at this point in the history
…1787)

* feat(relay): Add ExecutionRequestsV4 for relay submissions.

Re-add {Deposit,Withdrawal,Consolidation}Request structs to relevant EIPs.

* feat(requests): Add ExecutionRequestsV4::TryFrom<&Requests> using ssz_types.

* fixup(serde): Fix serde conditionals on eip6110/7002 using cfg_eval

* fixup: feature ssz_types should require ssz.

* fixup: deps cleanup - remove ssz_types, cfg_eval to workspace

* feat(serde): replace serde_with::DisplayFromStr with alloy_serde::ssz::json::uint

* fixup: doc comment typos

* fixup: derive thiserror::Error on TryFromRequestsError

* fixup: use Requests::with_capacity().

* fixup: cleanup cargo changes

* fixup: More efficient/readable try_from implementation.

* fixup: rename to serde::displayfromstr, other nits.

---------

Co-authored-by: Matthias Seitz <[email protected]>
  • Loading branch information
ryanschneider and mattsse authored Dec 16, 2024
1 parent 28ad3f7 commit bbae675
Show file tree
Hide file tree
Showing 10 changed files with 602 additions and 11 deletions.
66 changes: 64 additions & 2 deletions crates/eips/src/eip6110.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,74 @@
//! Contains Deposit request constants, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md).
//! Contains Deposit request 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
use alloy_primitives::{address, Address};
use alloy_primitives::{address, Address, FixedBytes, B256};

/// Mainnet deposit contract address.
pub const MAINNET_DEPOSIT_CONTRACT_ADDRESS: Address =
address!("00000000219ab540356cbb839cbe05303d7705fa");

/// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for deposit requests.
pub const DEPOSIT_REQUEST_TYPE: u8 = 0x00;

/// The [EIP-6110 Consensus Specs](https://github.com/ethereum/consensus-specs/blob/2660af05390aa61f06142e1c6311a3a3c633f720/specs/_features/eip6110/beacon-chain.md#constants) defined maximum payload size.
pub const MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: usize = 8192;

/// This structure maps onto the deposit object from [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
pub struct DepositRequest {
/// Validator public key
pub pubkey: FixedBytes<48>,
/// Withdrawal credentials
pub withdrawal_credentials: B256,
/// Amount of ether deposited in gwei
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::displayfromstr"))]
pub amount: u64,
/// Deposit signature
pub signature: FixedBytes<96>,
/// Deposit index
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::displayfromstr"))]
pub index: u64,
}

#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::hex;

#[test]
#[cfg(feature = "serde")]
fn test_serde_deposit_request() {
// Sample JSON input representing a deposit request
let json_data = r#"{"pubkey":"0x8e01a8f21bdc38991ada53ca86d6c78d874675a450a38431cc6aa0f12d5661e344784c56c8a211f7025224d1303ee801","withdrawal_credentials":"0x010000000000000000000000af6df504f08ddf582d604d2f0a593bc153c25dbd","amount":"18112749083033600","signature":"0xb65f3db79405544528d6d92040282f29171f4ff6e5abb2d59f9ee1f1254aced2a7000f87bc2684f543e913a7cc1007ea0e97289b349c553eecdf253cd3ef5814088ba3d4ac286f2634dac3d026d9a01e4c166dc75e249d626a0f1c180dab75ce","index":"13343631333247680512"}"#;

// Deserialize the JSON into a DepositRequest struct
let deposit_request: DepositRequest =
serde_json::from_str(json_data).expect("Failed to deserialize");

// Verify the deserialized content
assert_eq!(
deposit_request.pubkey,
FixedBytes::<48>::from(hex!("8E01A8F21BDC38991ADA53CA86D6C78D874675A450A38431CC6AA0F12D5661E344784C56C8A211F7025224D1303EE801"))
);
assert_eq!(
deposit_request.withdrawal_credentials,
B256::from(hex!("010000000000000000000000AF6DF504F08DDF582D604D2F0A593BC153C25DBD"))
);
assert_eq!(deposit_request.amount, 0x0040597307000000u64);
assert_eq!(
deposit_request.signature,
FixedBytes::<96>::from(hex!("B65F3DB79405544528D6D92040282F29171F4FF6E5ABB2D59F9EE1F1254ACED2A7000F87BC2684F543E913A7CC1007EA0E97289B349C553EECDF253CD3EF5814088BA3D4AC286F2634DAC3D026D9A01E4C166DC75E249D626A0F1C180DAB75CE"))
);
assert_eq!(deposit_request.index, 0xB92E1A0000000000u64);

// Serialize the struct back into JSON
let serialized_json = serde_json::to_string(&deposit_request).expect("Failed to serialize");

// Check if the serialized JSON matches the expected JSON structure
assert_eq!(serialized_json, json_data);
}
}
64 changes: 62 additions & 2 deletions crates/eips/src/eip7002.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Contains the system contract, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md).
//! Contains the system contract and [WithdrawalRequest] types, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md).
//!
//! See also [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002): Execution layer triggerable withdrawals
use alloy_primitives::{address, bytes, Address, Bytes};
use alloy_primitives::{address, bytes, Address, Bytes, FixedBytes};

/// The caller to be used when calling the EIP-7002 withdrawal requests contract at the end of the
/// block.
Expand All @@ -17,3 +17,63 @@ pub static WITHDRAWAL_REQUEST_PREDEPLOY_CODE: Bytes = bytes!(" 3373fffffffffff

/// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for withdrawal requests.
pub const WITHDRAWAL_REQUEST_TYPE: u8 = 0x01;

/// The [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) defined maximum withdrawal requests per block.
pub const MAX_WITHDRAWAL_REQUESTS_PER_BLOCK: usize = 16;

/// Represents an execution layer triggerable withdrawal request.
///
/// See [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
pub struct WithdrawalRequest {
/// Address of the source of the exit.
pub source_address: Address,
/// Validator public key.
pub validator_pubkey: FixedBytes<48>,
/// Amount of withdrawn ether in gwei.
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::displayfromstr"))]
pub amount: u64,
}

#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::hex;

#[test]
#[cfg(feature = "serde")]
fn test_serde_withdrawal_request() {
// Sample JSON input representing a withdrawal request
let json_data = r#"{
"source_address":"0xAE0E8770147AaA6828a0D6f642504663F10F7d1E",
"validator_pubkey":"0x8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b",
"amount":"1"
}"#;

// Deserialize the JSON into a WithdrawalRequest struct
let withdrawal_request: WithdrawalRequest =
serde_json::from_str(json_data).expect("Failed to deserialize");

// Verify the deserialized content
assert_eq!(
withdrawal_request.source_address,
address!("AE0E8770147AaA6828a0D6f642504663F10F7d1E")
);
assert_eq!(
withdrawal_request.validator_pubkey,
FixedBytes::<48>::from(hex!("8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b"))
);
assert_eq!(withdrawal_request.amount, 1);

// Serialize the struct back into JSON
let serialized_json =
serde_json::to_string(&withdrawal_request).expect("Failed to serialize");

// Check if the serialized JSON matches the expected JSON structure
let expected_json = r#"{"source_address":"0xae0e8770147aaa6828a0d6f642504663f10f7d1e","validator_pubkey":"0x8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b","amount":"1"}"#;
assert_eq!(serialized_json, expected_json);
}
}
64 changes: 62 additions & 2 deletions crates/eips/src/eip7251.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Contains consolidation code, first introduced in the [Prague hardfork](https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md).
//! Contains consolidation 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
use alloy_primitives::{address, bytes, Address, Bytes};
use alloy_primitives::{address, bytes, Address, Bytes, FixedBytes};

/// The address for the EIP-7251 consolidation requests contract:
/// `0x00b42dbF2194e931E80326D950320f7d9Dbeac02`
Expand All @@ -14,3 +14,63 @@ pub static CONSOLIDATION_REQUEST_PREDEPLOY_CODE: Bytes = bytes!("3373fffffffffff

/// The [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685) request type for consolidation requests.
pub const CONSOLIDATION_REQUEST_TYPE: u8 = 0x02;

/// The [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) defined maximum number of consolidation requests per block.
pub const MAX_CONSOLIDATION_REQUESTS_PER_BLOCK: usize = 2;

/// This structure maps onto the consolidation request object from [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
pub struct ConsolidationRequest {
/// Source address
pub source_address: Address,
/// Source public key
pub source_pubkey: FixedBytes<48>,
/// Target public key
pub target_pubkey: FixedBytes<48>,
}

#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::hex;
use core::str::FromStr;

#[test]
fn test_serde_consolidation_request() {
// Sample JSON input representing a consolidation request
let json_data = r#"{
"source_address":"0x007eABCA654E67103dF02f49EbdC5f6Cd9387a07",
"source_pubkey":"0xb13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7",
"target_pubkey":"0xd0e5be6b709f2dc02a49f6e37e0d03b7d832b79b0db1c8bbfd5b81b8e57b79a1282fb99a671b4629a0e0bfffa7cf6d4f"
}"#;

// Deserialize the JSON into a ConsolidationRequest struct
let consolidation_request: ConsolidationRequest =
serde_json::from_str(json_data).expect("Failed to deserialize");

// Verify the deserialized content
assert_eq!(
consolidation_request.source_address,
Address::from_str("0x007eABCA654E67103dF02f49EbdC5f6Cd9387a07").unwrap()
);
assert_eq!(
consolidation_request.source_pubkey,
FixedBytes::<48>::from(hex!("b13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7"))
);
assert_eq!(
consolidation_request.target_pubkey,
FixedBytes::<48>::from(hex!("d0e5be6b709f2dc02a49f6e37e0d03b7d832b79b0db1c8bbfd5b81b8e57b79a1282fb99a671b4629a0e0bfffa7cf6d4f"))
);

// Serialize the struct back into JSON
let serialized_json =
serde_json::to_string(&consolidation_request).expect("Failed to serialize");

// Check if the serialized JSON matches the expected JSON structure
let expected_json = r#"{"source_address":"0x007eabca654e67103df02f49ebdc5f6cd9387a07","source_pubkey":"0xb13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7","target_pubkey":"0xd0e5be6b709f2dc02a49f6e37e0d03b7d832b79b0db1c8bbfd5b81b8e57b79a1282fb99a671b4629a0e0bfffa7cf6d4f"}"#;
assert_eq!(serialized_json, expected_json);
}
}
1 change: 0 additions & 1 deletion crates/rpc-types-beacon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ workspace = true
alloy-eips = { workspace = true, features = ["serde"] }
alloy-rpc-types-engine = { workspace = true, features = ["serde"] }
alloy-primitives.workspace = true
alloy-serde.workspace = true

# ssz
ethereum_ssz_derive = { workspace = true, optional = true }
Expand Down
Loading

0 comments on commit bbae675

Please sign in to comment.