Skip to content

Commit

Permalink
feat: cast decode-eof & forge inspect <contract> eof (foundry-rs#…
Browse files Browse the repository at this point in the history
…8478)

* feat: cast decode-eof & forge inspect <contract> eof

* add docs

* clippy

* fix tests

* review fixes
  • Loading branch information
klkvr authored Jul 22, 2024
1 parent fe2acca commit 2544793
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 17 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,4 @@ tower-http = "0.5"
soldeer = "0.2.19"

proptest = "1"
comfy-table = "7"
2 changes: 1 addition & 1 deletion crates/cast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions crates/cast/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
6 changes: 5 additions & 1 deletion crates/cast/bin/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> },

/// Extracts function selectors and arguments from bytecode
Expand All @@ -918,6 +918,10 @@ pub enum CastSubcommand {
#[arg(long, short)]
resolve: bool,
},

/// Decodes EOF container bytes
#[command()]
DecodeEof { eof: Option<String> },
}

/// CLI arguments for `cast --to-base`.
Expand Down
19 changes: 19 additions & 0 deletions crates/cast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1990,6 +1991,24 @@ impl SimpleCast {
let tx = TxEnvelope::decode_2718(&mut tx_hex.as_slice())?;
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<String> {
let eof_hex = hex::decode(eof)?;
let eof = Eof::decode(eof_hex.into())?;
Ok(pretty_eof(&eof)?)
}
}

fn strip_0x(s: &str) -> &str {
Expand Down
2 changes: 1 addition & 1 deletion crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions crates/common/fmt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 75 additions & 0 deletions crates/common/fmt/src/eof.rs
Original file line number Diff line number Diff line change
@@ -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<String, fmt::Error> {
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)
}
3 changes: 3 additions & 0 deletions crates/common/fmt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
2 changes: 1 addition & 1 deletion crates/forge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 55 additions & 13 deletions crates/forge/bin/cmd/inspect.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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");

Expand All @@ -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.clone()]).compile(&project)?;

// Find the artifact
let artifact = output.find_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")
})?;

Expand Down Expand Up @@ -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(())
Expand Down Expand Up @@ -214,6 +222,8 @@ pub enum ContractArtifactField {
Ewasm,
Errors,
Events,
Eof,
EofInit,
}

macro_rules! impl_value_enum {
Expand Down Expand Up @@ -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",
}
}

Expand All @@ -324,6 +336,10 @@ impl From<ContractArtifactField> 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)),
}
}
}
Expand All @@ -347,7 +363,9 @@ impl PartialEq<ContractOutputSelection> 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(_)))
)
}
}
Expand Down Expand Up @@ -407,6 +425,30 @@ fn get_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result<String
Ok(s)
}

/// Pretty-prints bytecode decoded EOF.
fn print_eof(bytecode: Option<CompactBytecode>) -> 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::*;
Expand Down

0 comments on commit 2544793

Please sign in to comment.