aptos-disassemble
Version:
Aptos Move disassembler for TypeScript. This package provides utilities to disassemble Move bytecode and modules for the Aptos blockchain.
1,397 lines (1,384 loc) • 112 kB
JavaScript
// src/utils/SignatureUtils.ts
var AbilityValues = {
Copy: 1,
Drop: 2,
Store: 4,
Key: 8
};
function parseAbilities(abilities) {
const result = [];
if (abilities & AbilityValues.Copy) result.push("copy");
if (abilities & AbilityValues.Drop) result.push("drop");
if (abilities & AbilityValues.Store) result.push("store");
if (abilities & AbilityValues.Key) result.push("key");
return result;
}
function parseSignatureToken(token, module) {
switch (token.kind) {
case "Bool":
return "bool";
case "U8":
return "u8";
case "U16":
return "u16";
case "U32":
return "u32";
case "U64":
return "u64";
case "U128":
return "u128";
case "U256":
return "u256";
case "Address":
return "address";
case "Signer":
return "signer";
case "Vector":
return `vector<${parseSignatureToken(token.type, module)}>`;
case "Reference":
return `&${parseSignatureToken(token.type, module)}`;
case "MutableReference":
return `&mut ${parseSignatureToken(token.type, module)}`;
case "TypeParameter":
return `T${token.index}`;
case "Struct": {
const structHandle = module.struct_handles[token.handle];
if (!structHandle) {
throw new Error(`Struct handle at index ${token.handle} is out of bounds`);
}
const structName = module.identifiers[structHandle.name];
if (!structName) {
throw new Error(`Struct name at index ${structHandle.name} is out of bounds`);
}
if (structHandle.module === module.selfModuleHandleIdx) {
return structName;
} else {
const moduleHandle = module.module_handles[structHandle.module];
if (!moduleHandle) {
throw new Error(`Module handle at index ${structHandle.module} is out of bounds`);
}
const moduleName = module.identifiers[moduleHandle.name];
if (!moduleName) {
throw new Error(`Module name at index ${moduleHandle.name} is out of bounds`);
}
return `${moduleName}::${structName}`;
}
}
case "StructInstantiation": {
const structHandle = module.struct_handles[token.handle];
if (!structHandle) {
throw new Error(`Struct handle at index ${token.handle} is out of bounds`);
}
const structName = module.identifiers[structHandle.name];
if (!structName) {
throw new Error(`Struct name at index ${structHandle.name} is out of bounds`);
}
const typeParams = token.typeParams.map((tp) => parseSignatureToken(tp, module));
if (structHandle.module === module.selfModuleHandleIdx) {
return `${structName}<${typeParams.join(", ")}>`;
} else {
const moduleHandle = module.module_handles[structHandle.module];
if (!moduleHandle) {
throw new Error(`Module handle at index ${structHandle.module} is out of bounds`);
}
const moduleName = module.identifiers[moduleHandle.name];
if (!moduleName) {
throw new Error(`Module name at index ${moduleHandle.name} is out of bounds`);
}
return `${moduleName}::${structName}<${typeParams.join(", ")}>`;
}
}
case "Function": {
const args = token.args.map((arg) => parseSignatureToken(arg, module));
const results = token.results.map((result) => parseSignatureToken(result, module));
const abilities = parseAbilities(token.abilities);
const abilitiesStr = abilities.length > 0 ? ` + ${abilities.join(" + ")}` : "";
return `|${args.join(", ")}| -> (${results.join(", ")})${abilitiesStr}`;
}
default:
throw new Error(`Unknown signature token kind: ${token}`);
}
}
// src/types/DisassemblerOptions.ts
var DEFAULT_DISASSEMBLER_OPTIONS = {
printBasicBlocks: true
};
// src/core/DisassemblerContext.ts
var DisassemblerContext = class {
constructor(module, options) {
this.module = module;
this.options = { ...DEFAULT_DISASSEMBLER_OPTIONS, ...options };
}
options;
get version() {
return this.module.version;
}
get selfModuleHandleIdx() {
return this.module.selfModuleHandleIdx;
}
// Module handle utilities
getSelfModule() {
const selfModule = this.module.module_handles[this.selfModuleHandleIdx];
if (!selfModule) {
throw new Error("Self module handle index is out of bounds");
}
return selfModule;
}
getSelfModuleAddress() {
const selfModule = this.getSelfModule();
return this.getAddressIdentifier(selfModule.address);
}
getSelfModuleName() {
const selfModule = this.getSelfModule();
return this.getIdentifier(selfModule.name);
}
// Identifier utilities
getIdentifier(index) {
const identifier = this.module.identifiers[index];
if (identifier === void 0) {
throw new Error(`Identifier at index ${index} is out of bounds`);
}
return identifier;
}
getAddressIdentifier(index) {
const address = this.module.address_identifiers[index];
if (address === void 0) {
throw new Error(`Address identifier at index ${index} is out of bounds`);
}
return address;
}
// Signature utilities
getSignature(index) {
const signature = this.module.signatures[index];
if (!signature) {
throw new Error(`Signature at index ${index} is out of bounds`);
}
return signature;
}
parseSignatureToken(token) {
return parseSignatureToken(token, this.module);
}
parseAbilities(abilities) {
return parseAbilities(abilities);
}
// Handle utilities
getModuleHandle(index) {
const handle = this.module.module_handles[index];
if (!handle) {
throw new Error(`Module handle at index ${index} is out of bounds`);
}
return handle;
}
getStructHandle(index) {
const handle = this.module.struct_handles[index];
if (!handle) {
throw new Error(`Struct handle at index ${index} is out of bounds`);
}
return handle;
}
getFunctionHandle(index) {
const handle = this.module.function_handles[index];
if (!handle) {
throw new Error(`Function handle at index ${index} is out of bounds`);
}
return handle;
}
getFieldHandle(index) {
const handle = this.module.field_handles[index];
if (!handle) {
throw new Error(`Field handle at index ${index} is out of bounds`);
}
return handle;
}
// Definition utilities
getStructDefinition(index) {
const def = this.module.struct_defs[index];
if (!def) {
throw new Error(`Struct definition at index ${index} is out of bounds`);
}
return def;
}
getFunctionDefinition(index) {
const def = this.module.function_defs[index];
if (!def) {
throw new Error(`Function definition at index ${index} is out of bounds`);
}
return def;
}
// Instantiation utilities
getStructDefInstantiation(index) {
const inst = this.module.struct_defs_inst[index];
if (!inst) {
throw new Error(`Struct def instantiation at index ${index} is out of bounds`);
}
return inst;
}
getFunctionInstantiation(index) {
const inst = this.module.function_inst[index];
if (!inst) {
throw new Error(`Function instantiation at index ${index} is out of bounds`);
}
return inst;
}
getFieldInstantiation(index) {
const inst = this.module.field_insts[index];
if (!inst) {
throw new Error(`Field instantiation at index ${index} is out of bounds`);
}
return inst;
}
// Variant utilities (for enum support)
getStructVariantHandle(index) {
const handle = this.module.struct_variant_handles[index];
if (!handle) {
throw new Error(`Struct variant handle at index ${index} is out of bounds`);
}
return handle;
}
getStructVariantInstantiation(index) {
const inst = this.module.struct_variant_inst[index];
if (!inst) {
throw new Error(`Struct variant instantiation at index ${index} is out of bounds`);
}
return inst;
}
getVariantFieldHandle(index) {
const handle = this.module.variant_field_handles[index];
if (!handle) {
throw new Error(`Variant field handle at index ${index} is out of bounds`);
}
return handle;
}
getVariantFieldInstantiation(index) {
const inst = this.module.variant_field_inst[index];
if (!inst) {
throw new Error(`Variant field instantiation at index ${index} is out of bounds`);
}
return inst;
}
// Access to raw module for compatibility
getRawModule() {
return this.module;
}
};
// src/core/ControlFlowGraph.ts
var BasicBlock = class {
constructor(exit, successors) {
this.exit = exit;
this.successors = successors;
}
display(entry) {
console.log("+=======================+");
console.log(`| Enter: ${entry} |`);
console.log("+-----------------------+");
console.log(`==> Children: ${JSON.stringify(this.successors)}`);
console.log("+-----------------------+");
console.log(`| Exit: ${this.exit} |`);
console.log("+=======================+");
}
};
var ENTRY_BLOCK_ID = 0;
var VMControlFlowGraph = class _VMControlFlowGraph {
/** The basic blocks */
basicBlocks;
/** Basic block ordering for traversal */
traversalSuccessors;
/** Map of loop heads with all of their back edges */
loopHeads;
constructor(code) {
const codeLen = code.length;
const blockIds = /* @__PURE__ */ new Set();
blockIds.add(ENTRY_BLOCK_ID);
for (let pc = 0; pc < code.length; pc++) {
_VMControlFlowGraph.recordBlockIds(pc, code, blockIds);
}
const basicBlocks = /* @__PURE__ */ new Map();
let entry = 0;
const exitToEntry = /* @__PURE__ */ new Map();
for (let pc = 0; pc < code.length; pc++) {
const coPc = pc;
if (this.isEndOfBlock(coPc, code, blockIds)) {
const exit = coPc;
exitToEntry.set(exit, entry);
const successors = this.getSuccessors(coPc, code);
const bb = new BasicBlock(exit, successors);
basicBlocks.set(entry, bb);
entry = coPc + 1;
}
}
this.basicBlocks = basicBlocks;
console.assert(entry === codeLen, `Entry ${entry} should equal code length ${codeLen}`);
const exploration = /* @__PURE__ */ new Map();
const stack = [ENTRY_BLOCK_ID];
const loopHeads = /* @__PURE__ */ new Map();
const postOrder = [];
while (stack.length > 0) {
const block = stack.pop();
const explorationState = exploration.get(block);
if (explorationState === void 0) {
exploration.set(block, "InProgress" /* InProgress */);
stack.push(block);
const successors = this.basicBlocks.get(block)?.successors || [];
for (const succ of successors) {
const succExploration = exploration.get(succ);
if (succExploration === void 0) {
stack.push(succ);
} else if (succExploration === "InProgress" /* InProgress */) {
if (!loopHeads.has(succ)) {
loopHeads.set(succ, /* @__PURE__ */ new Set());
}
loopHeads.get(succ).add(block);
}
}
} else if (explorationState === "InProgress" /* InProgress */) {
postOrder.push(block);
exploration.set(block, "Done" /* Done */);
}
}
const traversalOrder = postOrder.reverse();
const traversalSuccessors = /* @__PURE__ */ new Map();
for (let i = 0; i < traversalOrder.length - 1; i++) {
traversalSuccessors.set(traversalOrder[i], traversalOrder[i + 1]);
}
this.traversalSuccessors = traversalSuccessors;
this.loopHeads = loopHeads;
}
display() {
for (const [entry, block] of this.basicBlocks) {
block.display(entry);
}
console.log("Traversal:", Object.fromEntries(this.traversalSuccessors));
}
isEndOfBlock(pc, code, blockIds) {
return pc + 1 === code.length || blockIds.has(pc + 1);
}
static recordBlockIds(pc, code, blockIds) {
const bytecode = code[pc];
const offset = this.getOffset(bytecode);
if (offset !== null) {
blockIds.add(offset);
}
if (this.isBranch(bytecode) && pc + 1 < code.length) {
blockIds.add(pc + 1);
}
}
static getOffset(bytecode) {
switch (bytecode.kind) {
case "BrTrue":
case "BrFalse":
case "Branch":
return bytecode.codeOffset;
default:
return null;
}
}
static isBranch(bytecode) {
switch (bytecode.kind) {
case "BrTrue":
case "BrFalse":
case "Branch":
case "Ret":
case "Abort":
return true;
default:
return false;
}
}
getSuccessors(pc, code) {
const bytecode = code[pc];
const successors = [];
switch (bytecode.kind) {
case "BrTrue":
case "BrFalse":
successors.push(bytecode.codeOffset);
if (pc + 1 < code.length) {
successors.push(pc + 1);
}
break;
case "Branch":
successors.push(bytecode.codeOffset);
break;
case "Ret":
case "Abort":
break;
default:
if (pc + 1 < code.length) {
successors.push(pc + 1);
}
break;
}
return successors;
}
/**
* A utility function that implements BFS-reachability from blockId
*/
traverseBy(blockId) {
const ret = [];
let index = 0;
const seen = /* @__PURE__ */ new Set();
ret.push(blockId);
seen.add(blockId);
while (index < ret.length) {
const currentBlockId = ret[index];
index += 1;
const successors = this.successors(currentBlockId);
for (const successor of successors) {
if (!seen.has(successor)) {
ret.push(successor);
seen.add(successor);
}
}
}
return ret;
}
reachableFrom(blockId) {
return this.traverseBy(blockId);
}
traversalIndex(blockId) {
const keys = Array.from(this.traversalSuccessors.keys());
const index = keys.indexOf(blockId);
return index === -1 ? this.traversalSuccessors.size : index;
}
// ControlFlowGraph interface implementation
blockStart(blockId) {
return blockId;
}
blockEnd(blockId) {
const block = this.basicBlocks.get(blockId);
if (!block) {
throw new Error(`Block ${blockId} not found`);
}
return block.exit;
}
successors(blockId) {
const block = this.basicBlocks.get(blockId);
if (!block) {
throw new Error(`Block ${blockId} not found`);
}
return [...block.successors];
}
nextBlock(blockId) {
console.assert(this.basicBlocks.has(blockId), `Block ${blockId} should exist`);
return this.traversalSuccessors.get(blockId) || null;
}
*instrIndexes(blockId) {
const start = this.blockStart(blockId);
const end = this.blockEnd(blockId);
for (let i = start; i <= end; i++) {
yield i;
}
}
blocks() {
return Array.from(this.basicBlocks.keys());
}
numBlocks() {
return this.basicBlocks.size;
}
entryBlockId() {
return ENTRY_BLOCK_ID;
}
isLoopHead(blockId) {
return this.loopHeads.has(blockId);
}
isBackEdge(cur, next) {
const backEdges = this.loopHeads.get(next);
return backEdges ? backEdges.has(cur) : false;
}
numBackEdges() {
let count = 0;
for (const edges of this.loopHeads.values()) {
count += edges.size;
}
return count;
}
};
// src/disassemblers/InstructionDisassembler.ts
var InstructionDisassembler = class {
constructor(context, params, locals) {
this.context = context;
this.params = params;
this.locals = locals;
}
disassemble(instruction) {
switch (instruction.kind) {
// Stack operations
case "Pop":
return "Pop";
case "Ret":
return "Ret";
case "Nop":
return "Nop";
// Branch operations
case "BrTrue":
return `BrTrue(${instruction.codeOffset})`;
case "BrFalse":
return `BrFalse(${instruction.codeOffset})`;
case "Branch":
return `Branch(${instruction.codeOffset})`;
// Load operations
case "LdU8":
return `LdU8(${instruction.value})`;
case "LdU16":
return `LdU16(${instruction.value})`;
case "LdU32":
return `LdU32(${instruction.value})`;
case "LdU64":
return `LdU64(${instruction.value})`;
case "LdU128":
return `LdU128(${instruction.value})`;
case "LdU256":
return `LdU256(${instruction.value})`;
case "LdTrue":
return "LdTrue";
case "LdFalse":
return "LdFalse";
case "LdConst":
return this.formatLdConst(instruction.constIdx);
// Cast operations
case "CastU8":
return "CastU8";
case "CastU16":
return "CastU16";
case "CastU32":
return "CastU32";
case "CastU64":
return "CastU64";
case "CastU128":
return "CastU128";
case "CastU256":
return "CastU256";
// Local variable operations
case "CopyLoc":
return this.formatLocalInstruction(instruction.localIdx, "CopyLoc");
case "MoveLoc":
return this.formatLocalInstruction(instruction.localIdx, "MoveLoc");
case "StLoc":
return this.formatLocalInstruction(instruction.localIdx, "StLoc");
case "MutBorrowLoc":
return this.formatLocalInstruction(instruction.localIdx, "MutBorrowLoc");
case "ImmBorrowLoc":
return this.formatLocalInstruction(instruction.localIdx, "ImmBorrowLoc");
// Function call operations
case "Call":
return this.formatCall(instruction.funcHandleIdx);
case "CallGeneric":
return this.formatCallGeneric(instruction.funcInstIdx);
case "CallClosure":
return this.formatCallClosure(instruction.sigIdx);
// Closure operations
case "PackClosure":
return this.formatPackClosure(instruction.fun, instruction.mask);
case "PackClosureGeneric":
return this.formatPackClosureGeneric(instruction.fun, instruction.mask);
// Struct operations
case "Pack":
return this.formatStructOperation(instruction.structDefIdx, "Pack");
case "Unpack":
return this.formatStructOperation(instruction.structDefIdx, "Unpack");
case "PackGeneric":
return this.formatGenericStructOperation(instruction.structInstIdx, "PackGeneric");
case "UnpackGeneric":
return this.formatGenericStructOperation(instruction.structInstIdx, "UnpackGeneric");
// Variant operations
case "PackVariant":
return this.formatVariantOperation(instruction.structVariantHandleIdx, "PackVariant");
case "UnpackVariant":
return this.formatVariantOperation(instruction.structVariantHandleIdx, "UnpackVariant");
case "TestVariant":
return this.formatVariantOperation(instruction.structVariantHandleIdx, "TestVariant");
case "PackVariantGeneric":
return this.formatGenericVariantOperation(
instruction.structVariantInstIdx,
"PackVariantGeneric"
);
case "UnpackVariantGeneric":
return this.formatGenericVariantOperation(
instruction.structVariantInstIdx,
"UnpackVariantGeneric"
);
case "TestVariantGeneric":
return this.formatGenericVariantOperation(
instruction.structVariantInstIdx,
"TestVariantGeneric"
);
// Field operations
case "MutBorrowField":
return this.formatFieldOperation(instruction.fieldHandleIdx, "MutBorrowField");
case "ImmBorrowField":
return this.formatFieldOperation(instruction.fieldHandleIdx, "ImmBorrowField");
case "MutBorrowFieldGeneric":
return this.formatGenericFieldOperation(instruction.fieldInstIdx, "MutBorrowFieldGeneric");
case "ImmBorrowFieldGeneric":
return this.formatGenericFieldOperation(instruction.fieldInstIdx, "ImmBorrowFieldGeneric");
// Variant field operations
case "MutBorrowVariantField":
return this.formatVariantFieldOperation(
instruction.variantFieldHandleIdx,
"MutBorrowVariantField"
);
case "ImmBorrowVariantField":
return this.formatVariantFieldOperation(
instruction.variantFieldHandleIdx,
"ImmBorrowVariantField"
);
case "MutBorrowVariantFieldGeneric":
return this.formatGenericVariantFieldOperation(
instruction.variantFieldInstIdx,
"MutBorrowVariantFieldGeneric"
);
case "ImmBorrowVariantFieldGeneric":
return this.formatGenericVariantFieldOperation(
instruction.variantFieldInstIdx,
"ImmBorrowVariantFieldGeneric"
);
// Global operations
case "MutBorrowGlobal":
return this.formatStructOperation(instruction.structDefIdx, "MutBorrowGlobal");
case "ImmBorrowGlobal":
return this.formatStructOperation(instruction.structDefIdx, "ImmBorrowGlobal");
case "MutBorrowGlobalGeneric":
return this.formatGenericStructOperation(
instruction.structInstIdx,
"MutBorrowGlobalGeneric"
);
case "ImmBorrowGlobalGeneric":
return this.formatGenericStructOperation(
instruction.structInstIdx,
"ImmBorrowGlobalGeneric"
);
case "Exists":
return this.formatStructOperation(instruction.structDefIdx, "Exists");
case "ExistsGeneric":
return this.formatGenericStructOperation(instruction.structInstIdx, "ExistsGeneric");
case "MoveFrom":
return this.formatStructOperation(instruction.structDefIdx, "MoveFrom");
case "MoveFromGeneric":
return this.formatGenericStructOperation(instruction.structInstIdx, "MoveFromGeneric");
case "MoveTo":
return this.formatStructOperation(instruction.structDefIdx, "MoveTo");
case "MoveToGeneric":
return this.formatGenericStructOperation(instruction.structInstIdx, "MoveToGeneric");
// Reference operations
case "ReadRef":
return "ReadRef";
case "WriteRef":
return "WriteRef";
case "FreezeRef":
return "FreezeRef";
// Arithmetic operations
case "Add":
return "Add";
case "Sub":
return "Sub";
case "Mul":
return "Mul";
case "Mod":
return "Mod";
case "Div":
return "Div";
case "Shl":
return "Shl";
case "Shr":
return "Shr";
// Bitwise operations
case "BitOr":
return "BitOr";
case "BitAnd":
return "BitAnd";
case "Xor":
return "Xor";
// Logical operations
case "Or":
return "Or";
case "And":
return "And";
case "Not":
return "Not";
// Comparison operations
case "Eq":
return "Eq";
case "Neq":
return "Neq";
case "Lt":
return "Lt";
case "Gt":
return "Gt";
case "Le":
return "Le";
case "Ge":
return "Ge";
// Other operations
case "Abort":
return "Abort";
// Vector operations
case "VecPack":
return this.formatVectorOperation(
instruction.elemTyIdx,
instruction.numElements,
"VecPack"
);
case "VecLen":
return this.formatVectorOperation(instruction.elemTyIdx, void 0, "VecLen");
case "VecImmBorrow":
return this.formatVectorOperation(instruction.elemTyIdx, void 0, "VecImmBorrow");
case "VecMutBorrow":
return this.formatVectorOperation(instruction.elemTyIdx, void 0, "VecMutBorrow");
case "VecPushBack":
return this.formatVectorOperation(instruction.elemTyIdx, void 0, "VecPushBack");
case "VecPopBack":
return this.formatVectorOperation(instruction.elemTyIdx, void 0, "VecPopBack");
case "VecUnpack":
return this.formatVectorOperation(
instruction.elemTyIdx,
instruction.numElements,
"VecUnpack"
);
case "VecSwap":
return this.formatVectorOperation(instruction.elemTyIdx, void 0, "VecSwap");
default:
throw new Error(`Unsupported instruction: ${instruction}`);
}
}
formatLocalInstruction(localIdx, instructionName) {
const localVar = this.getLocalVariable(localIdx);
return `${instructionName}[${localIdx}](${localVar})`;
}
getLocalVariable(localIdx) {
if (localIdx < this.params.length) {
return `arg${localIdx}: ${this.params[localIdx]}`;
} else if (localIdx < this.params.length + this.locals.length) {
const localIndex = localIdx - this.params.length;
return `loc${localIndex}: ${this.locals[localIndex]}`;
} else {
throw new Error(`Invalid local index: ${localIdx}`);
}
}
formatCall(funcHandleIdx) {
const functionHandle = this.context.getFunctionHandle(funcHandleIdx);
const functionName = this.context.getIdentifier(functionHandle.name);
const moduleHandle = this.context.getModuleHandle(functionHandle.module);
let functionString = "";
if (this.context.selfModuleHandleIdx === functionHandle.module) {
functionString = functionName;
} else {
const moduleName = this.context.getIdentifier(moduleHandle.name);
functionString = `${moduleName}::${functionName}`;
}
const paramSignature = this.context.getSignature(functionHandle.parameters);
const returnSignature = this.context.getSignature(functionHandle.return_);
const paramTypes = paramSignature.map((token) => this.context.parseSignatureToken(token));
const paramStr = `(${paramTypes.join(", ")})`;
let returnStr = "";
if (returnSignature.length > 0) {
const returnTypes = returnSignature.map((token) => this.context.parseSignatureToken(token));
if (returnTypes.length === 1) {
returnStr = `: ${returnTypes[0]}`;
} else {
returnStr = `: (${returnTypes.join(", ")})`;
}
}
return `Call ${functionString}${paramStr}${returnStr}`;
}
formatCallGeneric(funcInstIdx) {
const functionInst = this.context.getFunctionInstantiation(funcInstIdx);
const functionHandle = this.context.getFunctionHandle(functionInst.handle);
const functionName = this.context.getIdentifier(functionHandle.name);
const moduleHandle = this.context.getModuleHandle(functionHandle.module);
let functionString = "";
if (this.context.selfModuleHandleIdx === functionHandle.module) {
functionString = functionName;
} else {
const moduleName = this.context.getIdentifier(moduleHandle.name);
functionString = `${moduleName}::${functionName}`;
}
const typeParamSignature = this.context.getSignature(functionInst.type_parameters);
const typeParams = typeParamSignature.map((token) => this.context.parseSignatureToken(token));
const typeParamsStr = typeParams.length > 0 ? `<${typeParams.join(", ")}>` : "";
const paramSignature = this.context.getSignature(functionHandle.parameters);
const returnSignature = this.context.getSignature(functionHandle.return_);
const paramTypes = paramSignature.map((token) => this.context.parseSignatureToken(token));
const paramStr = `(${paramTypes.join(", ")})`;
let returnStr = "";
if (returnSignature.length > 0) {
const returnTypes = returnSignature.map((token) => this.context.parseSignatureToken(token));
if (returnTypes.length === 1) {
returnStr = `: ${returnTypes[0]}`;
} else {
returnStr = `: (${returnTypes.join(", ")})`;
}
}
return `Call ${functionString}${typeParamsStr}${paramStr}${returnStr}`;
}
formatCallClosure(sigIdx) {
const closureSignature = this.context.getSignature(sigIdx);
const closureType = closureSignature.map((tp) => this.context.parseSignatureToken(tp));
if (closureType.length !== 1) {
throw new Error("CallClosure with type parameters is not supported yet");
}
return `CallClosure[${sigIdx}](${closureType[0]})`;
}
formatPackClosure(fun, mask) {
const functionHandle = this.context.getFunctionHandle(fun);
const functionName = this.context.getIdentifier(functionHandle.name);
const moduleHandle = this.context.getModuleHandle(functionHandle.module);
let functionString = "";
if (this.context.selfModuleHandleIdx === functionHandle.module) {
functionString = functionName;
} else {
const moduleName = this.context.getIdentifier(moduleHandle.name);
functionString = `${moduleName}::${functionName}`;
}
const typeArguments = functionHandle.type_parameters.map(
(tp) => this.context.parseAbilities(tp)
);
const typeParamsStr = typeArguments.length > 0 ? `<${typeArguments.join(", ")}>` : "";
return `PackClosure#${mask}[${fun}](${functionString}${typeParamsStr})`;
}
formatPackClosureGeneric(fun, mask) {
return `PackClosureGeneric#${mask}[${fun}]`;
}
formatStructOperation(structDefIdx, instructionName) {
const structDef = this.context.getStructDefinition(structDefIdx);
const structHandle = this.context.getStructHandle(structDef.struct_handle);
const structName = this.context.getIdentifier(structHandle.name);
return `${instructionName}[${structDefIdx}](${structName})`;
}
formatGenericStructOperation(structInstIdx, instructionName) {
const structInst = this.context.getStructDefInstantiation(structInstIdx);
const structDef = this.context.getStructDefinition(structInst.def);
const structHandle = this.context.getStructHandle(structDef.struct_handle);
const structName = this.context.getIdentifier(structHandle.name);
const typeParams = this.context.getSignature(structInst.typeParameters).map((tp) => this.context.parseSignatureToken(tp));
const typeParamsStr = typeParams.length > 0 ? `<${typeParams.join(", ")}>` : "";
return `${instructionName}[${structInstIdx}](${structName}${typeParamsStr})`;
}
formatVariantOperation(structVariantHandleIdx, instructionName) {
const structVariantHandle = this.context.getStructVariantHandle(structVariantHandleIdx);
const { structName, variantName } = this.getVariantInfo(structVariantHandle);
return `${instructionName}[${structVariantHandleIdx}](${structName}/${variantName})`;
}
formatGenericVariantOperation(structVariantInstIdx, instructionName) {
const structVariantInst = this.context.getStructVariantInstantiation(structVariantInstIdx);
const typeParams = this.context.getSignature(structVariantInst.type_parameters);
const structVariantHandle = this.context.getStructVariantHandle(structVariantInst.handle);
const { structName, variantName } = this.getVariantInfo(structVariantHandle);
const structTypeParams = typeParams.map((tp) => this.context.parseSignatureToken(tp));
const typeParamsStr = structTypeParams.length > 0 ? `<${structTypeParams.join(", ")}>` : "";
return `${instructionName}[${structVariantInstIdx}](${structName}/${variantName}${typeParamsStr})`;
}
formatFieldOperation(fieldHandleIdx, instructionName) {
const fieldHandle = this.context.getFieldHandle(fieldHandleIdx);
const { structName, fieldName, fieldType } = this.getFieldInfo(fieldHandle);
return `${instructionName}[${fieldHandleIdx}](${structName}.${fieldName}: ${fieldType})`;
}
formatGenericFieldOperation(fieldInstIdx, instructionName) {
const fieldInst = this.context.getFieldInstantiation(fieldInstIdx);
const fieldHandle = this.context.getFieldHandle(fieldInst.handle);
const { structName, fieldName, fieldType } = this.getFieldInfo(fieldHandle);
return `${instructionName}[${fieldInstIdx}](${structName}.${fieldName}: ${fieldType})`;
}
formatVariantFieldOperation(variantFieldHandleIdx, instructionName) {
return `${instructionName}[${variantFieldHandleIdx}]`;
}
formatGenericVariantFieldOperation(variantFieldInstIdx, instructionName) {
return `${instructionName}[${variantFieldInstIdx}]`;
}
formatVectorOperation(elemTyIdx, numElements, instructionName) {
const elementsStr = numElements !== void 0 ? `, ${numElements}` : "";
return `${instructionName}(${elemTyIdx}${elementsStr})`;
}
formatLdConst(constIdx) {
const rawModule = this.context.getRawModule();
const constant = rawModule.constant_pool[constIdx];
if (!constant) {
throw new Error(`Constant at index ${constIdx} is out of bounds`);
}
const typeStr = this.context.parseSignatureToken(constant.type);
const dataStr = this.formatConstantData(constant.data, constant.type);
return `LdConst[${constIdx}](${typeStr}: ${dataStr})`;
}
formatConstantData(data, type) {
if (type.kind === "Address") {
return Array.from(data).join(",");
}
return Array.from(data).join(",");
}
getFieldInfo(fieldHandle) {
const structDef = this.context.getStructDefinition(fieldHandle.owner);
const structHandle = this.context.getStructHandle(structDef.struct_handle);
const structName = this.context.getIdentifier(structHandle.name);
let fields = [];
if (structDef.field_information.kind === "Declared") {
fields = structDef.field_information.fields;
}
const field = fields[fieldHandle.field];
if (!field) {
throw new Error(`Field not found: ${fieldHandle.field}`);
}
const fieldName = this.context.getIdentifier(field.name);
const fieldType = this.context.parseSignatureToken(field.type);
return { structName, fieldName, fieldType };
}
getVariantInfo(structVariantHandle) {
const structDef = this.context.getStructDefinition(structVariantHandle.struct_index);
const structHandle = this.context.getStructHandle(structDef.struct_handle);
const structName = this.context.getIdentifier(structHandle.name);
if (structDef.field_information.kind !== "DeclaredVariants") {
throw new Error(
`PackVariant is not supported for field information kind: ${structDef.field_information.kind}`
);
}
const variant = structDef.field_information.variants[structVariantHandle.variant];
if (!variant) {
throw new Error(`Variant not found: ${structVariantHandle.variant}`);
}
const variantName = this.context.getIdentifier(variant.name);
return { structName, variantName };
}
};
// src/disassemblers/ModuleDisassembler.ts
var ModuleDisassembler = class {
constructor(context) {
this.context = context;
this.options = this.context.options;
}
options;
disassemble() {
const { moduleNames, moduleAliases } = this.buildModuleAliases();
const header = this.generateHeader();
const imports = this.generateImports(moduleNames, moduleAliases);
const structs = this.generateStructs();
const functions = this.generateFunctions();
return `// Move bytecode v${this.context.version}
${header} {
${imports}
${structs}
${functions}
}`;
}
buildModuleAliases() {
const moduleNames = /* @__PURE__ */ new Map();
const moduleAliases = /* @__PURE__ */ new Map();
const rawModule = this.context.getRawModule();
const selfModuleName = this.context.getSelfModuleName();
moduleNames.set(selfModuleName, 0);
for (const moduleHandle of rawModule.module_handles) {
const name = this.context.getIdentifier(moduleHandle.name);
if (moduleNames.has(name)) {
const count = moduleNames.get(name);
moduleNames.set(name, count + 1);
moduleAliases.set(name, `${count + 1}${name}`);
} else {
moduleNames.set(name, 0);
moduleAliases.set(name, `${0}${name}`);
}
}
return { moduleNames, moduleAliases };
}
generateHeader() {
const selfModuleAddress = this.context.getSelfModuleAddress();
const selfModuleName = this.context.getSelfModuleName();
return `module ${selfModuleAddress}::${selfModuleName}`;
}
generateImports(moduleNames, moduleAliases) {
const rawModule = this.context.getRawModule();
const selfModuleAddress = this.context.getSelfModuleAddress();
const selfModuleName = this.context.getSelfModuleName();
return rawModule.module_handles.filter((m) => {
const mAddress = this.context.getAddressIdentifier(m.address);
const mName = this.context.getIdentifier(m.name);
return `${mAddress}${mName}` !== `${selfModuleAddress}${selfModuleName}`;
}).map((m) => {
const mAddress = this.context.getAddressIdentifier(m.address);
const mName = this.context.getIdentifier(m.name);
if (moduleNames.has(mName) && moduleNames.get(mName) > 0) {
return `use ${mAddress}::${mName} as ${moduleAliases.get(mName)};`;
}
return `use ${mAddress}::${mName};`;
}).join("\n");
}
generateStructs() {
const rawModule = this.context.getRawModule();
return rawModule.struct_defs.map((structDefinition) => {
const structHandle = this.context.getStructHandle(structDefinition.struct_handle);
const structName = this.context.getIdentifier(structHandle.name);
const abilities = this.context.parseAbilities(structHandle.abilities);
const structAbilities = abilities.length > 0 ? ` has ${abilities.join(", ")}` : "";
const structTypeParams = structHandle.type_parameters.map((tp, idx) => {
const abilities2 = this.context.parseAbilities(tp.constraints);
return `${tp.is_phantom ? "phantom " : ""}T${idx}${abilities2.length > 0 ? `: ${abilities2.join("+ ")}` : ""}`;
});
const typeParameters = structTypeParams.length > 0 ? `<${structTypeParams.join(", ")}>` : "";
switch (structDefinition.field_information.kind) {
case "Native":
return `native struct ${structName}${typeParameters}${structAbilities}`;
case "Declared": {
const fields = structDefinition.field_information.fields.map((field) => {
const fieldName = this.context.getIdentifier(field.name);
const fieldType = this.context.parseSignatureToken(field.type);
return ` ${fieldName}: ${fieldType}`;
}).join(",\n");
return `struct ${structName}${typeParameters}${structAbilities} {
${fields}
}`;
}
case "DeclaredVariants": {
const variants = structDefinition.field_information.variants.map((variant) => {
const variantName = this.context.getIdentifier(variant.name);
return ` ${variantName}`;
}).join(",\n");
return `struct ${structName}${typeParameters}${structAbilities} {
${variants}
}`;
}
default:
throw new Error("Unknown field information");
}
}).join("\n");
}
generateFunctions() {
const rawModule = this.context.getRawModule();
return rawModule.function_defs.map((functionDefinition) => {
const functionHandle = this.context.getFunctionHandle(functionDefinition.function);
const functionName = this.context.getIdentifier(functionHandle.name);
const modifiers = [];
if (functionDefinition.code === void 0) {
modifiers.push("native");
}
if (functionDefinition.isEntry) {
modifiers.push("entry");
}
switch (functionDefinition.visibility) {
case "public":
modifiers.push("public");
break;
case "friend":
modifiers.push("public(friend)");
break;
case "private":
break;
default:
throw new Error("Unknown function visibility: " + functionDefinition.visibility);
}
const typeParameters = functionHandle.type_parameters.map((abilitySet, idx) => {
const abilities = this.context.parseAbilities(abilitySet);
return `T${idx}${abilities.length > 0 ? `: ${abilities.join("+ ")}` : ""}`;
});
const params = this.context.getSignature(functionHandle.parameters).map((param) => {
return this.context.parseSignatureToken(param);
});
const retType = this.context.getSignature(functionHandle.return_).map((ret) => {
return this.context.parseSignatureToken(ret);
});
let retTypeStr = "";
if (retType.length === 0) {
retTypeStr = "";
} else if (retType.length === 1) {
retTypeStr = `: ${retType[0]}`;
} else {
retTypeStr = `: (${retType.join(", ")})`;
}
let body = [];
if (functionDefinition.code === void 0) {
body = [];
} else {
const signatures = this.context.getSignature(functionDefinition.code.locals);
const maxIdx = signatures.length + params.length - 1;
const width = String(maxIdx).length;
const locals = [];
signatures.forEach((local, idx) => {
const localType = this.context.parseSignatureToken(local);
body.push(`L${String(idx + params.length).padEnd(width, " ")} loc${idx}: ${localType}`);
locals.push(localType);
});
const instructionDisassembler = new InstructionDisassembler(this.context, params, locals);
let cfg;
let blockIdToNumber;
if (this.options.printBasicBlocks) {
cfg = new VMControlFlowGraph(functionDefinition.code.code);
blockIdToNumber = /* @__PURE__ */ new Map();
cfg.blocks().forEach((blockId, index) => {
blockIdToNumber.set(blockId, index);
});
}
functionDefinition.code.code.forEach((instruction, idx) => {
const instructionStr = instructionDisassembler.disassemble(instruction);
if (this.options.printBasicBlocks && cfg && blockIdToNumber) {
const blockId = cfg.blocks().find((blockId2) => cfg.blockStart(blockId2) === idx);
if (blockId !== void 0) {
const blockNumber = blockIdToNumber.get(blockId);
if (blockNumber !== void 0) {
body.push(`B${blockNumber}:`);
}
}
} else if (idx === 0 && !this.options.printBasicBlocks) {
body.push(`B0:`);
}
body.push(
`${"".padStart(4, " ")}${`${idx}`.padEnd(4, " ")}:${instructionStr}`
);
});
}
return `${modifiers.join(" ")}${modifiers.length > 0 ? " " : ""}fun ${functionName}${typeParameters.length > 0 ? `<${typeParameters.join(", ")}>` : ""} ( ${params.map((param, idx) => `arg${idx}: ${param}`).join(", ")} )${retTypeStr} {
${body.join("\n")}
}`;
}).join("\n\n");
}
};
// src/types/MoveModule.ts
import { bcs } from "aptos-bcs";
// src/types/serializedType.ts
function fromU8(value) {
switch (value) {
case 1 /* BOOL */:
return 1 /* BOOL */;
case 2 /* U8 */:
return 2 /* U8 */;
case 3 /* U64 */:
return 3 /* U64 */;
case 4 /* U128 */:
return 4 /* U128 */;
case 5 /* ADDRESS */:
return 5 /* ADDRESS */;
case 6 /* REFERENCE */:
return 6 /* REFERENCE */;
case 7 /* MUTABLE_REFERENCE */:
return 7 /* MUTABLE_REFERENCE */;
case 8 /* STRUCT */:
return 8 /* STRUCT */;
case 9 /* TYPE_PARAMETER */:
return 9 /* TYPE_PARAMETER */;
case 10 /* VECTOR */:
return 10 /* VECTOR */;
case 11 /* STRUCT_INST */:
return 11 /* STRUCT_INST */;
case 12 /* SIGNER */:
return 12 /* SIGNER */;
case 13 /* U16 */:
return 13 /* U16 */;
case 14 /* U32 */:
return 14 /* U32 */;
case 15 /* U256 */:
return 15 /* U256 */;
case 16 /* FUNCTION */:
return 16 /* FUNCTION */;
default:
throw new Error("Unknown serialized type: " + value);
}
}
// src/types/MoveModule.ts
var Ability = /* @__PURE__ */ ((Ability2) => {
Ability2["Copy"] = "copy";
Ability2["Drop"] = "drop";
Ability2["Store"] = "store";
Ability2["Key"] = "key";
return Ability2;
})(Ability || {});
var StructTypeParameter = bcs.Struct("StructTypeParameter", {
/** The type parameter constraints. */
constraints: bcs.U8,
/** Whether the parameter is declared as phantom. */
is_phantom: bcs.Bool
});
var StructHandle = bcs.Struct("StructHandle", {
module: bcs.U16,
name: bcs.U16,
abilities: bcs.U8,
typeParameters: bcs.Vector(StructTypeParameter)
});
var AccessKind = bcs.Enum("AccessKind", {
Reads: null,
Writes: null
});
var ResourceInstantiation = bcs.Struct("ResourceInstantiation", {
StructHandleIndex: bcs.Uleb128,
// StructHandleIndex
SignatureIndex: bcs.Uleb128
// SignatureIndex
});
var ResourceSpecifier = bcs.Enum("ResourceSpecifier", {
/** Any resource */
Any: null,
/** Resource declared at a specific address */
DeclaredAtAddress: bcs.Uleb128,
/** Resource declared in a specific module */
DeclaredInModule: bcs.Uleb128,
/** Resource type */
Resource: bcs.Uleb128,
/** Resource instantiation with type parameters */
ResourceInstantiation
});
var AddressSpecifier = bcs.Enum("AddressSpecifier", {
Any: null,
Literal: bcs.Uleb128,
Parameter: bcs.Uleb128
});
var AccessSpecifier = bcs.Struct("AccessSpecifier", {
/** The kind of access. */
kind: AccessKind,
/** Whether the specifier is negated. */
negated: bcs.Bool,
/** The resource specifier. */
resource: ResourceSpecifier,
/** The address where the resource is stored. */
address: AddressSpecifier
});
var FunctionAttribute = bcs.Enum("FunctionAttribute", {
Persistent: null,
ModuleLock: null
});
var FunctionHandle = bcs.Struct("FunctionHandle", {
module: bcs.U16,
name: bcs.U16,
parameters: bcs.U16,
return_: bcs.U16,
typeParameters: bcs.Vector(bcs.U8),
/** Optional access specifiers for the function. */
access_specifiers: bcs.Option(bcs.Vector(AccessSpecifier)),
/**
* Optional attributes for the function.
* - "Persistent": The function is treated like a public function on upgrade.
* - "ModuleLock": During execution, a module reentrancy lock is established.
*/
attributes: bcs.Vector(FunctionAttribute)
});
var read_next = (deSignatures, version) => {
const next_token_num = deSignatures.deserializeU8();
const token = fromU8(next_token_num);
switch (token) {
case 13 /* U16 */ | 14 /* U32 */ | 15 /* U256 */:
if (version < 6) {
throw new Error(`Unsupported token type: ${token} for version ${version}`);
}
break;
case 16 /* FUNCTION */:
if (version < 8) {
throw new Error(`Unsupported token type: ${token} for version ${version}`);
}
break;
default:
break;
}
let token_value = null;
switch (token) {
case 1 /* BOOL */:
token_value = { kind: "Bool", __signatureTokenBrand: true };
break;
case 2 /* U8 */:
token_value = { kind: "U8", __signatureTokenBrand: true };
break;
case 13 /* U16 */:
token_value = { kind: "U16", __signatureTokenBrand: true };
break;
case 14 /* U32 */:
token_value = { kind: "U32", __signatureTokenBrand: true };
break;
case 3 /* U64 */:
token_value = { kind: "U64", __signatureTokenBrand: true };
break;
case 4 /* U128 */:
token_value = { kind: "U128", __signatureTokenBrand: true };
break;
case 15 /* U256 */:
token_value = { kind: "U256", __signatureTokenBrand: true };
break;
case 5 /* ADDRESS */:
token_value = { kind: "Address", __signatureTokenBrand: true };
break;
case 6 /* REFERENCE */:
token_value = { kind: "Reference", __typeBuilderBrand: true };
break;
case 7 /* MUTABLE_REFERENCE */:
token_value = { kind: "MutableReference", __typeBuilderBrand: true };
break;
case 12 /* SIGNER */:
token_value = { kind: "Signer", __signatureTokenBrand: true };
break;
case 10 /* VECTOR */:
token_value = { kind: "Vector", __typeBuilderBrand: true };
break;
case 8 /* STRUCT */:
token_value = {
kind: "Struct",
__signatureTokenBrand: true,
handle: deSignatures.deserializeU8()
};
break;
case 11 /* STRUCT_INST */: {
const sh_idx = deSignatures.deserializeUleb128AsU32();
const arity = deSignatures.deserializeUleb128AsU32();
if (arity == 0) {
throw new Error(`Unsupported arity 0 for struct instantiation at index ${sh_idx}`);
}
token_value = {
kind: "StructInst",
__typeBuilderBrand: true,
sh_idx,
arity,
ty_args: []
};
break;
}
case 9 /* TYPE_PARAMETER */: {
token_value = {
kind: "TypeParameter",
index: deSignatures.deserializeUleb128AsU32(),
__signatureTokenBrand: true
};
break;
}
case 16 /* FUNCTION */: {
const abilities = deSignatures.deserializeUleb128AsU32();
const arg_count = deSignatures.deserializeUleb128AsU32();
const result_count = deSignatures.deserializeUleb128AsU32();
if (arg_count + result_count == 0) {
token_value = {
kind: "Function",
__signatureTokenBrand: true,
abilities,
arg_count,
result_count,
args: [],
results: []
};
} else {
token_value = {
kind: "Function",
__typeBuilderBrand: true,
abilities,
arg_count,
result_count,
args: [],
results: []
};
}
break;
}
default:
throw new Error(`Unknown serialized type: ${token}`);
}
return token_value;
};
function load_signature_token(deSignatures, version) {
const stack = [];
const token = read_next(deSignatures, version);
if (token == null) {
throw new Error(`Invalid token: ${token}`);
} else if ("__signatureTokenBrand" in token) {
return token;
} else {
stack.push(token);
}
while (1) {
if (stack.length > 256) {
throw new Error(`Stack overflow: ${stack.length} tokens`);
}
const last = stack.at(-1);
if (last == null) {
throw new Error(`Stack is empty`);
} else if (typeof last === "object" && last !== null && "__typeBuilderBrand" in last) {
const next_token = read_next(deSignatures, version);
if (next_token == null) {
throw new Error(`Invalid token: ${next_token}`);
}
stack.push(