Skip to content
Draft
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
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ alloy-provider = { version = "1.0.23", default-features = false }
alloy-pubsub = { version = "1.0.23", default-features = false }
alloy-rpc-client = { version = "1.0.23", default-features = false }
alloy-rpc-types = { version = "1.0.23", default-features = true }
alloy-rpc-types-beacon = { version = "1.0.30", default-features = true }
alloy-rpc-types-engine = { version = "1.0.30", default-features = true }
alloy-serde = { version = "1.0.23", default-features = false }
alloy-signer = { version = "1.0.30", default-features = false }
alloy-signer-aws = { version = "1.0.30", default-features = false }
Expand Down
2 changes: 2 additions & 0 deletions crates/cast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ alloy-provider = { workspace = true, features = [
] }
alloy-rlp.workspace = true
alloy-rpc-types = { workspace = true, features = ["eth", "trace"] }
alloy-rpc-types-beacon.workspace = true
alloy-rpc-types-engine.workspace = true
alloy-serde.workspace = true
alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] }
alloy-signer.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/cast/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?,
CastSubcommand::Artifact(cmd) => cmd.run().await?,
CastSubcommand::Bind(cmd) => cmd.run().await?,
CastSubcommand::B2EPayload(cmd) => cmd.run().await?,
CastSubcommand::PrettyCalldata { calldata, offline } => {
let calldata = stdin::unwrap_line(calldata)?;
sh_println!("{}", pretty_calldata(&calldata, offline).await?)?;
Expand Down
82 changes: 82 additions & 0 deletions crates/cast/src/cmd/b2e_payload.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use alloy_rpc_types_beacon::payload::execution_payload_from_beacon_str;
use alloy_rpc_types_engine::ExecutionPayload;
use clap::Parser;
use eyre::{Result, eyre};
use foundry_common::{fs, sh_print};
use std::path::PathBuf;

/// CLI arguments for `cast b2e-payload`, convert Beacon block's execution payload to Execution
/// JSON-RPC format.
#[derive(Parser)]
pub struct B2EPayloadArgs {
/// Input data provided through JSON file path.
#[arg(
long = "json-file",
value_name = "FILE",
help = "Path to the JSON file containing the beacon block"
)]
pub json_file: PathBuf,
}

impl B2EPayloadArgs {
pub async fn run(self) -> Result<()> {
// Get input beacon block data
let beacon_block = fs::read_to_string(&self.json_file)
.map_err(|e| eyre!("Failed to read JSON file '{}': {}", self.json_file.display(), e))?;
Comment on lines +23 to +25
Copy link
Member

Choose a reason for hiding this comment

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

I think we also want this to support piped inputs or maybe even fetch the beacon block directly

but def piped input so you can do curl ... | cast b2e


// Extract and convert execution payload
let execution_payload = Self::extract_and_convert_execution_payload(&beacon_block)?;

let json_rpc_output = format_as_json_rpc(execution_payload)?;
sh_print!("{}", json_rpc_output)?;

Ok(())
}

// Extracts the execution payload from a beacon block JSON string and converts it to
// `ExecutionPayload` It matches `beaconcha.in` json format
fn extract_and_convert_execution_payload(beacon_block: &str) -> Result<ExecutionPayload> {
let beacon_json: serde_json::Value = serde_json::from_str(beacon_block)
.map_err(|e| eyre!("Failed to parse beacon block JSON: {}", e))?;

// early detection if the format is not correct
if beacon_json
.get("message")
.and_then(|m| m.get("body"))
.and_then(|b| b.get("execution_payload"))
.is_none()
{
return Err(eyre!("Invalid beacon block format: missing 'message' field"));
Comment on lines +39 to +49
Copy link
Member

Choose a reason for hiding this comment

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

could we add this to alloy as well, so that we dont need to do this manually?

}
// Extract the "message.body.execution_payload" field from the beacon block JSON
// TODO: check if we extract from beacon api it works but not sure it will work with all API
// interfaces
let execution_payload_beacon_block = beacon_json
.get("message")
.and_then(|m| m.get("body"))
.and_then(|b| b.get("execution_payload"))
.ok_or_else(|| eyre!("Could not find execution_payload in beacon block"))?;

let execution_payload_str = serde_json::to_string(execution_payload_beacon_block)
.map_err(|e| eyre!("Failed to serialize execution payload: {}", e))?;

// Convert beacon block's execution payload to json rpc execution payload
let execution_payload = execution_payload_from_beacon_str(&execution_payload_str)?;

Ok(execution_payload)
}
}

// Helper to format the execution payload as JSON-RPC response
fn format_as_json_rpc(execution_payload: ExecutionPayload) -> Result<String> {
// TODO: check if we used this format and this method engine version
let json_rpc_request = serde_json::json!({
"jsonrpc": "2.0",
"method": "engine_newPayloadV3",
Copy link
Member

Choose a reason for hiding this comment

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

for this wee need some --version argument as well

and we need to do some additional work here, because these endpoint take additional args:

https://github.com/paradigmxyz/reth/blob/bd387cd495450a1a03c663ba0704d65575c25779/crates/optimism/rpc/src/engine.rs#L281-L299

Copy link
Contributor

Choose a reason for hiding this comment

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

let's either not do this conversion to Engine API -compatible request format at all, or do it optionally with an argument. The motivation to just have the execution payload in output is to be able to pipe this into reth-bench send-payload that accepts and RPC block.

"params": [execution_payload],
"id": 1
});
Comment on lines +71 to +78
Copy link
Member

Choose a reason for hiding this comment

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

we def need some serde tests for this as well because converting payload variants totally sucks 👍


serde_json::to_string_pretty(&json_rpc_request)
.map_err(|e| eyre!("Failed to serialize JSON-RPC response: {}", e))
}
1 change: 1 addition & 0 deletions crates/cast/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

pub mod access_list;
pub mod artifact;
pub mod b2e_payload;
pub mod bind;
pub mod call;
pub mod constructor_args;
Expand Down
15 changes: 10 additions & 5 deletions crates/cast/src/opts.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::cmd::{
access_list::AccessListArgs, artifact::ArtifactArgs, bind::BindArgs, call::CallArgs,
constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs,
da_estimate::DAEstimateArgs, estimate::EstimateArgs, find_block::FindBlockArgs,
interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs,
send::SendTxArgs, storage::StorageArgs, txpool::TxPoolSubcommands, wallet::WalletSubcommands,
access_list::AccessListArgs, artifact::ArtifactArgs, b2e_payload::B2EPayloadArgs,
bind::BindArgs, call::CallArgs, constructor_args::ConstructorArgsArgs, create2::Create2Args,
creation_code::CreationCodeArgs, da_estimate::DAEstimateArgs, estimate::EstimateArgs,
find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs,
rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, txpool::TxPoolSubcommands,
wallet::WalletSubcommands,
};
use alloy_ens::NameOrAddress;
use alloy_primitives::{Address, B256, Selector, U256};
Expand Down Expand Up @@ -1043,6 +1044,10 @@ pub enum CastSubcommand {
#[command(visible_alias = "bi")]
Bind(BindArgs),

/// Convert Beacon payload to execution payload.
#[command(visible_alias = "b2e")]
B2EPayload(B2EPayloadArgs),

/// Get the selector for a function.
#[command(visible_alias = "si")]
Sig {
Expand Down
Loading