Skip to content

Commit

Permalink
feat: add initial EIP-7547 engine types (#287)
Browse files Browse the repository at this point in the history
* feat: add initial EIP-7547 engine types

* move to eip7547 crate

* update all definitions to match the spec

* fix readme

* fix package name

* fix: use QUANTITY for nonce, proposer index, slot

* fix: use u64_hex
  • Loading branch information
Rjected authored Mar 29, 2024
1 parent c8d9819 commit bc8eace
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ rustdoc-args = ["--cfg", "docsrs"]
alloy-consensus = { version = "0.1.0", default-features = false, path = "crates/consensus" }
alloy-contract = { version = "0.1.0", default-features = false, path = "crates/contract" }
alloy-eips = { version = "0.1.0", default-features = false, path = "crates/eips" }
alloy-eip7547 = { version = "0.1.0", default-features = false, path = "crates/eip7547" }
alloy-genesis = { version = "0.1.0", default-features = false, path = "crates/genesis" }
alloy-json-rpc = { version = "0.1.0", default-features = false, path = "crates/json-rpc" }
alloy-network = { version = "0.1.0", default-features = false, path = "crates/network" }
Expand Down
23 changes: 23 additions & 0 deletions crates/eip7547/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "alloy-eip7547"
description = "EIP-7547: Inclusion Lists types"

version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
exclude.workspace = true

[dependencies]
alloy-primitives = { workspace = true, features = ["rlp", "serde"] }
alloy-rpc-engine-types = { workspace = true }
alloy-serde = { workspace = true }

# serde
serde = { workspace = true }

[dev-dependencies]
serde_json = { workspace = true }
5 changes: 5 additions & 0 deletions crates/eip7547/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# alloy-eip7547

Includes the EIP-7547 engine types.

Currently based on the [EIP-7547 engine API spec](https://github.com/michaelneuder/execution-apis/pull/1).
15 changes: 15 additions & 0 deletions crates/eip7547/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//! Constants related to EIP-7547.
/// The maximum gas allowed for the inclusion list.
///
/// Based on the following part of the spec:
///
/// ## Constants
///
/// | Name | Value |
/// | - | - |
/// | `INCLUSION_LIST_MAX_GAS` | `uint64(4194304) = 2**22` |
pub const INCLUSION_LIST_MAX_GAS: u64 = 4194304;

/// The capabilities for inclusion list engine API endpoints.
pub const CAPABILITIES: [&str; 2] = ["engine_newInclusionListV1", "engine_getInclusionListV1"];
20 changes: 20 additions & 0 deletions crates/eip7547/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg",
html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico"
)]
#![warn(
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
unreachable_pub,
clippy::missing_const_for_fn,
rustdoc::all
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![deny(unused_must_use, rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]

pub mod constants;

pub mod summary;
190 changes: 190 additions & 0 deletions crates/eip7547/src/summary.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//! Contains types related to the Inclusion lists that will be used by in the engine API RPC
//! definitions.
use alloy_primitives::{Address, B256};
use alloy_rpc_engine_types::PayloadStatusEnum;
use alloy_serde::u64_hex;
use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer};
use std::fmt;

/// This structure contains the result of processing an `engine_newInclusionListV1` RPC call.
///
/// From the spec:
///
/// ### InclusionListStatusV1
///
/// This structure contains the result of processing an inclusion list. The fields are encoded as
/// follows:
/// - `status`: `enum` - `"VALID" | "INVALID" | "SYNCING" | "ACCEPTED"`
/// - `validationError`: `String|null` - a message providing additional details on the validation
/// error if the payload is classified as `INVALID`.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InclusionListStatusV1 {
/// The status of the payload.
#[serde(flatten)]
pub status: PayloadStatusEnum,
}

impl InclusionListStatusV1 {
/// Initializes a new inclusion list status.
pub const fn new(status: PayloadStatusEnum) -> Self {
Self { status }
}

/// Returns true if the payload status is syncing.
pub const fn is_syncing(&self) -> bool {
self.status.is_syncing()
}

/// Returns true if the payload status is valid.
pub const fn is_valid(&self) -> bool {
self.status.is_valid()
}

/// Returns true if the payload status is invalid.
pub const fn is_invalid(&self) -> bool {
self.status.is_invalid()
}
}

impl fmt::Display for InclusionListStatusV1 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "InclusionListStatusV1 {{ status: {} }}", self.status)
}
}

impl Serialize for InclusionListStatusV1 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(3))?;
map.serialize_entry("status", self.status.as_str())?;
map.serialize_entry("validationError", &self.status.validation_error())?;
map.end()
}
}

/// This is an individual entry in the inclusion list summary, representing a transaction that
/// should be included in this block or the next block.
///
/// From the spec:
///
/// ### InclusionListSummaryEntryV1
///
/// This structure contains the details of each inclusion list entry.
///
/// - `address` : `DATA`, 20 Bytes
/// - `nonce` : `QUANTITY`, 64 Bits
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InclusionListSummaryEntryV1 {
/// The address of the inclusion list entry.
pub address: Address,
/// The nonce of the inclusion list entry.
#[serde(with = "u64_hex")]
pub nonce: u64,
}

impl fmt::Display for InclusionListSummaryEntryV1 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "InclusionListEntryV1 {{ address: {}, nonce: {} }}", self.address, self.nonce)
}
}

/// This structure contains the inclusion list summary input to the `engine_newInclusionListV1` RPC
/// call.
///
/// ### InclusionListSummaryV1
///
/// This structure contains the inclusion list summary.
///
/// - `slot` : `QUANTITY`, 64 Bits
/// - `proposer_index`: `QUANTITY`, 64 Bits
/// - `parent_hash`: `DATA`, 32 Bytes
/// - `summary`: `Array of InclusionListSummaryEntryV1`, Array of entries that must be satisfied.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InclusionListSummaryV1 {
/// The slot of the inclusion list summary.
#[serde(with = "u64_hex")]
pub slot: u64,
/// The proposer index of the inclusion list summary.
#[serde(with = "u64_hex")]
pub proposer_index: u64,
/// The parent hash of the inclusion list summary.
pub parent_hash: B256,
/// The summary of the inclusion list summary.
pub summary: Vec<InclusionListSummaryEntryV1>,
}

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

#[test]
fn inclusion_list_status_v1_serialization() {
let status = InclusionListStatusV1::new(PayloadStatusEnum::Valid);
let json = json!({
"status": "VALID",
"validationError": null,
});
assert_eq!(serde_json::to_value(status).unwrap(), json);
}

#[test]
fn inclusion_list_entry_v1_serialization() {
let entry = InclusionListSummaryEntryV1 {
address: Address::from_hex("0x0000000000000000000000000000000000000042").unwrap(),
nonce: 42,
};
let json = json!({
"address": "0x0000000000000000000000000000000000000042",
"nonce": "0x2a",
});
assert_eq!(serde_json::to_value(entry).unwrap(), json);
}

#[test]
fn inclusion_list_summary_v1_serialization() {
let summary = InclusionListSummaryV1 {
slot: 42,
proposer_index: 42,
parent_hash: B256::from_hex(
"0x2222222222222222222222222222222222222222222222222222222222222222",
)
.unwrap(),
summary: vec![
InclusionListSummaryEntryV1 {
address: Address::from_hex("0x0000000000000000000000000000000000000042")
.unwrap(),
nonce: 42,
},
InclusionListSummaryEntryV1 {
address: Address::from_hex("0x0000000000000000000000000000000000000043")
.unwrap(),
nonce: 43,
},
],
};
let json = json!({
"slot": "0x2a",
"proposerIndex": "0x2a",
"parentHash": "0x2222222222222222222222222222222222222222222222222222222222222222",
"summary": [
{
"address": "0x0000000000000000000000000000000000000042",
"nonce": "0x2a",
},
{
"address": "0x0000000000000000000000000000000000000043",
"nonce": "0x2b",
},
],
});
assert_eq!(serde_json::to_value(summary).unwrap(), json);
}
}

0 comments on commit bc8eace

Please sign in to comment.