From 8efbf56bb1cc7b5586ecb1b2ae7aff9564495c96 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 19 Jul 2024 22:17:44 +0800 Subject: [PATCH 1/5] feat: cast decode-eof & forge inspect eof --- Cargo.lock | 2 + Cargo.toml | 1 + crates/cast/Cargo.toml | 2 +- crates/cast/bin/main.rs | 4 ++ crates/cast/bin/opts.rs | 6 ++- crates/cast/src/lib.rs | 7 +++ crates/common/Cargo.toml | 2 +- crates/common/fmt/Cargo.toml | 2 + crates/common/fmt/src/eof.rs | 75 +++++++++++++++++++++++++++++++++ crates/common/fmt/src/lib.rs | 3 ++ crates/forge/Cargo.toml | 2 +- crates/forge/bin/cmd/inspect.rs | 62 +++++++++++++++++++++------ 12 files changed, 152 insertions(+), 16 deletions(-) create mode 100644 crates/common/fmt/src/eof.rs diff --git a/Cargo.lock b/Cargo.lock index 0ce89dfa8d907..8d5222f02b896 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3701,7 +3701,9 @@ dependencies = [ "alloy-rpc-types", "alloy-serde", "chrono", + "comfy-table", "foundry-macros", + "revm-primitives", "serde", "serde_json", "similar-asserts", diff --git a/Cargo.toml b/Cargo.toml index 02a78a204a86f..a194c48178ee9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,3 +263,4 @@ tower-http = "0.5" soldeer = "0.2.19" proptest = "1" +comfy-table = "7" \ No newline at end of file diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index 602ec57ae4952..1d899bbba2f49 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -68,7 +68,7 @@ foundry-cli.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "4" clap_complete_fig = "4" -comfy-table = "7" +comfy-table.workspace = true dunce.workspace = true indicatif = "0.17" itertools.workspace = true diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index b42e4231d0c0e..ff4f799cdbddb 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -567,6 +567,10 @@ async fn main() -> Result<()> { println!("{}", serde_json::to_string_pretty(&tx)?); } + CastSubcommand::DecodeEof { eof } => { + let eof = stdin::unwrap_line(eof)?; + println!("{}", SimpleCast::decode_eof(&eof)?); + } }; Ok(()) } diff --git a/crates/cast/bin/opts.rs b/crates/cast/bin/opts.rs index 975ce2041aff1..5b3c09c8a8696 100644 --- a/crates/cast/bin/opts.rs +++ b/crates/cast/bin/opts.rs @@ -905,7 +905,7 @@ pub enum CastSubcommand { }, /// Decodes a raw signed EIP 2718 typed transaction - #[command(visible_alias = "dt")] + #[command(visible_aliases = &["dt", "decode-tx"])] DecodeTransaction { tx: Option }, /// Extracts function selectors and arguments from bytecode @@ -918,6 +918,10 @@ pub enum CastSubcommand { #[arg(long, short)] resolve: bool, }, + + /// Decodes EOF container bytes + #[command()] + DecodeEof { eof: Option }, } /// CLI arguments for `cast --to-base`. diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index e1a70b36c8324..e299fa552829e 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -34,6 +34,7 @@ use foundry_compilers::flatten::Flattener; use foundry_config::Chain; use futures::{future::Either, FutureExt, StreamExt}; use rayon::prelude::*; +use revm::primitives::Eof; use std::{ borrow::Cow, io, @@ -1990,6 +1991,12 @@ impl SimpleCast { let tx = TxEnvelope::decode_2718(&mut tx_hex.as_slice())?; Ok(tx) } + + pub fn decode_eof(eof: &str) -> Result { + let eof_hex = hex::decode(strip_0x(eof))?; + let eof = Eof::decode(eof_hex.into())?; + Ok(pretty_eof(&eof)?) + } } fn strip_0x(s: &str) -> &str { diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index b9c872c652cfc..094f32bed6528 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -46,7 +46,7 @@ tower.workspace = true async-trait.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } -comfy-table = "7" +comfy-table.workspace = true dunce.workspace = true eyre.workspace = true num-format.workspace = true diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml index 285c3052dc0c6..fabe35a7a1a1d 100644 --- a/crates/common/fmt/Cargo.toml +++ b/crates/common/fmt/Cargo.toml @@ -25,6 +25,8 @@ alloy-serde.workspace = true serde.workspace = true serde_json.workspace = true chrono.workspace = true +revm-primitives.workspace = true +comfy-table.workspace = true [dev-dependencies] foundry-macros.workspace = true diff --git a/crates/common/fmt/src/eof.rs b/crates/common/fmt/src/eof.rs new file mode 100644 index 0000000000000..2c57266bdc1bf --- /dev/null +++ b/crates/common/fmt/src/eof.rs @@ -0,0 +1,75 @@ +use comfy_table::{ContentArrangement, Table}; +use revm_primitives::{ + eof::{EofBody, EofHeader}, + Eof, +}; +use std::fmt::{self, Write}; + +pub fn pretty_eof(eof: &Eof) -> Result { + let Eof { + header: + EofHeader { + types_size, + code_sizes, + container_sizes, + data_size, + sum_code_sizes: _, + sum_container_sizes: _, + }, + body: + EofBody { types_section, code_section, container_section, data_section, is_data_filled: _ }, + raw: _, + } = eof; + + let mut result = String::new(); + + let mut table = Table::new(); + table.add_row(vec!["type_size", &types_size.to_string()]); + table.add_row(vec!["num_code_sections", &code_sizes.len().to_string()]); + if !code_sizes.is_empty() { + table.add_row(vec!["code_sizes", &format!("{:?}", code_sizes)]); + } + table.add_row(vec!["num_container_sections", &container_sizes.len().to_string()]); + if !container_sizes.is_empty() { + table.add_row(vec!["container_sizes", &format!("{:?}", container_sizes)]); + } + table.add_row(vec!["data_size", &data_size.to_string()]); + + write!(result, "Header:\n{}", table)?; + + if !code_section.is_empty() { + let mut table = Table::new(); + table.set_content_arrangement(ContentArrangement::Dynamic); + table.set_header(vec!["", "Inputs", "Outputs", "Max stack height", "Code"]); + for (idx, (code, type_section)) in code_section.iter().zip(types_section).enumerate() { + table.add_row(vec![ + &idx.to_string(), + &type_section.inputs.to_string(), + &type_section.outputs.to_string(), + &type_section.max_stack_size.to_string(), + &code.to_string(), + ]); + } + + write!(result, "\n\nCode sections:\n{table}")?; + } + + if !container_section.is_empty() { + let mut table = Table::new(); + table.set_content_arrangement(ContentArrangement::Dynamic); + for (idx, container) in container_section.iter().enumerate() { + table.add_row(vec![&idx.to_string(), &container.to_string()]); + } + + write!(result, "\n\nContainer sections:\n{table}")?; + } + + if !data_section.is_empty() { + let mut table = Table::new(); + table.set_content_arrangement(ContentArrangement::Dynamic); + table.add_row(vec![&data_section.to_string()]); + write!(result, "\n\nData section:\n{table}")?; + } + + Ok(result) +} diff --git a/crates/common/fmt/src/lib.rs b/crates/common/fmt/src/lib.rs index 5271b73c755be..c020908097779 100644 --- a/crates/common/fmt/src/lib.rs +++ b/crates/common/fmt/src/lib.rs @@ -11,3 +11,6 @@ pub use exp::{format_int_exp, format_uint_exp, to_exp_notation}; mod ui; pub use ui::{get_pretty_block_attr, get_pretty_tx_attr, EthValue, UIfmt}; + +mod eof; +pub use eof::pretty_eof; diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 02bc6d0c8a648..eb3e94406a080 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -38,7 +38,7 @@ ethers-contract-abigen = { workspace = true, features = ["providers"] } revm-inspectors.workspace = true -comfy-table = "7" +comfy-table.workspace = true eyre.workspace = true proptest.workspace = true rayon.workspace = true diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index a6f1625560278..7bdc98b621cb7 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -1,15 +1,17 @@ +use alloy_primitives::Address; use clap::Parser; use comfy_table::{presets::ASCII_MARKDOWN, Table}; -use eyre::Result; +use eyre::{Context, Result}; +use forge::revm::primitives::Eof; use foundry_cli::opts::{CompilerArgs, CoreBuildArgs}; -use foundry_common::compile::ProjectCompiler; +use foundry_common::{compile::ProjectCompiler, fmt::pretty_eof}; use foundry_compilers::{ artifacts::{ output_selection::{ BytecodeOutputSelection, ContractOutputSelection, DeployedBytecodeOutputSelection, EvmOutputSelection, EwasmOutputSelection, }, - StorageLayout, + CompactBytecode, StorageLayout, }, info::ContractInfo, utils::canonicalize, @@ -39,7 +41,7 @@ pub struct InspectArgs { impl InspectArgs { pub fn run(self) -> Result<()> { - let Self { mut contract, field, build, pretty } = self; + let Self { contract, field, build, pretty } = self; trace!(target: "forge", ?field, ?contract, "running forge inspect"); @@ -64,16 +66,16 @@ impl InspectArgs { // Build the project let project = modified_build_args.project()?; - let mut compiler = ProjectCompiler::new().quiet(true); - if let Some(contract_path) = &mut contract.path { - let target_path = canonicalize(&*contract_path)?; - *contract_path = target_path.to_string_lossy().to_string(); - compiler = compiler.files([target_path]); - } - let output = compiler.compile(&project)?; + let compiler = ProjectCompiler::new().quiet(true); + let target_path = if let Some(path) = &contract.path { + canonicalize(project.root().join(path))? + } else { + project.find_contract_path(&contract.name)? + }; + let mut output = compiler.files([target_path]).compile(&project)?; // Find the artifact - let artifact = output.find_contract(&contract).ok_or_else(|| { + let artifact = output.remove_contract(&contract).ok_or_else(|| { eyre::eyre!("Could not find artifact `{contract}` in the compiled artifacts") })?; @@ -160,6 +162,12 @@ impl InspectArgs { } print_json(&out)?; } + ContractArtifactField::Eof => { + print_eof(artifact.deployed_bytecode.and_then(|b| b.bytecode))?; + } + ContractArtifactField::EofInit => { + print_eof(artifact.bytecode)?; + } }; Ok(()) @@ -214,6 +222,8 @@ pub enum ContractArtifactField { Ewasm, Errors, Events, + Eof, + EofInit, } macro_rules! impl_value_enum { @@ -300,6 +310,8 @@ impl_value_enum! { Ewasm => "ewasm" | "e-wasm", Errors => "errors" | "er", Events => "events" | "ev", + Eof => "eof" | "eof-container" | "eof-deployed", + EofInit => "eof-init" | "eof-initcode" | "eof-initcontainer", } } @@ -324,6 +336,10 @@ impl From for ContractOutputSelection { Caf::Ewasm => Self::Ewasm(EwasmOutputSelection::All), Caf::Errors => Self::Abi, Caf::Events => Self::Abi, + Caf::Eof => Self::Evm(EvmOutputSelection::DeployedByteCode( + DeployedBytecodeOutputSelection::All, + )), + Caf::EofInit => Self::Evm(EvmOutputSelection::ByteCode(BytecodeOutputSelection::All)), } } } @@ -407,6 +423,28 @@ fn get_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result) -> Result<()> { + let Some(mut bytecode) = bytecode else { eyre::bail!("No bytecode") }; + // Replace link references with zero address. + if bytecode.object.is_unlinked() { + for (file, references) in bytecode.link_references.clone() { + for (name, _) in references { + bytecode.link(&file, &name, Address::ZERO); + } + } + } + + let Some(bytecode) = bytecode.object.into_bytes() else { + eyre::bail!("Failed to link bytecode"); + }; + + let eof = Eof::decode(bytecode).wrap_err("Failed to decode EOF")?; + + println!("{}", pretty_eof(&eof)?); + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; From b96d6e0d9bf595c320a6a7a52f641e362b275af3 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 19 Jul 2024 22:27:07 +0800 Subject: [PATCH 2/5] add docs --- Cargo.toml | 2 +- crates/cast/src/lib.rs | 12 ++++++++++++ crates/forge/bin/cmd/inspect.rs | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a194c48178ee9..d392833fe1e4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,4 +263,4 @@ tower-http = "0.5" soldeer = "0.2.19" proptest = "1" -comfy-table = "7" \ No newline at end of file +comfy-table = "7" diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index e299fa552829e..490717d1e8034 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -1992,6 +1992,18 @@ impl SimpleCast { Ok(tx) } + /// Decodes EOF container bytes + /// Pretty prints the decoded EOF container contents + /// + /// # Example + /// + /// ``` + /// use cast::SimpleCast as Cast; + /// + /// let eof = "0xef0001010004020001005604002000008000046080806040526004361015e100035f80fd5f3560e01c63773d45e01415e1ffee6040600319360112e10028600435906024358201809211e100066020918152f3634e487b7160e01b5f52601160045260245ffd5f80fd0000000000000000000000000124189fc71496f8660db5189f296055ed757632"; + /// let decoded = Cast::decode_eof(&eof)?; + /// println!("{}", decoded); + /// # Ok::<(), eyre::Report>(()) pub fn decode_eof(eof: &str) -> Result { let eof_hex = hex::decode(strip_0x(eof))?; let eof = Eof::decode(eof_hex.into())?; diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index 7bdc98b621cb7..789797ef2863d 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -423,8 +423,10 @@ fn get_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result) -> Result<()> { let Some(mut bytecode) = bytecode else { eyre::bail!("No bytecode") }; + // Replace link references with zero address. if bytecode.object.is_unlinked() { for (file, references) in bytecode.link_references.clone() { From 02ab12f162560ebb9f5c5da258b3c9bf2951166b Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 19 Jul 2024 22:39:24 +0800 Subject: [PATCH 3/5] clippy --- crates/common/fmt/src/eof.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/common/fmt/src/eof.rs b/crates/common/fmt/src/eof.rs index 2c57266bdc1bf..639e175b45eaa 100644 --- a/crates/common/fmt/src/eof.rs +++ b/crates/common/fmt/src/eof.rs @@ -27,15 +27,15 @@ pub fn pretty_eof(eof: &Eof) -> Result { table.add_row(vec!["type_size", &types_size.to_string()]); table.add_row(vec!["num_code_sections", &code_sizes.len().to_string()]); if !code_sizes.is_empty() { - table.add_row(vec!["code_sizes", &format!("{:?}", code_sizes)]); + table.add_row(vec!["code_sizes", &format!("{code_sizes:?}")]); } table.add_row(vec!["num_container_sections", &container_sizes.len().to_string()]); if !container_sizes.is_empty() { - table.add_row(vec!["container_sizes", &format!("{:?}", container_sizes)]); + table.add_row(vec!["container_sizes", &format!("{container_sizes:?}")]); } table.add_row(vec!["data_size", &data_size.to_string()]); - write!(result, "Header:\n{}", table)?; + write!(result, "Header:\n{table}")?; if !code_section.is_empty() { let mut table = Table::new(); From b59a1a3d2417b91710bc297557d789a1cdaed3f3 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 19 Jul 2024 22:56:41 +0800 Subject: [PATCH 4/5] fix tests --- crates/forge/bin/cmd/inspect.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index 789797ef2863d..e5be3314c3ec9 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -72,10 +72,10 @@ impl InspectArgs { } else { project.find_contract_path(&contract.name)? }; - let mut output = compiler.files([target_path]).compile(&project)?; + let mut output = compiler.files([target_path.clone()]).compile(&project)?; // Find the artifact - let artifact = output.remove_contract(&contract).ok_or_else(|| { + let artifact = output.remove(&target_path, &contract.name).ok_or_else(|| { eyre::eyre!("Could not find artifact `{contract}` in the compiled artifacts") })?; @@ -363,7 +363,9 @@ impl PartialEq for ContractArtifactField { (Self::IrOptimized, Cos::IrOptimized) | (Self::Metadata, Cos::Metadata) | (Self::UserDoc, Cos::UserDoc) | - (Self::Ewasm, Cos::Ewasm(_)) + (Self::Ewasm, Cos::Ewasm(_)) | + (Self::Eof, Cos::Evm(Eos::DeployedByteCode(_))) | + (Self::EofInit, Cos::Evm(Eos::ByteCode(_))) ) } } From 2ae8803d6839887a5abfa76e7b1beffd2480f52c Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Sat, 20 Jul 2024 20:50:34 +0800 Subject: [PATCH 5/5] review fixes --- crates/cast/src/lib.rs | 2 +- crates/forge/bin/cmd/inspect.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 490717d1e8034..46610d6303b7d 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -2005,7 +2005,7 @@ impl SimpleCast { /// println!("{}", decoded); /// # Ok::<(), eyre::Report>(()) pub fn decode_eof(eof: &str) -> Result { - let eof_hex = hex::decode(strip_0x(eof))?; + let eof_hex = hex::decode(eof)?; let eof = Eof::decode(eof_hex.into())?; Ok(pretty_eof(&eof)?) } diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index e5be3314c3ec9..ac0ed41b3f342 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -310,8 +310,8 @@ impl_value_enum! { Ewasm => "ewasm" | "e-wasm", Errors => "errors" | "er", Events => "events" | "ev", - Eof => "eof" | "eof-container" | "eof-deployed", - EofInit => "eof-init" | "eof-initcode" | "eof-initcontainer", + Eof => "eof" | "eof-container" | "eof-deployed", + EofInit => "eof-init" | "eof-initcode" | "eof-initcontainer", } }