neo-decompiler 0.8.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 { scanSlotCounts, scanStaticSlotCount, slotIndex } from "./util.js";

export function inferTypes(instructions, methodGroups, manifest = null) {
  const staticCount = scanStaticSlotCount(instructions);
  const statics = Array.from({ length: staticCount }, () => "unknown");

  const methods = methodGroups.map((group) => {
    const [localCount, argCount] = scanSlotCounts(group.instructions);
    const locals = Array.from({ length: localCount }, () => "unknown");
    const argumentsTypes = Array.from({ length: argCount }, () => "unknown");

    if (group.source?.parameters) {
      while (argumentsTypes.length < group.source.parameters.length) {
        argumentsTypes.push("unknown");
      }
      for (let index = 0; index < group.source.parameters.length; index += 1) {
        argumentsTypes[index] = manifestType(group.source.parameters[index].kind);
      }
    }

    for (let index = 0; index < group.instructions.length; index += 1) {
      const instruction = group.instructions[index];
      const next = group.instructions[index + 1];
      const store = next?.opcode?.mnemonic;
      switch (instruction.opcode.mnemonic) {
        case "NEWMAP":
        case "PACKMAP":
          assignStoredType(locals, statics, store, next, "map");
          break;
        case "NEWSTRUCT0":
        case "NEWSTRUCT":
        case "PACKSTRUCT":
          assignStoredType(locals, statics, store, next, "struct");
          break;
        case "NEWARRAY0":
        case "NEWARRAY":
        case "NEWARRAY_T":
          assignStoredType(locals, statics, store, next, "array");
          break;
        case "NEWBUFFER":
          assignStoredType(locals, statics, store, next, "buffer");
          break;
        case "CONVERT": {
          const target = convertTargetType(instruction.operand);
          if (target) {
            assignStoredType(locals, statics, store, next, target);
          }
          break;
        }
      }
    }

    return {
      method: { offset: group.start, name: group.name },
      arguments: argumentsTypes,
      locals,
    };
  });

  return { methods, statics };
}

function assignStoredType(locals, statics, store, instruction, type) {
  if (store?.startsWith("STLOC")) {
    locals[slotIndex(store, instruction)] = type;
  } else if (store?.startsWith("STSFLD")) {
    statics[slotIndex(store, instruction)] = type;
  }
}

function manifestType(kind) {
  const normalized = String(kind).toLowerCase();
  if (normalized === "boolean") return "bool";
  if (normalized === "integer") return "integer";
  if (
    normalized === "string" ||
    normalized === "bytearray" ||
    normalized === "signature" ||
    normalized === "hash160" ||
    normalized === "hash256"
  ) {
    return "bytestring";
  }
  if (normalized === "array") return "array";
  if (normalized === "map") return "map";
  if (normalized === "interopinterface") return "interopinterface";
  return "unknown";
}

const CONVERT_TARGET_MAP = new Map([
  [0x00, "any"],
  [0x10, "pointer"],
  [0x20, "bool"],
  [0x21, "integer"],
  [0x28, "bytestring"],
  [0x30, "buffer"],
  [0x40, "array"],
  [0x41, "struct"],
  [0x48, "map"],
  [0x60, "interopinterface"],
]);

function convertTargetType(operand) {
  if (!operand || (operand.kind !== "U8" && operand.kind !== "I8")) {
    return null;
  }
  const byte = operand.kind === "U8" ? operand.value : operand.value & 0xff;
  return CONVERT_TARGET_MAP.get(byte) ?? null;
}