import { tryCollectionExpression, tryCollectionStatement } from "./high-level-collections.js";
import { createControlFlowHelpers } from "./high-level-control-flow.js";
import { emitLabelIfNeeded, tryControlTransferFallback } from "./high-level-labels.js";
import { tryInternalCall, tryIndirectCall, trySyscall, tryTokenCall } from "./high-level-calls.js";
import { captureStoreInfo, cloneState, createState, emptyContext, finishInstruction } from "./high-level-state.js";
import {
tryBinaryExpression,
tryControlStatement,
tryStackShapeOperation,
tryUnaryExpression,
} from "./high-level-stack.js";
import {
pushImmediate,
tryLoadLocalOrArg,
tryLoadStatic,
trySlotDeclarations,
tryStoreArgument,
tryStoreLocal,
tryStoreStatic,
} from "./high-level-slots.js";
import { renderUntranslatedInstruction, stripOuterParens } from "./high-level-utils.js";
import {
classifyPermissionContract,
extractContractName,
formatManifestParameters,
formatManifestType,
makeUniqueIdentifier,
sanitizeIdentifier,
} from "./manifest.js";
import { describeCallFlags } from "./nef.js";
import { describeMethodToken } from "./native-contracts.js";
import { upperHex } from "./util.js";
import { postprocess } from "./postprocess.js";
let CONTROL_FLOW;
function liftStructuredSlice(
instructions,
manifestMethod = null,
context = emptyContext(),
methodOffset = instructions[0]?.offset ?? 0,
) {
if (instructions.length === 0) {
return { statements: [], warnings: [] };
}
const result =
CONTROL_FLOW.tryLiftSimpleSwitch(instructions, manifestMethod, context, methodOffset) ??
CONTROL_FLOW.tryLiftSimpleLoop(instructions, manifestMethod, context, methodOffset) ??
CONTROL_FLOW.tryLiftSimpleTryBlock(instructions, manifestMethod, context, methodOffset) ??
CONTROL_FLOW.tryLiftSimpleBranch(instructions, manifestMethod, context, methodOffset) ??
liftStraightLineMethodBody(instructions, manifestMethod, context, undefined, methodOffset);
return result;
}
CONTROL_FLOW = createControlFlowHelpers({
createState,
cloneState,
executeStraightLine,
liftStructuredSlice,
});
export function renderHighLevelMethodGroups(groups, manifest, context = null) {
const contractName = extractContractName(manifest);
const lines = [`contract ${contractName} {`];
const scriptHash = context?.scriptHash;
const scriptHashLE = context?.scriptHashLE;
if (scriptHash) {
lines.push(` // script hash (little-endian): ${scriptHashLE}`);
lines.push(` // script hash (big-endian): ${scriptHash}`);
}
if (context?.compiler) {
lines.push(` // compiler: ${context.compiler}`);
}
if (context?.source) {
lines.push(` // source: ${context.source}`);
}
if (!manifest) {
lines.push(` // manifest not provided`);
}
if (manifest) {
if (manifest.supportedStandards?.length) {
const formatted = manifest.supportedStandards.map((s) => `"${s}"`).join(", ");
lines.push(` supported_standards = [${formatted}];`);
}
if (manifest.features && Object.keys(manifest.features).length > 0) {
lines.push(` features {`);
for (const [key, value] of Object.entries(manifest.features)) {
lines.push(` ${key} = ${JSON.stringify(value)};`);
}
lines.push(` }`);
}
if (manifest.groups?.length) {
lines.push(` groups {`);
for (const group of manifest.groups) {
if (group?.pubkey) {
lines.push(` pubkey=${group.pubkey}`);
}
}
lines.push(` }`);
}
if (manifest.permissions?.length) {
lines.push(` permissions {`);
for (const perm of manifest.permissions) {
const classified = classifyPermissionContract(perm.contract);
const contractPart =
classified.kind === "wildcard"
? `contract=${classified.value}`
: classified.kind === "hash"
? `contract=hash:${classified.hash}`
: classified.kind === "group"
? `contract=group:${classified.group}`
: `contract=${JSON.stringify(classified.value)}`;
const methodsPart =
typeof perm.methods === "string"
? `methods=${perm.methods}`
: Array.isArray(perm.methods)
? `methods=[${perm.methods.map((m) => `"${m}"`).join(", ")}]`
: `methods=${JSON.stringify(perm.methods)}`;
lines.push(` ${contractPart} ${methodsPart}`);
}
lines.push(` }`);
}
if (manifest.trusts !== null && manifest.trusts !== undefined) {
const formatted = formatManifestTrusts(manifest.trusts);
if (formatted !== null) {
lines.push(` trusts = ${formatted};`);
}
}
if (manifest.extra && typeof manifest.extra === "object" && !Array.isArray(manifest.extra)) {
for (const [key, value] of Object.entries(manifest.extra)) {
const rendered = renderExtraScalar(value);
if (rendered !== null) {
lines.push(` // ${key}: ${rendered}`);
}
}
}
if (manifest.abi?.methods?.length) {
lines.push(` // ABI methods`);
for (const method of manifest.abi.methods) {
const params = method.parameters
?.map((p) => `${sanitizeIdentifier(p.name)}: ${formatManifestType(p.kind)}`)
.join(", ") ?? "";
const returnType = formatManifestType(method.returnType ?? "Void");
const sanitisedName = sanitizeIdentifier(method.name);
const meta = [];
if (sanitisedName !== method.name) {
meta.push(`manifest ${JSON.stringify(method.name)}`);
}
if (method.safe) {
meta.push("safe");
}
if (typeof method.offset === "number") {
meta.push(`offset ${method.offset}`);
}
const metaComment = meta.length > 0 ? ` // ${meta.join(", ")}` : "";
lines.push(` fn ${sanitisedName}(${params}) -> ${returnType};${metaComment}`);
}
}
if (manifest.abi?.events?.length) {
lines.push(` // ABI events`);
for (const event of manifest.abi.events) {
const params = event.parameters
?.map((p) => `${sanitizeIdentifier(p.name)}: ${formatManifestType(p.kind)}`)
.join(", ") ?? "";
const sanitised = sanitizeIdentifier(event.name);
const note = sanitised !== event.name ? ` // manifest ${JSON.stringify(event.name)}` : "";
lines.push(` event ${sanitised}(${params});${note}`);
}
}
}
const methodTokens = context?.methodTokens ?? [];
if (methodTokens.length > 0) {
lines.push(` // method tokens declared in NEF`);
for (const token of methodTokens) {
const hint = describeMethodToken(token.hash, token.method);
const contractNote = hint ? ` (${hint.formattedLabel(token.method)})` : "";
const flagsHex = token.callFlags.toString(16).padStart(2, "0").toUpperCase();
lines.push(
` // ${token.method}${contractNote} hash=${upperHex(token.hash)} ` +
`params=${token.parametersCount} returns=${token.hasReturnValue} ` +
`flags=0x${flagsHex} (${describeCallFlags(token.callFlags)})`,
);
if (hint && !hint.hasExactMethod()) {
lines.push(
` // warning: native contract ${hint.contract} does not expose method ${token.method}`,
);
}
}
}
lines.push("");
const used = new Set();
let firstEmitted = true;
for (const group of groups) {
const body = liftMethodBody(group.instructions, group.source, context, group.start);
if (context?.highLevelWarnings) {
context.highLevelWarnings.push(...body.warnings);
}
const isInferred = !group.source;
if (isInferred && body.statements.length === 0) {
continue;
}
if (!firstEmitted) {
lines.push("");
}
firstEmitted = false;
const signature = renderMethodSignature(group, used, context);
lines.push(` ${signature} {`);
if (body.statements.length === 0) {
lines.push(" // no instructions decoded");
} else {
let indentLevel = 0;
for (const statement of body.statements) {
const trimmed = statement.trim();
if (trimmed.startsWith("}")) {
indentLevel = Math.max(0, indentLevel - 1);
}
lines.push(`${" ".repeat(8 + indentLevel * 4)}${trimmed}`);
if (trimmed.endsWith("{")) {
indentLevel += 1;
}
}
}
lines.push(" }");
}
lines.push("}");
return lines.join("\n") + "\n";
}
function renderExtraScalar(value) {
if (typeof value === "string") return value;
if (typeof value === "boolean") return value.toString();
if (typeof value === "number" && Number.isFinite(value)) return value.toString();
if (typeof value === "bigint") return value.toString();
return null;
}
function formatManifestTrusts(trusts) {
if (trusts === "*") {
return "*";
}
if (Array.isArray(trusts)) {
if (trusts.length === 0) {
return "[]";
}
if (trusts.every((entry) => typeof entry === "string")) {
return `[${trusts.map((entry) => `"${entry}"`).join(", ")}]`;
}
return JSON.stringify(trusts);
}
if (trusts && typeof trusts === "object") {
const structured = formatStructuredTrusts(trusts);
if (structured !== null) {
return structured;
}
}
return JSON.stringify(trusts);
}
function formatStructuredTrusts(object) {
const allowedKeys = new Set(["hashes", "groups"]);
for (const key of Object.keys(object)) {
if (!allowedKeys.has(key)) {
return null;
}
}
const hashes = parseTypedTrustEntries(object.hashes, "hash");
if (hashes === null) {
return null;
}
const groups = parseTypedTrustEntries(object.groups, "group");
if (groups === null) {
return null;
}
return `[${[...hashes, ...groups].join(", ")}]`;
}
function parseTypedTrustEntries(value, prefix) {
if (value === undefined || value === null) {
return [];
}
if (!Array.isArray(value)) {
return null;
}
const entries = [];
for (const entry of value) {
if (typeof entry !== "string") {
return null;
}
entries.push(`${prefix}:${entry}`);
}
return entries;
}
function renderMethodSignature(group, used, context = null) {
const name = makeUniqueIdentifier(group.name, used);
const parameters = group.source?.parameters ?? [];
let args;
if (parameters.length > 0) {
args = formatManifestParameters(parameters);
} else {
const inferredArgCount = context?.methodArgCountsByOffset?.get(group.start) ?? 0;
args = Array.from({ length: inferredArgCount }, (_, index) => `arg${index}`).join(", ");
}
const returnType = group.source?.returnType;
const renderedReturnType = returnType ? formatManifestType(returnType) : null;
if (renderedReturnType && renderedReturnType !== "void") {
return `fn ${name}(${args}) -> ${renderedReturnType}`;
}
return `fn ${name}(${args})`;
}
export const MAX_HIGH_LEVEL_METHOD_INSTRUCTIONS = 16384;
export function liftMethodBody(
instructions,
manifestMethod = null,
context = emptyContext(),
methodOffset = instructions[0]?.offset ?? 0,
) {
if (instructions.length > MAX_HIGH_LEVEL_METHOD_INSTRUCTIONS) {
const offsetHex = (instructions[0]?.offset ?? 0)
.toString(16)
.padStart(4, "0")
.toUpperCase();
return {
statements: [
`// method body too large for high-level lifting: ${instructions.length} instructions ` +
`exceeds the ${MAX_HIGH_LEVEL_METHOD_INSTRUCTIONS}-instruction limit; use the disassembler for the full listing`,
],
warnings: [
`high-level: method at 0x${offsetHex} skipped — ${instructions.length} instructions ` +
`exceeds the high-level lifting limit (${MAX_HIGH_LEVEL_METHOD_INSTRUCTIONS})`,
],
};
}
let result;
const switchLift = CONTROL_FLOW.tryLiftSimpleSwitch(instructions, manifestMethod, context, methodOffset);
if (switchLift !== null) {
result = switchLift;
} else {
const loopLift = CONTROL_FLOW.tryLiftSimpleLoop(instructions, manifestMethod, context, methodOffset);
if (loopLift !== null) {
result = loopLift;
} else {
const tryLift = CONTROL_FLOW.tryLiftSimpleTryBlock(instructions, manifestMethod, context, methodOffset);
if (tryLift !== null) {
result = tryLift;
} else {
const branchLift = CONTROL_FLOW.tryLiftSimpleBranch(instructions, manifestMethod, context, methodOffset);
if (branchLift !== null) {
result = branchLift;
} else {
result = liftStraightLineMethodBody(instructions, manifestMethod, context, undefined, methodOffset);
}
}
}
}
postprocess(result.statements, context.postprocessOptions);
return result;
}
function liftStraightLineMethodBody(
instructions,
manifestMethod = null,
context,
initialState,
methodOffset = instructions[0]?.offset ?? 0,
) {
const state = initialState ?? createState(manifestMethod, context, methodOffset, instructions);
executeStraightLine(state, instructions);
if (instructions.at(-1)?.opcode?.mnemonic !== "RET" && state.stack.length > 0) {
for (const expression of state.stack) {
state.statements.push(`${stripOuterParens(expression)};`);
}
state.stack.length = 0;
}
return { statements: state.statements, warnings: state.warnings };
}
function executeStraightLine(state, instructions) {
const {
statements,
initializedLocals,
initializedStatics,
parameterNames,
returnsVoid,
stack,
pointerTargetsByExpression,
pointerTargetsBySlot,
packedValuesByExpression,
packedValuesBySlot,
} = state;
for (const instruction of instructions) {
const mnemonic = instruction.opcode.mnemonic;
emitLabelIfNeeded(state, instruction.offset);
if (trySlotDeclarations(statements, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (pushImmediate(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (mnemonic === "NOP") {
finishInstruction(state, instruction);
continue;
}
if (mnemonic === "ENDFINALLY") {
finishInstruction(state, instruction);
continue;
}
if (tryLoadLocalOrArg(stack, mnemonic, parameterNames, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (tryLoadStatic(stack, mnemonic, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (
tryStoreLocal(
statements,
stack,
initializedLocals,
pointerTargetsByExpression,
pointerTargetsBySlot,
packedValuesByExpression,
packedValuesBySlot,
mnemonic,
instruction,
)
) {
state.previousStoreInfo = captureStoreInfo(instruction, state);
finishInstruction(state, instruction);
continue;
}
if (tryStoreArgument(statements, stack, parameterNames, mnemonic, instruction)) {
state.previousStoreInfo = captureStoreInfo(instruction, state);
finishInstruction(state, instruction);
continue;
}
if (
tryStoreStatic(
statements,
stack,
initializedStatics,
pointerTargetsByExpression,
pointerTargetsBySlot,
packedValuesByExpression,
packedValuesBySlot,
mnemonic,
instruction,
)
) {
state.previousStoreInfo = captureStoreInfo(instruction, state);
finishInstruction(state, instruction);
continue;
}
if (tryBinaryExpression(stack, mnemonic)) {
finishInstruction(state, instruction);
continue;
}
if (tryInternalCall(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (tryIndirectCall(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (tryTokenCall(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (trySyscall(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (tryCollectionExpression(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (tryCollectionStatement(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (tryStackShapeOperation(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (tryUnaryExpression(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (tryControlStatement(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (tryControlTransferFallback(state, instruction)) {
finishInstruction(state, instruction);
continue;
}
if (mnemonic === "RET") {
if (returnsVoid || stack.length === 0) {
statements.push("return;");
} else {
statements.push(`return ${stripOuterParens(stack.pop())};`);
}
finishInstruction(state, instruction);
continue;
}
statements.push(renderUntranslatedInstruction(instruction));
const opcodeName =
instruction.opcode.mnemonic === "UNKNOWN"
? `UNKNOWN_0x${instruction.opcode.byte.toString(16).padStart(2, "0").toUpperCase()}`
: instruction.opcode.mnemonic;
state.warnings.push(
`high-level: 0x${instruction.offset.toString(16).padStart(4, "0").toUpperCase()}: ${opcodeName} (not yet translated)`,
);
finishInstruction(state, instruction);
}
}