Skip to content
Closed
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
3 changes: 2 additions & 1 deletion crates/eips/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ alloy-rlp = { workspace = true, features = ["derive"] }
# serde
alloy-serde = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_with = { workspace = true, optional = true }

# kzg
c-kzg = { workspace = true, optional = true }
Expand Down Expand Up @@ -64,7 +65,7 @@ rand.workspace = true
default = ["std", "kzg-sidecar"]
std = ["alloy-primitives/std", "alloy-rlp/std",
"serde?/std", "c-kzg?/std", "once_cell?/std"]
serde = ["dep:alloy-serde", "dep:serde", "alloy-primitives/serde",
serde = ["dep:alloy-serde", "dep:serde", "dep:serde_with", "alloy-primitives/serde",
"c-kzg?/serde", "alloy-eip2930/serde", "alloy-eip7702/serde"]
serde-bincode-compat = ["alloy-eip7702/serde-bincode-compat"]
kzg = ["kzg-sidecar", "sha2", "dep:c-kzg", "dep:once_cell"]
Expand Down
130 changes: 128 additions & 2 deletions crates/eips/src/eip6110.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,138 @@
//! 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};
use alloy_rlp::{RlpDecodable, RlpEncodable};
use serde_with::{serde_as, DisplayFromStr};

/// 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;

/// This structure maps onto the deposit object from [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110).
#[cfg_attr(feature = "serde", serde_as)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable, 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
#[serde_as(as = "DisplayFromStr")]
pub amount: u64,
/// Deposit signature
pub signature: FixedBytes<96>,
/// Deposit index
#[serde_as(as = "DisplayFromStr")]
pub index: u64,
}

#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::hex;
use alloy_rlp::{Decodable, Encodable};

#[test]
fn test_encode_decode_request_roundtrip() {
// Define multiple test cases as tuples containing the test data
let test_cases = vec![
(
// https://etherscan.io/tx/0xab9e0b47767c6172f49f691e5fd96cb257c17f2d39cf64742d71e5435308403c#eventlog
FixedBytes::<48>::from(hex!("8E01A8F21BDC38991ADA53CA86D6C78D874675A450A38431CC6AA0F12D5661E344784C56C8A211F7025224D1303EE801")),
B256::from(hex!("010000000000000000000000AF6DF504F08DDF582D604D2F0A593BC153C25DBD")),
0x0040597307000000u64,
FixedBytes::<96>::from(hex!("B65F3DB79405544528D6D92040282F29171F4FF6E5ABB2D59F9EE1F1254ACED2A7000F87BC2684F543E913A7CC1007EA0E97289B349C553EECDF253CD3EF5814088BA3D4AC286F2634DAC3D026D9A01E4C166DC75E249D626A0F1C180DAB75CE")),
0xB92E1A0000000000u64,
),
// https://etherscan.io/tx/0x756a8aba9f8df9fba33519bc1ec1ad2251507f66ef65cb15eb0a80ddfd0bcbef#eventlog
(
FixedBytes::<48>::from(hex!("85BA6057EA5100DCB0B347D545BE0B688B2AD10A029C24A9D653F18BA3AC743B3CF8E022AE487AD8C5670D9C20D101D8")),
B256::from(hex!("010000000000000000000000E839A3E9EFB32C6A56AB7128E51056585275506C")),
0x0040597307000000u64,
FixedBytes::<96>::from(hex!("8F685E17FC36B8DE5EF6E81523995139EF59280F7D25D0C422C7BDF573217F8127B2425B87E414443430B4EE05EE5ABE19EEDB9B239BA1354FBB8133C5B068FA7B278296856F7C7592F1AF9332762AB4389B9FC224D32E209077368AE3CED710")),
0xDD2A1A0000000000u64,
),
(
// https://etherscan.io/tx/0xa5759378809a22bf8072de41c155e176a74a08323e94e4167ee2692887e83859#eventlog
FixedBytes::<48>::from(hex!("A3151E4E6BE6A4002249331B60EF426F6CDE5C33B27C9F14FC6639E6888A10F54C4A44AEE7AB0690BF09A89BDC00237C")),
B256::from(hex!("01000000000000000000000006676E8584342CC8B6052CFDF381C3A281F00AC8")),
0x0040597307000000u64,
FixedBytes::<96>::from(hex!("A3C85DF60DF11200166D49A055E96C4659D37AC630CDC6E5C3EE039478E5B558F50F390D306249CEC66ACAA09011B85300434FFB566FE599E3E1596162BC3BBCE7BEE9122DAEBF7D1F42124C0FF00BE6EE8B79E0F436044337148EB061E0B163")),
0x85A6130000000000u64,
),
(
// https://etherscan.io/tx/0x132bb7c90069d9699a84cdd041ddbe7a5cc42b2d26b604a8ac282aa5c17ed218#eventlog
FixedBytes::<48>::from(hex!("AAE673FEE94E4552CFEC432DCDDF46D1F613DD48E3DFB36179C973B73BCFDE5C463C5B719DD916DA8DA3981BFD0BAE29")),
B256::from(hex!("010000000000000000000000A8C62111E4652B07110A0FC81816303C42632F64")),
0x0040597307000000u64,
FixedBytes::<96>::from(hex!("B6BA4C2CA28E46BA0DD8757EBDD52BC3609DA3BCF17BCCEEDD181630797F2A51CA2D8B4BFBA8639074276ABA6A4B7316106AB9F1642BAB0D9A8058211F366BBEB6E42CB1C56D17155A84B2C32F61F772C41749134665CBD7EC43122691527050")),
0xC9A8130000000000u64,
),
(
// https://etherscan.io/tx/0xc97358e047333933278b8ab08ec4cfff8f6cf4028e2c3d877a5a89bd9f7303c9#eventlog
FixedBytes::<48>::from(hex!("A3CF35BEC7827E666C591BE49336B19FDE0F6ADA5F7139CFEEB719F372B200EDAC11FE73EDE7BE5B4A17E43BF055C58A")),
B256::from(hex!("0100000000000000000000008B7F4F725AE240D9B28D8129D35E6580D1251852")),
0x0040597307000000u64,
FixedBytes::<96>::from(hex!("B9844B35D0831E22DF5E8374FF6C405F98DED278E813EBF9F1A61CFC08F6019D99E3C4B975BFDE999DC2AAC8EA99C540112FFAB1557CE0ADD9D80E0F91EB2D370F3220DA04D96F6256A07288CEFDB2F27C2C3DDE26B49EBAF5801E99FDCA095D")),
0x62301A0000000000u64,
),
];

// Iterate over each test case
for (pubkey, withdrawal_credentials, amount, signature, index) in test_cases {
let original_request =
DepositRequest { pubkey, withdrawal_credentials, amount, signature, index };

// Encode the request
let mut buf = Vec::new();
original_request.encode(&mut buf);

// Decode the request
let decoded_request =
DepositRequest::decode(&mut &buf[..]).expect("Failed to decode request");

// Ensure the encoded and then decoded request matches the original
assert_eq!(original_request, decoded_request);
}
}

#[test]
fn test_serde_deposit_request() {
// Sample JSON input representing a deposit request
let json_data = r#"{"pubkey":"0x8e01a8f21bdc38991ada53ca86d6c78d874675a450a38431cc6aa0f12d5661e344784c56c8a211f7025224d1303ee801","withdrawalCredentials":"0x010000000000000000000000af6df504f08ddf582d604d2f0a593bc153c25dbd","amount":"0x40597307000000","signature":"0xb65f3db79405544528d6d92040282f29171f4ff6e5abb2d59f9ee1f1254aced2a7000f87bc2684f543e913a7cc1007ea0e97289b349c553eecdf253cd3ef5814088ba3d4ac286f2634dac3d026d9a01e4c166dc75e249d626a0f1c180dab75ce","index":"0xb92e1a0000000000"}"#;

// 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);
}
}
110 changes: 108 additions & 2 deletions crates/eips/src/eip7002.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! 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};
use alloy_rlp::{RlpDecodable, RlpEncodable};
use serde_with::{serde_as, DisplayFromStr};

/// The caller to be used when calling the EIP-7002 withdrawal requests contract at the end of the
/// block.
Expand All @@ -17,3 +19,107 @@ 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;

/// Represents an execution layer triggerable withdrawal request.
///
/// See [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002).
#[cfg_attr(feature = "serde", serde_as)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable, 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.
#[serde_as(as = "DisplayFromStr")]
pub amount: u64,
}

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

#[test]
fn test_encode_decode_request_roundtrip() {
// Define multiple test cases as tuples containing the test data.
//
// Examples are randomly generated using some random validators found on Etherscan.
let test_cases = vec![
(
Address::from_str("0xaE0E8770147AaA6828a0D6f642504663F10F7d1E").unwrap(),
FixedBytes::<48>::from(hex!("8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b")),
10
),
(
Address::from_str("0xf86f8D6A7d2AF439245c1145d88B04dAf2d7e509").unwrap(),
FixedBytes::<48>::from(hex!("a85d7a6aa90eedebe103b8d4d3dc86003aea8b6c8159d9d50f7685828bc97d211b2c512b1dcbb8d63b60a56c91dda8ea")),
354
),
(
Address::from_str("0xf86f8D6A7d2AF439245c1145d88B04dAf2d7e509").unwrap(),
FixedBytes::<48>::from(hex!("a77eec36b046fbbf088e9253aa8c6800863d882c56fc6fa04800bbed742820f1bc7eb837601322840a18bbe0d24893b2")),
19
),
(
Address::from_str("0xAFedF06777839D59eED3163cC3e0A5057b514399").unwrap(),
FixedBytes::<48>::from(hex!("a3ecb9359401bb22d00cefddf6f6879d14a2ee74d3325cc8cdff0796bd0b3b47c5f5b4d02e5a865d7b639eb8124286a5")),
9
),
];

// Iterate over each test case
for (source_address, validator_pubkey, amount) in test_cases {
let original_request = WithdrawalRequest { source_address, validator_pubkey, amount };

// Encode the request
let mut buf = Vec::new();
original_request.encode(&mut buf);

// Decode the request
let decoded_request =
WithdrawalRequest::decode(&mut &buf[..]).expect("Failed to decode request");

// Ensure the encoded and then decoded request matches the original
assert_eq!(original_request, decoded_request);
}
}

#[test]
fn test_serde_withdrawal_request() {
// Sample JSON input representing a withdrawal request
let json_data = r#"{
"sourceAddress":"0xAE0E8770147AaA6828a0D6f642504663F10F7d1E",
"validatorPubkey":"0x8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b",
"amount":"0x1"
}"#;

// 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::from_str("0xAE0E8770147AaA6828a0D6f642504663F10F7d1E").unwrap()
);
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#"{"sourceAddress":"0xae0e8770147aaa6828a0d6f642504663f10f7d1e","validatorPubkey":"0x8e8d8749f6bc79b78be7cc6e49ff640e608454840c360b344c3a4d9b7428e280e7f40d2271bad65d8cbbfdd43cb8793b","amount":"0x1"}"#;
assert_eq!(serialized_json, expected_json);
}
}
84 changes: 82 additions & 2 deletions crates/eips/src/eip7251.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! 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};
use alloy_rlp::{RlpDecodable, RlpEncodable};

/// The address for the EIP-7251 consolidation requests contract:
/// `0x00b42dbF2194e931E80326D950320f7d9Dbeac02`
Expand All @@ -14,3 +15,82 @@ 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;

/// 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, RlpEncodable, RlpDecodable, 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 alloy_rlp::{Decodable, Encodable};
use core::str::FromStr;

#[test]
fn test_encode_decode_consolidation_request_roundtrip() {
// Generate and test multiple cases in a single pass using an iterator.
(0..4).for_each(|_| {
let original_request = ConsolidationRequest {
source_address: Address::random(),
source_pubkey: FixedBytes::<48>::random(),
target_pubkey: FixedBytes::<48>::random(),
};

// Encode the request
let mut buf = Vec::new();
original_request.encode(&mut buf);

// Decode the request and assert equality
let decoded_request =
ConsolidationRequest::decode(&mut &buf[..]).expect("Failed to decode request");
assert_eq!(original_request, decoded_request);
});
}

#[test]
fn test_serde_consolidation_request() {
// Sample JSON input representing a consolidation request
let json_data = r#"{
"sourceAddress":"0x007eABCA654E67103dF02f49EbdC5f6Cd9387a07",
"sourcePubkey":"0xb13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7",
"targetPubkey":"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#"{"sourceAddress":"0x007eabca654e67103df02f49ebdc5f6cd9387a07","sourcePubkey":"0xb13ff174911d0137e5f2b739fbf172b22cba35a037ef1edb03683b75c9abf5b271f8d48ad279cc89c7fae91db631c1e7","targetPubkey":"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