Skip to content

Commit

Permalink
feat: inspect transaction inspection module (#221)
Browse files Browse the repository at this point in the history
* feat(inspect): init inspect

* feat(inspect): init transaction tracing (and decoding)

* feat(inspect): tracing, `Parameterize` trait

* feat(inspect): implement `DecodedTransactionTrace`

* feat(inspect): log decoding, joining to trace at correct addresses

* feat(inspect): impl `TryFrom<Log> for DecodedLog`

* feat(resources): add trace display

* feat(inspect): finalize trace builder with aliases

* fix(doctests): typo

* feat(inspect): storage diff in trace

* fix(hex): `U256::to_lower_hex()` padding fix

* chore(inspect): add inspect example

* chore(inspect): add tests

* chore(inspect): add ex to workspace

* chore: fix typo
  • Loading branch information
Jon-Becker committed Dec 11, 2023
1 parent c135098 commit 1c1e653
Show file tree
Hide file tree
Showing 31 changed files with 1,841 additions and 157 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions cache/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ keywords = ["ethereum", "web3", "decompiler", "evm", "crypto"]
clap = { version = "3.1.18", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
bincode = "1.3.3"
serde_json = "1.0.108"
20 changes: 12 additions & 8 deletions cache/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,9 @@ where
None => return None,
};

let binary_vec = decode_hex(&binary_string);
let binary_vec = decode_hex(&binary_string).ok()?;

if binary_vec.is_err() {
return None;
}

let cache: Cache<T> = match bincode::deserialize::<Cache<T>>(&binary_vec.unwrap()) {
let cache: Cache<T> = match bincode::deserialize::<Cache<T>>(&binary_vec) {
Ok(c) => {
// check if the cache has expired, if so, delete it and return None
if c.expiry <
Expand Down Expand Up @@ -233,7 +229,11 @@ where
/// store_cache("store_cache_key2", "value", Some(60 * 60 * 24));
/// ```
#[allow(deprecated)]
pub fn store_cache<T>(key: &str, value: T, expiry: Option<u64>)
pub fn store_cache<T>(
key: &str,
value: T,
expiry: Option<u64>,
) -> Result<(), Box<dyn std::error::Error>>
where
T: Serialize, {
let home = home_dir().unwrap();
Expand All @@ -247,9 +247,12 @@ where
);

let cache = Cache { value, expiry };
let encoded: Vec<u8> = bincode::serialize(&cache).unwrap();
let encoded: Vec<u8> = bincode::serialize(&cache)
.map_err(|e| format!("Failed to serialize cache object: {:?}", e))?;
let binary_string = encode_hex(encoded);
write_file(cache_file.to_str().unwrap(), &binary_string);

Ok(())
}

/// Cache subcommand handler
Expand Down Expand Up @@ -289,6 +292,7 @@ pub fn cache(args: CacheArgs) -> Result<(), Box<dyn std::error::Error>> {
}

#[allow(deprecated)]
#[allow(unused_must_use)]
#[cfg(test)]
mod tests {
use crate::{delete_cache, exists, keys, read_cache, store_cache};
Expand Down
56 changes: 54 additions & 2 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use heimdall_core::{
decompile::{decompile, out::abi::ABIStructure, DecompilerArgs},
disassemble::{disassemble, DisassemblerArgs},
dump::{dump, DumpArgs},
inspect::{inspect, InspectArgs},
snapshot::{snapshot, util::csv::generate_csv, SnapshotArgs},
};
use tui::{backend::CrosstermBackend, Terminal};
Expand Down Expand Up @@ -65,9 +66,16 @@ pub enum Subcommands {

#[clap(name = "dump", about = "Dump the value of all storage slots accessed by a contract")]
Dump(DumpArgs),

#[clap(
name = "inspect",
about = "Detailed inspection of Ethereum transactions, including calldata & trace decoding, log visualization, and more"
)]
Inspect(InspectArgs),

#[clap(
name = "snapshot",
about = "Infer functiogn information from bytecode, including access control, gas
about = "Infer function information from bytecode, including access control, gas
consumption, storage accesses, event emissions, and more"
)]
Snapshot(SnapshotArgs),
Expand Down Expand Up @@ -225,7 +233,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
cmd.openai_api_key = configuration.openai_api_key;
}

// set cmd.verbose to 6
// set cmd.verbose to 5
cmd.verbose = clap_verbosity_flag::Verbosity::new(5, 0);

let _ = decode(cmd).await;
Expand Down Expand Up @@ -330,6 +338,50 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}

Subcommands::Inspect(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.is_none() {
cmd.transpose_api_key = Some(configuration.transpose_api_key);
}

// if the user has passed an output filename, override the default filename
let mut filename = "decoded_trace.json".to_string();
let given_name = cmd.name.as_str();

if !given_name.is_empty() {
filename = format!("{}-{}", given_name, filename);
}

// set cmd.verbose to 5
cmd.verbose = clap_verbosity_flag::Verbosity::new(5, 0);

let inspect_result = inspect(cmd.clone()).await?;

if cmd.output == "print" {
let mut output_str = String::new();

if let Some(decoded_trace) = inspect_result.decoded_trace {
output_str.push_str(&format!(
"Decoded Trace:\n\n{}\n",
serde_json::to_string_pretty(&decoded_trace).unwrap()
));
}

print_with_less(&output_str).await?;
} else if let Some(decoded_trace) = inspect_result.decoded_trace {
// write decoded trace with serde
let output_path =
build_output_path(&cmd.output, &cmd.target, &cmd.rpc_url, &filename).await?;

write_file(&output_path, &serde_json::to_string_pretty(&decoded_trace).unwrap());
}
}

Subcommands::Config(cmd) => {
config(cmd);
}
Expand Down
7 changes: 5 additions & 2 deletions cli/src/output.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::{env, io::Write};

use heimdall_common::{constants::ADDRESS_REGEX, ether::rpc};
use heimdall_common::{
constants::{ADDRESS_REGEX, TRANSACTION_HASH_REGEX},
ether::rpc,
};

/// build a standardized output path for the given parameters. follows the following cases:
/// - if `output` is `print`, return `None`
Expand All @@ -19,7 +22,7 @@ pub async fn build_output_path(
// get the current working directory
let cwd = env::current_dir()?.into_os_string().into_string().unwrap();

if ADDRESS_REGEX.is_match(target)? {
if ADDRESS_REGEX.is_match(target)? || TRANSACTION_HASH_REGEX.is_match(target)? {
let chain_id = rpc::chain_id(rpc_url).await?;
return Ok(format!("{}/output/{}/{}/{}", cwd, chain_id, target, filename));
} else {
Expand Down

0 comments on commit 1c1e653

Please sign in to comment.