Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(decode): -o/--output support #536

Merged
merged 1 commit into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}
}
Loading