Skip to content

Commit

Permalink
feat(decode): -o/--output support (#536)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon-Becker authored Dec 16, 2024
1 parent 7ee0f23 commit 9c22599
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ false/*
*.sh

largest1k

bun.lockb
node_modules
1 change: 1 addition & 0 deletions Cargo.lock

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

13 changes: 11 additions & 2 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,18 @@ async fn main() -> Result<()> {
}

let result =
decode(cmd).await.map_err(|e| eyre!("failed to decode calldata: {}", e))?;
decode(cmd.clone()).await.map_err(|e| eyre!("failed to decode calldata: {}", e))?;

result.display()
if cmd.output == "print" {
result.display()
} else {
let output_path =
build_output_path(&cmd.output, &cmd.target, &cmd.rpc_url, "decoded.json")
.await
.map_err(|e| eyre!("failed to build output path: {}", e))?;
write_file(&output_path, &result.decoded.to_json()?)
.map_err(|e| eyre!("failed to write decoded output: {}", e))?;
}
}

Subcommands::Cfg(mut cmd) => {
Expand Down
27 changes: 27 additions & 0 deletions crates/common/src/ether/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ use heimdall_cache::{store_cache, with_cache};
use serde::{Deserialize, Serialize};
use tracing::{debug, trace};

use super::types::DynSolValueExt;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ResolvedFunction {
pub name: String,
Expand All @@ -30,6 +32,31 @@ impl ResolvedFunction {
pub fn inputs(&self) -> Vec<DynSolType> {
parse_function_parameters(&self.signature).expect("invalid signature")
}

/// A helper function to convert the struct into a JSON string.
/// We use this because `decoded_inputs` cannot be serialized by serde.
pub fn to_json(&self) -> Result<String> {
Ok(format!(
r#"{{
"name": "{}",
"signature": "{}",
"inputs": {},
"decoded_inputs": [{}]
}}"#,
&self.name,
&self.signature,
serde_json::to_string(&self.inputs)?,
if let Some(decoded_inputs) = &self.decoded_inputs {
decoded_inputs
.iter()
.map(|input| input.serialize().to_string())
.collect::<Vec<String>>()
.join(", ")
} else {
"".to_string()
}
))
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
Expand Down
3 changes: 3 additions & 0 deletions crates/core/tests/test_decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod integration_tests {
truncate_calldata: false,
skip_resolving: false,
raw: false,
output: String::from("print"),
};
let _ = heimdall_decoder::decode(args).await;
}
Expand All @@ -33,6 +34,8 @@ mod integration_tests {
truncate_calldata: false,
skip_resolving: false,
raw: false,
output: String::from("print"),

};
let _ = heimdall_decoder::decode(args).await;
}
Expand Down
7 changes: 6 additions & 1 deletion crates/decode/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ eyre = "0.6.12"
heimdall-vm.workspace = true
alloy-dyn-abi = "0.8.3"
alloy-json-abi = "0.8.3"
alloy = { version = "0.3.3", features = ["full", "rpc-types-debug", "rpc-types-trace"] }
alloy = { version = "0.3.3", features = [
"full",
"rpc-types-debug",
"rpc-types-trace",
] }
serde_json = "1.0"
hashbrown = "0.14.5"
serde = "1.0"
9 changes: 7 additions & 2 deletions crates/decode/src/interfaces/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct DecodeArgs {
pub rpc_url: String,

/// Your OpenAI API key, used for explaining calldata.
#[clap(long, short, default_value = "", hide_default_value = true)]
#[clap(long, default_value = "", hide_default_value = true)]
pub openai_api_key: String,

/// Whether to explain the decoded calldata using OpenAI.
Expand All @@ -46,12 +46,16 @@ pub struct DecodeArgs {

/// Whether to treat the target as a raw calldata string. Useful if the target is exactly 32
/// bytes.
#[clap(long, short)]
#[clap(long)]
pub raw: bool,

/// Path to an optional ABI file to use for resolving errors, functions, and events.
#[clap(long, short, default_value = None, hide_default_value = true)]
pub abi: Option<String>,

/// The output directory to write the output to or 'print' to print to the console
#[clap(long = "output", short = 'o', default_value = "print", hide_default_value = true)]
pub output: String,
}

impl DecodeArgs {
Expand All @@ -73,6 +77,7 @@ impl DecodeArgsBuilder {
skip_resolving: Some(false),
raw: Some(false),
abi: Some(None),
output: Some(String::from("print")),
}
}
}
9 changes: 9 additions & 0 deletions examples/typescript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Example: Invoking Heimdall via TypeScript

This TypeScript script demonstrates how to use the `heimdall` CLI tool to decode calldata via TypeScript. It provides a simple class structure to define arguments and manage the decode process, with support for customizing various decode options.

_Note: This is just an example for the decode module, but a similar approach will work for all heimdall modules._

## Overview

The script utilizes the `heimdall decode` command to decode a target. For ease of use, the script abstracts the command-line interface of `heimdall` into a TS class, allowing users to easily call the decode process in their TS projects.
103 changes: 103 additions & 0 deletions examples/typescript/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

interface DecodeArgsOptions {
target: string;
rpc_url?: string;
default?: boolean;
skip_resolving?: boolean;
raw?: boolean;
}

class DecodeArgs {
public target: string;
public rpc_url: string;
public default: boolean;
public skip_resolving: boolean;
public raw: boolean;

constructor(
target: string,
rpc_url: string = "",
useDefault: boolean = false,
skip_resolving: boolean = false,
raw: boolean = false
) {
this.target = target;
this.rpc_url = rpc_url;
this.default = useDefault;
this.skip_resolving = skip_resolving;
this.raw = raw;
}
}

class Decoder {
private args: DecodeArgs;

constructor(args: DecodeArgs) {
this.args = args;
}

public decode(): any | null {
try {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'decoder-'));

const command = ['decode', this.args.target, ];

if (this.args.rpc_url) {
command.push('--rpc-url', this.args.rpc_url);
}
if (this.args.default) {
command.push('--default');
}
if (this.args.skip_resolving) {
command.push('--skip-resolving');
}
if (this.args.raw) {
command.push('--raw');
}

// Execute heimdall command
execSync(`heimdall ${command.join(' ')}`, { stdio: 'inherit' });

// Here you would read and parse the output from `tempDir`
// For now, we return null since the original code doesn't show the parsing step.
return null;
} catch (e) {
console.error("Error: ", e);
return null;
}
}
}

function isHeimdallInstalled(): boolean {
try {
execSync('which heimdall', { stdio: 'pipe' });
return true;
} catch {
return false;
}
}

function main() {
if (!isHeimdallInstalled()) {
console.log("heimdall does not seem to be installed on your system.");
console.log("please install heimdall before running this script.");
return;
}

const args = new DecodeArgs(
"0x000000000000000000000000008dfede2ef0e61578c3bba84a7ed4b9d25795c30000000000000000000000000000000000000001431e0fae6d7217caa00000000000000000000000000000000000000000000000000000000000000000002710fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc7c0000000000000000000000000000000000000000000000a6af776004abf4e612ad000000000000000000000000000000000000000000000000000000012a05f20000000000000000000000000000000000000000000000000000000000000111700000000000000000000000001c5f545f5b46f76e440fa02dabf88fdc0b10851a00000000000000000000000000000000000000000000000000000002540be400",
"",
false,
false,
true
);

const decoded = new Decoder(args).decode();
console.log("Decoded Result:", decoded);
}

main();
16 changes: 16 additions & 0 deletions examples/typescript/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "heimdall-ts",
"version": "1.0.0",
"main": "dist/index.js",
"type": "module",
"scripts": {
"decode": "tsx index.ts"
},
"dependencies": {},
"devDependencies": {
"@types/node": "^20.0.0",
"ts-node": "^10.9.2",
"tsx": "^4.19.2",
"typescript": "^5.5.4"
}
}

0 comments on commit 9c22599

Please sign in to comment.