@ton/core
Version:
Core TypeScript library that implements low level primitives for TON blockchain.
237 lines (204 loc) • 6.1 kB
text/typescript
/**
* Copyright (c) Whales Corp.
* All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { beginCell, Builder } from "../boc/Builder";
import { findCommonPrefix } from "./utils/findCommonPrefix";
//
// Tree Build
//
function pad(src: string, size: number) {
while (src.length < size) {
src = '0' + src;
}
return src;
}
type Node<T> =
| {
type: 'fork',
left: Edge<T>,
right: Edge<T>
}
| {
type: 'leaf',
value: T
}
type Edge<T> = {
label: string,
node: Node<T>
};
function removePrefixMap<T>(src: Map<string, T>, length: number) {
if (length === 0) {
return src;
} else {
let res = new Map<string, T>();
for (let k of src.keys()) {
let d = src.get(k)!;
res.set(k.slice(length), d);
}
return res;
}
}
function forkMap<T>(src: Map<string, T>, prefixLen: number) {
if (src.size === 0) {
throw Error('Internal inconsistency');
}
let left = new Map<string, T>();
let right = new Map<string, T>();
for (let [k, d] of src.entries()) {
if (k[prefixLen] === '0') {
left.set(k, d);
} else {
right.set(k, d);
}
}
if (left.size === 0) {
throw Error('Internal inconsistency. Left emtpy.')
}
if (right.size === 0) {
throw Error('Internal inconsistency. Right emtpy.')
}
return { left, right };
}
function buildNode<T>(src: Map<string, T>, prefixLen: number): Node<T> {
if (src.size === 0) {
throw Error('Internal inconsistency');
}
if (src.size === 1) {
return { type: 'leaf', value: Array.from(src.values())[0] };
}
let { left, right } = forkMap(src, prefixLen);
return {
type: 'fork',
left: buildEdge(left, prefixLen + 1),
right: buildEdge(right, prefixLen + 1)
}
}
function buildEdge<T>(src: Map<string, T>, prefixLen = 0): Edge<T> {
if (src.size === 0) {
throw Error('Internal inconsistency');
}
const label = findCommonPrefix(Array.from(src.keys()), prefixLen);
return { label, node: buildNode(src, label.length + prefixLen) };
}
export function buildTree<T>(src: Map<bigint, T>, keyLength: number) {
// Convert map keys
let converted = new Map<string, T>();
for (let k of Array.from(src.keys())) {
const padded = pad(k.toString(2), keyLength);
converted.set(padded, src.get(k)!);
}
// Calculate root label
return buildEdge(converted);
}
//
// Serialization
//
export function writeLabelShort(src: string, to: Builder) {
// Header
to.storeBit(0);
// Unary length
for (let i = 0; i < src.length; i++) {
to.storeBit(1);
}
to.storeBit(0);
// Value
if (src.length > 0) {
to.storeUint(BigInt('0b' + src), src.length);
}
return to;
}
function labelShortLength(src: string) {
return 1 + src.length + 1 + src.length;
}
export function writeLabelLong(src: string, keyLength: number, to: Builder) {
// Header
to.storeBit(1);
to.storeBit(0);
// Length
let length = Math.ceil(Math.log2(keyLength + 1));
to.storeUint(src.length, length);
// Value
if (src.length > 0) {
to.storeUint(BigInt('0b' + src), src.length);
}
return to;
}
function labelLongLength(src: string, keyLength: number) {
return 1 + 1 + Math.ceil(Math.log2(keyLength + 1)) + src.length;
}
export function writeLabelSame(value: number | boolean, length: number, keyLength: number, to: Builder) {
// Header
to.storeBit(1);
to.storeBit(1);
// Value
to.storeBit(value);
// Length
let lenLen = Math.ceil(Math.log2(keyLength + 1));
to.storeUint(length, lenLen);
}
function labelSameLength(keyLength: number) {
return 1 + 1 + 1 + Math.ceil(Math.log2(keyLength + 1));
}
function isSame(src: string) {
if (src.length === 0 || src.length === 1) {
return true;
}
for (let i = 1; i < src.length; i++) {
if (src[i] !== src[0]) {
return false;
}
}
return true;
}
export function detectLabelType(src: string, keyLength: number) {
let kind: 'short' | 'long' | 'same' = 'short';
let kindLength = labelShortLength(src);
let longLength = labelLongLength(src, keyLength);
if (longLength < kindLength) {
kindLength = longLength;
kind = 'long';
}
if (isSame(src)) {
let sameLength = labelSameLength(keyLength);
if (sameLength < kindLength) {
kindLength = sameLength;
kind = 'same';
}
}
return kind;
}
function writeLabel(src: string, keyLength: number, to: Builder) {
let type = detectLabelType(src, keyLength);
if (type === 'short') {
writeLabelShort(src, to);
} else if (type === 'long') {
writeLabelLong(src, keyLength, to);
} else if (type === 'same') {
writeLabelSame(src[0] === '1', src.length, keyLength, to);
}
}
function writeNode<T>(src: Node<T>, keyLength: number, serializer: (src: T, cell: Builder) => void, to: Builder) {
if (src.type === 'leaf') {
serializer(src.value, to);
}
if (src.type === 'fork') {
const leftCell = beginCell();
const rightCell = beginCell();
writeEdge(src.left, keyLength - 1, serializer, leftCell);
writeEdge(src.right, keyLength - 1, serializer, rightCell);
to.storeRef(leftCell);
to.storeRef(rightCell);
}
}
function writeEdge<T>(src: Edge<T>, keyLength: number, serializer: (src: T, cell: Builder) => void, to: Builder) {
writeLabel(src.label, keyLength, to);
writeNode(src.node, keyLength - src.label.length, serializer, to);
}
export function serializeDict<T>(src: Map<bigint, T>, keyLength: number, serializer: (src: T, cell: Builder) => void, to: Builder) {
const tree = buildTree<T>(src, keyLength);
writeEdge(tree, keyLength, serializer, to);
}