import { buildCallGraph } from "./call-graph.js";
import { NeoDecompilerError, NefParseError, DisassemblyError, ManifestParseError } from "./errors.js";
import { parseNef, parseNefWithWarnings } from "./nef.js";
import { disassembleScript } from "./disassembler.js";
import { inferRequiredEntryStackDepth } from "./entry-stack.js";
import { renderGroupedPseudocode } from "./grouped-pseudocode.js";
import { renderHighLevelMethodGroups } from "./high-level.js";
import { classifyPermissionContract, parseManifest } from "./manifest.js";
import { buildMethodGroups } from "./methods.js";
import { describeMethodToken } from "./native-contracts.js";
import { renderPseudocode } from "./pseudocode.js";
import { inferTypes } from "./types.js";
import { buildXrefs } from "./xrefs.js";
export {
buildCallGraph,
buildMethodGroups,
buildXrefs,
classifyPermissionContract,
inferTypes,
parseManifest,
parseNef,
disassembleScript,
renderGroupedPseudocode,
renderPseudocode,
renderHighLevelMethodGroups,
NeoDecompilerError,
NefParseError,
DisassemblyError,
ManifestParseError,
};
export function decompileBytes(bytes, options = {}) {
const parsed = parseNefWithWarnings(bytes);
const nef = parsed.nef;
const disassembly = disassembleScript(nef.script, options);
return {
nef,
instructions: disassembly.instructions,
warnings: [...parsed.warnings, ...disassembly.warnings],
pseudocode: renderPseudocode(disassembly.instructions),
};
}
export function analyzeBytes(bytes, manifestInput = null, options = {}) {
const manifest = manifestInput ? parseManifest(manifestInput) : null;
const result = decompileBytes(bytes, options);
const methodGroups = buildMethodGroups(result.instructions, manifest);
const analysisGroups = buildMethodGroups(result.instructions, manifest, {
includePostTerminatorTails: false,
});
return {
...result,
manifest,
methodGroups,
callGraph: buildCallGraph(result.nef, result.instructions, analysisGroups),
xrefs: buildXrefs(result.instructions, analysisGroups),
types: inferTypes(result.instructions, analysisGroups, manifest, result.nef.methodTokens),
};
}
export function decompileBytesWithManifest(bytes, manifestInput, options = {}) {
const manifest = parseManifest(manifestInput);
const result = decompileBytes(bytes, options);
const methodGroups = buildMethodGroups(result.instructions, manifest);
return {
...result,
manifest,
methodGroups,
groupedPseudocode: renderGroupedPseudocode(methodGroups, manifest),
};
}
export function decompileHighLevelBytes(bytes, options = {}) {
const result = decompileBytes(bytes, options);
const methodGroups = buildMethodGroups(result.instructions, null);
const context = buildHighLevelContext(methodGroups, result.nef, options, null, result.instructions);
const highLevel = renderHighLevelMethodGroups(methodGroups, null, context);
return {
...result,
warnings: [...result.warnings, ...context.highLevelWarnings],
methodGroups,
highLevel,
};
}
export function decompileHighLevelBytesWithManifest(bytes, manifestInput, options = {}) {
const manifest = parseManifest(manifestInput);
const result = decompileBytes(bytes, options);
const methodGroups = buildMethodGroups(result.instructions, manifest);
const context = buildHighLevelContext(
methodGroups,
result.nef,
options,
manifest,
result.instructions,
);
const highLevel = renderHighLevelMethodGroups(methodGroups, manifest, context);
return {
...result,
warnings: [...result.warnings, ...context.highLevelWarnings],
manifest,
methodGroups,
highLevel,
groupedPseudocode: renderGroupedPseudocode(methodGroups, manifest),
};
}
function buildHighLevelContext(methodGroups, nef, options = {}, manifest = null, instructions = []) {
const entryOffset = methodGroups[0]?.start ?? 0;
const slotTypesByOffset = options.typedDeclarations
? buildSlotTypesByOffset(inferTypes(instructions, methodGroups, manifest, nef.methodTokens))
: new Map();
return {
methodLabelsByOffset: new Map(methodGroups.map((group) => [group.start, group.name])),
methodArgCountsByOffset: new Map(
methodGroups.map((group) => [group.start, inferMethodArgCount(group, entryOffset)]),
),
calltLabels: nef.methodTokens.map((token) => {
const hint = describeMethodToken(token.hash, token.method);
return hint ? hint.formattedLabel(token.method) : token.method;
}),
calltParamCounts: nef.methodTokens.map((token) => token.parametersCount),
calltReturnsValue: nef.methodTokens.map((token) => token.hasReturnValue),
methodTokens: nef.methodTokens,
scriptHash: nef.scriptHash,
scriptHashLE: nef.scriptHashLE,
compiler: nef.header?.compiler,
source: nef.header?.source,
highLevelWarnings: [],
typedDeclarations: !!options.typedDeclarations,
slotTypesByOffset,
postprocessOptions: {
inlineSingleUseTemps:
!!options.inlineSingleUseTemps || !!options.clean,
clean: !!options.clean,
},
};
}
function buildSlotTypesByOffset(types) {
const statics = types.statics.map(inferredTypeToPseudo);
return new Map(
types.methods.map((method) => [
method.method.offset,
{
arguments: method.arguments.map(inferredTypeToPseudo),
locals: method.locals.map(inferredTypeToPseudo),
statics,
},
]),
);
}
function inferredTypeToPseudo(ty) {
switch (ty) {
case "any":
case "null":
return "any";
case "bool":
return "bool";
case "integer":
return "int";
case "bytestring":
case "buffer":
return "byte[]";
case "array":
case "struct":
return "object[]";
case "map":
return "map";
case "interopinterface":
return "interop";
case "pointer":
return "pointer";
default:
return "";
}
}
function inferMethodArgCount(group, entryOffset) {
if (group.source?.parameters) {
return group.source.parameters.length;
}
const first = group.instructions[0];
if (first?.opcode?.mnemonic === "INITSLOT" && first.operand?.kind === "Bytes" && first.operand.value.length >= 2) {
return first.operand.value[1];
}
if (group.start === entryOffset) {
return 0;
}
return inferRequiredEntryStackDepth(group.instructions);
}