UNPKG

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
// 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(