Skip to content
Merged
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
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