UNPKG

ton-assembly

Version:

TON assembler and disassembler

615 lines 20.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.instructionNameForOpcode = exports.exotic = exports.LibraryCell = exports.DefaultExoticCell = exports.exoticCellBody = exports.boc = exports.bin = exports.hex = exports.PSEUDO_EXOTIC = exports.PSEUDO_PUSHREF_ALWAYS = exports.PSEUDO_PUSHREF = exports.PSEUDO_PUSHSLICE = exports.hash = exports.delta = exports.setcpArg = exports.s1 = exports.minusOne = exports.runvmArg = exports.largeInt = exports.tinyInt = exports.plduzArg = exports.control = exports.refs = exports.debugstr = exports.dictpush = exports.dictionary = exports.dictMap = exports.decompiledDict = exports.rawDict = exports.decompiledMethod = exports.slice = exports.inlineCodeSlice = exports.refCodeSlice = exports.codeSlice = exports.decompiledCode = exports.code = exports.rawCode = exports.int = exports.Loc = exports.Hash = void 0; exports.uint = uint; const core_1 = require("@ton/core"); const c = __importStar(require("./constructors")); const instr_1 = require("./instr"); const builder_1 = require("./builder"); const Dictionary_1 = require("../dict/Dictionary"); const constructors_1 = require("./constructors"); const instr_mapping_gen_1 = require("./instr-mapping-gen"); const compile_1 = require("./compile"); // TODO: split: // 1. like `constructors.ts` // 2. like `types.ts` // // TODO: add more range checks in load and store of every type var Hash; (function (Hash) { Hash[Hash["SHA256"] = 0] = "SHA256"; Hash[Hash["SHA512"] = 1] = "SHA512"; Hash[Hash["BLAKE2B"] = 2] = "BLAKE2B"; Hash[Hash["KECCAK256"] = 3] = "KECCAK256"; Hash[Hash["KECCAK512"] = 4] = "KECCAK512"; })(Hash || (exports.Hash = Hash = {})); const Loc = (file, line) => ({ file, line }); exports.Loc = Loc; function uint(bits) { return { store: (b, t, _options) => b.storeUint(t, bits), load: s => s.loadUint(bits), }; } const int = (bits) => ({ store: (b, t, _options) => b.storeInt(t, bits), load: s => s.loadInt(bits), }); exports.int = int; const rawCode = (slice) => ({ $: "Raw", slice, }); exports.rawCode = rawCode; const code = (instructions) => ({ $: "Instructions", instructions, }); exports.code = code; const decompiledCode = (instructions) => ({ $: "Instructions", instructions, }); exports.decompiledCode = decompiledCode; const processMappingInstructions = (mapping, b) => mapping.instructions.map(({ instr, offset, debugSection }) => ({ instr, offset: offset + b.bits, debugSection, })); const codeSlice = (refs, bits) => { return { store: (b, code, options) => { // TODO: extract logic of serialization to codeType if (code.$ === "Instructions") { const [cell, mapping] = (0, instr_1.compileCellWithMapping)(code.instructions, options); const slice = cell.asSlice(); refs.store(b, slice.remainingRefs, options); const length = slice.remainingBits; const y = Math.ceil(length / 8); bits.store(b, y, options); const instructions = processMappingInstructions(mapping, b); b.pushInstructions(...instructions); b.pushMappings(...mapping.subMappings); b.pushDictionaryInfo(...mapping.dictionaryInfo); b.storeSlice(slice); return; } const slice = code.slice; refs.store(b, slice.remainingRefs, options); const length = slice.remainingBits; const y = Math.ceil(length / 8); bits.store(b, y, options); b.storeSlice(slice); }, load: s => { const countRefs = refs.load(s); const y = bits.load(s); const realLength = y * 8; const r = s.loadBits(realLength); const b = new builder_1.CodeBuilder(); b.storeBits(r); for (let i = 0; i < countRefs; i++) { b.storeRef(s.loadRef()); } const slice = b.asSlice(); // TODO: move to codeType and rename to code try { return (0, exports.decompiledCode)((0, instr_1.codeType)().load(slice)); } catch { // continue with fallback } return (0, exports.rawCode)(slice); }, }; }; exports.codeSlice = codeSlice; // TODO: ref(code) === ^code exports.refCodeSlice = { store: (b, code, options) => { if (code.$ === "Instructions") { const [cell, mapping] = (0, instr_1.compileCellWithMapping)(code.instructions, options); b.storeRef(cell); b.pushMappings(mapping); return; } b.storeRef(code.slice.asCell()); }, load: s => { const cell = s.loadRef(); try { return (0, exports.decompiledCode)(processCell(cell)); } catch { // continue with fallback } return (0, exports.rawCode)(cell.beginParse(true)); }, }; const processCell = (cell) => { if (cell.isExotic) { return [c.PSEUDO_EXOTIC(exports.exotic.load(cell.beginParse(true)))]; } return (0, instr_1.codeType)().load(cell.asSlice()); }; const inlineCodeSlice = (bits) => { return { store: (b, code, options) => { if (code.$ === "Raw") { const slice = code.slice; const length = slice.remainingBits; const y = Math.ceil(length / 8); bits.store(b, y, options); b.storeSlice(slice); } else { const [cell, mapping] = (0, instr_1.compileCellWithMapping)(code.instructions, options); const slice = cell.asSlice(); const length = slice.remainingBits; const y = Math.ceil(length / 8); bits.store(b, y, options); const instructions = processMappingInstructions(mapping, b); b.pushInstructions(...instructions); b.pushMappings(...mapping.subMappings); b.pushDictionaryInfo(...mapping.dictionaryInfo); b.storeSlice(slice); } }, load: s => { const y = bits.load(s); const realLength = y * 8; const r = s.loadBits(realLength); const b = new builder_1.CodeBuilder(); b.storeBits(r); const slice = b.asSlice(); try { return (0, exports.decompiledCode)((0, instr_1.codeType)().load(slice)); } catch { // continue with fallback } return (0, exports.rawCode)(slice); }, }; }; exports.inlineCodeSlice = inlineCodeSlice; const slice = (refs, // TODO: remove union bits, pad) => { return { store: (b, slice, options) => { if (typeof refs !== "number") { refs.store(b, slice.remainingRefs, options); } const length = slice.remainingBits + 1; const y = Math.ceil((length - pad) / 8); bits.store(b, y, options); b.storeSlice(slice); b.storeUint(0x1, 1); const realLength = y * 8 + pad; b.storeUint(0x0, realLength - length); }, load: s => { const countRefs = typeof refs === "number" ? refs : refs.load(s); const y = bits.load(s); const realLength = y * 8 + pad; const r = s.loadBits(realLength); let length = 0; for (let i = realLength - 1; i >= 0; i--) { if (!r.at(i)) { // skip zeroes continue; } // found first 1, trim all after, exclusive length = i; break; } const realData = r.substring(0, length); const b = new builder_1.CodeBuilder(); b.storeBits(realData); for (let i = 0; i < countRefs; i++) { b.storeRef(s.loadRef()); } return b.asSlice(); }, }; }; exports.slice = slice; const decompiledMethod = (id, instructions) => ({ $: "DecompiledMethod", id, instructions, }); exports.decompiledMethod = decompiledMethod; const rawDict = (slice) => ({ $: "RawDict", slice, }); exports.rawDict = rawDict; const decompiledDict = (methods) => ({ $: "DecompiledDict", methods, }); exports.decompiledDict = decompiledDict; const dictMap = (mapping) => { return (0, exports.decompiledDict)([...mapping].map(([id, instructions]) => ({ $: "DecompiledMethod", id, instructions, }))); }; exports.dictMap = dictMap; const codeDictValue = { serialize: (src, builder) => { builder.pushDictionaryInfo({ builder, childCell: src, offset: builder.bits, }); builder.storeBits(src.bits); for (const ref of src.refs) { builder.storeRef(ref); } }, parse: (src) => { return src.clone(false).asCell(); }, }; const dictionary = (keyLength) => { return { load: slice => { const dictCell = slice.asCell(); const dict = Dictionary_1.Dictionary.loadDirect(Dictionary_1.Dictionary.Keys.Int(keyLength), codeDictValue, dictCell); const methods = [...dict].map(([key, cell]) => { return (0, exports.decompiledMethod)(key, (0, instr_1.codeType)().load(cell.asSlice())); }); // const b = new CodeBuilder() // dictpush.store(b, [keyLength, decompiledDict(methods)]) // const sliceAfter = b.asSlice() // const cellAfter = sliceAfter.loadRef() // // if (dictCell.toString() !== cellAfter.toString()) { // // We cannot compile back to equal cell // console.log(" (cannot decompile and compile back dict, fallback)") // return rawDict(slice) // } return (0, exports.decompiledDict)(methods); }, store: (b, dict, options) => { if (dict.$ === "RawDict") { b.storeRef(dict.slice.asCell()); } if (dict.$ === "DecompiledDict") { const dictMappings = []; const dictionary = Dictionary_1.Dictionary.empty(Dictionary_1.Dictionary.Keys.Int(keyLength), codeDictValue); for (const method of dict.methods) { const { id, instructions } = method; const [cell, mapping] = (0, instr_1.compileCellWithMapping)(instructions, { ...options, }, true, 16); dictMappings.push(mapping); dictionary.set(id, cell); } b.pushMappings(...dictMappings); const codeBuilder = new builder_1.CodeBuilder(); b.storeRef(codeBuilder.storeDictionaryDirect(dictionary).endCell()); b.pushDictionaryInfo(...codeBuilder.getDictionaryInfo()); } }, }; }; exports.dictionary = dictionary; exports.dictpush = { load(s) { const keyLength = s.loadUint(10); const dictCell = s.loadRef(); if (dictCell.bits.length === 0) { throw new Error("unexpected empty dictionary"); } try { const dict = (0, exports.dictionary)(keyLength).load(dictCell.asSlice()); return [keyLength, dict]; } catch { // fallback } return [keyLength, (0, exports.rawDict)(dictCell.beginParse(true))]; }, store(b, [keyLength, dict], options) { b.storeUint(keyLength, 10); (0, exports.dictionary)(keyLength).store(b, dict, options); }, }; exports.debugstr = { load(s) { const y = uint(4).load(s); const realLength = (y + 1) * 8; const r = s.loadBits(realLength); const b = new builder_1.CodeBuilder(); b.storeBits(r); return b.asSlice(); }, store(b, slice) { const length = slice.remainingBits; if (length < 8) { throw new Error(`DEBUGSTR slice should be larger that 8 bits, but ${length}-bit slice given`); } if (length % 8 !== 0) { throw new Error(`DEBUGSTR slice should be byte aligned, but ${length}-bit slice given`); } const y = Math.ceil((length - 8) / 8); b.storeUint(y, 4); b.storeSlice(slice); }, }; // TODO: revert const refs = (count) => { return count; }; exports.refs = refs; const uint3 = uint(3); const uint4 = uint(4); const uint5 = uint(5); const uint8 = uint(8); const uint12 = uint(12); exports.control = { store: (b, t, options) => { if (t === 6) { throw new Error("c6 doesn't exist"); } uint4.store(b, t, options); }, load: s => { const r = uint4.load(s); if (r === 6) { throw new Error("Invalid opcode: c6 doesn't exist"); } return r; }, }; exports.plduzArg = { store: (b, t, options) => { uint3.store(b, ((t >> 5) - 1) & 7, options); }, load: s => ((uint3.load(s) & 7) + 1) << 5, }; // special case: [-5, 10] exports.tinyInt = { store: (b, t, options) => { if (t < -5 || t > 10) { throw new Error(`Number must be in range [-5, 10]: ${t}`); } uint4.store(b, (t + 16) & 15, options); }, load: s => ((uint4.load(s) + 5) & 15) - 5, }; exports.largeInt = { store: (b, t, options) => { const len = t === 0n ? 1 : t.toString(2).length + (t < 0n ? 0 : 1); const len2 = Math.trunc((len + 7) / 8) - 2; if (len2 <= 0 || len2 >= 32) { // TODO: maybe 52 as in TVM?????????????????? b.storeUint(t, 24); return; } const countBits = Math.ceil((len - 19) / 8); uint5.store(b, countBits, options); const intCountBits = 8 * countBits + 19; b.storeInt(t, intCountBits); }, load: s => s.loadIntBig(3 + ((uint5.load(s) & 31) + 2) * 8), }; exports.runvmArg = { store: (b, t, options) => { uint12.store(b, t, options); }, load: s => uint12.load(s), }; // special case: CALLXARGS $ -1 exports.minusOne = { store: (_b, t, _options) => { if (t !== -1) { throw new Error("This opcode only takes -1"); } }, load: _s => -1, }; exports.s1 = { store: (_b, t, _options) => { if (t !== 1) { throw new Error("This opcode only takes s1"); } }, load: _s => 1, }; exports.setcpArg = { store: (b, t, options) => { if (t < -15 || t > 239) { throw new Error(`Number must be in range [-15, 239]: ${t}`); } uint4.store(b, (t + 0x10) & 0xff, options); }, load: s => ((uint4.load(s) + 0x10) & 0xff) - 0x10, }; const delta = (n, ty) => ({ store: (b, t, options) => { ty.store(b, t - n, options); }, load: s => ty.load(s) + n, }); exports.delta = delta; exports.hash = { store: (b, t, options) => { uint8.store(b, t, options); }, load: s => { const r = uint8.load(s); if (!(r in Hash)) { throw new Error("Wrong hash"); } return r; }, }; // TODO: slice exports.PSEUDO_PUSHSLICE = { load: _s => { throw new Error("unexpected PSEUDO_PUSHSLICE"); }, store: (b, val) => { b.storeSlice(val.arg0); }, }; // TODO: rename to ref exports.PSEUDO_PUSHREF = { load: _s => { throw new Error("unexpected PSEUDO_PUSHREF"); }, store: (b, val, options) => { if (val.arg0.$ === "Raw") { b.storeRef(val.arg0.slice.asCell()); } else { if (options.skipRefs) { // compile instructions without ref at all in place (0, compile_1.compileInstructions)(b, val.arg0.instructions, options); return; } exports.PSEUDO_PUSHREF_ALWAYS.store(b, val, options); } }, }; exports.PSEUDO_PUSHREF_ALWAYS = { load: _s => { throw new Error("unexpected PSEUDO_PUSHREF"); }, store: (b, val, options) => { if (val.arg0.$ === "Raw") { b.storeRef(val.arg0.slice.asCell()); } else { const [cell, mapping] = (0, instr_1.compileCellWithMapping)(val.arg0.instructions, options); // implicit JMPREF mapping.instructions.splice(0, 0, { offset: 0, instr: (0, constructors_1.JMPREF)(val.arg0, val.loc), debugSection: -1, }); b.storeRefWithMapping([cell, mapping]); } }, }; // TODO: exotic exports.PSEUDO_EXOTIC = { load: _s => { throw new Error("unexpected PSEUDO_EXOTIC"); }, store: (b, val, options) => { exports.exotic.store(b, val.arg0, options); }, }; const hex = (value) => { const b = new builder_1.CodeBuilder(); let res = ""; for (const ch of value) { if (ch === "_") break; res += Number.parseInt(ch, 16).toString(2).padStart(4, "0"); } if (value.endsWith("_")) { res = res.replace(/10*$/, ""); // TODO: rewrite } for (const ch of res) { b.storeBit(ch === "1"); } return b.asSlice(); }; exports.hex = hex; const bin = (value) => { const b = new builder_1.CodeBuilder(); for (const ch of value) { b.storeBit(ch === "1"); } return b.asSlice(); }; exports.bin = bin; // TODO: currently we don't have binary compatibility with BoCs, only with cells const boc = (value) => { return core_1.Cell.fromHex(value).asSlice(); }; exports.boc = boc; // TODO: add libraryCell etc. const exoticCellBody = (value) => { const slice = (0, exports.hex)(value); const bits = slice.loadBits(slice.remainingBits); return new core_1.Cell({ exotic: true, bits, refs: [], }); }; exports.exoticCellBody = exoticCellBody; const DefaultExoticCell = (cell) => ({ $: "DefaultExoticCell", cell, }); exports.DefaultExoticCell = DefaultExoticCell; const LibraryCell = (data) => ({ $: "LibraryCell", data, }); exports.LibraryCell = LibraryCell; exports.exotic = { load: s => { const cell = s.asCell(); const type = s.loadUint(8); if (type === 2) { return (0, exports.LibraryCell)(s); } return (0, exports.DefaultExoticCell)(cell); }, store: (b, t, _options) => { if (t.$ === "DefaultExoticCell") { b.storeSlice(t.cell.asSlice()); return; } b.storeUint(2, 8); // cell type b.storeSlice(t.data); }, }; const instructionNameForOpcode = (opcode) => { return instr_mapping_gen_1.rangeToName.find(it => opcode >= it.min && opcode < it.max)?.name; }; exports.instructionNameForOpcode = instructionNameForOpcode; //# sourceMappingURL=util.js.map