import { SYSCALLS } from "./generated/syscalls.js";
import { jumpTarget, stripOuterParens } from "./high-level-utils.js";
import { hex16, hex32, hexOffset, upperHex } from "./util.js";
import { lookup as lookupNativeContract } from "./native-contracts.js";
const CONTRACT_CALL_HASH = 0x525b7d62;
const CONTRACT_CALL_NATIVE_HASH = 0x677bf71a;
export function tryInternalCall(state, instruction) {
const mnemonic = instruction.opcode.mnemonic;
if (mnemonic !== "CALL" && mnemonic !== "CALL_L") {
return false;
}
const target = jumpTarget(instruction);
if (target === null) {
return false;
}
const callee =
state.context.methodLabelsByOffset.get(target) ?? `call_0x${hexOffset(target)}`;
const argCount = state.context.methodArgCountsByOffset.get(target) ?? 0;
const args = popCallArguments(state, instruction, callee, argCount);
const temp = `t${state.nextTempId}`;
state.nextTempId += 1;
state.statements.push(`let ${temp} = ${callee}(${args.join(", ")});`);
state.stack.push(temp);
return true;
}
export function tryIndirectCall(state, instruction) {
if (instruction.opcode.mnemonic !== "CALLA") {
return false;
}
const targetExpr = stripOuterParens(state.stack.pop() ?? "???");
const resolvedTarget =
state.pointerTargetsByExpression.get(targetExpr) ??
state.pointerTargetsBySlot.get(targetExpr) ??
null;
if (resolvedTarget !== null) {
const callee =
state.context.methodLabelsByOffset.get(resolvedTarget) ??
`sub_0x${hexOffset(resolvedTarget)}`;
const argCount = state.context.methodArgCountsByOffset.get(resolvedTarget) ?? 0;
const args = popCallArguments(state, instruction, callee, argCount);
state.stack.push(`${callee}(${args.join(", ")})`);
} else {
state.stack.push(`calla(${targetExpr})`);
}
return true;
}
export function tryTokenCall(state, instruction) {
if (instruction.opcode.mnemonic !== "CALLT") {
return false;
}
const index = instruction.operand?.kind === "U16" ? instruction.operand.value : null;
if (index === null) {
return false;
}
const resolved = state.context.calltLabels[index];
if (resolved === undefined) {
const temp = `t${state.nextTempId}`;
state.nextTempId += 1;
state.statements.push(`let ${temp} = callt(0x${hex16(index)});`);
state.stack.push(temp);
return true;
}
const argCount = state.context.calltParamCounts[index] ?? 0;
const returnsValue = state.context.calltReturnsValue[index] ?? true;
const args = popCallArguments(state, instruction, resolved, argCount);
const expression = `${resolved}(${args.join(", ")})`;
if (returnsValue) {
state.stack.push(expression);
} else {
state.statements.push(`${expression};`);
}
return true;
}
function popCallArguments(state, instruction, calleeLabel, argCount) {
const args = [];
let missingArgument = false;
for (let index = 0; index < argCount; index += 1) {
const value = state.stack.pop();
if (value === undefined) {
missingArgument = true;
args.push("???");
} else {
args.push(stripOuterParens(value));
}
}
if (missingArgument) {
const message = `missing call argument values for ${calleeLabel} (substituted ???)`;
state.statements.push(`// warning: ${message}`);
state.warnings.push(
`high-level: 0x${instruction.offset.toString(16).padStart(4, "0").toUpperCase()}: ${message}`,
);
}
return args;
}
export function trySyscall(state, instruction) {
if (instruction.opcode.mnemonic !== "SYSCALL") {
return false;
}
const hash =
instruction.operand?.kind === "Syscall" ? instruction.operand.value : null;
if (hash === null) {
return false;
}
if (hash === CONTRACT_CALL_HASH) {
return tryContractCall(state, instruction);
}
if (hash === CONTRACT_CALL_NATIVE_HASH) {
state.stack.pop();
state.statements.push('System.Contract.CallNative();');
return true;
}
const info = SYSCALLS.get(hash) ?? null;
const argCount = info?.param_count ?? 0;
const returnsValue = info?.returns_value ?? true;
const args = [];
let missingArgument = false;
for (let index = 0; index < argCount; index += 1) {
const value = state.stack.pop();
if (value === undefined) {
missingArgument = true;
args.push("???");
} else {
args.push(stripOuterParens(value));
}
}
const call = info
? args.length > 0
? `${info.name}(${args.join(", ")})`
: `${info.name}()`
: `syscall(0x${hex32(hash)})`;
if (missingArgument && info) {
let message = `missing syscall argument values for ${info.name} (substituted ???)`;
const context = missingSyscallArgumentContext(state, info.name);
if (context) {
message += `; ${context}`;
}
state.statements.push(`// warning: ${message}`);
state.warnings.push(
`high-level: 0x${instruction.offset.toString(16).padStart(4, "0").toUpperCase()}: ${message}`,
);
}
if (!info) {
const message = `unknown syscall 0x${hex32(hash)}`;
state.statements.push(`// warning: ${message}`);
state.warnings.push(
`high-level: 0x${instruction.offset.toString(16).padStart(4, "0").toUpperCase()}: ${message}`,
);
}
if (returnsValue) {
const temp = `t${state.nextTempId}`;
state.nextTempId += 1;
state.statements.push(`let ${temp} = ${call};`);
state.stack.push(temp);
} else {
state.statements.push(`${call};`);
}
return true;
}
function tryContractCall(state, instruction) {
const contractHashExpr = state.stack.pop();
const methodExpr = state.stack.pop();
const flagsExpr = state.stack.pop();
const argsExpr = state.stack.pop();
const contractHashBytes = contractHashBytesFromLiteral(state, contractHashExpr);
if (contractHashBytes === null) {
state.statements.push(
`System.Contract.Call(${contractHashExpr ?? "???"}, ${methodExpr ?? "???"}, ${flagsExpr ?? "???"}, ${argsExpr ?? "???"});`,
);
if (instruction?.operand?.value !== undefined) {
state.warnings.push(
`high-level: 0x${instruction.offset.toString(16).padStart(4, "0").toUpperCase()}: contract-call hash was not a tracked 20-byte literal; fell back to syscall form`,
);
}
return true;
}
const contractLabel = contractLabelFor(contractHashBytes);
const methodLabel = methodLabelFor(state, methodExpr);
const argsLabel = argsExpr ?? "???";
const callExpr = `${contractLabel}.${methodLabel}(${argsLabel})`;
const temp = `t${state.nextTempId}`;
state.nextTempId += 1;
state.statements.push(`let ${temp} = ${callExpr};`);
state.stack.push(temp);
return true;
}
function contractLabelFor(hashBytes) {
const contract = lookupNativeContract(hashBytes);
if (contract) {
return contract.name;
}
return `0x${upperHex(hashBytes)}`;
}
function methodLabelFor(state, methodExpr) {
if (methodExpr === undefined) {
return "?";
}
const literal = state?.literalValues?.get?.(methodExpr);
if (literal && literal.kind === "String") {
return literal.value;
}
return methodExpr;
}
function contractHashBytesFromLiteral(state, contractHashExpr) {
if (!contractHashExpr) {
return null;
}
const literal = state?.literalValues?.get?.(contractHashExpr);
if (literal && literal.kind === "ContractHash") {
return Array.from(literal.value);
}
return null;
}
function missingSyscallArgumentContext(state, syscallName) {
const previousInstruction = state.previousInstruction;
const previousStoreInfo = state.previousStoreInfo;
if (
!previousInstruction ||
!previousStoreInfo ||
previousInstruction.offset !== previousStoreInfo.offset
) {
return null;
}
const storedValue = previousStoreInfo.storedPacked
? "a packed value"
: "the last produced value";
return `preceding ${previousStoreInfo.opcode} stored ${storedValue} into ${previousStoreInfo.slotLabel}; no value remains on the evaluation stack before ${syscallName}`;
}