use alloy_primitives::hex;
use serde::Deserialize;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
pub fn log(s: &str);
}
fn decode_hex_code(input: &str) -> Result<Vec<u8>, JsError> {
hex::decode(input).map_err(|e| JsError::new(&format!("Failed to decode code hex input: {e}")))
}
#[wasm_bindgen(typescript_custom_section)]
const DOC_CONTRACT: &'static str = r#"
/**
* Contains the analysis results of a contract
* @property functions - Array of functions found in the contract. Not present if no functions were extracted.
* @property storage - Array of storage records found in the contract. Not present if storage layout was not extracted.
* @property disassembled - Array of bytecode instructions, where each element is a tuple of [offset: number, instruction: string]
* @property basicBlocks - Array of basic blocks found in the contract. Not present if basic blocks were not analyzed.
* @property controlFlowGraph - Control flow graph representation. Not present if CFG was not generated.
* @see ContractFunction
* @see StorageRecord
*/
export type Contract = {
functions?: ContractFunction[],
storage?: StorageRecord[],
disassembled?: [number, string][],
basicBlocks?: [number, number][],
controlFlowGraph?: ControlFlowGraph,
};
"#;
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_contract() {}
#[wasm_bindgen(typescript_custom_section)]
const DOC_FUNCTION: &'static str = r#"
/**
* Represents a function found in the contract bytecode
* @property selector - Function selector as a 4-byte hex string without '0x' prefix (e.g., 'aabbccdd').
* @property bytecodeOffset - Starting byte offset within the EVM bytecode for the function body.
* @property arguments - Function argument types in canonical format (e.g., 'uint256,address[]'). Not present if arguments were not extracted
* @property stateMutability - Function's state mutability ("pure", "view", "payable", or "nonpayable"). Not present if state mutability were not extracted
*/
export type ContractFunction = {
selector: string,
bytecodeOffset: number,
arguments?: string,
stateMutability?: string,
};
"#;
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_function() {}
#[wasm_bindgen(typescript_custom_section)]
const DOC_STORAGE: &'static str = r#"
/**
* Represents a storage record found in the contract
* @property slot - Storage slot number as a hex string (e.g., '0', '1b').
* @property offset - Byte offset within the storage slot (0-31).
* @property type - Variable type (e.g., 'uint256', 'mapping(address => uint256)', 'bytes32').
* @property reads - Array of function selectors that read from this storage location.
* @property writes - Array of function selectors that write to this storage location.
*/
export type StorageRecord = {
slot: string,
offset: number,
type: string,
reads: string[],
writes: string[]
};
"#;
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_storage_record() {}
#[wasm_bindgen(typescript_custom_section)]
const DOC_CONTROL_FLOW_GRAPH: &'static str = r#"
/**
Represents the control flow graph of the contract bytecode
* @property blocks - List of basic blocks in the control flow graph
*/
export type ControlFlowGraph = {
blocks: Block[],
};
"#;
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_control_flow_graph() {}
#[wasm_bindgen(typescript_custom_section)]
const DOC_BLOCK: &'static str = r#"
/**
Represents a basic block in the control flow graph
* @property id - Unique block identifier (CFG key)
* @property start - Byte offset where the block's first opcode begins
* @property end - Byte offset where the block's last opcode begins
* @property type - Block type
* @property data - Type-specific data
*/
export type Block = {
id: number,
start: number,
end: number,
type: 'Terminate' | 'Jump' | 'Jumpi' | 'DynamicJump' | 'DynamicJumpi';
} & (
| {
type: 'Terminate';
data: { success: boolean };
}
| {
type: 'Jump';
data: { to: number };
}
| {
type: 'Jumpi';
data: { true_to: number; false_to: number };
}
| {
type: 'DynamicJump';
data: { to: DynamicJump };
}
| {
type: 'DynamicJumpi';
data: { true_to: DynamicJump, false_to: number};
}
);
"#;
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_block() {}
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_block_data_terminate() {}
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_block_data_jump() {}
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_block_data_jumpi() {}
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_block_data_dynamic_jump() {}
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_block_data_dynamic_jumpi() {}
#[wasm_bindgen(typescript_custom_section)]
const DOC_DYNAMIC_JUMP: &'static str = r#"
/**
Represents a dynamic jump destination in the control flow
* @property path - Path of block IDs leading to this jump
* @property to - Destination block ID if known; use Block.start to get the bytecode offset
*/
export type DynamicJump = {
path: number[];
to?: number;
};
"#;
#[wasm_bindgen(skip_jsdoc)]
pub fn dummy_dynamic_jump() {}
#[derive(Deserialize)]
struct ContractInfoArgs {
#[serde(default)]
selectors: bool,
#[serde(default)]
arguments: bool,
#[serde(default, rename = "stateMutability")]
state_mutability: bool,
#[serde(default)]
storage: bool,
#[serde(default)]
disassemble: bool,
#[serde(default, rename = "basicBlocks")]
basic_blocks: bool,
#[serde(default, rename = "controlFlowGraph")]
control_flow_graph: bool,
}
#[wasm_bindgen(typescript_custom_section)]
const DOC_CONTRACT_INFO: &'static str = r#"
/**
* Analyzes contract bytecode and returns contract information based on specified options.
*
* @param code - Runtime bytecode as a hex string
* @param args - Configuration options for the analysis
* @param args.selectors - When true, includes function selectors in the output
* @param args.arguments - When true, includes function arguments information
* @param args.stateMutability - When true, includes state mutability information for functions
* @param args.storage - When true, includes contract storage layout information
* @param args.disassemble - When true, includes disassembled bytecode
* @param args.basicBlocks - When true, includes basic block analysis
* @param args.controlFlowGraph - When true, includes control flow graph analysis
* @returns Analyzed contract information
*/
export function contractInfo(code: string, args: {
selectors?: boolean,
arguments?: boolean,
stateMutability?: boolean,
storage?: boolean,
disassemble?: boolean,
basicBlocks?: boolean,
controlFlowGraph?: boolean
}): Contract;
"#;
#[wasm_bindgen(js_name = contractInfo, skip_typescript, skip_jsdoc)]
pub fn contract_info(code: &str, args: JsValue) -> Result<JsValue, JsError> {
let c = decode_hex_code(code)?;
let args: ContractInfoArgs = serde_wasm_bindgen::from_value(args)?;
let mut cargs = crate::ContractInfoArgs::new(&c);
if args.selectors {
cargs = cargs.with_selectors();
}
if args.arguments {
cargs = cargs.with_arguments();
}
if args.state_mutability {
cargs = cargs.with_state_mutability();
}
if args.storage {
cargs = cargs.with_storage();
}
if args.disassemble {
cargs = cargs.with_disassemble();
}
if args.basic_blocks {
cargs = cargs.with_basic_blocks();
}
if args.control_flow_graph {
cargs = cargs.with_control_flow_graph();
}
let info = crate::contract_info(cargs);
Ok(serde_wasm_bindgen::to_value(&info)?)
}