@ton/core
Version:
Core TypeScript library that implements low level primitives for TON blockchain.
250 lines (222 loc) • 6.68 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 { inspectSymbol } from "../inspect";
import { BitString } from "./BitString";
import { CellType } from "./CellType";
import { Slice } from "./Slice";
import { LevelMask } from "./cell/LevelMask";
import { resolveExotic } from "./cell/resolveExotic";
import { wonderCalculator } from "./cell/wonderCalculator";
import { deserializeBoc, serializeBoc } from "./cell/serialization";
import { BitReader } from "./BitReader";
import { beginCell } from "./Builder";
/**
* Cell as described in TVM spec
*/
export class Cell {
static readonly EMPTY = new Cell();
/**
* Deserialize cells from BOC
* @param src source buffer
* @returns array of cells
*/
static fromBoc(src: Buffer) {
return deserializeBoc(src);
}
/**
* Helper function that deserializes a single cell from BOC in base64
* @param src source string
*/
static fromBase64(src: string): Cell {
let parsed = Cell.fromBoc(Buffer.from(src, "base64"));
if (parsed.length !== 1) {
throw new Error("Deserialized more than one cell");
}
return parsed[0];
}
/**
* Helper function that deserializes a single cell from BOC in hex
* @param src source string
*/
static fromHex(src: string): Cell {
let parsed = Cell.fromBoc(Buffer.from(src, "hex"));
if (parsed.length !== 1) {
throw new Error("Deserialized more than one cell");
}
return parsed[0];
}
// Public properties
readonly type: CellType;
readonly bits: BitString;
readonly refs: Cell[];
readonly mask: LevelMask;
// Level and depth information
private _hashes: Buffer[] = [];
private _depths: number[] = [];
constructor(opts?: { exotic?: boolean; bits?: BitString; refs?: Cell[] }) {
// Resolve bits
let bits = BitString.EMPTY;
if (opts && opts.bits) {
bits = opts.bits;
}
// Resolve refs
let refs: Cell[] = [];
if (opts && opts.refs) {
refs = [...opts.refs];
}
// Resolve type
let hashes: Buffer[];
let depths: number[];
let mask: LevelMask;
let type = CellType.Ordinary;
if (opts && opts.exotic) {
// Resolve exotic cell
let resolved = resolveExotic(bits, refs);
// Perform wonders
let wonders = wonderCalculator(resolved.type, bits, refs);
// Copy results
mask = wonders.mask;
depths = wonders.depths;
hashes = wonders.hashes;
type = resolved.type;
} else {
// Check correctness
if (refs.length > 4) {
throw new Error("Invalid number of references");
}
if (bits.length > 1023) {
throw new Error(`Bits overflow: ${bits.length} > 1023`);
}
// Perform wonders
let wonders = wonderCalculator(CellType.Ordinary, bits, refs);
// Copy results
mask = wonders.mask;
depths = wonders.depths;
hashes = wonders.hashes;
type = CellType.Ordinary;
}
// Set fields
this.type = type;
this.bits = bits;
this.refs = refs;
this.mask = mask;
this._depths = depths;
this._hashes = hashes;
Object.freeze(this);
Object.freeze(this.refs);
Object.freeze(this.bits);
Object.freeze(this.mask);
Object.freeze(this._depths);
Object.freeze(this._hashes);
}
/**
* Check if cell is exotic
*/
get isExotic() {
return this.type !== CellType.Ordinary;
}
/**
* Beging cell parsing
* @returns a new slice
*/
beginParse = (allowExotic: boolean = false) => {
if (this.isExotic && !allowExotic) {
throw new Error("Exotic cells cannot be parsed");
}
return new Slice(new BitReader(this.bits), this.refs);
};
/**
* Get cell hash
* @param level level
* @returns cell hash
*/
hash = (level: number = 3): Buffer => {
return this._hashes[Math.min(this._hashes.length - 1, level)];
};
/**
* Get cell depth
* @param level level
* @returns cell depth
*/
depth = (level: number = 3): number => {
return this._depths[Math.min(this._depths.length - 1, level)];
};
/**
* Get cell level
* @returns cell level
*/
level = (): number => {
return this.mask.level;
};
/**
* Checks cell to be equal to another cell
* @param other other cell
* @returns true if cells are equal
*/
equals = (other: Cell): boolean => {
return this.hash().equals(other.hash());
};
/**
* Serializes cell to BOC
* @param opts options
*/
toBoc(opts?: {
idx?: boolean | null | undefined;
crc32?: boolean | null | undefined;
}): Buffer {
let idx =
opts && opts.idx !== null && opts.idx !== undefined
? opts.idx
: false;
let crc32 =
opts && opts.crc32 !== null && opts.crc32 !== undefined
? opts.crc32
: true;
return serializeBoc(this, { idx, crc32 });
}
/**
* Format cell to string
* @param indent indentation
* @returns string representation
*/
toString(indent?: string): string {
let id = indent || "";
let t = "x";
if (this.isExotic) {
if (this.type === CellType.MerkleProof) {
t = "p";
} else if (this.type === CellType.MerkleUpdate) {
t = "u";
} else if (this.type === CellType.PrunedBranch) {
t = "p";
}
}
let s =
id + (this.isExotic ? t : "x") + "{" + this.bits.toString() + "}";
for (let k in this.refs) {
const i = this.refs[k];
s += "\n" + i.toString(id + " ");
}
return s;
}
/**
* Covnert cell to slice
* @returns slice
*/
asSlice() {
return this.beginParse();
}
/**
* Convert cell to a builder that has this cell stored
* @returns builder
*/
asBuilder() {
return beginCell().storeSlice(this.asSlice());
}
private [inspectSymbol] = () => this.toString();
}