cast/cmd/
b2e_payload.rs

1//! Command Line handler to convert Beacon block's execution payload to Execution format.
2
3use std::path::PathBuf;
4
5use alloy_rpc_types_beacon::payload::BeaconBlockData;
6use clap::{Parser, builder::ValueParser};
7use eyre::{Result, eyre};
8use 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)]
13pub 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
20impl 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)]
44pub 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
51fn 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)]
62mod 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}