Skip to content

Commit a83dc1a

Browse files
authored
Add cast command to convert beacon payload to execution payload b2e-payload (#11629)
* feat: add v1 cast commande b2epayload * feat: add v1 cast commande b2epayload * chore: fmt * chore: use pathbuf * fix: restore long name * chore: fmt * chore: update lock * refactor: use ex payload alloy helper * refactor: use raw input source str * refactor: remove right now json rpc format output * test: add test for malformated input * chore: remove unused dep * chore: fmt
1 parent 054a088 commit a83dc1a

File tree

7 files changed

+140
-5
lines changed

7 files changed

+140
-5
lines changed

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ alloy-provider = { version = "1.0.36", default-features = false }
233233
alloy-pubsub = { version = "1.0.36", default-features = false }
234234
alloy-rpc-client = { version = "1.0.36", default-features = false }
235235
alloy-rpc-types = { version = "1.0.36", default-features = true }
236+
alloy-rpc-types-beacon = { version = "1.0.36", default-features = true }
236237
alloy-serde = { version = "1.0.36", default-features = false }
237238
alloy-signer = { version = "1.0.36", default-features = false }
238239
alloy-signer-aws = { version = "1.0.36", default-features = false }

crates/cast/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ alloy-provider = { workspace = true, features = [
4949
] }
5050
alloy-rlp.workspace = true
5151
alloy-rpc-types = { workspace = true, features = ["eth", "trace"] }
52+
alloy-rpc-types-beacon.workspace = true
5253
alloy-serde.workspace = true
5354
alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] }
5455
alloy-signer.workspace = true

crates/cast/src/args.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ pub async fn run_command(args: CastArgs) -> Result<()> {
273273
CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?,
274274
CastSubcommand::Artifact(cmd) => cmd.run().await?,
275275
CastSubcommand::Bind(cmd) => cmd.run().await?,
276+
CastSubcommand::B2EPayload(cmd) => cmd.run().await?,
276277
CastSubcommand::PrettyCalldata { calldata, offline } => {
277278
let calldata = stdin::unwrap_line(calldata)?;
278279
sh_println!("{}", pretty_calldata(&calldata, offline).await?)?;

crates/cast/src/cmd/b2e_payload.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! Command Line handler to convert Beacon block's execution payload to Execution format.
2+
3+
use std::path::PathBuf;
4+
5+
use alloy_rpc_types_beacon::payload::BeaconBlockData;
6+
use clap::{Parser, builder::ValueParser};
7+
use eyre::{Result, eyre};
8+
use foundry_common::{fs, sh_print};
9+
10+
/// CLI arguments for `cast b2e-payload`, convert Beacon block's execution payload to Execution
11+
/// format.
12+
#[derive(Parser)]
13+
pub struct B2EPayloadArgs {
14+
/// Input data, it can be either a file path to JSON file or raw JSON string containing the
15+
/// beacon block
16+
#[arg(value_name = "INPUT", value_parser=ValueParser::new(parse_input_source), help = "File path to JSON file or raw JSON string containing the beacon block")]
17+
pub input: InputSource,
18+
}
19+
20+
impl B2EPayloadArgs {
21+
pub async fn run(self) -> Result<()> {
22+
let beacon_block_json = match self.input {
23+
InputSource::Json(json) => json,
24+
InputSource::File(path) => fs::read_to_string(&path)
25+
.map_err(|e| eyre!("Failed to read JSON file '{}': {}", path.display(), e))?,
26+
};
27+
28+
let beacon_block_data: BeaconBlockData = serde_json::from_str(&beacon_block_json)
29+
.map_err(|e| eyre!("Failed to parse beacon block JSON: {}", e))?;
30+
31+
let execution_payload = beacon_block_data.execution_payload();
32+
33+
// Output raw execution payload
34+
let output = serde_json::to_string(&execution_payload)
35+
.map_err(|e| eyre!("Failed to serialize execution payload: {}", e))?;
36+
sh_print!("{}", output)?;
37+
38+
Ok(())
39+
}
40+
}
41+
42+
/// Represents the different input sources for beacon block data
43+
#[derive(Debug, Clone)]
44+
pub enum InputSource {
45+
/// Path to a JSON file containing beacon block data
46+
File(PathBuf),
47+
/// Raw JSON string containing beacon block data
48+
Json(String),
49+
}
50+
51+
fn parse_input_source(s: &str) -> Result<InputSource, String> {
52+
// Try parsing as JSON first
53+
if serde_json::from_str::<serde_json::Value>(s).is_ok() {
54+
return Ok(InputSource::Json(s.to_string()));
55+
}
56+
57+
// Otherwise treat as file path
58+
Ok(InputSource::File(PathBuf::from(s)))
59+
}
60+
61+
#[cfg(test)]
62+
mod tests {
63+
use super::*;
64+
65+
#[test]
66+
fn test_parse_input_source_json_object() {
67+
let json_input = r#"{"execution_payload": {"block_hash": "0x123"}}"#;
68+
let result = parse_input_source(json_input).unwrap();
69+
70+
match result {
71+
InputSource::Json(json) => assert_eq!(json, json_input),
72+
InputSource::File(_) => panic!("Expected JSON input, got File"),
73+
}
74+
}
75+
76+
#[test]
77+
fn test_parse_input_source_json_array() {
78+
let json_input = r#"[{"block": "data"}]"#;
79+
let result = parse_input_source(json_input).unwrap();
80+
81+
match result {
82+
InputSource::Json(json) => assert_eq!(json, json_input),
83+
InputSource::File(_) => panic!("Expected JSON input, got File"),
84+
}
85+
}
86+
87+
#[test]
88+
fn test_parse_input_source_file_path() {
89+
let file_path =
90+
"block-12225729-6ceadbf2a6adbbd64cbec33fdebbc582f25171cd30ac43f641cbe76ac7313ddf.json";
91+
let result = parse_input_source(file_path).unwrap();
92+
93+
match result {
94+
InputSource::File(path) => assert_eq!(path, PathBuf::from(file_path)),
95+
InputSource::Json(_) => panic!("Expected File input, got JSON"),
96+
}
97+
}
98+
99+
#[test]
100+
fn test_parse_input_source_malformed_but_not_json() {
101+
let malformed = "not-json-{";
102+
let result = parse_input_source(malformed).unwrap();
103+
104+
// Should be treated as file path since it's not valid JSON
105+
match result {
106+
InputSource::File(path) => assert_eq!(path, PathBuf::from(malformed)),
107+
InputSource::Json(_) => panic!("Expected File input, got File"),
108+
}
109+
}
110+
}

crates/cast/src/cmd/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
pub mod access_list;
99
pub mod artifact;
10+
pub mod b2e_payload;
1011
pub mod bind;
1112
pub mod call;
1213
pub mod constructor_args;

crates/cast/src/opts.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use crate::cmd::{
2-
access_list::AccessListArgs, artifact::ArtifactArgs, bind::BindArgs, call::CallArgs,
3-
constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs,
4-
da_estimate::DAEstimateArgs, estimate::EstimateArgs, find_block::FindBlockArgs,
5-
interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs,
6-
send::SendTxArgs, storage::StorageArgs, txpool::TxPoolSubcommands, wallet::WalletSubcommands,
2+
access_list::AccessListArgs, artifact::ArtifactArgs, b2e_payload::B2EPayloadArgs,
3+
bind::BindArgs, call::CallArgs, constructor_args::ConstructorArgsArgs, create2::Create2Args,
4+
creation_code::CreationCodeArgs, da_estimate::DAEstimateArgs, estimate::EstimateArgs,
5+
find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, mktx::MakeTxArgs,
6+
rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, txpool::TxPoolSubcommands,
7+
wallet::WalletSubcommands,
78
};
89
use alloy_ens::NameOrAddress;
910
use alloy_primitives::{Address, B256, Selector, U256};
@@ -1054,6 +1055,10 @@ pub enum CastSubcommand {
10541055
#[command(visible_alias = "bi")]
10551056
Bind(BindArgs),
10561057

1058+
/// Convert Beacon payload to execution payload.
1059+
#[command(visible_alias = "b2e")]
1060+
B2EPayload(B2EPayloadArgs),
1061+
10571062
/// Get the selector for a function.
10581063
#[command(visible_alias = "si")]
10591064
Sig {

0 commit comments

Comments
 (0)