Skip to content

Commit 9c22599

Browse files
authored
feat(decode): -o/--output support (#536)
1 parent 7ee0f23 commit 9c22599

File tree

10 files changed

+186
-5
lines changed

10 files changed

+186
-5
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ false/*
3636
*.sh
3737

3838
largest1k
39+
40+
bun.lockb
41+
node_modules

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/src/main.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,18 @@ async fn main() -> Result<()> {
160160
}
161161

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

165-
result.display()
165+
if cmd.output == "print" {
166+
result.display()
167+
} else {
168+
let output_path =
169+
build_output_path(&cmd.output, &cmd.target, &cmd.rpc_url, "decoded.json")
170+
.await
171+
.map_err(|e| eyre!("failed to build output path: {}", e))?;
172+
write_file(&output_path, &result.decoded.to_json()?)
173+
.map_err(|e| eyre!("failed to write decoded output: {}", e))?;
174+
}
166175
}
167176

168177
Subcommands::Cfg(mut cmd) => {

crates/common/src/ether/signatures.rs

+27
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ use heimdall_cache::{store_cache, with_cache};
1717
use serde::{Deserialize, Serialize};
1818
use tracing::{debug, trace};
1919

20+
use super::types::DynSolValueExt;
21+
2022
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2123
pub struct ResolvedFunction {
2224
pub name: String,
@@ -30,6 +32,31 @@ impl ResolvedFunction {
3032
pub fn inputs(&self) -> Vec<DynSolType> {
3133
parse_function_parameters(&self.signature).expect("invalid signature")
3234
}
35+
36+
/// A helper function to convert the struct into a JSON string.
37+
/// We use this because `decoded_inputs` cannot be serialized by serde.
38+
pub fn to_json(&self) -> Result<String> {
39+
Ok(format!(
40+
r#"{{
41+
"name": "{}",
42+
"signature": "{}",
43+
"inputs": {},
44+
"decoded_inputs": [{}]
45+
}}"#,
46+
&self.name,
47+
&self.signature,
48+
serde_json::to_string(&self.inputs)?,
49+
if let Some(decoded_inputs) = &self.decoded_inputs {
50+
decoded_inputs
51+
.iter()
52+
.map(|input| input.serialize().to_string())
53+
.collect::<Vec<String>>()
54+
.join(", ")
55+
} else {
56+
"".to_string()
57+
}
58+
))
59+
}
3360
}
3461

3562
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]

crates/core/tests/test_decode.rs

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ mod integration_tests {
1616
truncate_calldata: false,
1717
skip_resolving: false,
1818
raw: false,
19+
output: String::from("print"),
1920
};
2021
let _ = heimdall_decoder::decode(args).await;
2122
}
@@ -33,6 +34,8 @@ mod integration_tests {
3334
truncate_calldata: false,
3435
skip_resolving: false,
3536
raw: false,
37+
output: String::from("print"),
38+
3639
};
3740
let _ = heimdall_decoder::decode(args).await;
3841
}

crates/decode/Cargo.toml

+6-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ eyre = "0.6.12"
2525
heimdall-vm.workspace = true
2626
alloy-dyn-abi = "0.8.3"
2727
alloy-json-abi = "0.8.3"
28-
alloy = { version = "0.3.3", features = ["full", "rpc-types-debug", "rpc-types-trace"] }
28+
alloy = { version = "0.3.3", features = [
29+
"full",
30+
"rpc-types-debug",
31+
"rpc-types-trace",
32+
] }
2933
serde_json = "1.0"
3034
hashbrown = "0.14.5"
35+
serde = "1.0"

crates/decode/src/interfaces/args.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct DecodeArgs {
2121
pub rpc_url: String,
2222

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

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

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

5252
/// Path to an optional ABI file to use for resolving errors, functions, and events.
5353
#[clap(long, short, default_value = None, hide_default_value = true)]
5454
pub abi: Option<String>,
55+
56+
/// The output directory to write the output to or 'print' to print to the console
57+
#[clap(long = "output", short = 'o', default_value = "print", hide_default_value = true)]
58+
pub output: String,
5559
}
5660

5761
impl DecodeArgs {
@@ -73,6 +77,7 @@ impl DecodeArgsBuilder {
7377
skip_resolving: Some(false),
7478
raw: Some(false),
7579
abi: Some(None),
80+
output: Some(String::from("print")),
7681
}
7782
}
7883
}

examples/typescript/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Example: Invoking Heimdall via TypeScript
2+
3+
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.
4+
5+
_Note: This is just an example for the decode module, but a similar approach will work for all heimdall modules._
6+
7+
## Overview
8+
9+
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.

examples/typescript/index.ts

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { execSync } from 'child_process';
2+
import * as fs from 'fs';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
6+
interface DecodeArgsOptions {
7+
target: string;
8+
rpc_url?: string;
9+
default?: boolean;
10+
skip_resolving?: boolean;
11+
raw?: boolean;
12+
}
13+
14+
class DecodeArgs {
15+
public target: string;
16+
public rpc_url: string;
17+
public default: boolean;
18+
public skip_resolving: boolean;
19+
public raw: boolean;
20+
21+
constructor(
22+
target: string,
23+
rpc_url: string = "",
24+
useDefault: boolean = false,
25+
skip_resolving: boolean = false,
26+
raw: boolean = false
27+
) {
28+
this.target = target;
29+
this.rpc_url = rpc_url;
30+
this.default = useDefault;
31+
this.skip_resolving = skip_resolving;
32+
this.raw = raw;
33+
}
34+
}
35+
36+
class Decoder {
37+
private args: DecodeArgs;
38+
39+
constructor(args: DecodeArgs) {
40+
this.args = args;
41+
}
42+
43+
public decode(): any | null {
44+
try {
45+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'decoder-'));
46+
47+
const command = ['decode', this.args.target, ];
48+
49+
if (this.args.rpc_url) {
50+
command.push('--rpc-url', this.args.rpc_url);
51+
}
52+
if (this.args.default) {
53+
command.push('--default');
54+
}
55+
if (this.args.skip_resolving) {
56+
command.push('--skip-resolving');
57+
}
58+
if (this.args.raw) {
59+
command.push('--raw');
60+
}
61+
62+
// Execute heimdall command
63+
execSync(`heimdall ${command.join(' ')}`, { stdio: 'inherit' });
64+
65+
// Here you would read and parse the output from `tempDir`
66+
// For now, we return null since the original code doesn't show the parsing step.
67+
return null;
68+
} catch (e) {
69+
console.error("Error: ", e);
70+
return null;
71+
}
72+
}
73+
}
74+
75+
function isHeimdallInstalled(): boolean {
76+
try {
77+
execSync('which heimdall', { stdio: 'pipe' });
78+
return true;
79+
} catch {
80+
return false;
81+
}
82+
}
83+
84+
function main() {
85+
if (!isHeimdallInstalled()) {
86+
console.log("heimdall does not seem to be installed on your system.");
87+
console.log("please install heimdall before running this script.");
88+
return;
89+
}
90+
91+
const args = new DecodeArgs(
92+
"0x000000000000000000000000008dfede2ef0e61578c3bba84a7ed4b9d25795c30000000000000000000000000000000000000001431e0fae6d7217caa00000000000000000000000000000000000000000000000000000000000000000002710fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc7c0000000000000000000000000000000000000000000000a6af776004abf4e612ad000000000000000000000000000000000000000000000000000000012a05f20000000000000000000000000000000000000000000000000000000000000111700000000000000000000000001c5f545f5b46f76e440fa02dabf88fdc0b10851a00000000000000000000000000000000000000000000000000000002540be400",
93+
"",
94+
false,
95+
false,
96+
true
97+
);
98+
99+
const decoded = new Decoder(args).decode();
100+
console.log("Decoded Result:", decoded);
101+
}
102+
103+
main();

examples/typescript/package.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "heimdall-ts",
3+
"version": "1.0.0",
4+
"main": "dist/index.js",
5+
"type": "module",
6+
"scripts": {
7+
"decode": "tsx index.ts"
8+
},
9+
"dependencies": {},
10+
"devDependencies": {
11+
"@types/node": "^20.0.0",
12+
"ts-node": "^10.9.2",
13+
"tsx": "^4.19.2",
14+
"typescript": "^5.5.4"
15+
}
16+
}

0 commit comments

Comments
 (0)