ton-assembly
Version:
TON assembler and disassembler
615 lines • 20.6 kB
JavaScript
;
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