From e0b5afa3a395eb726a6305c876c08e33f97e295e Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Mon, 25 Sep 2023 18:45:39 -0400 Subject: [PATCH 01/18] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20renam?= =?UTF-8?q?e=20`heimdall`=20to=20`heimdall-core`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 68 ++--- Cargo.toml | 2 +- cache/Cargo.toml | 2 - cli/Cargo.toml | 25 ++ cli/src/main.rs | 244 ++++++++++++++++++ common/src/utils/strings.rs | 28 +- {heimdall => core}/Cargo.toml | 3 +- {heimdall => core}/examples/cfg.rs | 2 +- {heimdall => core}/examples/decode.rs | 2 +- {heimdall => core}/examples/decompile.rs | 2 +- {heimdall => core}/examples/snapshot.rs | 2 +- {heimdall => core}/src/cfg/graph.rs | 0 {heimdall => core}/src/cfg/mod.rs | 4 +- {heimdall => core}/src/cfg/output.rs | 0 {heimdall => core}/src/decode/mod.rs | 2 +- {heimdall => core}/src/decode/util.rs | 0 .../src/decompile/analyzers/mod.rs | 0 .../src/decompile/analyzers/solidity.rs | 0 .../src/decompile/analyzers/yul.rs | 0 {heimdall => core}/src/decompile/constants.rs | 0 {heimdall => core}/src/decompile/mod.rs | 4 +- {heimdall => core}/src/decompile/out/mod.rs | 0 .../src/decompile/out/postprocessers/mod.rs | 0 .../decompile/out/postprocessers/solidity.rs | 0 .../src/decompile/out/postprocessers/tests.rs | 0 .../src/decompile/out/postprocessers/yul.rs | 0 .../src/decompile/out/solidity.rs | 0 {heimdall => core}/src/decompile/out/yul.rs | 0 .../src/decompile/precompile.rs | 0 {heimdall => core}/src/decompile/resolve.rs | 0 {heimdall => core}/src/decompile/util.rs | 0 {heimdall => core}/src/dump/constants.rs | 0 .../src/dump/menus/command_palette.rs | 0 {heimdall => core}/src/dump/menus/help.rs | 0 {heimdall => core}/src/dump/menus/main.rs | 0 {heimdall => core}/src/dump/menus/mod.rs | 0 {heimdall => core}/src/dump/mod.rs | 0 .../src/dump/structures/dump_state.rs | 0 {heimdall => core}/src/dump/structures/mod.rs | 0 .../src/dump/structures/storage_slot.rs | 0 .../src/dump/structures/transaction.rs | 0 {heimdall => core}/src/dump/util/csv.rs | 0 {heimdall => core}/src/dump/util/mod.rs | 0 {heimdall => core}/src/dump/util/table.rs | 0 .../src/dump/util/threads/indexer.rs | 0 .../src/dump/util/threads/mod.rs | 0 .../src/dump/util/threads/tui.rs | 0 {heimdall => core}/src/heimdall.rs | 0 {heimdall => core}/src/lib.rs | 1 + {heimdall => core}/src/snapshot/analyze.rs | 0 {heimdall => core}/src/snapshot/constants.rs | 0 .../src/snapshot/menus/command_palette.rs | 0 {heimdall => core}/src/snapshot/menus/help.rs | 0 {heimdall => core}/src/snapshot/menus/main.rs | 0 {heimdall => core}/src/snapshot/menus/mod.rs | 0 {heimdall => core}/src/snapshot/mod.rs | 4 +- {heimdall => core}/src/snapshot/resolve.rs | 0 .../src/snapshot/structures/mod.rs | 0 .../src/snapshot/structures/snapshot.rs | 0 .../src/snapshot/structures/state.rs | 0 {heimdall => core}/src/snapshot/util/csv.rs | 0 {heimdall => core}/src/snapshot/util/mod.rs | 0 {heimdall => core}/src/snapshot/util/table.rs | 0 {heimdall => core}/src/snapshot/util/tui.rs | 0 {heimdall => core}/tests/test_cfg.rs | 8 +- {heimdall => core}/tests/test_decode.rs | 14 +- {heimdall => core}/tests/test_decompile.rs | 12 +- {heimdall => core}/tests/test_disassemble.rs | 0 {heimdall => core}/tests/test_snapshot.rs | 14 +- 69 files changed, 359 insertions(+), 84 deletions(-) create mode 100644 cli/Cargo.toml create mode 100644 cli/src/main.rs rename {heimdall => core}/Cargo.toml (96%) rename {heimdall => core}/examples/cfg.rs (97%) rename {heimdall => core}/examples/decode.rs (94%) rename {heimdall => core}/examples/decompile.rs (96%) rename {heimdall => core}/examples/snapshot.rs (96%) rename {heimdall => core}/src/cfg/graph.rs (100%) rename {heimdall => core}/src/cfg/mod.rs (99%) rename {heimdall => core}/src/cfg/output.rs (100%) rename {heimdall => core}/src/decode/mod.rs (99%) rename {heimdall => core}/src/decode/util.rs (100%) rename {heimdall => core}/src/decompile/analyzers/mod.rs (100%) rename {heimdall => core}/src/decompile/analyzers/solidity.rs (100%) rename {heimdall => core}/src/decompile/analyzers/yul.rs (100%) rename {heimdall => core}/src/decompile/constants.rs (100%) rename {heimdall => core}/src/decompile/mod.rs (99%) rename {heimdall => core}/src/decompile/out/mod.rs (100%) rename {heimdall => core}/src/decompile/out/postprocessers/mod.rs (100%) rename {heimdall => core}/src/decompile/out/postprocessers/solidity.rs (100%) rename {heimdall => core}/src/decompile/out/postprocessers/tests.rs (100%) rename {heimdall => core}/src/decompile/out/postprocessers/yul.rs (100%) rename {heimdall => core}/src/decompile/out/solidity.rs (100%) rename {heimdall => core}/src/decompile/out/yul.rs (100%) rename {heimdall => core}/src/decompile/precompile.rs (100%) rename {heimdall => core}/src/decompile/resolve.rs (100%) rename {heimdall => core}/src/decompile/util.rs (100%) rename {heimdall => core}/src/dump/constants.rs (100%) rename {heimdall => core}/src/dump/menus/command_palette.rs (100%) rename {heimdall => core}/src/dump/menus/help.rs (100%) rename {heimdall => core}/src/dump/menus/main.rs (100%) rename {heimdall => core}/src/dump/menus/mod.rs (100%) rename {heimdall => core}/src/dump/mod.rs (100%) rename {heimdall => core}/src/dump/structures/dump_state.rs (100%) rename {heimdall => core}/src/dump/structures/mod.rs (100%) rename {heimdall => core}/src/dump/structures/storage_slot.rs (100%) rename {heimdall => core}/src/dump/structures/transaction.rs (100%) rename {heimdall => core}/src/dump/util/csv.rs (100%) rename {heimdall => core}/src/dump/util/mod.rs (100%) rename {heimdall => core}/src/dump/util/table.rs (100%) rename {heimdall => core}/src/dump/util/threads/indexer.rs (100%) rename {heimdall => core}/src/dump/util/threads/mod.rs (100%) rename {heimdall => core}/src/dump/util/threads/tui.rs (100%) rename {heimdall => core}/src/heimdall.rs (100%) rename {heimdall => core}/src/lib.rs (82%) rename {heimdall => core}/src/snapshot/analyze.rs (100%) rename {heimdall => core}/src/snapshot/constants.rs (100%) rename {heimdall => core}/src/snapshot/menus/command_palette.rs (100%) rename {heimdall => core}/src/snapshot/menus/help.rs (100%) rename {heimdall => core}/src/snapshot/menus/main.rs (100%) rename {heimdall => core}/src/snapshot/menus/mod.rs (100%) rename {heimdall => core}/src/snapshot/mod.rs (99%) rename {heimdall => core}/src/snapshot/resolve.rs (100%) rename {heimdall => core}/src/snapshot/structures/mod.rs (100%) rename {heimdall => core}/src/snapshot/structures/snapshot.rs (100%) rename {heimdall => core}/src/snapshot/structures/state.rs (100%) rename {heimdall => core}/src/snapshot/util/csv.rs (100%) rename {heimdall => core}/src/snapshot/util/mod.rs (100%) rename {heimdall => core}/src/snapshot/util/table.rs (100%) rename {heimdall => core}/src/snapshot/util/tui.rs (100%) rename {heimdall => core}/tests/test_cfg.rs (94%) rename {heimdall => core}/tests/test_decode.rs (96%) rename {heimdall => core}/tests/test_decompile.rs (97%) rename {heimdall => core}/tests/test_disassemble.rs (100%) rename {heimdall => core}/tests/test_snapshot.rs (95%) diff --git a/Cargo.lock b/Cargo.lock index 003e5778..faea6202 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -837,12 +837,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dot" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74b6c4d4a1cff5f454164363c16b72fa12463ca6b31f4b5f2035a65fa3d5906" - [[package]] name = "dunce" version = "1.0.4" @@ -1620,44 +1614,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "heimdall" -version = "0.5.3" +name = "heimdall-cache" +version = "0.5.4" +dependencies = [ + "bincode", + "clap", + "serde", +] + +[[package]] +name = "heimdall-cli" +version = "0.5.4" dependencies = [ "backtrace", "clap", "clap-verbosity-flag", "colored", "crossterm 0.26.1", - "dot", - "ethers", - "fancy-regex", "heimdall-cache", "heimdall-common", "heimdall-config", - "indicatif", - "lazy_static", - "petgraph", - "serde", - "serde_json", - "strsim", + "heimdall-core", "tokio", "tui", ] -[[package]] -name = "heimdall-cache" -version = "0.5.3" -dependencies = [ - "bincode", - "clap", - "clap-verbosity-flag", - "serde", - "toml", -] - [[package]] name = "heimdall-common" -version = "0.5.3" +version = "0.5.4" dependencies = [ "async-openai", "clap", @@ -1679,7 +1663,7 @@ dependencies = [ [[package]] name = "heimdall-config" -version = "0.5.3" +version = "0.5.4" dependencies = [ "clap", "clap-verbosity-flag", @@ -1688,6 +1672,30 @@ dependencies = [ "toml", ] +[[package]] +name = "heimdall-core" +version = "0.5.4" +dependencies = [ + "backtrace", + "clap", + "clap-verbosity-flag", + "colored", + "crossterm 0.26.1", + "ethers", + "fancy-regex", + "heimdall-cache", + "heimdall-common", + "heimdall-config", + "indicatif", + "lazy_static", + "petgraph", + "serde", + "serde_json", + "strsim", + "tokio", + "tui", +] + [[package]] name = "hermit-abi" version = "0.1.19" diff --git a/Cargo.toml b/Cargo.toml index 132bb69a..e8ff1db5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["common", "config", "heimdall"] +members = ["common", "config", "core", "cli"] version = "0.5.4" diff --git a/cache/Cargo.toml b/cache/Cargo.toml index 11a7dd95..1ca3900b 100644 --- a/cache/Cargo.toml +++ b/cache/Cargo.toml @@ -8,8 +8,6 @@ description = "Cache management for Heimdall." keywords = ["ethereum", "web3", "decompiler", "evm", "crypto"] [dependencies] -clap-verbosity-flag = "1.0.0" clap = { version = "3.1.18", features = ["derive"] } serde = { version = "1.0", features = ["derive"] } -toml = { version = "0.7.6" } bincode = "1.3.3" diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 00000000..323a5ff1 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "heimdall-cli" +version = "0.5.4" +edition = "2021" +license = "MIT" +readme = "README.md" +description = "CLI for the heimdall-rs toolkit" +keywords = ["ethereum", "web3", "decompiler", "evm", "crypto"] + +[dependencies] +backtrace = "0.3" +clap = {version = "3.1.18", features = ["derive"]} +clap-verbosity-flag = "1.0.0" +colored = "2" +crossterm = "0.26.1" +heimdall-cache = {path = "./../cache"} +heimdall-core = {path = "./../core"} +heimdall-common = {path = "./../common"} +heimdall-config = {path = "./../config"} +tokio = {version = "1", features = ["full"]} +tui = "0.19" + +[[bin]] +name = "heimdall" +path = "src/main.rs" diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 00000000..44d24e68 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,244 @@ +use backtrace::Backtrace; +use std::{io, panic}; + +use clap::{Parser, Subcommand}; +use colored::Colorize; +use crossterm::{ + event::DisableMouseCapture, + execute, + terminal::{disable_raw_mode, LeaveAlternateScreen}, +}; + +use heimdall_cache::{cache, CacheArgs}; +use heimdall_common::{ + ether::evm::ext::disassemble::*, + io::logging::Logger, + utils::version::{current_version, remote_version}, +}; +use heimdall_config::{config, get_config, ConfigArgs}; +use heimdall_core::{ + cfg::{cfg, CFGArgs}, + decode::{decode, DecodeArgs}, + decompile::{decompile, DecompilerArgs}, + dump::{dump, DumpArgs}, + snapshot::{snapshot, SnapshotArgs}, +}; +use tui::{backend::CrosstermBackend, Terminal}; + +#[derive(Debug, Parser)] +#[clap(name = "heimdall", author = "Jonathan Becker ", version)] +pub struct Arguments { + #[clap(subcommand)] + pub sub: Subcommands, +} + +#[derive(Debug, Subcommand)] +#[clap( + about = "Heimdall is an advanced Ethereum smart contract toolkit for forensic and heuristic analysis.", + after_help = "For more information, read the wiki: https://jbecker.dev/r/heimdall-rs/wiki" +)] +#[allow(clippy::large_enum_variant)] +pub enum Subcommands { + #[clap(name = "disassemble", about = "Disassemble EVM bytecode to assembly")] + Disassemble(DisassemblerArgs), + + #[clap(name = "decompile", about = "Decompile EVM bytecode to Solidity")] + Decompile(DecompilerArgs), + + #[clap(name = "cfg", about = "Generate a visual control flow graph for EVM bytecode")] + CFG(CFGArgs), + + #[clap(name = "decode", about = "Decode calldata into readable types")] + Decode(DecodeArgs), + + #[clap(name = "config", about = "Display and edit the current configuration")] + Config(ConfigArgs), + + #[clap(name = "cache", about = "Manage heimdall-rs' cached files")] + Cache(CacheArgs), + + #[clap(name = "dump", about = "Dump the value of all storage slots accessed by a contract")] + Dump(DumpArgs), + #[clap( + name = "snapshot", + about = "Infer function information from bytecode, including access control, gas + consumption, storage accesses, event emissions, and more" + )] + Snapshot(SnapshotArgs), +} + +fn main() { + let args = Arguments::parse(); + + // handle catching panics with + panic::set_hook(Box::new(|panic_info| { + // cleanup the terminal + let stdout = io::stdout(); + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend).unwrap(); + disable_raw_mode().unwrap(); + execute!(terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture).unwrap(); + terminal.show_cursor().unwrap(); + + // print the panic message + let backtrace = Backtrace::new(); + let (logger, _) = Logger::new("TRACE"); + logger.fatal(&format!( + "thread 'main' encountered a fatal error: '{}'!", + panic_info.to_string().bright_white().on_bright_red().bold(), + )); + logger.fatal(&format!("Stack Trace:\n\n{backtrace:#?}")); + })); + + let configuration = get_config(); + match args.sub { + Subcommands::Disassemble(mut cmd) => { + // if the user has not specified a rpc url, use the default + if cmd.rpc_url.as_str() == "" { + cmd.rpc_url = configuration.rpc_url; + } + + // set logger environment variable if not already set + if std::env::var("RUST_LOG").is_err() { + std::env::set_var( + "RUST_LOG", + match cmd.verbose.log_level() { + Some(level) => level.as_str(), + None => "SILENT", + }, + ); + } + + disassemble(cmd); + } + + Subcommands::Decompile(mut cmd) => { + // if the user has not specified a rpc url, use the default + if cmd.rpc_url.as_str() == "" { + cmd.rpc_url = configuration.rpc_url; + } + + // set logger environment variable if not already set + if std::env::var("RUST_LOG").is_err() { + std::env::set_var( + "RUST_LOG", + match cmd.verbose.log_level() { + Some(level) => level.as_str(), + None => "SILENT", + }, + ); + } + + decompile(cmd); + } + + Subcommands::Decode(mut cmd) => { + // if the user has not specified a rpc url, use the default + if cmd.rpc_url.as_str() == "" { + cmd.rpc_url = configuration.rpc_url; + } + + // if the user has not specified a openai api key, use the default + if cmd.openai_api_key.as_str() == "" { + cmd.openai_api_key = configuration.openai_api_key; + } + + // set logger environment variable if not already set + if std::env::var("RUST_LOG").is_err() { + std::env::set_var( + "RUST_LOG", + match cmd.verbose.log_level() { + Some(level) => level.as_str(), + None => "SILENT", + }, + ); + } + + decode(cmd); + } + + Subcommands::CFG(mut cmd) => { + // if the user has not specified a rpc url, use the default + if cmd.rpc_url.as_str() == "" { + cmd.rpc_url = configuration.rpc_url; + } + + // set logger environment variable if not already set + if std::env::var("RUST_LOG").is_err() { + std::env::set_var( + "RUST_LOG", + match cmd.verbose.log_level() { + Some(level) => level.as_str(), + None => "SILENT", + }, + ); + } + + cfg(cmd); + } + + Subcommands::Dump(mut cmd) => { + // if the user has not specified a rpc url, use the default + if cmd.rpc_url.as_str() == "" { + cmd.rpc_url = configuration.rpc_url; + } + + // if the user has not specified a transpose api key, use the default + if cmd.transpose_api_key.as_str() == "" { + cmd.transpose_api_key = configuration.transpose_api_key; + } + + // set logger environment variable if not already set + if std::env::var("RUST_LOG").is_err() { + std::env::set_var( + "RUST_LOG", + match cmd.verbose.log_level() { + Some(level) => level.as_str(), + None => "SILENT", + }, + ); + } + + dump(cmd); + } + + Subcommands::Snapshot(mut cmd) => { + // if the user has not specified a rpc url, use the default + if cmd.rpc_url.as_str() == "" { + cmd.rpc_url = configuration.rpc_url; + } + + // set logger environment variable if not already set + if std::env::var("RUST_LOG").is_err() { + std::env::set_var( + "RUST_LOG", + match cmd.verbose.log_level() { + Some(level) => level.as_str(), + None => "SILENT", + }, + ); + } + + snapshot(cmd); + } + Subcommands::Config(cmd) => { + config(cmd); + } + + Subcommands::Cache(cmd) => { + _ = cache(cmd); + } + } + + // check if the version is up to date + let remote_version = remote_version(); + let current_version = current_version(); + + if remote_version.gt(¤t_version) { + let (logger, _) = Logger::new("TRACE"); + println!(); + logger.info("great news! An update is available!"); + logger + .info(&format!("you can update now by running: `bifrost --version {remote_version}`")); + } +} diff --git a/common/src/utils/strings.rs b/common/src/utils/strings.rs index ca6aca97..4b53bad4 100644 --- a/common/src/utils/strings.rs +++ b/common/src/utils/strings.rs @@ -19,7 +19,7 @@ use crate::constants::REDUCE_HEX_REGEX; /// ## Example /// ```no_run /// use ethers::prelude::{I256, U256}; -/// use heimdall::utils::strings::unsign_int; +/// use heimdall_core::utils::strings::unsign_int; /// /// let signed = I256::from(-1); /// let unsigned = unsign_int(signed); @@ -40,7 +40,7 @@ pub fn sign_uint(unsigned: U256) -> I256 { /// /// ## Example /// ```no_run -/// use heimdall::utils::strings::decode_hex; +/// use heimdall_core::utils::strings::decode_hex; /// /// let decoded = decode_hex("00010203"); /// assert_eq!(decoded, Ok(vec![0, 1, 2, 3])); @@ -59,7 +59,7 @@ pub fn decode_hex(s: &str) -> Result, ParseIntError> { /// /// ## Example /// ```no_run -/// use heimdall::utils::strings::encode_hex; +/// use heimdall_core::utils::strings::encode_hex; /// /// let encoded = encode_hex(vec![0, 1, 2, 3]); /// assert_eq!(encoded, String::from("00010203")); @@ -82,7 +82,7 @@ pub fn encode_hex(s: Vec) -> String { /// ## Example /// ```no_run /// use ethers::prelude::U256; -/// use heimdall::utils::strings::encode_hex_reduced; +/// use heimdall_core::utils::strings::encode_hex_reduced; /// /// let encoded = encode_hex_reduced(U256::from(0)); /// assert_eq!(encoded, String::from("0")); @@ -108,7 +108,7 @@ pub fn encode_hex_reduced(s: U256) -> String { /// /// ## Example /// ```no_run -/// use heimdall::utils::strings::hex_to_ascii; +/// use heimdall_core::utils::strings::hex_to_ascii; /// /// let ascii = hex_to_ascii("0x68656c6c6f20776f726c64"); /// assert_eq!(ascii, String::from("hello world")); @@ -139,7 +139,7 @@ pub fn hex_to_ascii(s: &str) -> String { /// /// ## Example /// ```no_run -/// use heimdall::utils::strings::replace_last; +/// use heimdall_core::utils::strings::replace_last; /// /// let replaced = replace_last(String::from("arg0 + arg1"), "arg1", "arg2"); /// assert_eq!(replaced, String::from("arg0 + arg2")); @@ -164,7 +164,7 @@ pub fn replace_last(s: &str, old: &str, new: &str) -> String { /// /// ## Example /// ```no_run -/// use heimdall::utils::strings::find_balanced_encapsulator; +/// use heimdall_core::utils::strings::find_balanced_encapsulator; /// /// let (start, end, is_balanced) = find_balanced_encapsulator(String::from("arg0 + arg1"), ('(', ')')); /// assert_eq!(start, 0); @@ -205,7 +205,7 @@ pub fn find_balanced_encapsulator(s: &str, encap: (char, char)) -> (usize, usize /// /// ## Example /// ```no_run -/// use heimdall::utils::strings::find_balanced_encapsulator_backwards; +/// use heimdall_core::utils::strings::find_balanced_encapsulator_backwards; /// /// let (start, end, is_balanced) = find_balanced_encapsulator_backwards(String::from("arg0 + arg1"), ('(', ')')); /// assert_eq!(start, 0); @@ -244,7 +244,7 @@ pub fn find_balanced_encapsulator_backwards(s: &str, encap: (char, char)) -> (us /// /// ## Example /// ```no_run -/// use heimdall::utils::strings::base26_encode; +/// use heimdall_core::utils::strings::base26_encode; /// /// let encoded = base26_encode(0); /// assert_eq!(encoded, String::from("a")); @@ -278,7 +278,7 @@ pub fn base26_encode(n: usize) -> String { /// ## Example /// ```no_run /// use fancy_regex::Regex; -/// use heimdall::utils::strings::split_string_by_regex; +/// use heimdall_core::utils::strings::split_string_by_regex; /// /// let pattern = Regex::new(r"\s+").unwrap(); /// let substrings = split_string_by_regex("arg0 + arg1", pattern); @@ -319,7 +319,7 @@ pub fn split_string_by_regex(input: &str, pattern: Regex) -> Vec { /// /// ## Example /// ```no_run -/// use heimdall::utils::strings::extract_condition; +/// use heimdall_core::utils::strings::extract_condition; /// /// let condition = extract_condition("require(arg0 > 0)", "require"); /// assert_eq!(condition, Some(String::from("arg0 > 0"))); @@ -327,7 +327,7 @@ pub fn split_string_by_regex(input: &str, pattern: Regex) -> Vec { /// /// ## Example 2 /// ```no_run -/// use heimdall::utils::strings::extract_condition; +/// use heimdall_core::utils::strings::extract_condition; /// /// let condition = extract_condition("if (arg0 > 0) {", "if"); /// assert_eq!(condition, Some(String::from("arg0 > 0"))); @@ -367,7 +367,7 @@ pub fn extract_condition(s: &str, keyword: &str) -> Option { /// /// ## Example /// ```no_run -/// use heimdall::utils::strings::tokenize; +/// use heimdall_core::utils::strings::tokenize; /// /// let tokens = tokenize("arg0 + arg1"); /// assert_eq!(tokens, vec!["arg0", "+", "arg1"]); @@ -458,7 +458,7 @@ pub enum TokenType { /// /// ## Example /// ```no_run -/// use heimdall::utils::strings::classify_token; +/// use heimdall_core::utils::strings::classify_token; /// /// let (classification, precedence) = classify_token("0x01"); /// assert_eq!(classification, TokenType::Constant); diff --git a/heimdall/Cargo.toml b/core/Cargo.toml similarity index 96% rename from heimdall/Cargo.toml rename to core/Cargo.toml index d0ba590e..2fd6a811 100644 --- a/heimdall/Cargo.toml +++ b/core/Cargo.toml @@ -3,7 +3,7 @@ description = "Heimdall is an advanced Ethereum smart contract toolkit for foren edition = "2021" keywords = ["ethereum", "web3", "decompiler", "evm", "crypto"] license = "MIT" -name = "heimdall" +name = "heimdall-core" readme = "README.md" version = "0.5.4" @@ -13,7 +13,6 @@ clap = {version = "3.1.18", features = ["derive"]} clap-verbosity-flag = "1.0.0" colored = "2" crossterm = "0.26.1" -dot = "0.1.4" ethers = "2.0.4" fancy-regex = "0.11.0" heimdall-cache = {path = "./../cache"} diff --git a/heimdall/examples/cfg.rs b/core/examples/cfg.rs similarity index 97% rename from heimdall/examples/cfg.rs rename to core/examples/cfg.rs index 629d3c51..07c76750 100644 --- a/heimdall/examples/cfg.rs +++ b/core/examples/cfg.rs @@ -1,4 +1,4 @@ -use heimdall::cfg::CFGBuilder; +use heimdall_core::cfg::CFGBuilder; fn main() { // Trace the bytecode and save the results. diff --git a/heimdall/examples/decode.rs b/core/examples/decode.rs similarity index 94% rename from heimdall/examples/decode.rs rename to core/examples/decode.rs index 2e9dc137..68e18463 100644 --- a/heimdall/examples/decode.rs +++ b/core/examples/decode.rs @@ -1,4 +1,4 @@ -use heimdall::decode::decode_calldata; +use heimdall_core::decode::decode_calldata; fn main() { // Decode the calldata and save the results. diff --git a/heimdall/examples/decompile.rs b/core/examples/decompile.rs similarity index 96% rename from heimdall/examples/decompile.rs rename to core/examples/decompile.rs index 830be984..a6adeb92 100644 --- a/heimdall/examples/decompile.rs +++ b/core/examples/decompile.rs @@ -1,4 +1,4 @@ -use heimdall::decompile::DecompileBuilder; +use heimdall_core::decompile::DecompileBuilder; fn main() { // Decompile the bytecode and save the results. diff --git a/heimdall/examples/snapshot.rs b/core/examples/snapshot.rs similarity index 96% rename from heimdall/examples/snapshot.rs rename to core/examples/snapshot.rs index 5247b921..7a3c2a0c 100644 --- a/heimdall/examples/snapshot.rs +++ b/core/examples/snapshot.rs @@ -1,4 +1,4 @@ -use heimdall::snapshot::SnapshotBuilder; +use heimdall_core::snapshot::SnapshotBuilder; fn main() { // Decompile the bytecode and save the results. diff --git a/heimdall/src/cfg/graph.rs b/core/src/cfg/graph.rs similarity index 100% rename from heimdall/src/cfg/graph.rs rename to core/src/cfg/graph.rs diff --git a/heimdall/src/cfg/mod.rs b/core/src/cfg/mod.rs similarity index 99% rename from heimdall/src/cfg/mod.rs rename to core/src/cfg/mod.rs index e3603e80..ec711d3d 100644 --- a/heimdall/src/cfg/mod.rs +++ b/core/src/cfg/mod.rs @@ -252,7 +252,7 @@ pub fn cfg(args: CFGArgs) { /// ## Example /// Use with normal settings: /// ```no_run -/// # use crate::heimdall::cfg::CFGBuilder; +/// # use crate::heimdall_core::cfg::CFGBuilder; /// const SOURCE: &'static str = "7312/* snip */04ad"; /// /// CFGBuilder::new(SOURCE) @@ -260,7 +260,7 @@ pub fn cfg(args: CFGArgs) { /// ``` /// Or change settings individually: /// ```no_run -/// # use crate::heimdall::cfg::CFGBuilder; +/// # use crate::heimdall_core::cfg::CFGBuilder; /// /// const SOURCE: &'static str = "7312/* snip */04ad"; /// CFGBuilder::new(SOURCE) diff --git a/heimdall/src/cfg/output.rs b/core/src/cfg/output.rs similarity index 100% rename from heimdall/src/cfg/output.rs rename to core/src/cfg/output.rs diff --git a/heimdall/src/decode/mod.rs b/core/src/decode/mod.rs similarity index 99% rename from heimdall/src/decode/mod.rs rename to core/src/decode/mod.rs index bcbf1f3e..e5f20930 100644 --- a/heimdall/src/decode/mod.rs +++ b/core/src/decode/mod.rs @@ -404,7 +404,7 @@ pub fn decode(args: DecodeArgs) { /// Decode calldata into a Vec of potential ResolvedFunctions /// ## Example /// ```no_run -/// # use crate::heimdall::decode::decode_calldata; +/// # use crate::heimdall_core::decode::decode_calldata; /// const CALLDATA: &'static str = "0xd57e/* snip */2867" /// /// let potential_matches = decode_calldata(CALLDATA.to_string()); diff --git a/heimdall/src/decode/util.rs b/core/src/decode/util.rs similarity index 100% rename from heimdall/src/decode/util.rs rename to core/src/decode/util.rs diff --git a/heimdall/src/decompile/analyzers/mod.rs b/core/src/decompile/analyzers/mod.rs similarity index 100% rename from heimdall/src/decompile/analyzers/mod.rs rename to core/src/decompile/analyzers/mod.rs diff --git a/heimdall/src/decompile/analyzers/solidity.rs b/core/src/decompile/analyzers/solidity.rs similarity index 100% rename from heimdall/src/decompile/analyzers/solidity.rs rename to core/src/decompile/analyzers/solidity.rs diff --git a/heimdall/src/decompile/analyzers/yul.rs b/core/src/decompile/analyzers/yul.rs similarity index 100% rename from heimdall/src/decompile/analyzers/yul.rs rename to core/src/decompile/analyzers/yul.rs diff --git a/heimdall/src/decompile/constants.rs b/core/src/decompile/constants.rs similarity index 100% rename from heimdall/src/decompile/constants.rs rename to core/src/decompile/constants.rs diff --git a/heimdall/src/decompile/mod.rs b/core/src/decompile/mod.rs similarity index 99% rename from heimdall/src/decompile/mod.rs rename to core/src/decompile/mod.rs index 796fc564..12f17e34 100644 --- a/heimdall/src/decompile/mod.rs +++ b/core/src/decompile/mod.rs @@ -641,7 +641,7 @@ pub fn decompile(args: DecompilerArgs) { /// ## Example /// Use with normal settings: /// ```no_run -/// # use crate::heimdall::decompile::DecompileBuilder; +/// # use crate::heimdall_core::decompile::DecompileBuilder; /// const SOURCE: &'static str = "7312/* snip */04ad"; /// /// DecompileBuilder::new(SOURCE) @@ -649,7 +649,7 @@ pub fn decompile(args: DecompilerArgs) { /// ``` /// Or change settings individually: /// ```no_run -/// # use crate::heimdall::decompile::DecompileBuilder; +/// # use crate::heimdall_core::decompile::DecompileBuilder; /// /// const SOURCE: &'static str = "7312/* snip */04ad"; /// DecompileBuilder::new(SOURCE) diff --git a/heimdall/src/decompile/out/mod.rs b/core/src/decompile/out/mod.rs similarity index 100% rename from heimdall/src/decompile/out/mod.rs rename to core/src/decompile/out/mod.rs diff --git a/heimdall/src/decompile/out/postprocessers/mod.rs b/core/src/decompile/out/postprocessers/mod.rs similarity index 100% rename from heimdall/src/decompile/out/postprocessers/mod.rs rename to core/src/decompile/out/postprocessers/mod.rs diff --git a/heimdall/src/decompile/out/postprocessers/solidity.rs b/core/src/decompile/out/postprocessers/solidity.rs similarity index 100% rename from heimdall/src/decompile/out/postprocessers/solidity.rs rename to core/src/decompile/out/postprocessers/solidity.rs diff --git a/heimdall/src/decompile/out/postprocessers/tests.rs b/core/src/decompile/out/postprocessers/tests.rs similarity index 100% rename from heimdall/src/decompile/out/postprocessers/tests.rs rename to core/src/decompile/out/postprocessers/tests.rs diff --git a/heimdall/src/decompile/out/postprocessers/yul.rs b/core/src/decompile/out/postprocessers/yul.rs similarity index 100% rename from heimdall/src/decompile/out/postprocessers/yul.rs rename to core/src/decompile/out/postprocessers/yul.rs diff --git a/heimdall/src/decompile/out/solidity.rs b/core/src/decompile/out/solidity.rs similarity index 100% rename from heimdall/src/decompile/out/solidity.rs rename to core/src/decompile/out/solidity.rs diff --git a/heimdall/src/decompile/out/yul.rs b/core/src/decompile/out/yul.rs similarity index 100% rename from heimdall/src/decompile/out/yul.rs rename to core/src/decompile/out/yul.rs diff --git a/heimdall/src/decompile/precompile.rs b/core/src/decompile/precompile.rs similarity index 100% rename from heimdall/src/decompile/precompile.rs rename to core/src/decompile/precompile.rs diff --git a/heimdall/src/decompile/resolve.rs b/core/src/decompile/resolve.rs similarity index 100% rename from heimdall/src/decompile/resolve.rs rename to core/src/decompile/resolve.rs diff --git a/heimdall/src/decompile/util.rs b/core/src/decompile/util.rs similarity index 100% rename from heimdall/src/decompile/util.rs rename to core/src/decompile/util.rs diff --git a/heimdall/src/dump/constants.rs b/core/src/dump/constants.rs similarity index 100% rename from heimdall/src/dump/constants.rs rename to core/src/dump/constants.rs diff --git a/heimdall/src/dump/menus/command_palette.rs b/core/src/dump/menus/command_palette.rs similarity index 100% rename from heimdall/src/dump/menus/command_palette.rs rename to core/src/dump/menus/command_palette.rs diff --git a/heimdall/src/dump/menus/help.rs b/core/src/dump/menus/help.rs similarity index 100% rename from heimdall/src/dump/menus/help.rs rename to core/src/dump/menus/help.rs diff --git a/heimdall/src/dump/menus/main.rs b/core/src/dump/menus/main.rs similarity index 100% rename from heimdall/src/dump/menus/main.rs rename to core/src/dump/menus/main.rs diff --git a/heimdall/src/dump/menus/mod.rs b/core/src/dump/menus/mod.rs similarity index 100% rename from heimdall/src/dump/menus/mod.rs rename to core/src/dump/menus/mod.rs diff --git a/heimdall/src/dump/mod.rs b/core/src/dump/mod.rs similarity index 100% rename from heimdall/src/dump/mod.rs rename to core/src/dump/mod.rs diff --git a/heimdall/src/dump/structures/dump_state.rs b/core/src/dump/structures/dump_state.rs similarity index 100% rename from heimdall/src/dump/structures/dump_state.rs rename to core/src/dump/structures/dump_state.rs diff --git a/heimdall/src/dump/structures/mod.rs b/core/src/dump/structures/mod.rs similarity index 100% rename from heimdall/src/dump/structures/mod.rs rename to core/src/dump/structures/mod.rs diff --git a/heimdall/src/dump/structures/storage_slot.rs b/core/src/dump/structures/storage_slot.rs similarity index 100% rename from heimdall/src/dump/structures/storage_slot.rs rename to core/src/dump/structures/storage_slot.rs diff --git a/heimdall/src/dump/structures/transaction.rs b/core/src/dump/structures/transaction.rs similarity index 100% rename from heimdall/src/dump/structures/transaction.rs rename to core/src/dump/structures/transaction.rs diff --git a/heimdall/src/dump/util/csv.rs b/core/src/dump/util/csv.rs similarity index 100% rename from heimdall/src/dump/util/csv.rs rename to core/src/dump/util/csv.rs diff --git a/heimdall/src/dump/util/mod.rs b/core/src/dump/util/mod.rs similarity index 100% rename from heimdall/src/dump/util/mod.rs rename to core/src/dump/util/mod.rs diff --git a/heimdall/src/dump/util/table.rs b/core/src/dump/util/table.rs similarity index 100% rename from heimdall/src/dump/util/table.rs rename to core/src/dump/util/table.rs diff --git a/heimdall/src/dump/util/threads/indexer.rs b/core/src/dump/util/threads/indexer.rs similarity index 100% rename from heimdall/src/dump/util/threads/indexer.rs rename to core/src/dump/util/threads/indexer.rs diff --git a/heimdall/src/dump/util/threads/mod.rs b/core/src/dump/util/threads/mod.rs similarity index 100% rename from heimdall/src/dump/util/threads/mod.rs rename to core/src/dump/util/threads/mod.rs diff --git a/heimdall/src/dump/util/threads/tui.rs b/core/src/dump/util/threads/tui.rs similarity index 100% rename from heimdall/src/dump/util/threads/tui.rs rename to core/src/dump/util/threads/tui.rs diff --git a/heimdall/src/heimdall.rs b/core/src/heimdall.rs similarity index 100% rename from heimdall/src/heimdall.rs rename to core/src/heimdall.rs diff --git a/heimdall/src/lib.rs b/core/src/lib.rs similarity index 82% rename from heimdall/src/lib.rs rename to core/src/lib.rs index 9365301e..3860f2b3 100644 --- a/heimdall/src/lib.rs +++ b/core/src/lib.rs @@ -1,4 +1,5 @@ pub mod cfg; pub mod decode; pub mod decompile; +pub mod dump; pub mod snapshot; diff --git a/heimdall/src/snapshot/analyze.rs b/core/src/snapshot/analyze.rs similarity index 100% rename from heimdall/src/snapshot/analyze.rs rename to core/src/snapshot/analyze.rs diff --git a/heimdall/src/snapshot/constants.rs b/core/src/snapshot/constants.rs similarity index 100% rename from heimdall/src/snapshot/constants.rs rename to core/src/snapshot/constants.rs diff --git a/heimdall/src/snapshot/menus/command_palette.rs b/core/src/snapshot/menus/command_palette.rs similarity index 100% rename from heimdall/src/snapshot/menus/command_palette.rs rename to core/src/snapshot/menus/command_palette.rs diff --git a/heimdall/src/snapshot/menus/help.rs b/core/src/snapshot/menus/help.rs similarity index 100% rename from heimdall/src/snapshot/menus/help.rs rename to core/src/snapshot/menus/help.rs diff --git a/heimdall/src/snapshot/menus/main.rs b/core/src/snapshot/menus/main.rs similarity index 100% rename from heimdall/src/snapshot/menus/main.rs rename to core/src/snapshot/menus/main.rs diff --git a/heimdall/src/snapshot/menus/mod.rs b/core/src/snapshot/menus/mod.rs similarity index 100% rename from heimdall/src/snapshot/menus/mod.rs rename to core/src/snapshot/menus/mod.rs diff --git a/heimdall/src/snapshot/mod.rs b/core/src/snapshot/mod.rs similarity index 99% rename from heimdall/src/snapshot/mod.rs rename to core/src/snapshot/mod.rs index e973dc70..1c865004 100644 --- a/heimdall/src/snapshot/mod.rs +++ b/core/src/snapshot/mod.rs @@ -561,7 +561,7 @@ pub fn snapshot(args: SnapshotArgs) { /// ## Example /// Use with normal settings: /// ```no_run -/// # use crate::heimdall::snapshot::SnapshotBuilder; +/// # use crate::heimdall_core::snapshot::SnapshotBuilder; /// const SOURCE: &'static str = "7312/* snip */04ad"; /// /// SnapshotBuilder::new(SOURCE) @@ -569,7 +569,7 @@ pub fn snapshot(args: SnapshotArgs) { /// ``` /// Or change settings individually: /// ```no_run -/// # use crate::heimdall::snapshot::SnapshotBuilder; +/// # use crate::heimdall_core::snapshot::SnapshotBuilder; /// /// const SOURCE: &'static str = "7312/* snip */04ad"; /// SnapshotBuilder::new(SOURCE) diff --git a/heimdall/src/snapshot/resolve.rs b/core/src/snapshot/resolve.rs similarity index 100% rename from heimdall/src/snapshot/resolve.rs rename to core/src/snapshot/resolve.rs diff --git a/heimdall/src/snapshot/structures/mod.rs b/core/src/snapshot/structures/mod.rs similarity index 100% rename from heimdall/src/snapshot/structures/mod.rs rename to core/src/snapshot/structures/mod.rs diff --git a/heimdall/src/snapshot/structures/snapshot.rs b/core/src/snapshot/structures/snapshot.rs similarity index 100% rename from heimdall/src/snapshot/structures/snapshot.rs rename to core/src/snapshot/structures/snapshot.rs diff --git a/heimdall/src/snapshot/structures/state.rs b/core/src/snapshot/structures/state.rs similarity index 100% rename from heimdall/src/snapshot/structures/state.rs rename to core/src/snapshot/structures/state.rs diff --git a/heimdall/src/snapshot/util/csv.rs b/core/src/snapshot/util/csv.rs similarity index 100% rename from heimdall/src/snapshot/util/csv.rs rename to core/src/snapshot/util/csv.rs diff --git a/heimdall/src/snapshot/util/mod.rs b/core/src/snapshot/util/mod.rs similarity index 100% rename from heimdall/src/snapshot/util/mod.rs rename to core/src/snapshot/util/mod.rs diff --git a/heimdall/src/snapshot/util/table.rs b/core/src/snapshot/util/table.rs similarity index 100% rename from heimdall/src/snapshot/util/table.rs rename to core/src/snapshot/util/table.rs diff --git a/heimdall/src/snapshot/util/tui.rs b/core/src/snapshot/util/tui.rs similarity index 100% rename from heimdall/src/snapshot/util/tui.rs rename to core/src/snapshot/util/tui.rs diff --git a/heimdall/tests/test_cfg.rs b/core/tests/test_cfg.rs similarity index 94% rename from heimdall/tests/test_cfg.rs rename to core/tests/test_cfg.rs index 694002f9..8cbd438a 100644 --- a/heimdall/tests/test_cfg.rs +++ b/core/tests/test_cfg.rs @@ -3,7 +3,7 @@ mod benchmark { use clap_verbosity_flag::Verbosity; use heimdall_common::testing::benchmarks::benchmark; - use heimdall::cfg::CFGArgs; + use heimdall_core::cfg::CFGArgs; #[test] fn benchmark_generate_cfg_simple() { @@ -17,7 +17,7 @@ mod benchmark { color_edges: false, format: String::from(""), }; - heimdall::cfg::cfg(args) + heimdall_core::cfg::cfg(args) } benchmark("benchmark_generate_cfg_simple", 100, bench) @@ -35,7 +35,7 @@ mod benchmark { color_edges: false, format: String::from(""), }; - heimdall::cfg::cfg(args) + heimdall_core::cfg::cfg(args) } benchmark("benchmark_generate_cfg_complex", 100, bench) @@ -46,7 +46,7 @@ mod benchmark { mod integration_tests { use heimdall_common::io::file::{delete_path, read_file}; - use heimdall::cfg::CFGBuilder; + use heimdall_core::cfg::CFGBuilder; #[test] fn test_cfg_simple() { diff --git a/heimdall/tests/test_decode.rs b/core/tests/test_decode.rs similarity index 96% rename from heimdall/tests/test_decode.rs rename to core/tests/test_decode.rs index 81711091..a019480f 100644 --- a/heimdall/tests/test_decode.rs +++ b/core/tests/test_decode.rs @@ -3,7 +3,7 @@ mod benchmark { use clap_verbosity_flag::Verbosity; use heimdall_common::testing::benchmarks::benchmark; - use heimdall::decode::DecodeArgs; + use heimdall_core::decode::DecodeArgs; #[test] fn benchmark_decode_transfer() { @@ -17,7 +17,7 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall::decode::decode(args) + heimdall_core::decode::decode(args) } benchmark("benchmark_decode_transfer", 100, bench) @@ -35,7 +35,7 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall::decode::decode(args) + heimdall_core::decode::decode(args) } benchmark("benchmark_decode_uniswap_simple", 100, bench) @@ -53,7 +53,7 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall::decode::decode(args) + heimdall_core::decode::decode(args) } benchmark("benchmark_decode_seaport_simple", 100, bench) @@ -71,7 +71,7 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall::decode::decode(args) + heimdall_core::decode::decode(args) } benchmark("benchmark_decode_seaport_complex", 100, bench) @@ -88,7 +88,7 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall::decode::decode(args); + heimdall_core::decode::decode(args); } #[test] @@ -102,6 +102,6 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall::decode::decode(args); + heimdall_core::decode::decode(args); } } diff --git a/heimdall/tests/test_decompile.rs b/core/tests/test_decompile.rs similarity index 97% rename from heimdall/tests/test_decompile.rs rename to core/tests/test_decompile.rs index 5da45340..3bf97c66 100644 --- a/heimdall/tests/test_decompile.rs +++ b/core/tests/test_decompile.rs @@ -3,7 +3,7 @@ mod benchmark { use clap_verbosity_flag::Verbosity; use heimdall_common::testing::benchmarks::benchmark; - use heimdall::decompile::DecompilerArgs; + use heimdall_core::decompile::DecompilerArgs; #[test] fn benchmark_decompile_complex() { @@ -18,7 +18,7 @@ mod benchmark { include_solidity: true, include_yul: false, }; - heimdall::decompile::decompile(args) + heimdall_core::decompile::decompile(args) } benchmark("benchmark_decompile_complex", 100, bench) @@ -37,7 +37,7 @@ mod benchmark { include_solidity: true, include_yul: false, }; - heimdall::decompile::decompile(args) + heimdall_core::decompile::decompile(args) } benchmark("benchmark_decompile_simple", 100, bench) @@ -56,7 +56,7 @@ mod benchmark { include_solidity: false, include_yul: false, }; - heimdall::decompile::decompile(args) + heimdall_core::decompile::decompile(args) } benchmark("benchmark_build_abi_simple", 100, bench) @@ -75,7 +75,7 @@ mod benchmark { include_solidity: false, include_yul: false, }; - heimdall::decompile::decompile(args) + heimdall_core::decompile::decompile(args) } benchmark("benchmark_build_abi_complex", 100, bench) @@ -84,8 +84,8 @@ mod benchmark { #[cfg(test)] mod integration_tests { - use heimdall::decompile::DecompileBuilder; use heimdall_common::io::file::{delete_path, read_file}; + use heimdall_core::decompile::DecompileBuilder; #[test] fn test_decompile_precompile() { diff --git a/heimdall/tests/test_disassemble.rs b/core/tests/test_disassemble.rs similarity index 100% rename from heimdall/tests/test_disassemble.rs rename to core/tests/test_disassemble.rs diff --git a/heimdall/tests/test_snapshot.rs b/core/tests/test_snapshot.rs similarity index 95% rename from heimdall/tests/test_snapshot.rs rename to core/tests/test_snapshot.rs index da2a8f91..37c8b7b1 100644 --- a/heimdall/tests/test_snapshot.rs +++ b/core/tests/test_snapshot.rs @@ -3,7 +3,7 @@ mod benchmark { use clap_verbosity_flag::Verbosity; use heimdall_common::testing::benchmarks::benchmark; - use heimdall::snapshot::SnapshotArgs; + use heimdall_core::snapshot::SnapshotArgs; #[test] fn benchmark_snapshot_complex() { @@ -17,7 +17,7 @@ mod benchmark { skip_resolving: true, no_tui: true, }; - heimdall::snapshot::snapshot(args) + heimdall_core::snapshot::snapshot(args) } benchmark("benchmark_snapshot_complex", 100, bench) @@ -35,7 +35,7 @@ mod benchmark { skip_resolving: true, no_tui: true, }; - heimdall::snapshot::snapshot(args) + heimdall_core::snapshot::snapshot(args) } benchmark("benchmark_snapshot_simple", 100, bench) @@ -45,8 +45,8 @@ mod benchmark { #[cfg(test)] mod integration_tests { use clap_verbosity_flag::Verbosity; - use heimdall::snapshot::{SnapshotArgs, SnapshotBuilder}; use heimdall_common::io::file::delete_path; + use heimdall_core::snapshot::{SnapshotArgs, SnapshotBuilder}; #[test] fn test_snapshot_library() { @@ -73,7 +73,7 @@ mod integration_tests { skip_resolving: true, no_tui: true, }; - heimdall::snapshot::snapshot(args); + heimdall_core::snapshot::snapshot(args); // drop path delete_path(&String::from("./output/tests/snapshot/test1")); @@ -90,7 +90,7 @@ mod integration_tests { skip_resolving: true, no_tui: true, }; - heimdall::snapshot::snapshot(args); + heimdall_core::snapshot::snapshot(args); // drop path delete_path(&String::from("./output/tests/snapshot/test2")); @@ -180,7 +180,7 @@ mod integration_tests { skip_resolving: true, no_tui: true, }; - heimdall::snapshot::snapshot(args); + heimdall_core::snapshot::snapshot(args); } delete_path(&String::from("./output/tests/snapshot/integration")); From c17f9f2c426a4c2b7031e9d234a5e1a0b793ec76 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Mon, 25 Sep 2023 18:51:50 -0400 Subject: [PATCH 02/18] =?UTF-8?q?=F0=9F=92=AB=20meta:=20remove=20cwd=20fro?= =?UTF-8?q?m=20github=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr.yml | 4 +--- .github/workflows/tests.yml | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 1b611caa..3f834131 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -16,13 +16,11 @@ jobs: - uses: actions/checkout@v3 - name: Build Binaries - working-directory: ./heimdall run: | cargo clean - cargo build + cargo build --verbose - name: Run Tests - working-directory: ./heimdall run: | cargo test --package heimdall -- test_ --nocapture cargo test --package heimdall-config -- test_ --nocapture diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d69878c1..18726fe5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,11 +12,9 @@ jobs: - uses: actions/checkout@v3 - name: Compile - working-directory: ./heimdall run: cargo build --verbose - name: Run Tests - working-directory: ./heimdall run: | cargo test --package heimdall -- test_ --nocapture cargo test --package heimdall-config -- test_ --nocapture From 1d0716719d202a0db33ddac90ee89a5a435796df Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Mon, 25 Sep 2023 18:58:28 -0400 Subject: [PATCH 03/18] =?UTF-8?q?=F0=9F=92=AB=20meta:=20rename=20packages?= =?UTF-8?q?=20in=20github=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr.yml | 3 ++- .github/workflows/tests.yml | 3 ++- core/Cargo.toml | 4 ---- scripts/test | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3f834131..3ad68a5d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -22,6 +22,7 @@ jobs: - name: Run Tests run: | - cargo test --package heimdall -- test_ --nocapture + cargo test --package heimdall-cli -- test_ --nocapture + cargo test --package heimdall-core -- test_ --nocapture cargo test --package heimdall-config -- test_ --nocapture cargo test --package heimdall-common -- test_ --nocapture diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 18726fe5..5f07c049 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,7 @@ jobs: - name: Run Tests run: | - cargo test --package heimdall -- test_ --nocapture + cargo test --package heimdall-cli -- test_ --nocapture + cargo test --package heimdall-core -- test_ --nocapture cargo test --package heimdall-config -- test_ --nocapture cargo test --package heimdall-common -- test_ --nocapture diff --git a/core/Cargo.toml b/core/Cargo.toml index 2fd6a811..0919c68c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -26,7 +26,3 @@ serde_json = "1.0" strsim = "0.10.0" tokio = {version = "1", features = ["full"]} tui = "0.19" - -[[bin]] -name = "heimdall" -path = "src/heimdall.rs" diff --git a/scripts/test b/scripts/test index 5f324654..5abf3704 100644 --- a/scripts/test +++ b/scripts/test @@ -2,8 +2,8 @@ if [ "$1" = "--cov" ]; then # specify regex to ignore files - # ignore rpc.rs, logging.rs, http.rs, transpose.rs, resources.rs, tests.rs, heimdall.rs, lib.rs - regex=".*dump.*\.rs|.*rpc\.rs|.*logging\.rs|.*http\.rs|.*transpose\.rs|.*resources.*\.rs|.*test(s)?.rs|heimdall\.rs|.*lib\.rs" + # ignore rpc.rs, logging.rs, http.rs, transpose.rs, resources.rs, tests.rs, main.rs, lib.rs + regex=".*dump.*\.rs|.*rpc\.rs|.*logging\.rs|.*http\.rs|.*transpose\.rs|.*resources.*\.rs|.*test(s)?.rs|main\.rs|.*lib\.rs" # if -v or --verbose is passed, then we want to print the coverage report if [ "$2" = "-v" ] || [ "$2" = "--verbose" ]; then From ee9c3aa16586790f5d7114ed274ff5dd2cfa7452 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Tue, 26 Sep 2023 21:06:39 -0400 Subject: [PATCH 04/18] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20more?= =?UTF-8?q?=20async=20&=20io=20refactors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cli/src/main.rs | 7 +- common/src/ether/evm/ext/disassemble.rs | 56 +---- common/src/ether/rpc.rs | 168 ++++++------- common/src/resources/openai.rs | 59 ++--- common/src/utils/mod.rs | 1 + common/src/utils/sync.rs | 8 + core/src/cfg/mod.rs | 155 +----------- core/src/decode/mod.rs | 6 +- core/src/decode/util.rs | 4 +- core/src/decompile/mod.rs | 207 +++------------ core/src/decompile/out/abi.rs | 302 ++++++++++++++++++++++ core/src/decompile/out/mod.rs | 1 + core/src/decompile/out/solidity.rs | 306 +---------------------- core/src/decompile/out/yul.rs | 318 +----------------------- core/src/dump/mod.rs | 2 +- core/src/dump/util/mod.rs | 94 +++---- core/src/dump/util/threads/indexer.rs | 8 +- 17 files changed, 559 insertions(+), 1143 deletions(-) create mode 100644 common/src/utils/sync.rs create mode 100644 core/src/decompile/out/abi.rs diff --git a/cli/src/main.rs b/cli/src/main.rs index 44d24e68..7a5f4d70 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -67,12 +67,13 @@ pub enum Subcommands { Snapshot(SnapshotArgs), } -fn main() { +#[tokio::main] +async fn main() -> Result<(), Box> { let args = Arguments::parse(); // handle catching panics with panic::set_hook(Box::new(|panic_info| { - // cleanup the terminal + // cleanup the terminal (break out of alternate screen, disable mouse capture, and show the cursor) let stdout = io::stdout(); let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend).unwrap(); @@ -241,4 +242,6 @@ fn main() { logger .info(&format!("you can update now by running: `bifrost --version {remote_version}`")); } + + Ok(()) } diff --git a/common/src/ether/evm/ext/disassemble.rs b/common/src/ether/evm/ext/disassemble.rs index 5cdb0407..9aa5a4ee 100644 --- a/common/src/ether/evm/ext/disassemble.rs +++ b/common/src/ether/evm/ext/disassemble.rs @@ -1,9 +1,9 @@ -use std::{env, fs}; +use std::fs; use crate::{ constants::{ADDRESS_REGEX, BYTECODE_REGEX}, ether::{evm::core::opcodes::opcode, rpc::get_code}, - io::{file::*, logging::*}, + io::logging::*, utils::strings::{decode_hex, encode_hex}, }; use clap::{AppSettings, Parser}; @@ -22,10 +22,6 @@ pub struct DisassemblerArgs { #[clap(flatten)] pub verbose: clap_verbosity_flag::Verbosity, - /// The output directory to write the disassembled bytecode to - #[clap(long = "output", short, default_value = "", hide_default_value = true)] - pub output: String, - /// The RPC provider to use for fetching target bytecode. #[clap(long = "rpc-url", short, default_value = "", hide_default_value = true)] pub rpc_url: String, @@ -35,7 +31,7 @@ pub struct DisassemblerArgs { pub decimal_counter: bool, } -pub fn disassemble(args: DisassemblerArgs) -> String { +pub async fn disassemble(args: DisassemblerArgs) -> Result> { use std::time::Instant; let now = Instant::now(); @@ -44,44 +40,19 @@ pub fn disassemble(args: DisassemblerArgs) -> String { None => "SILENT", }); - // parse the output directory - let mut output_dir: String; - if args.output.is_empty() { - output_dir = match env::current_dir() { - Ok(dir) => dir.into_os_string().into_string().unwrap(), - Err(_) => { - logger.error("failed to get current directory."); - std::process::exit(1); - } - }; - output_dir.push_str("/output"); - } else { - output_dir = args.output.clone(); - } - let contract_bytecode: String; - if ADDRESS_REGEX.is_match(&args.target).unwrap() { - // push the address to the output directory - if output_dir != args.output { - output_dir.push_str(&format!("/{}", &args.target)); - } - + if ADDRESS_REGEX.is_match(&args.target)? { // We are disassembling a contract address, so we need to fetch the bytecode from the RPC // provider. - contract_bytecode = get_code(&args.target, &args.rpc_url); - } else if BYTECODE_REGEX.is_match(&args.target).unwrap() { + contract_bytecode = get_code(&args.target, &args.rpc_url).await?; + } else if BYTECODE_REGEX.is_match(&args.target)? { contract_bytecode = args.target; } else { - // push the address to the output directory - if output_dir != args.output { - output_dir.push_str("/local"); - } - // We are disassembling a file, so we need to read the bytecode from the file. contract_bytecode = match fs::read_to_string(&args.target) { Ok(contents) => { let _contents = contents.replace('\n', ""); - if BYTECODE_REGEX.is_match(&_contents).unwrap() && _contents.len() % 2 == 0 { + if BYTECODE_REGEX.is_match(&_contents)? && _contents.len() % 2 == 0 { _contents } else { logger @@ -100,14 +71,14 @@ pub fn disassemble(args: DisassemblerArgs) -> String { let mut output: String = String::new(); // Iterate over the bytecode, disassembling each instruction. - let byte_array = decode_hex(&contract_bytecode.replacen("0x", "", 1)).unwrap(); + let byte_array = decode_hex(&contract_bytecode.replacen("0x", "", 1))?; while program_counter < byte_array.len() { let operation = opcode(byte_array[program_counter]); let mut pushed_bytes: String = String::new(); if operation.name.contains("PUSH") { - let byte_count_to_push: u8 = operation.name.replace("PUSH", "").parse().unwrap(); + let byte_count_to_push: u8 = operation.name.strip_prefix("PUSH").unwrap().parse()?; pushed_bytes = match byte_array .get(program_counter + 1..program_counter + 1 + byte_count_to_push as usize) @@ -135,14 +106,7 @@ pub fn disassemble(args: DisassemblerArgs) -> String { } logger.info(&format!("disassembled {program_counter} bytes successfully.")); - - // write the output to a file - write_file(&format!("{output_dir}/bytecode.evm"), &contract_bytecode); - let file_path = write_file(&format!("{output_dir}/disassembled.asm"), &output); - logger.success(&format!("wrote disassembled bytecode to '{file_path}' .")); - - // log the time it took to disassemble the bytecode logger.debug(&format!("disassembly completed in {} ms.", now.elapsed().as_millis())); - output + Ok(output) } diff --git a/common/src/ether/rpc.rs b/common/src/ether/rpc.rs index 8604db5b..6cdea2a4 100644 --- a/common/src/ether/rpc.rs +++ b/common/src/ether/rpc.rs @@ -8,9 +8,10 @@ use ethers::{ }; use heimdall_cache::{read_cache, store_cache}; -pub fn get_code(contract_address: &str, rpc_url: &str) -> String { - // create new runtime block - let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); +pub async fn get_code( + contract_address: &str, + rpc_url: &str, +) -> Result> { // get a new logger let level = std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".into()); @@ -19,56 +20,59 @@ pub fn get_code(contract_address: &str, rpc_url: &str) -> String { logger .debug_max(&format!("fetching bytecode from node for contract: '{}' .", &contract_address)); - rt.block_on(async { - - // check the cache for a matching address - if let Some(bytecode) = read_cache(&format!("contract.{}", &contract_address)) { - logger.debug(&format!("found cached bytecode for '{}' .", &contract_address)); - return bytecode; + // check the cache for a matching address + if let Some(bytecode) = read_cache(&format!("contract.{}", &contract_address)) { + logger.debug(&format!("found cached bytecode for '{}' .", &contract_address)); + return Ok(bytecode); + } + + // make sure the RPC provider isn't empty + if rpc_url.is_empty() { + logger.error("disassembling an on-chain contract requires an RPC provider. Use `heimdall disassemble --help` for more information."); + std::process::exit(1); + } + + // create new provider + let provider = match Provider::::try_from(rpc_url) { + Ok(provider) => provider, + Err(_) => { + logger.error(&format!("failed to connect to RPC provider '{}' .", &rpc_url)); + std::process::exit(1) } - - // make sure the RPC provider isn't empty - if rpc_url.is_empty() { - logger.error("disassembling an on-chain contract requires an RPC provider. Use `heimdall disassemble --help` for more information."); - std::process::exit(1); + }; + + // safely unwrap the address + let address = match contract_address.parse::
() { + Ok(address) => address, + Err(_) => { + logger.error(&format!("failed to parse address '{}' .", &contract_address)); + std::process::exit(1) } + }; + + // fetch the bytecode at the address + let bytecode_as_bytes = match provider.get_code(address, None).await { + Ok(bytecode) => bytecode, + Err(_) => { + logger.error(&format!("failed to fetch bytecode from '{}' .", &contract_address)); + std::process::exit(1) + } + }; - // create new provider - let provider = match Provider::::try_from(rpc_url) { - Ok(provider) => provider, - Err(_) => { - logger.error(&format!("failed to connect to RPC provider '{}' .", &rpc_url)); - std::process::exit(1) - } - }; - - // safely unwrap the address - let address = match contract_address.parse::
() { - Ok(address) => address, - Err(_) => { - logger.error(&format!("failed to parse address '{}' .", &contract_address)); - std::process::exit(1) - } - }; - - // fetch the bytecode at the address - let bytecode_as_bytes = match provider.get_code(address, None).await { - Ok(bytecode) => bytecode, - Err(_) => { - logger.error(&format!("failed to fetch bytecode from '{}' .", &contract_address)); - std::process::exit(1) - } - }; - - // cache the results - store_cache(&format!("contract.{}", &contract_address), bytecode_as_bytes.to_string().replacen("0x", "", 1), None); + // cache the results + store_cache( + &format!("contract.{}", &contract_address), + bytecode_as_bytes.to_string().replacen("0x", "", 1), + None, + ); - bytecode_as_bytes.to_string() - }) + Ok(bytecode_as_bytes.to_string()) } -pub fn get_transaction(transaction_hash: &str, rpc_url: &str) -> Transaction { - let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); +pub async fn get_transaction( + transaction_hash: &str, + rpc_url: &str, +) -> Result> { // get a new logger let level = std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".into()); @@ -80,47 +84,43 @@ pub fn get_transaction(transaction_hash: &str, rpc_url: &str) -> Transaction { )); // We are decoding a transaction hash, so we need to fetch the calldata from the RPC provider. - rt.block_on(async { - // make sure the RPC provider isn't empty - if rpc_url.is_empty() { - logger.error("decoding an on-chain transaction requires an RPC provider. Use `heimdall decode --help` for more information."); - std::process::exit(1); + // make sure the RPC provider isn't empty + if rpc_url.is_empty() { + logger.error("decoding an on-chain transaction requires an RPC provider. Use `heimdall decode --help` for more information."); + std::process::exit(1); + } + + // create new provider + let provider = match Provider::::try_from(rpc_url) { + Ok(provider) => provider, + Err(_) => { + logger.error(&format!("failed to connect to RPC provider '{}' .", &rpc_url)); + std::process::exit(1) } - - // create new provider - let provider = match Provider::::try_from(rpc_url) { - Ok(provider) => provider, - Err(_) => { - logger.error(&format!("failed to connect to RPC provider '{}' .", &rpc_url)); - std::process::exit(1) - } - }; - - // safely unwrap the transaction hash - let transaction_hash = match H256::from_str(transaction_hash) { - Ok(transaction_hash) => transaction_hash, - Err(_) => { - logger.error(&format!("failed to parse transaction hash '{}' .", &transaction_hash)); - std::process::exit(1) - } - }; - - // fetch the transaction from the node - match provider.get_transaction(transaction_hash).await { - Ok(tx) => { - match tx { - Some(tx) => tx, - None => { - logger.error(&format!("transaction '{}' doesn't exist.", &transaction_hash)); - std::process::exit(1) - } - } - }, - Err(_) => { - logger.error(&format!("failed to fetch calldata from '{}' .", &transaction_hash)); + }; + + // safely unwrap the transaction hash + let transaction_hash = match H256::from_str(transaction_hash) { + Ok(transaction_hash) => transaction_hash, + Err(_) => { + logger.error(&format!("failed to parse transaction hash '{}' .", &transaction_hash)); + std::process::exit(1) + } + }; + + // fetch the transaction from the node + Ok(match provider.get_transaction(transaction_hash).await { + Ok(tx) => match tx { + Some(tx) => tx, + None => { + logger.error(&format!("transaction '{}' doesn't exist.", &transaction_hash)); std::process::exit(1) } + }, + Err(_) => { + logger.error(&format!("failed to fetch calldata from '{}' .", &transaction_hash)); + std::process::exit(1) } }) } diff --git a/common/src/resources/openai.rs b/common/src/resources/openai.rs index 744d7b85..e4d932e0 100644 --- a/common/src/resources/openai.rs +++ b/common/src/resources/openai.rs @@ -1,48 +1,39 @@ use crate::io::logging::Logger; use async_openai::{types::CreateCompletionRequestArgs, Client}; -pub fn complete(prompt: &str, api_key: &str) -> Option { +pub async fn complete(prompt: &str, api_key: &str) -> Option { let client = Client::new().with_api_key(api_key); // get a new logger let level = std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".into()); let (logger, _) = Logger::new(&level); - - // create new runtime block - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() + let request = match CreateCompletionRequestArgs::default() + .model("text-davinci-003") + .prompt(prompt) + .max_tokens(512_u16) + .temperature(0.75) + .frequency_penalty(1.1) + .n(2) .build() - .expect("failed to create runtime"); - - rt.block_on(async { - let request = match CreateCompletionRequestArgs::default() - .model("text-davinci-003") - .prompt(prompt) - .max_tokens(512_u16) - .temperature(0.75) - .frequency_penalty(1.1) - .n(2) - .build() - { - Ok(request) => request, - Err(e) => { - logger.error(&format!("failed to create completion request: {e}")); - return None - } - }; + { + Ok(request) => request, + Err(e) => { + logger.error(&format!("failed to create completion request: {e}")); + return None + } + }; - match client.completions().create(request).await { - Ok(response) => { - if !response.choices.is_empty() { - Some(response.choices[0].text.clone()) - } else { - None - } - } - Err(e) => { - logger.error(&format!("failed to create completion request: {e}")); + match client.completions().create(request).await { + Ok(response) => { + if !response.choices.is_empty() { + Some(response.choices[0].text.clone()) + } else { None } } - }) + Err(e) => { + logger.error(&format!("failed to create completion request: {e}")); + None + } + } } diff --git a/common/src/utils/mod.rs b/common/src/utils/mod.rs index 0f63c280..8e76f53f 100644 --- a/common/src/utils/mod.rs +++ b/common/src/utils/mod.rs @@ -5,3 +5,4 @@ mod tests; pub mod threading; pub mod time; pub mod version; +pub mod sync; diff --git a/common/src/utils/sync.rs b/common/src/utils/sync.rs new file mode 100644 index 00000000..5119a7f3 --- /dev/null +++ b/common/src/utils/sync.rs @@ -0,0 +1,8 @@ + +/// Take in a non-async function and await it. This functions should be blocking. +pub fn blocking_await(f: F) -> T +where + F: FnOnce() -> T, +{ + tokio::task::block_in_place(f) +} diff --git a/core/src/cfg/mod.rs b/core/src/cfg/mod.rs index ec711d3d..830c24cb 100644 --- a/core/src/cfg/mod.rs +++ b/core/src/cfg/mod.rs @@ -4,7 +4,7 @@ use heimdall_common::ether::{ compiler::detect_compiler, rpc::get_code, selectors::find_function_selectors, }; use indicatif::ProgressBar; -use std::{env, fs, time::Duration}; +use std::{fs, time::Duration}; use clap::{AppSettings, Parser}; use heimdall_common::{ @@ -17,7 +17,7 @@ use heimdall_common::{ }; use petgraph::Graph; -use crate::cfg::{graph::build_cfg, output::build_output}; +use crate::cfg::graph::build_cfg; #[derive(Debug, Clone, Parser)] #[clap( @@ -35,10 +35,6 @@ pub struct CFGArgs { #[clap(flatten)] pub verbose: clap_verbosity_flag::Verbosity, - /// The output directory to write the output to - #[clap(long = "output", short, default_value = "", hide_default_value = true)] - pub output: String, - /// The RPC provider to use for fetching target bytecode. #[clap(long = "rpc-url", short, default_value = "", hide_default_value = true)] pub rpc_url: String, @@ -58,7 +54,7 @@ pub struct CFGArgs { pub color_edges: bool, } -pub fn cfg(args: CFGArgs) { +pub async fn cfg(args: CFGArgs) -> Result, Box> { use std::time::Instant; let now = Instant::now(); @@ -85,39 +81,17 @@ pub fn cfg(args: CFGArgs) { "()".to_string(), ); - // parse the output directory - let mut output_dir: String; - if args.output.is_empty() { - output_dir = match env::current_dir() { - Ok(dir) => dir.into_os_string().into_string().unwrap(), - Err(_) => { - logger.error("failed to get current directory."); - std::process::exit(1); - } - }; - output_dir.push_str("/output"); - } else { - output_dir = args.output.clone(); - } - // fetch bytecode let contract_bytecode: String; if ADDRESS_REGEX.is_match(&args.target).unwrap() { - // push the address to the output directory - if output_dir != args.output { - output_dir.push_str(&format!("/{}", &args.target)); - } - // We are working with a contract address, so we need to fetch the bytecode from the RPC - // provider. - contract_bytecode = get_code(&args.target, &args.rpc_url); + // We are working with a contract address, so we need to fetch the bytecode from the RPC provider + contract_bytecode = get_code(&args.target, &args.rpc_url).await?; } else if BYTECODE_REGEX.is_match(&args.target).unwrap() { + logger.debug_max("using provided bytecode for cfg generation"); contract_bytecode = args.target.replacen("0x", "", 1); } else { - // push the address to the output directory - if output_dir != args.output { - output_dir.push_str("/local"); - } + logger.debug_max("using provided file for decompilation."); // We are analyzing a file, so we need to read the bytecode from the file. contract_bytecode = match fs::read_to_string(&args.target) { @@ -142,10 +116,9 @@ pub fn cfg(args: CFGArgs) { let disassembled_bytecode = disassemble(DisassemblerArgs { target: contract_bytecode.clone(), verbose: args.verbose.clone(), - output: output_dir.clone(), rpc_url: args.rpc_url.clone(), decimal_counter: false, - }); + }).await?; // add the call to the trace trace.add_call( @@ -234,121 +207,13 @@ pub fn cfg(args: CFGArgs) { &format!("traced and executed {jumpdest_count} possible paths."), ); + logger.debug_max("building control flow graph from symbolic execution trace"); build_cfg(map, &mut contract_cfg, None, false); progress.finish_and_clear(); logger.info("symbolic execution completed."); - - // build the dot file - build_output(&contract_cfg, &args, output_dir.clone()); - logger.debug(&format!("Control flow graph generated in {:?}.", now.elapsed())); trace.display(); -} - -/// Builder pattern for using cfg genertion as a library. -/// -/// Default values may be overriden individually. -/// ## Example -/// Use with normal settings: -/// ```no_run -/// # use crate::heimdall_core::cfg::CFGBuilder; -/// const SOURCE: &'static str = "7312/* snip */04ad"; -/// -/// CFGBuilder::new(SOURCE) -/// .generate(); -/// ``` -/// Or change settings individually: -/// ```no_run -/// # use crate::heimdall_core::cfg::CFGBuilder; -/// -/// const SOURCE: &'static str = "7312/* snip */04ad"; -/// CFGBuilder::new(SOURCE) -/// .default(false) -/// .output("my_contract_dir") -/// .rpc("https://127.0.0.1:8545") -/// .format("svg") -/// .verbosity(4) -/// .color_edges(true) -/// .generate(); -/// ``` -#[allow(dead_code)] -pub struct CFGBuilder { - args: CFGArgs, -} - -impl CFGBuilder { - /// A new builder for the control flow graph generation of the specified target. - /// - /// The target may be a file, bytecode, contract address, or ENS name. - #[allow(dead_code)] - pub fn new(target: &str) -> Self { - CFGBuilder { - args: CFGArgs { - target: target.to_string(), - verbose: clap_verbosity_flag::Verbosity::new(0, 0), - output: String::from(""), - rpc_url: String::from(""), - format: String::from(""), - color_edges: false, - default: true, - }, - } - } - /// Set the output verbosity level. - /// - /// - 0 Error - /// - 1 Warn - /// - 2 Info - /// - 3 Debug - /// - 4 Trace - #[allow(dead_code)] - pub fn verbosity(mut self, level: i8) -> CFGBuilder { - // Calculated by the log library as: 1 + verbose - quiet. - // Set quiet as 1, and the level corresponds to the appropriate Log level. - self.args.verbose = clap_verbosity_flag::Verbosity::new(level, 0); - self - } - - /// The output directory to write the decompiled files to - #[allow(dead_code)] - pub fn output(mut self, directory: &str) -> CFGBuilder { - self.args.output = directory.to_string(); - self - } - - /// The RPC provider to use for fetching target bytecode. - #[allow(dead_code)] - pub fn rpc(mut self, url: &str) -> CFGBuilder { - self.args.rpc_url = url.to_string(); - self - } - - /// When prompted, always select the default value. - #[allow(dead_code)] - pub fn default(mut self, accept: bool) -> CFGBuilder { - self.args.default = accept; - self - } - - /// The format to additionally generate to. (e.g. svg, png, pdf) - #[allow(dead_code)] - pub fn format(mut self, format: String) -> CFGBuilder { - self.args.format = format; - self - } - - /// Whether to color the edges of the graph based on the JUMPI condition. - #[allow(dead_code)] - pub fn color_edges(mut self, color_edges: bool) -> CFGBuilder { - self.args.color_edges = color_edges; - self - } - - /// Starts the decompilation. - #[allow(dead_code)] - pub fn generate(self) { - cfg(self.args) - } + Ok(contract_cfg) } diff --git a/core/src/decode/mod.rs b/core/src/decode/mod.rs index e5f20930..eed37f03 100644 --- a/core/src/decode/mod.rs +++ b/core/src/decode/mod.rs @@ -62,7 +62,7 @@ pub struct DecodeArgs { } #[allow(deprecated)] -pub fn decode(args: DecodeArgs) { +pub async fn decode(args: DecodeArgs) { let (logger, mut trace) = Logger::new(match args.verbose.log_level() { Some(level) => level.as_str(), None => "SILENT", @@ -80,7 +80,7 @@ pub fn decode(args: DecodeArgs) { if TRANSACTION_HASH_REGEX.is_match(&args.target).unwrap() { // We are decoding a transaction hash, so we need to fetch the calldata from the RPC // provider. - raw_transaction = get_transaction(&args.target, &args.rpc_url); + raw_transaction = get_transaction(&args.target, &args.rpc_url).await.unwrap(); calldata = raw_transaction.input.to_string().replacen("0x", "", 1); } else { @@ -386,7 +386,7 @@ pub fn decode(args: DecodeArgs) { explain_progress.set_style(logger.info_spinner()); explain_progress.set_message("attempting to explain calldata..."); - match get_explanation(decoded_string.to_string(), raw_transaction, &args.openai_api_key) + match get_explanation(decoded_string.to_string(), raw_transaction, &args.openai_api_key).await { Some(explanation) => { explain_progress.finish_and_clear(); diff --git a/core/src/decode/util.rs b/core/src/decode/util.rs index de2d5062..eea7d90a 100644 --- a/core/src/decode/util.rs +++ b/core/src/decode/util.rs @@ -1,7 +1,7 @@ use ethers::types::Transaction; use heimdall_cache::util::encode_hex; -pub fn get_explanation( +pub async fn get_explanation( decoded: String, transaction: Transaction, openai_api_key: &str, @@ -23,5 +23,5 @@ pub fn get_explanation( transaction.value, decoded ); - heimdall_common::resources::openai::complete(&prompt, openai_api_key) + heimdall_common::resources::openai::complete(&prompt, openai_api_key).await } diff --git a/core/src/decompile/mod.rs b/core/src/decompile/mod.rs index 12f17e34..3a7213db 100644 --- a/core/src/decompile/mod.rs +++ b/core/src/decompile/mod.rs @@ -8,7 +8,7 @@ pub mod util; use crate::decompile::{ analyzers::{solidity::analyze_sol, yul::analyze_yul}, resolve::*, - util::*, + util::*, out::abi::build_abi, }; use heimdall_common::{ @@ -20,7 +20,7 @@ use heimdall_common::{ utils::strings::encode_hex_reduced, }; use indicatif::ProgressBar; -use std::{collections::HashMap, env, fs, time::Duration}; +use std::{collections::HashMap, fs, time::Duration}; use clap::{AppSettings, Parser}; use heimdall_common::{ @@ -35,6 +35,8 @@ use heimdall_common::{ io::logging::*, }; +use self::out::abi::ABIStructure; + #[derive(Debug, Clone, Parser)] #[clap( about = "Decompile EVM bytecode to Solidity", @@ -51,10 +53,6 @@ pub struct DecompilerArgs { #[clap(flatten)] pub verbose: clap_verbosity_flag::Verbosity, - /// The output directory to write the decompiled files to - #[clap(long = "output", short, default_value = "", hide_default_value = true)] - pub output: String, - /// The RPC provider to use for fetching target bytecode. #[clap(long = "rpc-url", short, default_value = "", hide_default_value = true)] pub rpc_url: String, @@ -76,7 +74,13 @@ pub struct DecompilerArgs { pub include_yul: bool, } -pub fn decompile(args: DecompilerArgs) { +#[derive(Debug, Clone)] +pub struct DecompileResult { + pub source: Option, + pub abi: Option>, +} + +pub async fn decompile(args: DecompilerArgs) -> Result> { use std::time::Instant; let now = Instant::now(); @@ -109,49 +113,24 @@ pub fn decompile(args: DecompilerArgs) { "()".to_string(), ); - // parse the output directory - let mut output_dir: String; - if args.output.is_empty() { - output_dir = match env::current_dir() { - Ok(dir) => dir.into_os_string().into_string().unwrap(), - Err(_) => { - logger.error("failed to get current directory."); - std::process::exit(1); - } - }; - output_dir.push_str("/output"); - } else { - output_dir = args.output.clone(); - } - // parse the various formats that are accepted as targets // i.e, file, bytecode, contract address let contract_bytecode: String; - if ADDRESS_REGEX.is_match(&args.target).unwrap() { - // push the address to the output directory - if output_dir != args.output { - output_dir.push_str(&format!("/{}", &args.target)); - } + if ADDRESS_REGEX.is_match(&args.target)? { - // We are decompiling a contract address, so we need to fetch the bytecode from the RPC - // provider. - contract_bytecode = get_code(&args.target, &args.rpc_url); - } else if BYTECODE_REGEX.is_match(&args.target).unwrap() { + // We are decompiling a contract address, so we need to fetch the bytecode from the RPC provider + contract_bytecode = get_code(&args.target, &args.rpc_url).await?; + } else if BYTECODE_REGEX.is_match(&args.target)? { logger.debug_max("using provided bytecode for decompilation"); contract_bytecode = args.target.clone().replacen("0x", "", 1); } else { - // push the address to the output directory - if output_dir != args.output { - output_dir.push_str("/local"); - } - logger.debug_max("using provided file for decompilation."); // We are decompiling a file, so we need to read the bytecode from the file. contract_bytecode = match fs::read_to_string(&args.target) { Ok(contents) => { let _contents = contents.replace('\n', ""); - if BYTECODE_REGEX.is_match(&_contents).unwrap() && _contents.len() % 2 == 0 { + if BYTECODE_REGEX.is_match(&_contents)? && _contents.len() % 2 == 0 { _contents.replacen("0x", "", 1) } else { logger @@ -170,10 +149,9 @@ pub fn decompile(args: DecompilerArgs) { let disassembled_bytecode = disassemble(DisassemblerArgs { target: contract_bytecode.clone(), verbose: args.verbose.clone(), - output: output_dir.clone(), rpc_url: args.rpc_url.clone(), decimal_counter: false, - }); + }).await?; trace.add_call( decompile_call, line!(), @@ -222,7 +200,7 @@ pub fn decompile(args: DecompilerArgs) { line!(), "contract".to_string(), shortened_target.clone(), - (contract_bytecode.len() / 2usize).try_into().unwrap(), + (contract_bytecode.len() / 2usize).try_into()?, ); // find and resolve all selectors in the bytecode @@ -272,7 +250,7 @@ pub fn decompile(args: DecompilerArgs) { trace.add_info( func_analysis_trace, - function_entry_point.try_into().unwrap(), + function_entry_point.try_into()?, &format!("discovered entry point: {function_entry_point}"), ); @@ -282,7 +260,7 @@ pub fn decompile(args: DecompilerArgs) { trace.add_debug( func_analysis_trace, - function_entry_point.try_into().unwrap(), + function_entry_point.try_into()?, &format!( "execution tree {}", match jumpdest_count { @@ -609,143 +587,20 @@ pub fn decompile(args: DecompilerArgs) { logger.info("symbolic execution completed."); logger.info("building decompilation output."); - // create the decompiled source output - if args.include_yul { - out::yul::output( - &args, - output_dir, - analyzed_functions, - all_resolved_events, - &mut trace, - decompile_call, - ); - } else { - out::solidity::output( - &args, - output_dir, - analyzed_functions, - all_resolved_errors, - all_resolved_events, - &mut trace, - decompile_call, - ); - } - + let abi = build_abi(&args, analyzed_functions, all_resolved_events, &mut trace, decompile_call)?; trace.display(); logger.debug(&format!("decompilation completed in {:?}.", now.elapsed())); -} -/// Builder pattern for using decompile method as a library. -/// -/// Default values may be overriden individually. -/// ## Example -/// Use with normal settings: -/// ```no_run -/// # use crate::heimdall_core::decompile::DecompileBuilder; -/// const SOURCE: &'static str = "7312/* snip */04ad"; -/// -/// DecompileBuilder::new(SOURCE) -/// .decompile(); -/// ``` -/// Or change settings individually: -/// ```no_run -/// # use crate::heimdall_core::decompile::DecompileBuilder; -/// -/// const SOURCE: &'static str = "7312/* snip */04ad"; -/// DecompileBuilder::new(SOURCE) -/// .default(false) -/// .include_sol(false) -/// .output("my_contract_dir") -/// .rpc("https://127.0.0.1:8545") -/// .skip_resolving(true) -/// .verbosity(5) -/// .decompile(); -/// ``` -#[allow(dead_code)] -pub struct DecompileBuilder { - args: DecompilerArgs, -} - -impl DecompileBuilder { - /// A new builder for the decompilation of the specified target. - /// - /// The target may be a file, bytecode, contract address, or ENS name. - #[allow(dead_code)] - pub fn new(target: &str) -> Self { - DecompileBuilder { - args: DecompilerArgs { - target: target.to_string(), - verbose: clap_verbosity_flag::Verbosity::new(0, 0), - output: String::from(""), - rpc_url: String::from(""), - default: true, - skip_resolving: false, - include_solidity: true, - include_yul: false, + Ok( + DecompileResult { + source: if args.include_solidity { + None + } else if args.include_yul { + None + } else { + None }, + abi: Some(abi), } - } - - /// Set the output verbosity level. - /// - /// - 0 Error - /// - 1 Warn - /// - 2 Info - /// - 3 Debug - /// - 4 Trace - #[allow(dead_code)] - pub fn verbosity(mut self, level: i8) -> DecompileBuilder { - // Calculated by the log library as: 1 + verbose - quiet. - // Set quiet as 1, and the level corresponds to the appropriate Log level. - self.args.verbose = clap_verbosity_flag::Verbosity::new(level, 0); - self - } - - /// The output directory to write the decompiled files to - #[allow(dead_code)] - pub fn output(mut self, directory: &str) -> DecompileBuilder { - self.args.output = directory.to_string(); - self - } - - /// The RPC provider to use for fetching target bytecode. - #[allow(dead_code)] - pub fn rpc(mut self, url: &str) -> DecompileBuilder { - self.args.rpc_url = url.to_string(); - self - } - - /// When prompted, always select the default value. - #[allow(dead_code)] - pub fn default(mut self, accept: bool) -> DecompileBuilder { - self.args.default = accept; - self - } - - /// Whether to skip resolving function selectors. - #[allow(dead_code)] - pub fn skip_resolving(mut self, skip: bool) -> DecompileBuilder { - self.args.skip_resolving = skip; - self - } - - /// Whether to include solidity source code in the output (in beta). - #[allow(dead_code)] - pub fn include_sol(mut self, include: bool) -> DecompileBuilder { - self.args.include_solidity = include; - self - } - - /// Whether to include yul source code in the output (in beta). - #[allow(dead_code)] - pub fn include_yul(mut self, include: bool) -> DecompileBuilder { - self.args.include_yul = include; - self - } - - /// Starts the decompilation. - #[allow(dead_code)] - pub fn decompile(self) { - decompile(self.args) - } + ) } diff --git a/core/src/decompile/out/abi.rs b/core/src/decompile/out/abi.rs new file mode 100644 index 00000000..feaf5b1b --- /dev/null +++ b/core/src/decompile/out/abi.rs @@ -0,0 +1,302 @@ + +use std::{collections::HashMap, time::Duration}; + +use ethers::abi::AbiEncode; +use heimdall_common::{ + ether::signatures::ResolvedLog, + io::{logging::{Logger, TraceFactory}, file::short_path}, +}; +use indicatif::ProgressBar; +use serde::{Deserialize, Serialize}; + +use crate::decompile::{DecompilerArgs, util::Function}; + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct ABIToken { + pub name: String, + #[serde(rename = "internalType")] + pub internal_type: String, + #[serde(rename = "type")] + pub type_: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct FunctionABI { + #[serde(rename = "type")] + pub type_: String, + pub name: String, + pub inputs: Vec, + pub outputs: Vec, + #[serde(rename = "stateMutability")] + pub state_mutability: String, + pub constant: bool, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct ErrorABI { + #[serde(rename = "type")] + pub type_: String, + pub name: String, + pub inputs: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub struct EventABI { + #[serde(rename = "type")] + pub type_: String, + pub name: String, + pub inputs: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +pub enum ABIStructure { + Function(FunctionABI), + Error(ErrorABI), + Event(EventABI), +} + + +pub fn build_abi( + args: &DecompilerArgs, + functions: Vec, + all_resolved_events: HashMap, + trace: &mut TraceFactory, + trace_parent: u32, +) -> Result, Box> { + // get a new logger + let level = std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".into()); + let (logger, _) = Logger::new(&level); + + // get a new progress bar + let progress_bar = ProgressBar::new_spinner(); + progress_bar.enable_steady_tick(Duration::from_millis(100)); + progress_bar.set_style(logger.info_spinner()); + + // truncate target for prettier display + let mut shortened_target = args.target.clone(); + if shortened_target.len() > 66 { + shortened_target = shortened_target.chars().take(66).collect::() + + "..." + + &shortened_target.chars().skip(shortened_target.len() - 16).collect::(); + } + + // add the call to the trace + trace.add_call( + trace_parent, + line!(), + "heimdall".to_string(), + "build_abi".to_string(), + vec![args.target.to_string()], + short_path(&shortened_target), + ); + + // build the decompiled contract's ABI + let mut abi: Vec = Vec::new(); + + // build the ABI for each function + for function in &functions { + progress_bar.set_message(format!("building ABI for '0x{}'", function.selector)); + + // get the function's name parameters for both resolved and unresolved functions + let (function_name, function_inputs, function_outputs) = match &function.resolved_function { + Some(resolved_function) => { + // get the function's name and parameters from the resolved function + let mut inputs = Vec::new(); + let mut outputs = Vec::new(); + + for (index, input) in resolved_function.inputs.iter().enumerate() { + inputs.push(ABIToken { + name: format!("arg{index}"), + internal_type: input.to_owned(), + type_: input.to_owned(), + }); + } + + match &function.returns { + Some(returns) => { + outputs.push(ABIToken { + name: "ret0".to_owned(), + internal_type: returns.to_owned(), + type_: returns.to_owned(), + }); + } + None => {} + } + + (resolved_function.name.clone(), inputs, outputs) + } + None => { + // if the function is unresolved, use the decompiler's potential types + let mut inputs = Vec::new(); + let mut outputs = Vec::new(); + + for (index, (_, (_, potential_types))) in + function.arguments.clone().iter().enumerate() + { + inputs.push(ABIToken { + name: format!("arg{index}"), + internal_type: potential_types[0].to_owned(), + type_: potential_types[0].to_owned(), + }); + } + + match &function.returns { + Some(returns) => { + outputs.push(ABIToken { + name: "ret0".to_owned(), + internal_type: returns.to_owned(), + type_: returns.to_owned(), + }); + } + None => {} + } + + (format!("Unresolved_{}", function.selector), inputs, outputs) + } + }; + + // determine the state mutability of the function + let state_mutability = match function.payable { + true => "payable", + false => match function.pure { + true => "pure", + false => match function.view { + true => "view", + false => "nonpayable", + }, + }, + }; + + let constant = state_mutability == "pure" && function_inputs.is_empty(); + + // add the function to the ABI + abi.push(ABIStructure::Function(FunctionABI { + type_: "function".to_string(), + name: function_name, + inputs: function_inputs, + outputs: function_outputs, + state_mutability: state_mutability.to_string(), + constant: constant, + })); + + // build the function's custom errors + for (error_selector, resolved_error) in &function.errors { + progress_bar.set_message(format!("building ABI for '0x{error_selector}'")); + + match resolved_error { + Some(resolved_error) => { + let mut inputs = Vec::new(); + + for (index, input) in resolved_error.inputs.iter().enumerate() { + if !input.is_empty() { + inputs.push(ABIToken { + name: format!("arg{index}"), + internal_type: input.to_owned(), + type_: input.to_owned(), + }); + } + } + + // check if the error is already in the ABI + if abi.iter().any(|x| match x { + ABIStructure::Error(x) => x.name == resolved_error.name, + _ => false, + }) { + continue + } + + abi.push(ABIStructure::Error(ErrorABI { + type_: "error".to_string(), + name: resolved_error.name.clone(), + inputs: inputs, + })); + } + None => { + // check if the error is already in the ABI + if abi.iter().any(|x| match x { + ABIStructure::Error(x) => { + x.name == + format!( + "CustomError_{}", + &error_selector.encode_hex().replacen("0x", "", 1) + ) + } + _ => false, + }) { + continue + } + + abi.push(ABIStructure::Error(ErrorABI { + type_: "error".to_string(), + name: format!( + "CustomError_{}", + &error_selector.encode_hex().replacen("0x", "", 1) + ), + inputs: Vec::new(), + })); + } + } + } + + // build the function's events + for (event_selector, (resolved_event, _)) in &function.events { + progress_bar.set_message(format!("building ABI for '0x{event_selector}'")); + + match resolved_event { + Some(resolved_event) => { + let mut inputs = Vec::new(); + + for (index, input) in resolved_event.inputs.iter().enumerate() { + if !input.is_empty() { + inputs.push(ABIToken { + name: format!("arg{index}"), + internal_type: input.to_owned(), + type_: input.to_owned(), + }); + } + } + + // check if the event is already in the ABI + if abi.iter().any(|x| match x { + ABIStructure::Event(x) => x.name == resolved_event.name, + _ => false, + }) { + continue + } + + abi.push(ABIStructure::Event(EventABI { + type_: "event".to_string(), + name: resolved_event.name.clone(), + inputs: inputs, + })); + } + None => { + // check if the event is already in the ABI + if abi.iter().any(|x| match x { + ABIStructure::Event(x) => { + x.name == + format!( + "Event_{}", + &event_selector.encode_hex().replacen("0x", "", 1)[0..8] + ) + } + _ => false, + }) { + continue + } + + abi.push(ABIStructure::Event(EventABI { + type_: "event".to_string(), + name: format!( + "Event_{}", + &event_selector.encode_hex().replacen("0x", "", 1)[0..8] + ), + inputs: Vec::new(), + })); + } + } + } + } + + Ok(abi) +} diff --git a/core/src/decompile/out/mod.rs b/core/src/decompile/out/mod.rs index a9ee636c..3b88f09e 100644 --- a/core/src/decompile/out/mod.rs +++ b/core/src/decompile/out/mod.rs @@ -1,3 +1,4 @@ pub mod postprocessers; pub mod solidity; pub mod yul; +pub mod abi; diff --git a/core/src/decompile/out/solidity.rs b/core/src/decompile/out/solidity.rs index 959a773b..740ecca3 100644 --- a/core/src/decompile/out/solidity.rs +++ b/core/src/decompile/out/solidity.rs @@ -1,10 +1,9 @@ use std::{collections::HashMap, time::Duration}; -use ethers::abi::AbiEncode; use heimdall_common::{ ether::signatures::{ResolvedError, ResolvedLog}, io::{ - file::{short_path, write_file, write_lines_to_file}, + file::short_path, logging::{Logger, TraceFactory}, }, utils::strings::find_balanced_encapsulator, @@ -17,63 +16,19 @@ use super::{ util::Function, DecompilerArgs, }, - postprocessers::solidity::postprocess, + postprocessers::solidity::postprocess, abi::ABIStructure, }; -use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, PartialEq, Clone)] -struct ABIToken { - name: String, - #[serde(rename = "internalType")] - internal_type: String, - #[serde(rename = "type")] - type_: String, -} - -#[derive(Serialize, Deserialize, PartialEq, Clone)] -struct FunctionABI { - #[serde(rename = "type")] - type_: String, - name: String, - inputs: Vec, - outputs: Vec, - #[serde(rename = "stateMutability")] - state_mutability: String, - constant: bool, -} - -#[derive(Serialize, Deserialize, PartialEq, Clone)] -struct ErrorABI { - #[serde(rename = "type")] - type_: String, - name: String, - inputs: Vec, -} - -#[derive(Serialize, Deserialize, PartialEq, Clone)] -struct EventABI { - #[serde(rename = "type")] - type_: String, - name: String, - inputs: Vec, -} - -#[derive(Serialize, Deserialize, PartialEq, Clone)] -enum ABIStructure { - Function(FunctionABI), - Error(ErrorABI), - Event(EventABI), -} - -pub fn output( +pub fn build_solidity_output( args: &DecompilerArgs, - output_dir: String, + abi: &Vec, functions: Vec, all_resolved_errors: HashMap, all_resolved_events: HashMap, trace: &mut TraceFactory, trace_parent: u32, -) { +) -> Result> { + // get a new logger let level = std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".into()); let (logger, _) = Logger::new(&level); @@ -86,239 +41,6 @@ pub fn output( progress_bar.enable_steady_tick(Duration::from_millis(100)); progress_bar.set_style(logger.info_spinner()); - let abi_output_path = format!("{output_dir}/abi.json"); - let decompiled_output_path = format!("{output_dir}/decompiled.sol"); - - // build the decompiled contract's ABI - let mut abi: Vec = Vec::new(); - - // build the ABI for each function - for function in &functions { - progress_bar.set_message(format!("writing ABI for '0x{}'", function.selector)); - - // get the function's name parameters for both resolved and unresolved functions - let (function_name, function_inputs, function_outputs) = match &function.resolved_function { - Some(resolved_function) => { - // get the function's name and parameters from the resolved function - let mut inputs = Vec::new(); - let mut outputs = Vec::new(); - - for (index, input) in resolved_function.inputs.iter().enumerate() { - inputs.push(ABIToken { - name: format!("arg{index}"), - internal_type: input.to_owned(), - type_: input.to_owned(), - }); - } - - match &function.returns { - Some(returns) => { - outputs.push(ABIToken { - name: "ret0".to_owned(), - internal_type: returns.to_owned(), - type_: returns.to_owned(), - }); - } - None => {} - } - - (resolved_function.name.clone(), inputs, outputs) - } - None => { - // if the function is unresolved, use the decompiler's potential types - let mut inputs = Vec::new(); - let mut outputs = Vec::new(); - - for (index, (_, (_, potential_types))) in - function.arguments.clone().iter().enumerate() - { - inputs.push(ABIToken { - name: format!("arg{index}"), - internal_type: potential_types[0].to_owned(), - type_: potential_types[0].to_owned(), - }); - } - - match &function.returns { - Some(returns) => { - outputs.push(ABIToken { - name: "ret0".to_owned(), - internal_type: returns.to_owned(), - type_: returns.to_owned(), - }); - } - None => {} - } - - (format!("Unresolved_{}", function.selector), inputs, outputs) - } - }; - - // determine the state mutability of the function - let state_mutability = match function.payable { - true => "payable", - false => match function.pure { - true => "pure", - false => match function.view { - true => "view", - false => "nonpayable", - }, - }, - }; - - let constant = state_mutability == "pure" && function_inputs.is_empty(); - - // add the function to the ABI - abi.push(ABIStructure::Function(FunctionABI { - type_: "function".to_string(), - name: function_name, - inputs: function_inputs, - outputs: function_outputs, - state_mutability: state_mutability.to_string(), - constant: constant, - })); - - // write the function's custom errors - for (error_selector, resolved_error) in &function.errors { - progress_bar.set_message(format!("writing ABI for '0x{error_selector}'")); - - match resolved_error { - Some(resolved_error) => { - let mut inputs = Vec::new(); - - for (index, input) in resolved_error.inputs.iter().enumerate() { - if !input.is_empty() { - inputs.push(ABIToken { - name: format!("arg{index}"), - internal_type: input.to_owned(), - type_: input.to_owned(), - }); - } - } - - // check if the error is already in the ABI - if abi.iter().any(|x| match x { - ABIStructure::Error(x) => x.name == resolved_error.name, - _ => false, - }) { - continue - } - - abi.push(ABIStructure::Error(ErrorABI { - type_: "error".to_string(), - name: resolved_error.name.clone(), - inputs: inputs, - })); - } - None => { - // check if the error is already in the ABI - if abi.iter().any(|x| match x { - ABIStructure::Error(x) => { - x.name == - format!( - "CustomError_{}", - &error_selector.encode_hex().replacen("0x", "", 1) - ) - } - _ => false, - }) { - continue - } - - abi.push(ABIStructure::Error(ErrorABI { - type_: "error".to_string(), - name: format!( - "CustomError_{}", - &error_selector.encode_hex().replacen("0x", "", 1) - ), - inputs: Vec::new(), - })); - } - } - } - - // write the function's events - for (event_selector, (resolved_event, _)) in &function.events { - progress_bar.set_message(format!("writing ABI for '0x{event_selector}'")); - - match resolved_event { - Some(resolved_event) => { - let mut inputs = Vec::new(); - - for (index, input) in resolved_event.inputs.iter().enumerate() { - if !input.is_empty() { - inputs.push(ABIToken { - name: format!("arg{index}"), - internal_type: input.to_owned(), - type_: input.to_owned(), - }); - } - } - - // check if the event is already in the ABI - if abi.iter().any(|x| match x { - ABIStructure::Event(x) => x.name == resolved_event.name, - _ => false, - }) { - continue - } - - abi.push(ABIStructure::Event(EventABI { - type_: "event".to_string(), - name: resolved_event.name.clone(), - inputs: inputs, - })); - } - None => { - // check if the event is already in the ABI - if abi.iter().any(|x| match x { - ABIStructure::Event(x) => { - x.name == - format!( - "Event_{}", - &event_selector.encode_hex().replacen("0x", "", 1)[0..8] - ) - } - _ => false, - }) { - continue - } - - abi.push(ABIStructure::Event(EventABI { - type_: "event".to_string(), - name: format!( - "Event_{}", - &event_selector.encode_hex().replacen("0x", "", 1)[0..8] - ), - inputs: Vec::new(), - })); - } - } - } - } - - // write the ABI to a file - write_file( - &abi_output_path, - &format!( - "[{}]", - abi.iter() - .map(|x| { - match x { - ABIStructure::Function(x) => serde_json::to_string_pretty(x).unwrap(), - ABIStructure::Error(x) => serde_json::to_string_pretty(x).unwrap(), - ABIStructure::Event(x) => serde_json::to_string_pretty(x).unwrap(), - } - }) - .collect::>() - .join(",\n") - ), - ); - - progress_bar.suspend(|| { - logger.success(&format!("wrote decompiled ABI to '{}' .", &abi_output_path)); - }); - // write the decompiled source to file let mut decompiled_output: Vec = Vec::new(); @@ -334,9 +56,9 @@ pub fn output( trace_parent, line!(), "heimdall".to_string(), - "build_output".to_string(), + "build_solidity_output".to_string(), vec![shortened_target], - short_path(&decompiled_output_path), + short_path(&shortened_target), ); // write the header to the output file @@ -531,14 +253,6 @@ pub fn output( decompiled_output.push(String::from("}")); - if args.include_solidity { - write_lines_to_file( - &decompiled_output_path, - postprocess(decompiled_output, all_resolved_errors, all_resolved_events, &progress_bar), - ); - logger.success(&format!("wrote decompiled contract to '{}' .", &decompiled_output_path)); - progress_bar.finish_and_clear(); - } else { - progress_bar.finish_and_clear(); - } + progress_bar.finish_and_clear(); + Ok(postprocess(decompiled_output, all_resolved_errors, all_resolved_events, &progress_bar).join("\n")) } diff --git a/core/src/decompile/out/yul.rs b/core/src/decompile/out/yul.rs index 6365a42d..916465eb 100644 --- a/core/src/decompile/out/yul.rs +++ b/core/src/decompile/out/yul.rs @@ -1,73 +1,21 @@ use std::{collections::HashMap, time::Duration}; -use ethers::abi::AbiEncode; use heimdall_common::{ ether::signatures::ResolvedLog, - io::{ - file::{short_path, write_file, write_lines_to_file}, - logging::{Logger, TraceFactory}, - }, + io::{logging::{Logger, TraceFactory}, file::short_path}, }; use indicatif::ProgressBar; +use crate::decompile::{DecompilerArgs, util::Function, constants::DECOMPILED_SOURCE_HEADER_YUL}; -use super::{ - super::{constants::DECOMPILED_SOURCE_HEADER_YUL, util::Function, DecompilerArgs}, - postprocessers::yul::postprocess, -}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, PartialEq)] -struct ABIToken { - name: String, - #[serde(rename = "internalType")] - internal_type: String, - #[serde(rename = "type")] - type_: String, -} - -#[derive(Serialize, Deserialize, PartialEq)] -struct FunctionABI { - #[serde(rename = "type")] - type_: String, - name: String, - inputs: Vec, - outputs: Vec, - #[serde(rename = "stateMutability")] - state_mutability: String, - constant: bool, -} - -#[derive(Serialize, Deserialize, PartialEq)] -struct ErrorABI { - #[serde(rename = "type")] - type_: String, - name: String, - inputs: Vec, -} +use super::postprocessers::yul::postprocess; -#[derive(Serialize, Deserialize, PartialEq)] -struct EventABI { - #[serde(rename = "type")] - type_: String, - name: String, - inputs: Vec, -} - -#[derive(Serialize, Deserialize, PartialEq)] -enum ABIStructure { - Function(FunctionABI), - Error(ErrorABI), - Event(EventABI), -} - -pub fn output( +pub fn build_yul_output( args: &DecompilerArgs, - output_dir: String, functions: Vec, all_resolved_events: HashMap, trace: &mut TraceFactory, trace_parent: u32, -) { +) -> Result> { // get a new logger let level = std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".into()); let (logger, _) = Logger::new(&level); @@ -77,241 +25,7 @@ pub fn output( progress_bar.enable_steady_tick(Duration::from_millis(100)); progress_bar.set_style(logger.info_spinner()); - let abi_output_path = format!("{output_dir}/abi.json"); - let decompiled_output_path = format!("{output_dir}/decompiled.yul"); - - // build the decompiled contract's ABI - let mut abi: Vec = Vec::new(); - - // build the ABI for each function - for function in &functions { - progress_bar.set_message(format!("writing ABI for '0x{}'", function.selector)); - - // get the function's name parameters for both resolved and unresolved functions - let (function_name, function_inputs, function_outputs) = match &function.resolved_function { - Some(resolved_function) => { - // get the function's name and parameters from the resolved function - let mut inputs = Vec::new(); - let mut outputs = Vec::new(); - - for (index, input) in resolved_function.inputs.iter().enumerate() { - inputs.push(ABIToken { - name: format!("arg{index}"), - internal_type: input.to_owned(), - type_: input.to_owned(), - }); - } - - match &function.returns { - Some(returns) => { - outputs.push(ABIToken { - name: "ret0".to_owned(), - internal_type: returns.to_owned(), - type_: returns.to_owned(), - }); - } - None => {} - } - - (resolved_function.name.clone(), inputs, outputs) - } - None => { - // if the function is unresolved, use the decompiler's potential types - let mut inputs = Vec::new(); - let mut outputs = Vec::new(); - - for (index, (_, (_, potential_types))) in - function.arguments.clone().iter().enumerate() - { - inputs.push(ABIToken { - name: format!("arg{index}"), - internal_type: potential_types[0].to_owned(), - type_: potential_types[0].to_owned(), - }); - } - - match &function.returns { - Some(returns) => { - outputs.push(ABIToken { - name: "ret0".to_owned(), - internal_type: returns.to_owned(), - type_: returns.to_owned(), - }); - } - None => {} - } - - (format!("Unresolved_{}", function.selector), inputs, outputs) - } - }; - - // determine the state mutability of the function - let state_mutability = match function.payable { - true => "payable", - false => match function.pure { - true => "pure", - false => match function.view { - true => "view", - false => "nonpayable", - }, - }, - }; - - let constant = state_mutability == "pure" && function_inputs.is_empty(); - - // add the function to the ABI - abi.push(ABIStructure::Function(FunctionABI { - type_: "function".to_string(), - name: function_name, - inputs: function_inputs, - outputs: function_outputs, - state_mutability: state_mutability.to_string(), - constant: constant, - })); - - // write the function's custom errors - for (error_selector, resolved_error) in &function.errors { - progress_bar.set_message(format!("writing ABI for '0x{error_selector}'")); - - match resolved_error { - Some(resolved_error) => { - let mut inputs = Vec::new(); - - for (index, input) in resolved_error.inputs.iter().enumerate() { - if !input.is_empty() { - inputs.push(ABIToken { - name: format!("arg{index}"), - internal_type: input.to_owned(), - type_: input.to_owned(), - }); - } - } - - // check if the error is already in the ABI - if abi.iter().any(|x| match x { - ABIStructure::Error(x) => x.name == resolved_error.name, - _ => false, - }) { - continue - } - - abi.push(ABIStructure::Error(ErrorABI { - type_: "error".to_string(), - name: resolved_error.name.clone(), - inputs: inputs, - })); - } - None => { - // check if the error is already in the ABI - if abi.iter().any(|x| match x { - ABIStructure::Error(x) => { - x.name == - format!( - "CustomError_{}", - &error_selector.encode_hex().replacen("0x", "", 1) - ) - } - _ => false, - }) { - continue - } - - abi.push(ABIStructure::Error(ErrorABI { - type_: "error".to_string(), - name: format!( - "CustomError_{}", - &error_selector.encode_hex().replacen("0x", "", 1) - ), - inputs: Vec::new(), - })); - } - } - } - - // write the function's events - for (event_selector, (resolved_event, _)) in &function.events { - progress_bar.set_message(format!("writing ABI for '0x{event_selector}'")); - - match resolved_event { - Some(resolved_event) => { - let mut inputs = Vec::new(); - - for (index, input) in resolved_event.inputs.iter().enumerate() { - if !input.is_empty() { - inputs.push(ABIToken { - name: format!("arg{index}"), - internal_type: input.to_owned(), - type_: input.to_owned(), - }); - } - } - - // check if the event is already in the ABI - if abi.iter().any(|x| match x { - ABIStructure::Event(x) => x.name == resolved_event.name, - _ => false, - }) { - continue - } - - abi.push(ABIStructure::Event(EventABI { - type_: "event".to_string(), - name: resolved_event.name.clone(), - inputs: inputs, - })); - } - None => { - // check if the event is already in the ABI - if abi.iter().any(|x| match x { - ABIStructure::Event(x) => { - x.name == - format!( - "Event_{}", - &event_selector.encode_hex().replacen("0x", "", 1)[0..8] - ) - } - _ => false, - }) { - continue - } - - abi.push(ABIStructure::Event(EventABI { - type_: "event".to_string(), - name: format!( - "Event_{}", - &event_selector.encode_hex().replacen("0x", "", 1)[0..8] - ), - inputs: Vec::new(), - })); - } - } - } - } - - // write the ABI to a file - write_file( - &abi_output_path, - &format!( - "[{}]", - abi.iter() - .map(|x| { - match x { - ABIStructure::Function(x) => serde_json::to_string_pretty(x).unwrap(), - ABIStructure::Error(x) => serde_json::to_string_pretty(x).unwrap(), - ABIStructure::Event(x) => serde_json::to_string_pretty(x).unwrap(), - } - }) - .collect::>() - .join(",\n") - ), - ); - - // log the ABI output path - progress_bar.suspend(|| { - logger.success(&format!("wrote decompiled ABI to '{}' .", &abi_output_path)); - }); - - // write the decompiled source to file + // build the decompiled source let mut decompiled_output: Vec = Vec::new(); // truncate target for prettier display @@ -327,12 +41,12 @@ pub fn output( trace_parent, line!(), "heimdall".to_string(), - "build_output".to_string(), + "build_yul_output".to_string(), vec![args.target.to_string()], short_path(&shortened_target), ); - // write the header to the output file + // add the header to the output decompiled_output.extend( DECOMPILED_SOURCE_HEADER_YUL .replace("{}", env!("CARGO_PKG_VERSION")) @@ -340,9 +54,9 @@ pub fn output( .map(|x| x.to_string()), ); - // write contract logic + // build contract logic for function in functions { - progress_bar.set_message(format!("writing logic for '0x{}'", function.selector)); + progress_bar.set_message(format!("building logic for '0x{}'", function.selector)); // build the function's header and parameters let function_header = match function.resolved_function { @@ -419,14 +133,6 @@ pub fn output( "}".to_string(), ]); - if args.include_yul { - write_lines_to_file( - &decompiled_output_path, - postprocess(decompiled_output, all_resolved_events, &progress_bar), - ); - logger.success(&format!("wrote decompiled contract to '{}' .", &decompiled_output_path)); - progress_bar.finish_and_clear(); - } else { - progress_bar.finish_and_clear(); - } + progress_bar.finish_and_clear(); + Ok(postprocess(decompiled_output, all_resolved_events, &progress_bar).join("\n")) } diff --git a/core/src/dump/mod.rs b/core/src/dump/mod.rs index cd556dfb..302b31fa 100644 --- a/core/src/dump/mod.rs +++ b/core/src/dump/mod.rs @@ -68,7 +68,7 @@ pub struct DumpArgs { pub chain: String, } -pub fn dump(args: DumpArgs) { +pub async fn dump(args: DumpArgs) -> Result<(), Box> { let (logger, _) = Logger::new(match args.verbose.log_level() { Some(level) => level.as_str(), None => "SILENT", diff --git a/core/src/dump/util/mod.rs b/core/src/dump/util/mod.rs index 4b890dda..facc4eaa 100644 --- a/core/src/dump/util/mod.rs +++ b/core/src/dump/util/mod.rs @@ -30,65 +30,67 @@ pub fn cleanup_terminal() { } // get the state diff for the given transaction -pub fn get_storage_diff(tx: &Transaction, args: &DumpArgs) -> Option { +pub async fn get_storage_diff(tx: &Transaction, args: &DumpArgs) -> Option { // create new logger let (logger, _) = Logger::new(match args.verbose.log_level() { Some(level) => level.as_str(), None => "SILENT", }); - // create new runtime block - let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + // check the cache for a matching address + if let Some(state_diff) = read_cache(&format!("diff.{}", &tx.hash)) { + return state_diff; + } - rt.block_on(async { + // make sure the RPC provider isn't empty + if args.rpc_url.is_empty() { + cleanup_terminal(); + logger.error("fetching an on-chain transaction requires an RPC provider. Use `heimdall dump --help` for more information."); + std::process::exit(1); + } - // check the cache for a matching address - if let Some(state_diff) = read_cache(&format!("diff.{}", &tx.hash)) { - return state_diff; + // create new provider + let provider = match Provider::::try_from(&args.rpc_url) { + Ok(provider) => provider, + Err(_) => { + cleanup_terminal(); + logger.error(&format!("failed to connect to RPC provider '{}' .", &args.rpc_url)); + std::process::exit(1) } + }; - // make sure the RPC provider isn't empty - if args.rpc_url.is_empty() { + // safely unwrap the transaction hash + let transaction_hash = match H256::from_str(&tx.hash) { + Ok(transaction_hash) => transaction_hash, + Err(_) => { cleanup_terminal(); - logger.error("fetching an on-chain transaction requires an RPC provider. Use `heimdall dump --help` for more information."); - std::process::exit(1); + logger.error(&format!("failed to parse transaction hash '{}' .", &tx.hash)); + std::process::exit(1) } + }; - // create new provider - let provider = match Provider::::try_from(&args.rpc_url) { - Ok(provider) => provider, - Err(_) => { - cleanup_terminal(); - logger.error(&format!("failed to connect to RPC provider '{}' .", &args.rpc_url)); - std::process::exit(1) - } - }; - - // safely unwrap the transaction hash - let transaction_hash = match H256::from_str(&tx.hash) { - Ok(transaction_hash) => transaction_hash, - Err(_) => { - cleanup_terminal(); - logger.error(&format!("failed to parse transaction hash '{}' .", &tx.hash)); - std::process::exit(1) - } - }; - - // fetch the state diff for the transaction - let state_diff = match provider.trace_replay_transaction(transaction_hash, vec![TraceType::StateDiff]).await { - Ok(traces) => traces.state_diff, - Err(e) => { - cleanup_terminal(); - logger.error(&format!("failed to replay and trace transaction '{}' . does your RPC provider support it?", &tx.hash)); - logger.error(&format!("error: '{e}' .")); - std::process::exit(1) - } - }; + // fetch the state diff for the transaction + let state_diff = match provider + .trace_replay_transaction(transaction_hash, vec![TraceType::StateDiff]) + .await + { + Ok(traces) => traces.state_diff, + Err(e) => { + cleanup_terminal(); + logger.error(&format!( + "failed to replay and trace transaction '{}' . does your RPC provider support it?", + &tx.hash + )); + logger.error(&format!("error: '{e}' .")); + std::process::exit(1) + } + }; - // write the state diff to the cache - let expiry = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + 60 * 60 * 24 * 7; - store_cache(&format!("diff.{}", &tx.hash), &state_diff, Some(expiry)); + // write the state diff to the cache + let expiry = + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + + 60 * 60 * 24 * 7; + store_cache(&format!("diff.{}", &tx.hash), &state_diff, Some(expiry)); - state_diff - }) + state_diff } diff --git a/core/src/dump/util/threads/indexer.rs b/core/src/dump/util/threads/indexer.rs index e300ac1c..fe217329 100644 --- a/core/src/dump/util/threads/indexer.rs +++ b/core/src/dump/util/threads/indexer.rs @@ -8,7 +8,7 @@ use crate::dump::{ constants::DUMP_STATE, structures::storage_slot::StorageSlot, util::get_storage_diff, }; -pub fn handle(addr_hash: H160) { +pub async fn handle(addr_hash: H160) { let state = DUMP_STATE.lock().unwrap(); let transactions = state.transactions.clone(); let args = state.args.clone(); @@ -33,8 +33,12 @@ pub fn handle(addr_hash: H160) { } task_pool(transactions, num_indexing_threads, move |tx| { + // get new blocking runtime + let rt = tokio::runtime::Runtime::new().unwrap(); + // get the storage diff for this transaction - let state_diff = get_storage_diff(&tx, &args); + let state_diff = rt.block_on(get_storage_diff(&tx, &args)); + // unlock state let mut state = DUMP_STATE.lock().unwrap(); From 5a9b67d174c2ee0a3597e1d4902dd96a3decd674 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 11:40:23 -0400 Subject: [PATCH 05/18] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20dump?= =?UTF-8?q?=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/src/dump/mod.rs | 7 +-- core/src/dump/util/csv.rs | 105 ++++++++++++++++++++++---------------- 2 files changed, 66 insertions(+), 46 deletions(-) diff --git a/core/src/dump/mod.rs b/core/src/dump/mod.rs index 302b31fa..dce8c51b 100644 --- a/core/src/dump/mod.rs +++ b/core/src/dump/mod.rs @@ -15,7 +15,7 @@ use self::{ constants::DUMP_STATE, menus::TUIView, structures::{dump_state::DumpState, transaction::Transaction}, - util::csv::write_storage_to_csv, + util::csv::{DumpRow, build_csv}, }; #[derive(Debug, Clone, Parser)] @@ -68,7 +68,7 @@ pub struct DumpArgs { pub chain: String, } -pub async fn dump(args: DumpArgs) -> Result<(), Box> { +pub async fn dump(args: DumpArgs) -> Result, Box> { let (logger, _) = Logger::new(match args.verbose.log_level() { Some(level) => level.as_str(), None => "SILENT", @@ -197,11 +197,12 @@ pub async fn dump(args: DumpArgs) -> Result<(), Box> { // write storage slots to csv let state = DUMP_STATE.lock().unwrap(); - write_storage_to_csv(&_output_dir, "storage_dump.csv", &state); + let csv = build_csv(&state); logger.success(&format!("Wrote storage dump to '{_output_dir}/storage_dump.csv'.")); logger.info(&format!( "Dumped {} storage values from '{}' .", state.storage.len(), &_args.target )); + Ok(csv) } diff --git a/core/src/dump/util/csv.rs b/core/src/dump/util/csv.rs index e41031d0..e75d0c80 100644 --- a/core/src/dump/util/csv.rs +++ b/core/src/dump/util/csv.rs @@ -2,54 +2,73 @@ use ethers::{ abi::{decode, ParamType}, types::U256, }; -use heimdall_common::{ - io::file::write_lines_to_file, - utils::strings::{encode_hex, hex_to_ascii}, -}; +use heimdall_common::{utils::strings::{encode_hex, hex_to_ascii}, io::file::write_lines_to_file}; use crate::dump::{constants::DECODE_AS_TYPES, structures::dump_state::DumpState}; +pub struct DumpRow { + pub last_modified: String, + pub alias: String, + pub slot: String, + pub decoded_type: String, + pub value: String, +} + +pub fn build_csv(state: &DumpState) -> Vec { + let mut lines: Vec = Vec::new(); + + // sort by key ascending + let mut storage_iter = state.storage.iter().collect::>(); + storage_iter.sort_by_key(|(slot, _)| *slot); + + for (slot, value) in storage_iter { + let decoded_value = match value.decode_as_type_index { + 0 => format!("0x{}", encode_hex(value.value.to_fixed_bytes().into())), + 1 => format!("{}", !value.value.is_zero()), + 2 => format!( + "0x{}", + encode_hex(value.value.to_fixed_bytes().into()).get(24..).unwrap_or("") + ), + 3 => match decode(&[ParamType::String], value.value.as_bytes()) { + Ok(decoded) => decoded[0].to_string(), + Err(_) => hex_to_ascii(&encode_hex(value.value.to_fixed_bytes().into())), + }, + 4 => { + let decoded = U256::from_big_endian(&value.value.to_fixed_bytes()); + format!("{decoded}") + } + _ => "decoding error".to_string(), + }; + + lines.push(DumpRow { + last_modified: value.modifiers.iter().max_by_key(|m| m.0).unwrap().0.to_string(), + alias: value.alias.as_ref().unwrap_or(&String::from("None")).to_string(), + slot: encode_hex(slot.to_fixed_bytes().into()), + decoded_type: DECODE_AS_TYPES[value.decode_as_type_index].to_string(), + value: decoded_value, + }) + } + lines +} + pub fn write_storage_to_csv(output_dir: &str, file_name: &str, state: &DumpState) { - let mut lines = { - let mut lines = Vec::new(); - - // sort by key ascending - let mut storage_iter = state.storage.iter().collect::>(); - storage_iter.sort_by_key(|(slot, _)| *slot); - - for (slot, value) in storage_iter { - let decoded_value = match value.decode_as_type_index { - 0 => format!("0x{}", encode_hex(value.value.to_fixed_bytes().into())), - 1 => format!("{}", !value.value.is_zero()), - 2 => format!( - "0x{}", - encode_hex(value.value.to_fixed_bytes().into()).get(24..).unwrap_or("") - ), - 3 => match decode(&[ParamType::String], value.value.as_bytes()) { - Ok(decoded) => decoded[0].to_string(), - Err(_) => hex_to_ascii(&encode_hex(value.value.to_fixed_bytes().into())), - }, - 4 => { - let decoded = U256::from_big_endian(&value.value.to_fixed_bytes()); - format!("{decoded}") - } - _ => "decoding error".to_string(), - }; - lines.push(format!( - "\"{}\",\"{}\",\"{}\",\"{}\",\"{}\"", - value.modifiers.iter().max_by_key(|m| m.0).unwrap().0, - value.alias.as_ref().unwrap_or(&String::from("None")), - encode_hex(slot.to_fixed_bytes().into()), - DECODE_AS_TYPES[value.decode_as_type_index], - decoded_value, - )); - } - lines - }; + let mut csv_rows = build_csv(state); + let mut lines: Vec = Vec::new(); + + // sort by last modified descending + csv_rows.sort_by(|a, b| b.last_modified.cmp(&a.last_modified)); // add header - lines.insert(0, String::from("last_modified,alias,slot,decoded_type,value")); + lines.push(String::from("last_modified,alias,slot,decoded_type,value")); + + // add rows + for row in csv_rows { + lines.push(format!( + "{},{},{},{},{}", + row.last_modified, row.alias, row.slot, row.decoded_type, row.value + )); + } - // save to file - write_lines_to_file(&format!("{output_dir}/{file_name}"), lines); + // write to file + write_lines_to_file(&format!("{}/{}", output_dir, file_name), lines); } From c18f41836e1dfdc707000d7763c54524c276bf1a Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 12:14:13 -0400 Subject: [PATCH 06/18] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20refactor:=20snaps?= =?UTF-8?q?hot=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cli/src/main.rs | 3 +- common/src/ether/rpc.rs | 4 +- common/src/utils/mod.rs | 2 +- common/src/utils/sync.rs | 4 +- core/src/cfg/mod.rs | 9 +- core/src/decode/mod.rs | 3 +- core/src/decompile/mod.rs | 38 +++--- core/src/decompile/out/abi.rs | 13 +- core/src/decompile/out/mod.rs | 2 +- core/src/decompile/out/solidity.rs | 9 +- core/src/decompile/out/yul.rs | 7 +- core/src/dump/mod.rs | 5 +- core/src/dump/util/csv.rs | 5 +- core/src/dump/util/mod.rs | 32 ++--- core/src/snapshot/mod.rs | 191 ++++------------------------- 15 files changed, 94 insertions(+), 233 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 7a5f4d70..d90b4c44 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -73,7 +73,8 @@ async fn main() -> Result<(), Box> { // handle catching panics with panic::set_hook(Box::new(|panic_info| { - // cleanup the terminal (break out of alternate screen, disable mouse capture, and show the cursor) + // cleanup the terminal (break out of alternate screen, disable mouse capture, and show the + // cursor) let stdout = io::stdout(); let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend).unwrap(); diff --git a/common/src/ether/rpc.rs b/common/src/ether/rpc.rs index 6cdea2a4..d53cd2f4 100644 --- a/common/src/ether/rpc.rs +++ b/common/src/ether/rpc.rs @@ -12,7 +12,6 @@ pub async fn get_code( contract_address: &str, rpc_url: &str, ) -> Result> { - // get a new logger let level = std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".into()); let (logger, _) = Logger::new(&level); @@ -23,7 +22,7 @@ pub async fn get_code( // check the cache for a matching address if let Some(bytecode) = read_cache(&format!("contract.{}", &contract_address)) { logger.debug(&format!("found cached bytecode for '{}' .", &contract_address)); - return Ok(bytecode); + return Ok(bytecode) } // make sure the RPC provider isn't empty @@ -73,7 +72,6 @@ pub async fn get_transaction( transaction_hash: &str, rpc_url: &str, ) -> Result> { - // get a new logger let level = std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".into()); let (logger, _) = Logger::new(&level); diff --git a/common/src/utils/mod.rs b/common/src/utils/mod.rs index 8e76f53f..8b686a03 100644 --- a/common/src/utils/mod.rs +++ b/common/src/utils/mod.rs @@ -1,8 +1,8 @@ pub mod http; pub mod integers; pub mod strings; +pub mod sync; mod tests; pub mod threading; pub mod time; pub mod version; -pub mod sync; diff --git a/common/src/utils/sync.rs b/common/src/utils/sync.rs index 5119a7f3..1e9bdb94 100644 --- a/common/src/utils/sync.rs +++ b/common/src/utils/sync.rs @@ -1,8 +1,6 @@ - /// Take in a non-async function and await it. This functions should be blocking. pub fn blocking_await(f: F) -> T where - F: FnOnce() -> T, -{ + F: FnOnce() -> T, { tokio::task::block_in_place(f) } diff --git a/core/src/cfg/mod.rs b/core/src/cfg/mod.rs index 830c24cb..3d477585 100644 --- a/core/src/cfg/mod.rs +++ b/core/src/cfg/mod.rs @@ -84,14 +84,14 @@ pub async fn cfg(args: CFGArgs) -> Result, Box Result, Box { explain_progress.finish_and_clear(); diff --git a/core/src/decompile/mod.rs b/core/src/decompile/mod.rs index 3a7213db..57f15f97 100644 --- a/core/src/decompile/mod.rs +++ b/core/src/decompile/mod.rs @@ -7,8 +7,9 @@ pub mod util; use crate::decompile::{ analyzers::{solidity::analyze_sol, yul::analyze_yul}, + out::abi::build_abi, resolve::*, - util::*, out::abi::build_abi, + util::*, }; use heimdall_common::{ @@ -80,7 +81,9 @@ pub struct DecompileResult { pub abi: Option>, } -pub async fn decompile(args: DecompilerArgs) -> Result> { +pub async fn decompile( + args: DecompilerArgs, +) -> Result> { use std::time::Instant; let now = Instant::now(); @@ -117,8 +120,8 @@ pub async fn decompile(args: DecompilerArgs) -> Result Result Result, - all_resolved_events: HashMap, trace: &mut TraceFactory, trace_parent: u32, ) -> Result, Box> { diff --git a/core/src/decompile/out/mod.rs b/core/src/decompile/out/mod.rs index 3b88f09e..ceb07337 100644 --- a/core/src/decompile/out/mod.rs +++ b/core/src/decompile/out/mod.rs @@ -1,4 +1,4 @@ +pub mod abi; pub mod postprocessers; pub mod solidity; pub mod yul; -pub mod abi; diff --git a/core/src/decompile/out/solidity.rs b/core/src/decompile/out/solidity.rs index 740ecca3..ad162ba4 100644 --- a/core/src/decompile/out/solidity.rs +++ b/core/src/decompile/out/solidity.rs @@ -16,7 +16,8 @@ use super::{ util::Function, DecompilerArgs, }, - postprocessers::solidity::postprocess, abi::ABIStructure, + abi::ABIStructure, + postprocessers::solidity::postprocess, }; pub fn build_solidity_output( @@ -28,7 +29,6 @@ pub fn build_solidity_output( trace: &mut TraceFactory, trace_parent: u32, ) -> Result> { - // get a new logger let level = std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".into()); let (logger, _) = Logger::new(&level); @@ -57,7 +57,7 @@ pub fn build_solidity_output( line!(), "heimdall".to_string(), "build_solidity_output".to_string(), - vec![shortened_target], + vec![shortened_target.clone()], short_path(&shortened_target), ); @@ -254,5 +254,6 @@ pub fn build_solidity_output( decompiled_output.push(String::from("}")); progress_bar.finish_and_clear(); - Ok(postprocess(decompiled_output, all_resolved_errors, all_resolved_events, &progress_bar).join("\n")) + Ok(postprocess(decompiled_output, all_resolved_errors, all_resolved_events, &progress_bar) + .join("\n")) } diff --git a/core/src/decompile/out/yul.rs b/core/src/decompile/out/yul.rs index 916465eb..d818f624 100644 --- a/core/src/decompile/out/yul.rs +++ b/core/src/decompile/out/yul.rs @@ -1,11 +1,14 @@ use std::{collections::HashMap, time::Duration}; +use crate::decompile::{constants::DECOMPILED_SOURCE_HEADER_YUL, util::Function, DecompilerArgs}; use heimdall_common::{ ether::signatures::ResolvedLog, - io::{logging::{Logger, TraceFactory}, file::short_path}, + io::{ + file::short_path, + logging::{Logger, TraceFactory}, + }, }; use indicatif::ProgressBar; -use crate::decompile::{DecompilerArgs, util::Function, constants::DECOMPILED_SOURCE_HEADER_YUL}; use super::postprocessers::yul::postprocess; diff --git a/core/src/dump/mod.rs b/core/src/dump/mod.rs index dce8c51b..e36aad8c 100644 --- a/core/src/dump/mod.rs +++ b/core/src/dump/mod.rs @@ -15,7 +15,7 @@ use self::{ constants::DUMP_STATE, menus::TUIView, structures::{dump_state::DumpState, transaction::Transaction}, - util::csv::{DumpRow, build_csv}, + util::csv::{build_csv, DumpRow}, }; #[derive(Debug, Clone, Parser)] @@ -170,7 +170,8 @@ pub async fn dump(args: DumpArgs) -> Result, Box Option Option traces.state_diff, - Err(e) => { - cleanup_terminal(); - logger.error(&format!( + let state_diff = + match provider.trace_replay_transaction(transaction_hash, vec![TraceType::StateDiff]).await + { + Ok(traces) => traces.state_diff, + Err(e) => { + cleanup_terminal(); + logger.error(&format!( "failed to replay and trace transaction '{}' . does your RPC provider support it?", &tx.hash )); - logger.error(&format!("error: '{e}' .")); - std::process::exit(1) - } - }; + logger.error(&format!("error: '{e}' .")); + std::process::exit(1) + } + }; // write the state diff to the cache let expiry = - std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() - + 60 * 60 * 24 * 7; + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() + + 60 * 60 * 24 * 7; store_cache(&format!("diff.{}", &tx.hash), &state_diff, Some(expiry)); + logger.debug_max(&format!("fetched state diff for transaction '{}' .", &tx.hash)); + state_diff } diff --git a/core/src/snapshot/mod.rs b/core/src/snapshot/mod.rs index 1c865004..3ca71ab7 100644 --- a/core/src/snapshot/mod.rs +++ b/core/src/snapshot/mod.rs @@ -7,7 +7,7 @@ pub mod util; use std::{ collections::{HashMap, HashSet}, - env, fs, + fs, time::Duration, }; @@ -24,7 +24,7 @@ use heimdall_common::{ selectors::{find_function_selectors, resolve_selectors}, signatures::{score_signature, ResolvedError, ResolvedFunction, ResolvedLog}, }, - io::{file::short_path, logging::*}, + io::logging::*, utils::strings::{decode_hex, encode_hex_reduced}, }; use indicatif::ProgressBar; @@ -33,7 +33,7 @@ use crate::snapshot::{ analyze::snapshot_trace, resolve::match_parameters, structures::snapshot::{GasUsed, Snapshot}, - util::{csv::generate_and_write_contract_csv, tui}, + util::tui, }; #[derive(Debug, Clone, Parser)] #[clap( @@ -51,10 +51,6 @@ pub struct SnapshotArgs { #[clap(flatten)] pub verbose: clap_verbosity_flag::Verbosity, - /// The output directory to write the output files to - #[clap(long = "output", short, default_value = "", hide_default_value = true)] - pub output: String, - /// The RPC provider to use for fetching target bytecode. #[clap(long = "rpc-url", short, default_value = "", hide_default_value = true)] pub rpc_url: String, @@ -72,7 +68,7 @@ pub struct SnapshotArgs { pub no_tui: bool, } -pub fn snapshot(args: SnapshotArgs) { +pub async fn snapshot(args: SnapshotArgs) -> Result, Box> { use std::time::Instant; let now = Instant::now(); @@ -99,44 +95,22 @@ pub fn snapshot(args: SnapshotArgs) { "()".to_string(), ); - // parse the output directory - let mut output_dir: String; - if args.output.is_empty() { - output_dir = match env::current_dir() { - Ok(dir) => dir.into_os_string().into_string().unwrap(), - Err(_) => { - logger.error("failed to get current directory."); - std::process::exit(1); - } - }; - output_dir.push_str("/output"); - } else { - output_dir = args.output.clone(); - } - let contract_bytecode: String; - if ADDRESS_REGEX.is_match(&args.target).unwrap() { - // push the address to the output directory - if output_dir != args.output { - output_dir.push_str(&format!("/{}", &args.target)); - } - + if ADDRESS_REGEX.is_match(&args.target)? { // We are snapshotting a contract address, so we need to fetch the bytecode from the RPC // provider. - contract_bytecode = get_code(&args.target, &args.rpc_url); - } else if BYTECODE_REGEX.is_match(&args.target).unwrap() { + contract_bytecode = get_code(&args.target, &args.rpc_url).await?; + } else if BYTECODE_REGEX.is_match(&args.target)? { + logger.debug_max("using provided bytecode for snapshotting."); contract_bytecode = args.target.clone().replacen("0x", "", 1); } else { - // push the address to the output directory - if output_dir != args.output { - output_dir.push_str("/local"); - } + logger.debug_max("using provided file for snapshotting."); // We are snapshotting a file, so we need to read the bytecode from the file. contract_bytecode = match fs::read_to_string(&args.target) { Ok(contents) => { let _contents = contents.replace('\n', ""); - if BYTECODE_REGEX.is_match(&_contents).unwrap() && _contents.len() % 2 == 0 { + if BYTECODE_REGEX.is_match(&_contents)? && _contents.len() % 2 == 0 { _contents.replacen("0x", "", 1) } else { logger @@ -155,10 +129,10 @@ pub fn snapshot(args: SnapshotArgs) { let disassembled_bytecode = disassemble(DisassemblerArgs { target: contract_bytecode.clone(), verbose: args.verbose.clone(), - output: output_dir.clone(), rpc_url: args.rpc_url, decimal_counter: false, - }); + }) + .await?; trace.add_call( snapshot_call, line!(), @@ -207,7 +181,7 @@ pub fn snapshot(args: SnapshotArgs) { line!(), "contract".to_string(), shortened_target.clone(), - (contract_bytecode.len() / 2usize).try_into().unwrap(), + (contract_bytecode.len() / 2usize).try_into()?, ); // find and resolve all selectors in the bytecode @@ -258,7 +232,7 @@ pub fn snapshot(args: SnapshotArgs) { trace.add_info( func_analysis_trace, - function_entry_point.try_into().unwrap(), + function_entry_point.try_into()?, &format!("discovered entry point: {function_entry_point}"), ); @@ -268,7 +242,7 @@ pub fn snapshot(args: SnapshotArgs) { trace.add_debug( func_analysis_trace, - function_entry_point.try_into().unwrap(), + function_entry_point.try_into()?, &format!( "execution tree {}", match jumpdest_count { @@ -280,11 +254,15 @@ pub fn snapshot(args: SnapshotArgs) { ), ); + logger.debug_max(&format!( + "building snapshot for selector {} from symbolic execution trace", + selector + )); let mut snapshot = snapshot_trace( map, Snapshot { selector: selector.clone(), - bytecode: decode_hex(&contract_bytecode.replacen("0x", "", 1)).unwrap(), + bytecode: decode_hex(&contract_bytecode.replacen("0x", "", 1))?, entry_point: function_entry_point, arguments: HashMap::new(), storage: HashSet::new(), @@ -522,29 +500,10 @@ pub fn snapshot(args: SnapshotArgs) { logger.info("symbolic execution completed."); logger.debug(&format!("snapshot completed in {:?}.", now.elapsed())); - // build output path - let snapshot_csv_output_path = format!("{}/snapshot.csv", output_dir); - trace.add_call( - snapshot_call, - line!(), - "heimdall".to_string(), - "build_csv".to_string(), - vec![shortened_target.clone()], - short_path(&snapshot_csv_output_path), - ); - - // write the csv to disk - generate_and_write_contract_csv( - &snapshots, - &all_resolved_errors, - &all_resolved_events, - &snapshot_csv_output_path, - ); - // open the tui if !args.no_tui { tui::handle( - snapshots, + snapshots.clone(), &all_resolved_errors, &all_resolved_events, if args.target.len() > 64 { &shortened_target } else { args.target.as_str() }, @@ -553,111 +512,5 @@ pub fn snapshot(args: SnapshotArgs) { } trace.display(); -} - -/// Builder pattern for using snapshot method as a library. -/// -/// Default values may be overriden individually. -/// ## Example -/// Use with normal settings: -/// ```no_run -/// # use crate::heimdall_core::snapshot::SnapshotBuilder; -/// const SOURCE: &'static str = "7312/* snip */04ad"; -/// -/// SnapshotBuilder::new(SOURCE) -/// .snapshot(); -/// ``` -/// Or change settings individually: -/// ```no_run -/// # use crate::heimdall_core::snapshot::SnapshotBuilder; -/// -/// const SOURCE: &'static str = "7312/* snip */04ad"; -/// SnapshotBuilder::new(SOURCE) -/// .default(false) -/// .output("my_contract_dir") -/// .rpc("https://127.0.0.1:8545") -/// .skip_resolving(true) -/// .verbosity(5) -/// .no_tui(true) -/// .snapshot(); -/// ``` -#[allow(dead_code)] -pub struct SnapshotBuilder { - args: SnapshotArgs, -} - -impl SnapshotBuilder { - /// A new builder for the decompilation of the specified target. - /// - /// The target may be a file, bytecode, contract address, or ENS name. - #[allow(dead_code)] - pub fn new(target: &str) -> Self { - SnapshotBuilder { - args: SnapshotArgs { - target: target.to_string(), - verbose: clap_verbosity_flag::Verbosity::new(0, 0), - output: String::from(""), - rpc_url: String::from(""), - default: true, - skip_resolving: false, - no_tui: false, - }, - } - } - - /// Set the output verbosity level. - /// - /// - 0 Error - /// - 1 Warn - /// - 2 Info - /// - 3 Debug - /// - 4 Trace - #[allow(dead_code)] - pub fn verbosity(mut self, level: i8) -> SnapshotBuilder { - // Calculated by the log library as: 1 + verbose - quiet. - // Set quiet as 1, and the level corresponds to the appropriate Log level. - self.args.verbose = clap_verbosity_flag::Verbosity::new(level, 0); - self - } - - /// The output directory to write the snapshotted files to - #[allow(dead_code)] - pub fn output(mut self, directory: &str) -> SnapshotBuilder { - self.args.output = directory.to_string(); - self - } - - /// The RPC provider to use for fetching target bytecode. - #[allow(dead_code)] - pub fn rpc(mut self, url: &str) -> SnapshotBuilder { - self.args.rpc_url = url.to_string(); - self - } - - /// When prompted, always select the default value. - #[allow(dead_code)] - pub fn default(mut self, accept: bool) -> SnapshotBuilder { - self.args.default = accept; - self - } - - /// Whether to skip resolving function selectors. - #[allow(dead_code)] - pub fn skip_resolving(mut self, skip: bool) -> SnapshotBuilder { - self.args.skip_resolving = skip; - self - } - - /// Whether to show the TUI or not. - #[allow(dead_code)] - pub fn no_tui(mut self, no_tui: bool) -> SnapshotBuilder { - self.args.no_tui = no_tui; - self - } - - /// Starts the decompilation. - #[allow(dead_code)] - pub fn snapshot(self) { - snapshot(self.args) - } + Ok(snapshots) } From dbe583277c659e23da49832fc4e815d9f8216ec2 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 13:11:01 -0400 Subject: [PATCH 07/18] =?UTF-8?q?=E2=9C=85=20tests:=20refactor=20`decompil?= =?UTF-8?q?e`=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/src/testing/benchmarks.rs | 48 ++++++++ core/src/decompile/mod.rs | 22 +++- core/tests/test_decompile.rs | 203 +++++++++++++++++++------------ 3 files changed, 188 insertions(+), 85 deletions(-) diff --git a/common/src/testing/benchmarks.rs b/common/src/testing/benchmarks.rs index 06b5ea22..e2dbd41c 100644 --- a/common/src/testing/benchmarks.rs +++ b/common/src/testing/benchmarks.rs @@ -45,6 +45,54 @@ pub fn benchmark(benchmark_name: &str, runs: usize, to_bench: fn()) { ); } +#[allow(dead_code)] +pub async fn async_bench(benchmark_name: &str, runs: usize, to_bench: F) +where + F: Fn() -> Fut, + Fut: std::future::Future, { + let mut time = 0usize; + let mut times = Vec::with_capacity(runs); + let mut max = usize::MIN; + let mut min = usize::MAX; + + // warm up + thread::sleep(std::time::Duration::from_secs(2)); + + for _ in 0..runs { + let start_time = Instant::now(); + to_bench().await; + let end_time = start_time.elapsed().as_nanos() as usize; + + max = std::cmp::max(max, end_time); + min = std::cmp::min(min, end_time); + time += end_time; + times.push(end_time); + } + + let mean = time / runs; + let variance = times + .iter() + .map(|x| { + let x_i64 = *x as i64; + let diff = x_i64 - mean as i64; + diff * diff + }) + .sum::() / + (runs - 1) as i64; + let std_dev = f64::sqrt(variance as f64) as usize; + + let _ = io::stdout().write_all( + format!( + " {}:\n {} ± {} per run ( with {} runs ).\n\n", + benchmark_name, + format_nanos(mean), + format_nanos(std_dev), + runs + ) + .as_bytes(), + ); +} + #[allow(dead_code)] fn format_nanos(nanos: usize) -> String { let mut nanos = nanos; diff --git a/core/src/decompile/mod.rs b/core/src/decompile/mod.rs index 57f15f97..686eb590 100644 --- a/core/src/decompile/mod.rs +++ b/core/src/decompile/mod.rs @@ -7,7 +7,7 @@ pub mod util; use crate::decompile::{ analyzers::{solidity::analyze_sol, yul::analyze_yul}, - out::abi::build_abi, + out::{abi::build_abi, solidity::build_solidity_output, yul::build_yul_output}, resolve::*, util::*, }; @@ -591,15 +591,29 @@ pub async fn decompile( logger.info("symbolic execution completed."); logger.info("building decompilation output."); - let abi = build_abi(&args, analyzed_functions, &mut trace, decompile_call)?; + let abi = build_abi(&args, analyzed_functions.clone(), &mut trace, decompile_call)?; trace.display(); logger.debug(&format!("decompilation completed in {:?}.", now.elapsed())); Ok(DecompileResult { source: if args.include_solidity { - None + Some(build_solidity_output( + &args, + &abi, + analyzed_functions, + all_resolved_errors, + all_resolved_events, + &mut trace, + decompile_call, + )?) } else if args.include_yul { - None + Some(build_yul_output( + &args, + analyzed_functions, + all_resolved_events, + &mut trace, + decompile_call, + )?) } else { None }, diff --git a/core/tests/test_decompile.rs b/core/tests/test_decompile.rs index 3bf97c66..b2c6da15 100644 --- a/core/tests/test_decompile.rs +++ b/core/tests/test_decompile.rs @@ -1,128 +1,166 @@ #[cfg(test)] mod benchmark { use clap_verbosity_flag::Verbosity; - use heimdall_common::testing::benchmarks::benchmark; + use heimdall_common::testing::benchmarks::async_bench; use heimdall_core::decompile::DecompilerArgs; - #[test] - fn benchmark_decompile_complex() { - fn bench() { + #[tokio::test] + async fn benchmark_decompile_solidity_simple() { + async fn bench() { let args = DecompilerArgs { - target: String::from("0xE90d8Fb7B79C8930B5C8891e61c298b412a6e81a"), + target: String::from("0x1bf797219482a29013d804ad96d1c6f84fba4c45"), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from("https://eth.llamarpc.com"), default: true, skip_resolving: true, include_solidity: true, include_yul: false, }; - heimdall_core::decompile::decompile(args) + let _ = heimdall_core::decompile::decompile(args).await; } - benchmark("benchmark_decompile_complex", 100, bench) + async_bench("benchmark_decompile_solidity_simple", 100, bench).await; } - #[test] - fn benchmark_decompile_simple() { - fn bench() { + #[tokio::test] + async fn benchmark_decompile_solidity_complex() { + async fn bench() { let args = DecompilerArgs { - target: String::from("0x1bf797219482a29013d804ad96d1c6f84fba4c45"), + target: String::from("0xE90d8Fb7B79C8930B5C8891e61c298b412a6e81a"), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from("https://eth.llamarpc.com"), default: true, skip_resolving: true, include_solidity: true, include_yul: false, }; - heimdall_core::decompile::decompile(args) + let _ = heimdall_core::decompile::decompile(args).await; + } + + async_bench("benchmark_decompile_solidity_complex", 100, bench).await; + } + + #[tokio::test] + async fn benchmark_decompile_yul_simple() { + async fn bench() { + let args = DecompilerArgs { + target: String::from("0x1bf797219482a29013d804ad96d1c6f84fba4c45"), + verbose: Verbosity::new(0, 0), + rpc_url: String::from("https://eth.llamarpc.com"), + default: true, + skip_resolving: true, + include_solidity: false, + include_yul: true, + }; + let _ = heimdall_core::decompile::decompile(args).await; + } + + async_bench("benchmark_decompile_yul_simple", 100, bench).await; + } + + #[tokio::test] + async fn benchmark_decompile_yul_complex() { + async fn bench() { + let args = DecompilerArgs { + target: String::from("0xE90d8Fb7B79C8930B5C8891e61c298b412a6e81a"), + verbose: Verbosity::new(0, 0), + rpc_url: String::from("https://eth.llamarpc.com"), + default: true, + skip_resolving: true, + include_solidity: false, + include_yul: true, + }; + let _ = heimdall_core::decompile::decompile(args).await; } - benchmark("benchmark_decompile_simple", 100, bench) + async_bench("benchmark_decompile_yul_complex", 100, bench).await; } - #[test] - fn benchmark_build_abi_simple() { - fn bench() { + #[tokio::test] + async fn benchmark_build_abi_simple() { + async fn bench() { let args = DecompilerArgs { target: String::from("0x1bf797219482a29013d804ad96d1c6f84fba4c45"), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from("https://eth.llamarpc.com"), default: true, skip_resolving: true, include_solidity: false, include_yul: false, }; - heimdall_core::decompile::decompile(args) + let _ = heimdall_core::decompile::decompile(args).await; } - benchmark("benchmark_build_abi_simple", 100, bench) + async_bench("benchmark_build_abi_simple", 100, bench).await; } - #[test] - fn benchmark_build_abi_complex() { - fn bench() { + #[tokio::test] + async fn benchmark_build_abi_complex() { + async fn bench() { let args = DecompilerArgs { target: String::from("0xE90d8Fb7B79C8930B5C8891e61c298b412a6e81a"), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from("https://eth.llamarpc.com"), default: true, skip_resolving: true, include_solidity: false, include_yul: false, }; - heimdall_core::decompile::decompile(args) + let _ = heimdall_core::decompile::decompile(args).await; + return } - benchmark("benchmark_build_abi_complex", 100, bench) + async_bench("benchmark_build_abi_complex", 100, bench).await; } } #[cfg(test)] mod integration_tests { - use heimdall_common::io::file::{delete_path, read_file}; - use heimdall_core::decompile::DecompileBuilder; - - #[test] - fn test_decompile_precompile() { - DecompileBuilder::new("0x1bf797219482a29013d804ad96d1c6f84fba4c45") - .output("./output/tests/decompile/test1") - .rpc("https://eth.llamarpc.com") - .include_sol(true) - .default(true) - .skip_resolving(true) - .decompile(); - - // throws if not found. asserts success - let output = read_file(&String::from("./output/tests/decompile/test1/decompiled.sol")); + use clap_verbosity_flag::Verbosity; + use heimdall_common::io::file::delete_path; + use heimdall_core::decompile::DecompilerArgs; + + #[tokio::test] + async fn test_decompile_precompile() { + let result = heimdall_core::decompile::decompile(DecompilerArgs { + target: String::from("0x1bf797219482a29013d804ad96d1c6f84fba4c45"), + verbose: Verbosity::new(0, 0), + rpc_url: String::from("https://eth.llamarpc.com"), + default: true, + skip_resolving: true, + include_solidity: true, + include_yul: false, + }) + .await + .unwrap(); + + println!("{result:?}"); // assert that the output is correct for line in &["function Unresolved_19045a25(bytes memory arg0, bytes memory arg1) public payable returns (address) {", " = ecrecover("] { println!("{line}"); - assert!(output.contains(line)); + assert!(result.source.clone().unwrap().contains(line)); } // drop path delete_path(&String::from("./output/tests/decompile/test1")); } - #[test] - fn test_decompile_weth() { - DecompileBuilder::new("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") - .output("./output/tests/decompile/test2") - .rpc("https://eth.llamarpc.com") - .include_sol(true) - .default(true) - .skip_resolving(true) - .decompile(); - - // throws if not found. asserts success - let output = read_file(&String::from("./output/tests/decompile/test2/decompiled.sol")); + #[tokio::test] + async fn test_decompile_weth() { + let result = heimdall_core::decompile::decompile(DecompilerArgs { + target: String::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"), + verbose: Verbosity::new(0, 0), + rpc_url: String::from("https://eth.llamarpc.com"), + default: true, + skip_resolving: true, + include_solidity: true, + include_yul: false, + }) + .await + .unwrap(); // assert that the output is correct for line in &["function Unresolved_06fdde03() public view returns (bytes memory) {", @@ -137,39 +175,40 @@ mod integration_tests { "function Unresolved_d0e30db0() public payable {", "function Unresolved_dd62ed3e(address arg0, address arg1) public view returns (uint256) {"] { println!("{line}"); - assert!(output.contains(line)); + assert!(result.source.clone().unwrap().contains(line)); } // drop path delete_path(&String::from("./output/tests/decompile/test2")); } - #[test] - fn test_decompile_ctf() { - DecompileBuilder::new("0x9f00c43700bc0000Ff91bE00841F8e04c0495000") - .output("./output/tests/decompile/test3") - .rpc("https://eth.llamarpc.com") - .include_sol(true) - .default(true) - .skip_resolving(true) - .decompile(); - - // throws if not found. asserts success - let output = read_file(&String::from("./output/tests/decompile/test3/decompiled.sol")); + #[tokio::test] + async fn test_decompile_ctf() { + let result = heimdall_core::decompile::decompile(DecompilerArgs { + target: String::from("0x9f00c43700bc0000Ff91bE00841F8e04c0495000"), + verbose: Verbosity::new(0, 0), + rpc_url: String::from("https://eth.llamarpc.com"), + default: true, + skip_resolving: true, + include_solidity: true, + include_yul: false, + }) + .await + .unwrap(); // assert that the output is correct for line in &["function Unresolved_2fa61cd8(address arg0) public view payable returns (uint16) {", "function Unresolved_41161b10(bytes memory arg0, address arg1) public payable returns (bool) {", "function Unresolved_06fdde03() public pure payable returns (bytes memory) {"] { println!("{line}"); - assert!(output.contains(line)); + assert!(result.source.clone().unwrap().contains(line)); } // drop path delete_path(&String::from("./output/tests/decompile/test3")); } - #[test] + #[tokio::test] /// Thorough testing for decompilation across a large number of contracts /// Runs on the top 100 contracts for 2023-06-26 /// @@ -187,7 +226,7 @@ mod integration_tests { /// - The ABI matches the solidity outline /// - There is no unreachable code (TODO) /// - There are no empty branches (TODO) - fn test_decompile_thorough() { + async fn test_decompile_thorough() { let contracts = [ "0xdAC17F958D2ee523a2206206994597C13D831ec7", "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD", @@ -249,19 +288,21 @@ mod integration_tests { for contract in contracts { println!("Testing contract: {contract}"); - DecompileBuilder::new(contract) - .output("./output/tests/decompile/integration") - .rpc("https://eth.llamarpc.com") - .include_sol(true) - .default(true) - .skip_resolving(true) - .decompile(); + let result = heimdall_core::decompile::decompile(DecompilerArgs { + target: contract.to_string(), + verbose: Verbosity::new(0, 0), + rpc_url: String::from("https://eth.llamarpc.com"), + default: true, + skip_resolving: true, + include_solidity: true, + include_yul: false, + }) + .await + .unwrap(); // assert that the number of opening and closing brackets, parentheses, and curly braces // are equal - let output = heimdall_common::io::file::read_file(&String::from( - "./output/tests/decompile/integration/decompiled.sol", - )); + let output = result.source.unwrap(); let open_brackets = output.matches('{').count(); let close_brackets = output.matches('}').count(); assert_eq!(open_brackets, close_brackets); From 7e712c90af916adedf1633131a5d194fe1960049 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 13:39:56 -0400 Subject: [PATCH 08/18] =?UTF-8?q?=E2=9C=85=20tests:=20refactor=20`cfg`=20t?= =?UTF-8?q?ests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/tests/test_cfg.rs | 102 +++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/core/tests/test_cfg.rs b/core/tests/test_cfg.rs index 8cbd438a..b7d50678 100644 --- a/core/tests/test_cfg.rs +++ b/core/tests/test_cfg.rs @@ -1,89 +1,91 @@ #[cfg(test)] mod benchmark { use clap_verbosity_flag::Verbosity; - use heimdall_common::testing::benchmarks::benchmark; + use heimdall_common::testing::benchmarks::async_bench; use heimdall_core::cfg::CFGArgs; - #[test] - fn benchmark_generate_cfg_simple() { - fn bench() { + #[tokio::test] + async fn benchmark_generate_cfg_simple() { + async fn bench() { let args = CFGArgs { target: String::from("0x1bf797219482a29013d804ad96d1c6f84fba4c45"), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from("https://eth.llamarpc.com"), default: true, color_edges: false, - format: String::from(""), + format: String::from("png"), }; - heimdall_core::cfg::cfg(args) + let _ = heimdall_core::cfg::cfg(args).await; } - benchmark("benchmark_generate_cfg_simple", 100, bench) + async_bench("benchmark_generate_cfg_simple", 100, bench).await; } - #[test] - fn benchmark_generate_cfg_complex() { - fn bench() { + #[tokio::test] + async fn benchmark_generate_cfg_complex() { + async fn bench() { let args = CFGArgs { target: String::from("0xE90d8Fb7B79C8930B5C8891e61c298b412a6e81a"), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from("https://eth.llamarpc.com"), default: true, color_edges: false, - format: String::from(""), + format: String::from("png"), }; - heimdall_core::cfg::cfg(args) + let _ = heimdall_core::cfg::cfg(args).await; } - benchmark("benchmark_generate_cfg_complex", 100, bench) + async_bench("benchmark_generate_cfg_complex", 100, bench).await; } } #[cfg(test)] mod integration_tests { - use heimdall_common::io::file::{delete_path, read_file}; - - use heimdall_core::cfg::CFGBuilder; - - #[test] - fn test_cfg_simple() { - let expected_lines = vec![ + use clap_verbosity_flag::Verbosity; + use heimdall_core::cfg::CFGArgs; + use petgraph::dot::Dot; + + #[tokio::test] + async fn test_cfg_simple() { + let result = heimdall_core::cfg::cfg(CFGArgs { + target: String::from("0x1bf797219482a29013d804ad96d1c6f84fba4c45"), + verbose: Verbosity::new(0, 0), + rpc_url: String::from("https://eth.llamarpc.com"), + default: true, + color_edges: false, + format: String::from("png"), + }) + .await + .unwrap(); + + let output = format!("{}", Dot::with_config(&result, &[])); + + for line in &[ String::from("0 [ label = \"0x01 PUSH20 0x1bf797219482a29013d804ad96d1c6f84fba4c45\\l0x16 ADDRESS \\l0x17 EQ \\l0x18 PUSH1 0x80\\l0x1a PUSH1 0x40\\l0x1c MSTORE \\l0x1d PUSH1 0x04\\l0x1f CALLDATASIZE \\l0x20 LT \\l0x21 PUSH2 0x58\\l0x24 JUMPI \\l\" ]"), String::from("0 -> 13 []") - ]; - - CFGBuilder::new("0x1bf797219482a29013d804ad96d1c6f84fba4c45") - .rpc("https://eth.llamarpc.com") - .output("./output/tests/cfg/test1") - .generate(); - - let dot = read_file(&String::from("./output/tests/cfg/test1/cfg.dot")); - - for line in expected_lines { - assert!(dot.contains(&line)) + ] { + output.contains(line); } - - delete_path(&String::from("./output/tests/cfg/test1")); } - #[test] - fn test_cfg_complex() { - let expected_lines = vec![String::from("\"0x03a0 JUMPDEST \\l0x03a1 STOP \\l\"")]; - - CFGBuilder::new("0xE90d8Fb7B79C8930B5C8891e61c298b412a6e81a") - .rpc("https://eth.llamarpc.com") - .output("./output/tests/cfg/test2/") - .generate(); - - let dot = read_file(&String::from("./output/tests/cfg/test2/cfg.dot")); - - for line in expected_lines { - assert!(dot.contains(&line)) + #[tokio::test] + async fn test_cfg_complex() { + let result = heimdall_core::cfg::cfg(CFGArgs { + target: String::from("0xE90d8Fb7B79C8930B5C8891e61c298b412a6e81a"), + verbose: Verbosity::new(0, 0), + rpc_url: String::from("https://eth.llamarpc.com"), + default: true, + color_edges: false, + format: String::from("png"), + }) + .await + .unwrap(); + + let output = format!("{}", Dot::with_config(&result, &[])); + + for line in &[String::from("\"0x03a0 JUMPDEST \\l0x03a1 STOP \\l\"")] { + assert!(output.contains(&*line)) } - - delete_path(&String::from("./output/tests/cfg/test2")); } } From 0c9dacda4a5cc56db11608eeeb99dd4b91e19aa4 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 13:50:53 -0400 Subject: [PATCH 09/18] =?UTF-8?q?=E2=9C=85=20tests:=20refactor=20`disassem?= =?UTF-8?q?ble`=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/tests/test_cfg.rs | 2 +- core/tests/test_decode.rs | 54 +++++++++++++-------------- core/tests/test_decompile.rs | 1 - core/tests/test_disassemble.rs | 68 ++++++++++++++++++---------------- 4 files changed, 65 insertions(+), 60 deletions(-) diff --git a/core/tests/test_cfg.rs b/core/tests/test_cfg.rs index b7d50678..e5ee9874 100644 --- a/core/tests/test_cfg.rs +++ b/core/tests/test_cfg.rs @@ -85,7 +85,7 @@ mod integration_tests { let output = format!("{}", Dot::with_config(&result, &[])); for line in &[String::from("\"0x03a0 JUMPDEST \\l0x03a1 STOP \\l\"")] { - assert!(output.contains(&*line)) + assert!(output.contains(line)) } } } diff --git a/core/tests/test_decode.rs b/core/tests/test_decode.rs index a019480f..bbb9b41a 100644 --- a/core/tests/test_decode.rs +++ b/core/tests/test_decode.rs @@ -1,13 +1,13 @@ #[cfg(test)] mod benchmark { use clap_verbosity_flag::Verbosity; - use heimdall_common::testing::benchmarks::benchmark; + use heimdall_common::testing::benchmarks::async_bench; use heimdall_core::decode::DecodeArgs; - #[test] - fn benchmark_decode_transfer() { - fn bench() { + #[tokio::test] + async fn benchmark_decode_transfer() { + async fn bench() { let args = DecodeArgs { target: String::from("0xc47f00270000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b6a6265636b65722e657468000000000000000000000000000000000000000000"), verbose: Verbosity::new(0, 0), @@ -17,15 +17,15 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall_core::decode::decode(args) + let _ = heimdall_core::decode::decode(args).await; } - benchmark("benchmark_decode_transfer", 100, bench) + let _ = async_bench("benchmark_decode_transfer", 100, bench); } - #[test] - fn benchmark_decode_uniswap_simple() { - fn bench() { + #[tokio::test] + async fn benchmark_decode_uniswap_simple() { + async fn bench() { let args = DecodeArgs { target: String::from("791ac947000000000000000000000000000000000000000000ac03e3c2829679f93600000000000000000000000000000000000000000000000000000200b952bc426b0c00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000bffadf2903e6ec32dc0a7b5a2b57de0e728ec0b500000000000000000000000000000000000000000000000000000000645c1321000000000000000000000000000000000000000000000000000000000000000200000000000000000000000039702bb7c6a482bdde6aec96175bc6cdc277b999000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), verbose: Verbosity::new(0, 0), @@ -35,15 +35,15 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall_core::decode::decode(args) + let _ = heimdall_core::decode::decode(args).await; } - benchmark("benchmark_decode_uniswap_simple", 100, bench) + let _ = async_bench("benchmark_decode_uniswap_simple", 100, bench); } - #[test] - fn benchmark_decode_seaport_simple() { - fn bench() { + #[tokio::test] + async fn benchmark_decode_seaport_simple() { + async fn bench() { let args = DecodeArgs { target: String::from("0xfb0f3ee100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ec9c58de0a8000000000000000000000000000d2f8a98bde7c701ae961d10d0d1fc3a751be737f000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000005008c2a3af41024e9f0bd0432df4f75828602598000000000000000000000000000000000000000000000000000000000000110600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006358934b00000000000000000000000000000000000000000000000000000000637e22710000000000000000000000000000000000000000000000000000000000000000360c6ebe000000000000000000000000000000000000000038844ef19f04aecf0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000066517289880000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000cca2e51310000000000000000000000000000cecf12f47d2896c90f6e19b7376fa3b169fabd920000000000000000000000000000000000000000000000000000000000000041447858c6d8251fb8ffba546bedb410457ff77148fdf59ac8e046993936a134b028f535c5b1f760508b6e0c3c18d44927d82da0502c66688c0dc961a434a9b0071c00000000000000000000000000000000000000000000000000000000000000"), verbose: Verbosity::new(0, 0), @@ -53,15 +53,15 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall_core::decode::decode(args) + let _ = heimdall_core::decode::decode(args).await; } - benchmark("benchmark_decode_seaport_simple", 100, bench) + let _ = async_bench("benchmark_decode_seaport_simple", 100, bench); } - #[test] - fn benchmark_decode_seaport_complex() { - fn bench() { + #[tokio::test] + async fn benchmark_decode_seaport_complex() { + async fn bench() { let args = DecodeArgs { target: String::from("0xc47f00270000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b6a6265636b65722e657468000000000000000000000000000000000000000000"), verbose: Verbosity::new(0, 0), @@ -71,14 +71,14 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall_core::decode::decode(args) + let _ = heimdall_core::decode::decode(args).await; } - benchmark("benchmark_decode_seaport_complex", 100, bench) + let _ = async_bench("benchmark_decode_seaport_complex", 100, bench); } - #[test] - fn test_decode_transfer() { + #[tokio::test] + async fn test_decode_transfer() { let args = DecodeArgs { target: String::from("0xfb0f3ee100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ec9c58de0a8000000000000000000000000000d2f8a98bde7c701ae961d10d0d1fc3a751be737f000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000005008c2a3af41024e9f0bd0432df4f75828602598000000000000000000000000000000000000000000000000000000000000110600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006358934b00000000000000000000000000000000000000000000000000000000637e22710000000000000000000000000000000000000000000000000000000000000000360c6ebe000000000000000000000000000000000000000038844ef19f04aecf0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000066517289880000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000cca2e51310000000000000000000000000000cecf12f47d2896c90f6e19b7376fa3b169fabd920000000000000000000000000000000000000000000000000000000000000041447858c6d8251fb8ffba546bedb410457ff77148fdf59ac8e046993936a134b028f535c5b1f760508b6e0c3c18d44927d82da0502c66688c0dc961a434a9b0071c00000000000000000000000000000000000000000000000000000000000000"), verbose: Verbosity::new(0, 0), @@ -88,11 +88,11 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall_core::decode::decode(args); + let _ = heimdall_core::decode::decode(args).await; } - #[test] - fn test_decode_seaport_simple() { + #[tokio::test] + async fn test_decode_seaport_simple() { let args = DecodeArgs { target: String::from("0xfb0f3ee100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ec9c58de0a8000000000000000000000000000d2f8a98bde7c701ae961d10d0d1fc3a751be737f000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c000000000000000000000000005008c2a3af41024e9f0bd0432df4f75828602598000000000000000000000000000000000000000000000000000000000000110600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000006358934b00000000000000000000000000000000000000000000000000000000637e22710000000000000000000000000000000000000000000000000000000000000000360c6ebe000000000000000000000000000000000000000038844ef19f04aecf0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000066517289880000000000000000000000000000000a26b00c1f0df003000390027140000faa719000000000000000000000000000000000000000000000000000cca2e51310000000000000000000000000000cecf12f47d2896c90f6e19b7376fa3b169fabd920000000000000000000000000000000000000000000000000000000000000041447858c6d8251fb8ffba546bedb410457ff77148fdf59ac8e046993936a134b028f535c5b1f760508b6e0c3c18d44927d82da0502c66688c0dc961a434a9b0071c00000000000000000000000000000000000000000000000000000000000000"), verbose: Verbosity::new(0, 0), @@ -102,6 +102,6 @@ mod benchmark { default: true, truncate_calldata: false, }; - heimdall_core::decode::decode(args); + let _ = heimdall_core::decode::decode(args).await; } } diff --git a/core/tests/test_decompile.rs b/core/tests/test_decompile.rs index b2c6da15..1e7c9747 100644 --- a/core/tests/test_decompile.rs +++ b/core/tests/test_decompile.rs @@ -108,7 +108,6 @@ mod benchmark { include_yul: false, }; let _ = heimdall_core::decompile::decompile(args).await; - return } async_bench("benchmark_build_abi_complex", 100, bench).await; diff --git a/core/tests/test_disassemble.rs b/core/tests/test_disassemble.rs index f690c884..a089dede 100644 --- a/core/tests/test_disassemble.rs +++ b/core/tests/test_disassemble.rs @@ -4,22 +4,21 @@ mod benchmarks { use heimdall_common::{ ether::evm::ext::disassemble::{disassemble, DisassemblerArgs}, - testing::benchmarks::benchmark, + testing::benchmarks::async_bench, }; - #[test] - fn benchmark_disassemble_simple() { - fn bench() { + #[tokio::test] + async fn benchmark_disassemble_simple() { + async fn bench() { disassemble(DisassemblerArgs { target: String::from("731bf797219482a29013d804ad96d1c6f84fba4c453014608060405260043610610058576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319045a251461005d575b600080fd5b6100c56004803603810190808035600019169060200190929190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610107565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000806000806041855114151561012157600093506101f6565b6020850151925060408501519150606085015160001a9050601b8160ff16101561014c57601b810190505b601b8160ff16141580156101645750601c8160ff1614155b1561017257600093506101f6565b600186828585604051600081526020016040526040518085600019166000191681526020018460ff1660ff1681526020018360001916600019168152602001826000191660001916815260200194505050505060206040516020810390808403906000865af11580156101e9573d6000803e3d6000fd5b5050506020604051035193505b505050929150505600a165627a7a72305820aacffa0494cd3f043493eee9c720bca9d5ef505ae7230ffc3d88c49ceeb7441e0029"), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from(""), decimal_counter: true, - }); + }).await.unwrap(); } - benchmark("benchmark_disassemble_simple", 100, bench) + let _ = async_bench("benchmark_disassemble_simple", 100, bench); } } @@ -31,72 +30,76 @@ mod integration_tests { use heimdall_common::ether::evm::ext::disassemble::{disassemble, DisassemblerArgs}; - #[test] - fn test_disassemble_nominal() { + #[tokio::test] + async fn test_disassemble_nominal() { let bytecode = "366000600037611000600036600073"; let expected = String::from("000000 CALLDATASIZE \n000002 PUSH1 00\n000004 PUSH1 00\n000005 CALLDATACOPY \n000008 PUSH2 1000\n00000a PUSH1 00\n00000b CALLDATASIZE \n00000d PUSH1 00\n"); let assembly = disassemble(DisassemblerArgs { target: bytecode.to_owned(), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from(""), decimal_counter: false, - }); + }) + .await + .unwrap(); assert_eq!(expected, assembly); } - #[test] - fn test_disassemble_decimal_counter_nominal() { + #[tokio::test] + async fn test_disassemble_decimal_counter_nominal() { let bytecode = "366000600037611000600036600073"; let expected = String::from("0 CALLDATASIZE \n2 PUSH1 00\n4 PUSH1 00\n5 CALLDATACOPY \n8 PUSH2 1000\n10 PUSH1 00\n11 CALLDATASIZE \n13 PUSH1 00\n"); let assembly = disassemble(DisassemblerArgs { target: bytecode.to_owned(), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from(""), decimal_counter: true, - }); + }) + .await + .unwrap(); assert_eq!(expected, assembly); } - #[test] - fn test_disassemble_with_custom_output() { + #[tokio::test] + async fn test_disassemble_with_custom_output() { let bytecode = "366000600037611000600036600073"; let expected = String::from("0 CALLDATASIZE \n2 PUSH1 00\n4 PUSH1 00\n5 CALLDATACOPY \n8 PUSH2 1000\n10 PUSH1 00\n11 CALLDATASIZE \n13 PUSH1 00\n"); let assembly = disassemble(DisassemblerArgs { target: bytecode.to_owned(), verbose: Verbosity::new(0, 0), - output: String::from("/tmp/heimdall-rs/"), rpc_url: String::from(""), decimal_counter: true, - }); + }) + .await + .unwrap(); assert_eq!(expected, assembly); } - #[test] - fn test_disassemble_silent() { + #[tokio::test] + async fn test_disassemble_silent() { let bytecode = "366000600037611000600036600073"; let expected = String::from("0 CALLDATASIZE \n2 PUSH1 00\n4 PUSH1 00\n5 CALLDATACOPY \n8 PUSH2 1000\n10 PUSH1 00\n11 CALLDATASIZE \n13 PUSH1 00\n"); let assembly = disassemble(DisassemblerArgs { target: bytecode.to_owned(), verbose: Verbosity::new(0, 1), - output: String::from("/tmp/heimdall-rs/"), rpc_url: String::from(""), decimal_counter: true, - }); + }) + .await + .unwrap(); assert_eq!(expected, assembly); } - #[test] - fn test_disassemble_from_file() { + #[tokio::test] + async fn test_disassemble_from_file() { let bytecode = "366000600037611000600036600073"; let expected = String::from("0 CALLDATASIZE \n2 PUSH1 00\n4 PUSH1 00\n5 CALLDATACOPY \n8 PUSH2 1000\n10 PUSH1 00\n11 CALLDATASIZE \n13 PUSH1 00\n"); @@ -106,27 +109,30 @@ mod integration_tests { let assembly = disassemble(DisassemblerArgs { target: String::from("test_disassemble_from_file"), verbose: Verbosity::new(0, 0), - output: String::from("/tmp/heimdall-rs/"), rpc_url: String::from(""), decimal_counter: true, - }); + }) + .await + .unwrap(); assert_eq!(expected, assembly); + // delete the file std::fs::remove_file("test_disassemble_from_file").unwrap(); } - #[test] - fn test_disassemble_from_rpc() { + #[tokio::test] + async fn test_disassemble_from_rpc() { let expected = String::from("1 PUSH1 80\n3 PUSH1 40\n4 MSTORE \n25 PUSH20 ffffffffffffffffffffffffffffffffffffffff\n27 PUSH1 00\n28 SLOAD \n29 AND \n30 CALLDATASIZE \n32 PUSH1 00\n33 DUP1 \n34 CALLDATACOPY \n36 PUSH1 00\n37 DUP1 \n38 CALLDATASIZE \n40 PUSH1 00\n41 DUP5 \n42 GAS \n43 DELEGATECALL \n44 RETURNDATASIZE \n46 PUSH1 00\n47 DUP1 \n48 RETURNDATACOPY \n50 PUSH1 00\n51 DUP2 \n52 EQ \n53 ISZERO \n55 PUSH1 3d\n56 JUMPI \n57 RETURNDATASIZE \n59 PUSH1 00\n60 REVERT \n61 JUMPDEST \n62 RETURNDATASIZE \n64 PUSH1 00\n65 RETURN \n66 INVALID \n67 LOG1 \n74 PUSH6 627a7a723058\n75 SHA3 \n76 unknown \n107 PUSH30 648b83cfac072cbccefc2ffc62a6999d4a050ee87a721942de1da9670db8\n108 STOP \n109 unknown \n"); let assembly = disassemble(DisassemblerArgs { target: String::from("0xafc2f2d803479a2af3a72022d54cc0901a0ec0d6"), verbose: Verbosity::new(0, 0), - output: String::from("/tmp/heimdall-rs/"), rpc_url: String::from("https://eth.llamarpc.com"), decimal_counter: true, - }); + }) + .await + .unwrap(); assert_eq!(expected, assembly); } From fba561af69c9c1726754a17083f607db9e8997c8 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 14:00:14 -0400 Subject: [PATCH 10/18] =?UTF-8?q?=E2=9C=85=20tests:=20refactor=20`snapshot?= =?UTF-8?q?`=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/tests/test_decode.rs | 8 ++--- core/tests/test_disassemble.rs | 2 +- core/tests/test_snapshot.rs | 65 +++++++++++----------------------- 3 files changed, 26 insertions(+), 49 deletions(-) diff --git a/core/tests/test_decode.rs b/core/tests/test_decode.rs index bbb9b41a..b7f63fc1 100644 --- a/core/tests/test_decode.rs +++ b/core/tests/test_decode.rs @@ -20,7 +20,7 @@ mod benchmark { let _ = heimdall_core::decode::decode(args).await; } - let _ = async_bench("benchmark_decode_transfer", 100, bench); + async_bench("benchmark_decode_transfer", 100, bench).await; } #[tokio::test] @@ -38,7 +38,7 @@ mod benchmark { let _ = heimdall_core::decode::decode(args).await; } - let _ = async_bench("benchmark_decode_uniswap_simple", 100, bench); + async_bench("benchmark_decode_uniswap_simple", 100, bench).await; } #[tokio::test] @@ -56,7 +56,7 @@ mod benchmark { let _ = heimdall_core::decode::decode(args).await; } - let _ = async_bench("benchmark_decode_seaport_simple", 100, bench); + async_bench("benchmark_decode_seaport_simple", 100, bench).await; } #[tokio::test] @@ -74,7 +74,7 @@ mod benchmark { let _ = heimdall_core::decode::decode(args).await; } - let _ = async_bench("benchmark_decode_seaport_complex", 100, bench); + async_bench("benchmark_decode_seaport_complex", 100, bench).await; } #[tokio::test] diff --git a/core/tests/test_disassemble.rs b/core/tests/test_disassemble.rs index a089dede..9aad9d87 100644 --- a/core/tests/test_disassemble.rs +++ b/core/tests/test_disassemble.rs @@ -18,7 +18,7 @@ mod benchmarks { }).await.unwrap(); } - let _ = async_bench("benchmark_disassemble_simple", 100, bench); + async_bench("benchmark_disassemble_simple", 100, bench).await; } } diff --git a/core/tests/test_snapshot.rs b/core/tests/test_snapshot.rs index 37c8b7b1..1daf41b9 100644 --- a/core/tests/test_snapshot.rs +++ b/core/tests/test_snapshot.rs @@ -1,44 +1,42 @@ #[cfg(test)] mod benchmark { use clap_verbosity_flag::Verbosity; - use heimdall_common::testing::benchmarks::benchmark; + use heimdall_common::testing::benchmarks::async_bench; use heimdall_core::snapshot::SnapshotArgs; - #[test] - fn benchmark_snapshot_complex() { - fn bench() { + #[tokio::test] + async fn benchmark_snapshot_complex() { + async fn bench() { let args = SnapshotArgs { target: String::from("0xE90d8Fb7B79C8930B5C8891e61c298b412a6e81a"), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from("https://eth.llamarpc.com"), default: true, skip_resolving: true, no_tui: true, }; - heimdall_core::snapshot::snapshot(args) + let _ = heimdall_core::snapshot::snapshot(args).await.unwrap(); } - benchmark("benchmark_snapshot_complex", 100, bench) + async_bench("benchmark_snapshot_complex", 100, bench).await; } - #[test] - fn benchmark_snapshot_simple() { - fn bench() { + #[tokio::test] + async fn benchmark_snapshot_simple() { + async fn bench() { let args = SnapshotArgs { target: String::from("0x1bf797219482a29013d804ad96d1c6f84fba4c45"), verbose: Verbosity::new(0, 0), - output: String::from(""), rpc_url: String::from("https://eth.llamarpc.com"), default: true, skip_resolving: true, no_tui: true, }; - heimdall_core::snapshot::snapshot(args) + let _ = heimdall_core::snapshot::snapshot(args).await.unwrap(); } - benchmark("benchmark_snapshot_simple", 100, bench) + async_bench("benchmark_snapshot_complex", 100, bench).await; } } @@ -46,57 +44,37 @@ mod benchmark { mod integration_tests { use clap_verbosity_flag::Verbosity; use heimdall_common::io::file::delete_path; - use heimdall_core::snapshot::{SnapshotArgs, SnapshotBuilder}; - - #[test] - fn test_snapshot_library() { - SnapshotBuilder::new("0x1bf797219482a29013d804ad96d1c6f84fba4c45") - .output("./output/tests/snapshot/libtest") - .rpc("https://eth.llamarpc.com") - .default(true) - .skip_resolving(true) - .no_tui(true) - .snapshot(); - - // drop path - delete_path(&String::from("./output/tests/snapshot/libtest")); - } + use heimdall_core::snapshot::SnapshotArgs; - #[test] - fn test_snapshot_weth() { + #[tokio::test] + async fn test_snapshot_weth() { let args = SnapshotArgs { target: String::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), verbose: Verbosity::new(0, 0), - output: String::from("./output/tests/snapshot/test1"), rpc_url: String::from("https://eth.llamarpc.com"), default: true, skip_resolving: true, no_tui: true, }; - heimdall_core::snapshot::snapshot(args); - // drop path - delete_path(&String::from("./output/tests/snapshot/test1")); + let _ = heimdall_core::snapshot::snapshot(args).await.unwrap(); } - #[test] - fn test_snapshot_ctf() { + #[tokio::test] + async fn test_snapshot_ctf() { let args = SnapshotArgs { target: String::from("0x9f00c43700bc0000Ff91bE00841F8e04c0495000"), verbose: Verbosity::new(0, 0), - output: String::from("./output/tests/snapshot/test2"), rpc_url: String::from("https://eth.llamarpc.com"), default: true, skip_resolving: true, no_tui: true, }; - heimdall_core::snapshot::snapshot(args); - // drop path - delete_path(&String::from("./output/tests/snapshot/test2")); + let _ = heimdall_core::snapshot::snapshot(args).await.unwrap(); } - #[test] + #[tokio::test] /// Thorough testing for snapshot across a large number of contracts /// Runs on the top 100 contracts for 2023-06-26 /// @@ -114,7 +92,7 @@ mod integration_tests { /// - The ABI matches the solidity outline /// - There is no unreachable code (TODO) /// - There are no empty branches (TODO) - fn test_snapshot_thorough() { + async fn test_snapshot_thorough() { let contracts = [ "0xdAC17F958D2ee523a2206206994597C13D831ec7", "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD", @@ -174,13 +152,12 @@ mod integration_tests { let args = SnapshotArgs { target: String::from(contract), verbose: Verbosity::new(0, 0), - output: String::from("./output/tests/snapshot/integration"), rpc_url: String::from("https://eth.llamarpc.com"), default: true, skip_resolving: true, no_tui: true, }; - heimdall_core::snapshot::snapshot(args); + let _ = heimdall_core::snapshot::snapshot(args).await.unwrap(); } delete_path(&String::from("./output/tests/snapshot/integration")); From 3868dff0bcd81b690ab5399dfd7d82a226dc9076 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 14:05:19 -0400 Subject: [PATCH 11/18] :broom: clean: remove outdated examples --- core/examples/cfg.rs | 8 -------- core/examples/decode.rs | 8 -------- core/examples/decompile.rs | 8 -------- core/examples/snapshot.rs | 8 -------- 4 files changed, 32 deletions(-) delete mode 100644 core/examples/cfg.rs delete mode 100644 core/examples/decode.rs delete mode 100644 core/examples/decompile.rs delete mode 100644 core/examples/snapshot.rs diff --git a/core/examples/cfg.rs b/core/examples/cfg.rs deleted file mode 100644 index 07c76750..00000000 --- a/core/examples/cfg.rs +++ /dev/null @@ -1,8 +0,0 @@ -use heimdall_core::cfg::CFGBuilder; - -fn main() { - // Trace the bytecode and save the results. - CFGBuilder::new(BYTECODE).generate(); -} - -const BYTECODE: &str = "731bf797219482a29013d804ad96d1c6f84fba4c453014608060405260043610610058576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319045a251461005d575b600080fd5b6100c56004803603810190808035600019169060200190929190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610107565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000806000806041855114151561012157600093506101f6565b6020850151925060408501519150606085015160001a9050601b8160ff16101561014c57601b810190505b601b8160ff16141580156101645750601c8160ff1614155b1561017257600093506101f6565b600186828585604051600081526020016040526040518085600019166000191681526020018460ff1660ff1681526020018360001916600019168152602001826000191660001916815260200194505050505060206040516020810390808403906000865af11580156101e9573d6000803e3d6000fd5b5050506020604051035193505b505050929150505600a165627a7a72305820aacffa0494cd3f043493eee9c720bca9d5ef505ae7230ffc3d88c49ceeb7441e0029"; diff --git a/core/examples/decode.rs b/core/examples/decode.rs deleted file mode 100644 index 68e18463..00000000 --- a/core/examples/decode.rs +++ /dev/null @@ -1,8 +0,0 @@ -use heimdall_core::decode::decode_calldata; - -fn main() { - // Decode the calldata and save the results. - println!("{:#?}", decode_calldata(CALLDATA)); -} - -const CALLDATA: &str = "0xd57eafac000000000000000000000000b59419389d1fb089135a6a2c4bb32e5e8aa8b3330000000000000000000000001b84765de8b7566e4ceaf4d0fd3c5af52d3dde4f000000000000000000000000000000000000000000000f41a839dee4932bd176000000000000000000000000000000000000000000000004afd16002efcae82f0000000000000000000000001116898dda4015ed8ddefb84b6e8bc24528af2d80000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000101c599f9f0000000000000000000000000000000000000000000000000000000063eaa06d8514775c2c1663f55720b8c4291fbb33e6524316ebc6919a2d9a459811072867"; diff --git a/core/examples/decompile.rs b/core/examples/decompile.rs deleted file mode 100644 index a6adeb92..00000000 --- a/core/examples/decompile.rs +++ /dev/null @@ -1,8 +0,0 @@ -use heimdall_core::decompile::DecompileBuilder; - -fn main() { - // Decompile the bytecode and save the results. - DecompileBuilder::new(BYTECODE).decompile(); -} - -const BYTECODE: &str = "731bf797219482a29013d804ad96d1c6f84fba4c453014608060405260043610610058576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319045a251461005d575b600080fd5b6100c56004803603810190808035600019169060200190929190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610107565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000806000806041855114151561012157600093506101f6565b6020850151925060408501519150606085015160001a9050601b8160ff16101561014c57601b810190505b601b8160ff16141580156101645750601c8160ff1614155b1561017257600093506101f6565b600186828585604051600081526020016040526040518085600019166000191681526020018460ff1660ff1681526020018360001916600019168152602001826000191660001916815260200194505050505060206040516020810390808403906000865af11580156101e9573d6000803e3d6000fd5b5050506020604051035193505b505050929150505600a165627a7a72305820aacffa0494cd3f043493eee9c720bca9d5ef505ae7230ffc3d88c49ceeb7441e0029"; diff --git a/core/examples/snapshot.rs b/core/examples/snapshot.rs deleted file mode 100644 index 7a3c2a0c..00000000 --- a/core/examples/snapshot.rs +++ /dev/null @@ -1,8 +0,0 @@ -use heimdall_core::snapshot::SnapshotBuilder; - -fn main() { - // Decompile the bytecode and save the results. - SnapshotBuilder::new(BYTECODE).snapshot(); -} - -const BYTECODE: &str = "731bf797219482a29013d804ad96d1c6f84fba4c453014608060405260043610610058576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319045a251461005d575b600080fd5b6100c56004803603810190808035600019169060200190929190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610107565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000806000806041855114151561012157600093506101f6565b6020850151925060408501519150606085015160001a9050601b8160ff16101561014c57601b810190505b601b8160ff16141580156101645750601c8160ff1614155b1561017257600093506101f6565b600186828585604051600081526020016040526040518085600019166000191681526020018460ff1660ff1681526020018360001916600019168152602001826000191660001916815260200194505050505060206040516020810390808403906000865af11580156101e9573d6000803e3d6000fd5b5050506020604051035193505b505050929150505600a165627a7a72305820aacffa0494cd3f043493eee9c720bca9d5ef505ae7230ffc3d88c49ceeb7441e0029"; From a3a4add79a531964797b1ab6f85e4d7aef85f639 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 15:16:38 -0400 Subject: [PATCH 12/18] :sparkles: feat: `heimdall-cli` module --- Cargo.lock | 2 + cli/Cargo.toml | 2 + cli/src/main.rs | 132 +++++++++++++++++++++++++++++++++++---- core/src/cfg/output.rs | 2 +- core/src/dump/mod.rs | 1 - core/src/snapshot/mod.rs | 14 ++++- 6 files changed, 137 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index faea6202..12ccc972 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1635,6 +1635,8 @@ dependencies = [ "heimdall-common", "heimdall-config", "heimdall-core", + "serde", + "serde_json", "tokio", "tui", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 323a5ff1..7a0e9a35 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -19,6 +19,8 @@ heimdall-common = {path = "./../common"} heimdall-config = {path = "./../config"} tokio = {version = "1", features = ["full"]} tui = "0.19" +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" [[bin]] name = "heimdall" diff --git a/cli/src/main.rs b/cli/src/main.rs index d90b4c44..0e6fbb22 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,5 +1,5 @@ use backtrace::Backtrace; -use std::{io, panic}; +use std::{env, io, panic}; use clap::{Parser, Subcommand}; use colored::Colorize; @@ -11,17 +11,21 @@ use crossterm::{ use heimdall_cache::{cache, CacheArgs}; use heimdall_common::{ + constants::ADDRESS_REGEX, ether::evm::ext::disassemble::*, - io::logging::Logger, + io::{ + file::{write_file, write_lines_to_file}, + logging::Logger, + }, utils::version::{current_version, remote_version}, }; use heimdall_config::{config, get_config, ConfigArgs}; use heimdall_core::{ - cfg::{cfg, CFGArgs}, + cfg::{cfg, output::write_cfg_to_file, CFGArgs}, decode::{decode, DecodeArgs}, - decompile::{decompile, DecompilerArgs}, + decompile::{decompile, out::abi::ABIStructure, DecompilerArgs}, dump::{dump, DumpArgs}, - snapshot::{snapshot, SnapshotArgs}, + snapshot::{snapshot, util::csv::generate_and_write_contract_csv, SnapshotArgs}, }; use tui::{backend::CrosstermBackend, Terminal}; @@ -93,6 +97,11 @@ async fn main() -> Result<(), Box> { })); let configuration = get_config(); + + // get the current working directory + let mut output_path = env::current_dir()?.into_os_string().into_string().unwrap(); + output_path.push_str("/output"); + match args.sub { Subcommands::Disassemble(mut cmd) => { // if the user has not specified a rpc url, use the default @@ -111,7 +120,15 @@ async fn main() -> Result<(), Box> { ); } - disassemble(cmd); + let assembly = disassemble(cmd.clone()).await?; + + // write to file + if ADDRESS_REGEX.is_match(&cmd.target).unwrap() { + output_path.push_str(&format!("/{}/disassembled.asm", &cmd.target)); + } else { + output_path.push_str("/local/disassembled.asm"); + } + write_file(&output_path, &assembly); } Subcommands::Decompile(mut cmd) => { @@ -131,7 +148,54 @@ async fn main() -> Result<(), Box> { ); } - decompile(cmd); + let result = decompile(cmd.clone()).await?; + + // write to file + let abi_output_path; + let solidity_output_path; + let yul_output_path; + if ADDRESS_REGEX.is_match(&cmd.target).unwrap() { + abi_output_path = format!("{}/{}/abi.json", &output_path, &cmd.target); + solidity_output_path = format!("{}/{}/decompiled.sol", &output_path, &cmd.target); + yul_output_path = format!("{}/{}/decompiled.yul", &output_path, &cmd.target); + } else { + abi_output_path = format!("{}/local/abi.json", &output_path); + solidity_output_path = format!("{}/local/decompiled.sol", &output_path); + yul_output_path = format!("{}/local/decompiled.yul", &output_path); + } + + if let Some(abi) = result.abi { + // write the ABI to a file + write_file( + &abi_output_path, + &format!( + "[{}]", + abi.iter() + .map(|x| { + match x { + ABIStructure::Function(x) => { + serde_json::to_string_pretty(x).unwrap() + } + ABIStructure::Error(x) => { + serde_json::to_string_pretty(x).unwrap() + } + ABIStructure::Event(x) => { + serde_json::to_string_pretty(x).unwrap() + } + } + }) + .collect::>() + .join(",\n") + ), + ); + } + if let Some(source) = result.source { + if cmd.include_solidity { + write_file(&solidity_output_path, &source); + } else { + write_file(&yul_output_path, &source); + } + } } Subcommands::Decode(mut cmd) => { @@ -156,7 +220,7 @@ async fn main() -> Result<(), Box> { ); } - decode(cmd); + decode(cmd).await; } Subcommands::CFG(mut cmd) => { @@ -176,7 +240,16 @@ async fn main() -> Result<(), Box> { ); } - cfg(cmd); + let cfg = cfg(cmd.clone()).await?; + + // write to file + if ADDRESS_REGEX.is_match(&cmd.target).unwrap() { + output_path.push_str(&format!("/{}", &cmd.target)); + } else { + output_path.push_str("/local"); + } + + write_cfg_to_file(&cfg, &cmd, output_path) } Subcommands::Dump(mut cmd) => { @@ -201,7 +274,29 @@ async fn main() -> Result<(), Box> { ); } - dump(cmd); + let result = dump(cmd.clone()).await?; + let mut lines = Vec::new(); + + // write to file + if ADDRESS_REGEX.is_match(&cmd.target).unwrap() { + output_path.push_str(&format!("/{}/dump.csv", &cmd.target)); + } else { + output_path.push_str("/local/dump.csv"); + } + + // add header + lines.push(String::from("last_modified,alias,slot,decoded_type,value")); + + // add rows + for row in result { + lines.push(format!( + "{},{},{},{},{}", + row.last_modified, row.alias, row.slot, row.decoded_type, row.value + )); + } + + // write to file + write_lines_to_file(&output_path, lines); } Subcommands::Snapshot(mut cmd) => { @@ -221,10 +316,23 @@ async fn main() -> Result<(), Box> { ); } - snapshot(cmd); + // write to file + if ADDRESS_REGEX.is_match(&cmd.target).unwrap() { + output_path.push_str(&format!("/{}/snapshot.csv", &cmd.target)); + } else { + output_path.push_str("/local/snapshot.csv"); + } + + let snapshot = snapshot(cmd).await?; + generate_and_write_contract_csv( + &snapshot.snapshots, + &snapshot.resolved_errors, + &snapshot.resolved_events, + &output_path, + ) } Subcommands::Config(cmd) => { - config(cmd); + _ = config(cmd); } Subcommands::Cache(cmd) => { diff --git a/core/src/cfg/output.rs b/core/src/cfg/output.rs index d7687cc3..e07f4050 100644 --- a/core/src/cfg/output.rs +++ b/core/src/cfg/output.rs @@ -6,7 +6,7 @@ use petgraph::{dot::Dot, graph::Graph}; use super::CFGArgs; -pub fn build_output(contract_cfg: &Graph, args: &CFGArgs, output_dir: String) { +pub fn write_cfg_to_file(contract_cfg: &Graph, args: &CFGArgs, output_dir: String) { // get a new logger let level = std::env::var("RUST_LOG").unwrap_or_else(|_| "INFO".into()); let (logger, _) = Logger::new(&level); diff --git a/core/src/dump/mod.rs b/core/src/dump/mod.rs index e36aad8c..3ac9f855 100644 --- a/core/src/dump/mod.rs +++ b/core/src/dump/mod.rs @@ -199,7 +199,6 @@ pub async fn dump(args: DumpArgs) -> Result, Box Result, Box> { +pub struct SnapshotResult { + pub snapshots: Vec, + pub resolved_errors: HashMap, + pub resolved_events: HashMap, +} + +pub async fn snapshot(args: SnapshotArgs) -> Result> { use std::time::Instant; let now = Instant::now(); @@ -512,5 +518,9 @@ pub async fn snapshot(args: SnapshotArgs) -> Result, Box Date: Thu, 28 Sep 2023 15:17:11 -0400 Subject: [PATCH 13/18] :broom: clean: make clippy happy --- cli/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 0e6fbb22..5583757c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -332,7 +332,7 @@ async fn main() -> Result<(), Box> { ) } Subcommands::Config(cmd) => { - _ = config(cmd); + config(cmd); } Subcommands::Cache(cmd) => { From 3cbd5acc2e54c10610f99b50444ac1146504a420 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 15:24:58 -0400 Subject: [PATCH 14/18] :wrench: fix: separate decode tests & benchmarks --- core/tests/test_decode.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/tests/test_decode.rs b/core/tests/test_decode.rs index b7f63fc1..f0c69318 100644 --- a/core/tests/test_decode.rs +++ b/core/tests/test_decode.rs @@ -76,7 +76,10 @@ mod benchmark { async_bench("benchmark_decode_seaport_complex", 100, bench).await; } +} +#[cfg(test)] +mod tests { #[tokio::test] async fn test_decode_transfer() { let args = DecodeArgs { From cf09ea1634259d7e34c90168cbb32758327c901c Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 15:26:23 -0400 Subject: [PATCH 15/18] :wrench: fix: separate decode tests & benchmarks --- core/tests/test_decode.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/tests/test_decode.rs b/core/tests/test_decode.rs index f0c69318..d1a00ec2 100644 --- a/core/tests/test_decode.rs +++ b/core/tests/test_decode.rs @@ -80,6 +80,9 @@ mod benchmark { #[cfg(test)] mod tests { + use clap_verbosity_flag::Verbosity; + use heimdall_core::decode::DecodeArgs; + #[tokio::test] async fn test_decode_transfer() { let args = DecodeArgs { From 542c105ded47cc04229974bda0b6952c09d7698a Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 15:28:07 -0400 Subject: [PATCH 16/18] =?UTF-8?q?=F0=9F=92=AB=20meta:=20simplify=20unit=20?= =?UTF-8?q?test=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/tests.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5f07c049..7ebc675e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,4 @@ jobs: - name: Run Tests run: | - cargo test --package heimdall-cli -- test_ --nocapture - cargo test --package heimdall-core -- test_ --nocapture - cargo test --package heimdall-config -- test_ --nocapture - cargo test --package heimdall-common -- test_ --nocapture + cargo test --release -- test_ --nocapture From 7b3193dfa79416e7e6877d665078e61b139779ab Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 15:31:57 -0400 Subject: [PATCH 17/18] =?UTF-8?q?=F0=9F=92=AB=20meta:=20remove=20unnecessa?= =?UTF-8?q?ry=20CI=20step?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/tests.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7ebc675e..519f3357 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,9 +11,6 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Compile - run: cargo build --verbose - - name: Run Tests run: | cargo test --release -- test_ --nocapture From 8f94952c9b357739976cad709e548b190845dc22 Mon Sep 17 00:00:00 2001 From: Jon-Becker Date: Thu, 28 Sep 2023 15:39:55 -0400 Subject: [PATCH 18/18] =?UTF-8?q?=F0=9F=91=B7=20build:=20bump=20version=20?= =?UTF-8?q?to=200.6.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- cache/Cargo.toml | 2 +- cli/Cargo.toml | 2 +- common/Cargo.toml | 2 +- config/Cargo.toml | 2 +- core/Cargo.toml | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12ccc972..7f78e2cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1615,7 +1615,7 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heimdall-cache" -version = "0.5.4" +version = "0.6.0" dependencies = [ "bincode", "clap", @@ -1624,7 +1624,7 @@ dependencies = [ [[package]] name = "heimdall-cli" -version = "0.5.4" +version = "0.6.0" dependencies = [ "backtrace", "clap", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "heimdall-common" -version = "0.5.4" +version = "0.6.0" dependencies = [ "async-openai", "clap", @@ -1665,7 +1665,7 @@ dependencies = [ [[package]] name = "heimdall-config" -version = "0.5.4" +version = "0.6.0" dependencies = [ "clap", "clap-verbosity-flag", @@ -1676,7 +1676,7 @@ dependencies = [ [[package]] name = "heimdall-core" -version = "0.5.4" +version = "0.6.0" dependencies = [ "backtrace", "clap", diff --git a/Cargo.toml b/Cargo.toml index e8ff1db5..f7f8b76d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] members = ["common", "config", "core", "cli"] -version = "0.5.4" +version = "0.6.0" diff --git a/cache/Cargo.toml b/cache/Cargo.toml index 1ca3900b..2de48317 100644 --- a/cache/Cargo.toml +++ b/cache/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "heimdall-cache" -version = "0.5.4" +version = "0.6.0" edition = "2021" license = "MIT" readme = "README.md" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 7a0e9a35..ff25b5c2 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "heimdall-cli" -version = "0.5.4" +version = "0.6.0" edition = "2021" license = "MIT" readme = "README.md" diff --git a/common/Cargo.toml b/common/Cargo.toml index 4d8ca53c..4628808e 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -5,7 +5,7 @@ keywords = ["ethereum", "web3", "decompiler", "evm", "crypto"] license = "MIT" name = "heimdall-common" readme = "README.md" -version = "0.5.4" +version = "0.6.0" [dependencies] async-openai = "0.10.0" diff --git a/config/Cargo.toml b/config/Cargo.toml index b4fe7821..5410cbb0 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "heimdall-config" -version = "0.5.4" +version = "0.6.0" edition = "2021" license = "MIT" readme = "README.md" diff --git a/core/Cargo.toml b/core/Cargo.toml index 0919c68c..bdcf41a3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -5,7 +5,7 @@ keywords = ["ethereum", "web3", "decompiler", "evm", "crypto"] license = "MIT" name = "heimdall-core" readme = "README.md" -version = "0.5.4" +version = "0.6.0" [dependencies] backtrace = "0.3"