neo-decompiler 0.6.2

Minimal tooling for inspecting Neo N3 NEF bytecode
Documentation
import { createHash } from "node:crypto";

export function asUint8Array(value) {
  if (value instanceof Uint8Array) {
    return value;
  }
  if (ArrayBuffer.isView(value)) {
    return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
  }
  if (value instanceof ArrayBuffer) {
    return new Uint8Array(value);
  }
  throw new TypeError("expected Uint8Array-compatible input");
}

export function upperHex(bytes) {
  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0").toUpperCase()).join("");
}

export function readU16LE(bytes, offset) {
  return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint16(offset, true);
}

export function readI16LE(bytes, offset) {
  return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getInt16(offset, true);
}

export function readU32LE(bytes, offset) {
  return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(offset, true);
}

export function readI32LE(bytes, offset) {
  return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getInt32(offset, true);
}

export function readI64LE(bytes, offset) {
  return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getBigInt64(offset, true);
}

export function readU64LE(bytes, offset) {
  return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getBigUint64(offset, true);
}

export function computeChecksum(bytes) {
  const first = createHash("sha256").update(Buffer.from(bytes)).digest();
  const second = createHash("sha256").update(first).digest();
  return new Uint8Array(second.subarray(0, 4));
}

export function computeScriptHash(script) {
  const sha256 = createHash("sha256").update(Buffer.from(script)).digest();
  const ripemd160 = createHash("ripemd160").update(sha256).digest();
  return new Uint8Array(ripemd160);
}

export function scanSlotCounts(instructions) {
  for (const instruction of instructions) {
    if (
      instruction.opcode.mnemonic === "INITSLOT" &&
      instruction.operand?.kind === "Bytes" &&
      instruction.operand.value.length >= 2
    ) {
      return [instruction.operand.value[0], instruction.operand.value[1]];
    }
  }

  let maxLocal = -1;
  let maxArg = -1;
  for (const instruction of instructions) {
    const mnemonic = instruction.opcode.mnemonic;
    if (/^(?:LD|ST)LOC(?:\d+)?$/u.test(mnemonic)) {
      maxLocal = Math.max(maxLocal, slotIndex(mnemonic, instruction));
    }
    if (/^(?:LD|ST)ARG(?:\d+)?$/u.test(mnemonic)) {
      maxArg = Math.max(maxArg, slotIndex(mnemonic, instruction));
    }
  }
  return [maxLocal + 1, maxArg + 1];
}

export function scanStaticSlotCount(instructions) {
  for (const instruction of instructions) {
    if (instruction.opcode.mnemonic === "INITSSLOT" && instruction.operand?.kind === "U8") {
      return instruction.operand.value;
    }
  }
  return 0;
}

export function slotIndex(mnemonic, instruction) {
  const exact = mnemonic.match(/(?:LD|ST)(?:LOC|ARG|SFLD)(\d+)$/u);
  if (exact) {
    return Number(exact[1]);
  }
  if (instruction.operand?.kind === "U8") {
    return instruction.operand.value;
  }
  return 0;
}