neo-decompiler 0.11.0

Neo N3 NEF decompiler: parse, disassemble, lift bytecode to high-level pseudocode and C# skeletons, with a CLI, JSON reports, and optional WebAssembly bindings.
Documentation
import { SYSCALLS } from "./generated/syscalls.js";
import { applyIndexedStackInstruction } from "./entry-stack-indexed.js";
import {
  BINARY_VALUE_MNEMONICS,
  LOAD_SLOT_MNEMONICS,
  ONE_POP_NO_PUSH_MNEMONICS,
  STORE_SLOT_MNEMONICS,
  UNARY_VALUE_MNEMONICS,
  UNKNOWN_PUSH_MNEMONICS,
  ZERO_EFFECT_MNEMONICS,
} from "./entry-stack-opcodes.js";
import { MAX_SIMULATED_POPS, popCount } from "./entry-stack-state.js";

export function applyInstructionForArgInference(instruction, state) {
  const mnemonic = instruction.opcode.mnemonic;
  if (["PICK", "ROLL", "XDROP", "REVERSEN"].includes(mnemonic)) {
    return applyIndexedStackInstruction(mnemonic, state);
  }

  const effect = stackEffectForArgInference(instruction, state.stack);
  if (!effect) {
    return false;
  }
  popCount(state, effect.pops);
  state.stack.push(...effect.pushes);
  return true;
}

function stackEffectForArgInference(instruction, stack) {
  const mnemonic = instruction.opcode.mnemonic;
  const literal = literalPushValue(instruction);
  if (literal !== undefined) {
    return { pops: 0, pushes: [literal] };
  }

  if (ZERO_EFFECT_MNEMONICS.has(mnemonic)) {
    return { pops: 0, pushes: [] };
  }
  if (UNKNOWN_PUSH_MNEMONICS.has(mnemonic) || LOAD_SLOT_MNEMONICS.test(mnemonic)) {
    return { pops: 0, pushes: [null] };
  }
  if (STORE_SLOT_MNEMONICS.test(mnemonic) || ONE_POP_NO_PUSH_MNEMONICS.has(mnemonic)) {
    return { pops: 1, pushes: [] };
  }
  if (UNARY_VALUE_MNEMONICS.has(mnemonic)) {
    return { pops: 1, pushes: [null] };
  }

  switch (mnemonic) {
    case "DUP": {
      const top = stack.at(-1) ?? null;
      return { pops: 1, pushes: [top, top] };
    }
    case "NIP":
      return { pops: 2, pushes: [null] };
    case "OVER":
    case "TUCK":
      return { pops: 2, pushes: [null, null, null] };
    case "SWAP":
      return { pops: 2, pushes: [null, null] };
    case "ROT":
    case "REVERSE3":
      return { pops: 3, pushes: [null, null, null] };
    case "REVERSE4":
      return { pops: 4, pushes: [null, null, null, null] };
    case "APPEND":
    case "REMOVE":
      return { pops: 2, pushes: [] };
    case "SUBSTR":
    case "MODMUL":
    case "MODPOW":
    case "WITHIN":
      return { pops: 3, pushes: [null] };
    case "SETITEM":
      return { pops: 3, pushes: [] };
    case "MEMCPY":
      return { pops: 5, pushes: [] };
    case "PACK":
    case "PACKSTRUCT":
      return packEffect(stack, 1);
    case "PACKMAP":
      return packEffect(stack, 2);
    case "SYSCALL":
      return syscallEffect(instruction);
    default:
      break;
  }

  if (BINARY_VALUE_MNEMONICS.has(mnemonic)) {
    return { pops: 2, pushes: [null] };
  }
  return null;
}

function packEffect(stack, itemWidth) {
  const count = stack.at(-1);
  if (!Number.isInteger(count) || count < 0) {
    return null;
  }
  return {
    pops: Math.min(count * itemWidth + 1, MAX_SIMULATED_POPS),
    pushes: [null],
  };
}

function syscallEffect(instruction) {
  if (instruction.operand?.kind !== "Syscall") {
    return null;
  }
  const info = SYSCALLS.get(instruction.operand.value) ?? null;
  if (!info) {
    return null;
  }
  return {
    pops: info.param_count ?? 0,
    pushes: info.returns_value ?? true ? [null] : [],
  };
}

function literalPushValue(instruction) {
  const mnemonic = instruction.opcode.mnemonic;
  if (mnemonic === "PUSHM1") {
    return null;
  }
  const small = /^PUSH(\d+)$/u.exec(mnemonic);
  if (small) {
    return Number(small[1]);
  }
  if (!["PUSHINT8", "PUSHINT16", "PUSHINT32", "PUSHINT64"].includes(mnemonic)) {
    return undefined;
  }
  const value = integerOperandValue(instruction.operand);
  return value === null ? null : value;
}

function integerOperandValue(operand) {
  if (!operand || !["I8", "I16", "I32", "I64"].includes(operand.kind)) {
    return null;
  }
  const value = Number(operand.value);
  return Number.isSafeInteger(value) && value >= 0 ? value : null;
}