Cells, Builders and Slices
Cells, Builders and Slices are low-level primitives of TON Blockchain. The virtual machine of TON Blockchain, TVM (opens in a new tab), uses cells to represent all data structures in persistent storage, and most in memory.
Cells
Cell
is a primitive and a data structure, which ordinarly consists of up to continuously laid out bits and up to references to other cells. Circular references are forbidden and cannot be created by the means of TVM (opens in a new tab), which means cells can be viewed as quadtrees (opens in a new tab) or directed acyclic graphs (DAGs) (opens in a new tab) of themselves. Contract code itself is represented by a tree of cells.
Cells and cell primitives are bit-oriented, not byte-oriented: TVM (opens in a new tab) regards data kept in cells as sequences (strings or streams) of up to bits, not bytes. If necessary, contracts are free to use, say, -bit integer fields serialized into TVM (opens in a new tab) cells, thus using fewer persistent storage bytes to represent the same data.
Kinds
While the TVM (opens in a new tab) type Cell
refers to all cells, there are different cell kinds with various memory layouts. The one described earlier is commonly referred to as an ordinary (or simple) cell — that's the most simple and most commonly used flavor of cells, which can only contain data. The grand majority of descriptions, guides and references to cells and their usage assumes ordinary ones.
Other kinds of cells are collectively called exotic (or special) cells. They sometimes appear in actual representations of blocks and other data structures on TON Blockchain. Their memory layouts and purposes significantly differ from ordinary cells.
Kinds (or subtypes) of all cells are encoded by an integer between and . Ordinary cells are encoded by , while exotic ones can be encoded by any other integer in that range. The subtype of an exotic cell is stored in the first bits of its data, which means valid exotic cells always have at least data bits.
TVM (opens in a new tab) currently supports the following exotic cell subtypes:
- Pruned branch cell (opens in a new tab), with subtype encoded as — they represent deleted subtrees of cells.
- Library reference cell (opens in a new tab), with subtype encoded as — they are used for storing libraries, and usually, in masterchain contexts.
- Merkle proof cell (opens in a new tab), with subtype encoded as — they are used for verifying that certain portions of other cell's tree data belong to the full tree.
- Merkle update cell (opens in a new tab), with subtype encoded as — they always have two references and behave like a Merkle proof (opens in a new tab) for both of them.
Levels
Every cell, being a quadtree (opens in a new tab), has an attribute called level, which is represented by an integer between and . The level of an ordinary cell is always equal to the maximum of the levels of all its references. That is, level of an ordinary cell without references is equal to .
Exotic cells have different rules for determining their level, which are described on this page in TON Docs (opens in a new tab).
Standard representation
To be resolved by #280 (opens in a new tab).
Immutability
Cells are read-only and immutable, but there are two major sets of ordinary cell manipulation instructions in TVM (opens in a new tab):
- Cell creation (or serialization) instructions, which are used to construct new cells from previously kept values and cells;
- And cell parsing (or deserialization) instructions, which are used to extract or load data previously stored into cells via serialization instructions.
On top of that, there are instructions specific to exotic cells to create them and expect their values. However, ordinary cell parsing instructions can still be used on exotic ones, in which case they are automatically replaced by ordinary cells during such deserialization attempts.
All cell manipulation instructions require transforming values of Cell
type to either Builder
or Slice
types before such cells can be modified or inspected.
Builders
Builder
is a cell manipulation primitive for using cell creation instructions. They're immutable just like cells are, and allow constructing new cells from previously kept values and cells. Unlike cells, values of type Builder
appear only on TVM (opens in a new tab) stack and cannot be stored in persistent storage. That means, for example, that persistent storage fields with type Builder
would actually be stored as cells under the hood.
Builder
type represents partially composed cells, for which fast operations for appending integers, other cells, references to other cells and many others are defined:
Builder.storeUint()
in Core libraryBuilder.storeInt()
in Core libraryBuilder.storeBool()
in Core libraryBuilder.storeSlice()
in Core libraryBuilder.storeCoins()
in Core libraryBuilder.storeAddress()
in Core libraryBuilder.storeRef()
in Core library
While you may use them for manual construction of the cells, it's strongly recommended to use Structs instead: Construction of cells with Structs.
Slices
Slice
is a cell manipulation primitive for using cell parsing instructions. Unlike cells, they're mutable and allow extracting or loading data previously stored into cells via serialization instructions. Also unlike cells, values of type Slice
appear only on TVM (opens in a new tab) stack and cannot be stored in persistent storage. That means, for example, that persistent storage fields with type Slice
would actually be stored as cells under the hood.
Slice
type represents either the remainder of a partially parsed cell, or a value (subcell) residing inside such a cell and extracted from it by a parsing instruction:
Slice.loadUint()
in Core librarySlice.loadInt()
in Core librarySlice.loadBool()
in Core librarySlice.loadSlice()
in Core librarySlice.loadCoins()
in Core librarySlice.loadAddress()
in Core librarySlice.loadRef()
in Core library
While you may use them for manual parsing of the cells, it's strongly recommended to use Structs instead: Parsing of cells with Structs.
Serialization
Similar to serialization options of Int
type, Cell
, Builder
and Slice
also have various representations for encoding their values in the following cases:
- as fields of contracts and traits,
- as fields of Structs and Messages,
- and as key/value types of maps.
contract SerializationExample {
someCell: Cell as remaining;
someSlice: Slice as bytes32;
}
remaining
To be resolved by #26 (opens in a new tab).
bytes32
To be resolved by #94 (opens in a new tab).
bytes64
To be resolved by #94 (opens in a new tab).
Bag of Cells
Bag of Cells, or BoC for short, is a format for serializing and deserializing cells into byte arrays as described in boc.tlb (opens in a new tab) TL-B schema (opens in a new tab).
Read more about BoC in TON Docs: Bag of Cells (opens in a new tab).
Advanced information on Cell
serialization: Canonical Cell
Serialization (opens in a new tab).
Operations
Construct and parse
In Tact, there are at least two ways to construct and parse cells:
- Manually, which involves active use of
Builder
,Slice
and relevant methods. - Using Structs, which is a recommended and much more convenient approach.
Manually
Using Structs (recommended)
Structs and Messages are almost like living TL-B schemas (opens in a new tab). Which means that they're, essentially, TL-B schemas (opens in a new tab) expressed in maintainable, verifiable and user-friendly Tact code.
It is strongly recommended to use them and their methods like Struct.toCell()
and Struct.fromCell()
instead of manually constructing and parsing cells, as this allows for much more declarative and self-explanatory contracts.
The examples of manual parsing above could be re-written using Structs, with descriptive names of fields if one so desires:
// First Struct
struct Showcase {
id: Int as uint8;
someImportantNumber: Int as int8;
isThatCool: Bool;
payload: Slice;
nanoToncoins: Int as coins;
wackyTacky: Address;
jojoRef: Adventure; // another Struct
}
// Here it is
struct Adventure {
bizarre: Bool = true;
time: Bool = false;
}
fun example() {
// Basics
let s = Showcase.fromCell(
Showcase{
id: 7,
someImportantNumber: 42,
isThatCool: true,
payload: emptySlice(),
nanoToncoins: 1330 + 7,
wackyTacky: myAddress(),
jojoRef: Adventure{ bizarre: true, time: false },
}.toCell());
s.isThatCool; // true
}
Note, that Tact's auto-layout algorithm is greedy. For example, struct Adventure
occupies very little space, and it won't be stored as a reference Cell
, but will be provided directly as a Slice
.
By using Structs and Messages over manual Cell
composition and parsing, those details would be simplified away and won't cause any hassle when the optimized layout changes.
Check if empty
Neither Cell
nor Builder
can be checked for emptiness directly — one needs to convert them to Slice
first.
To check if there are any bits, use Slice.dataEmpty()
. To check if there are any references, use Slice.refsEmpty()
. And to check both at the same time, use Slice.empty()
.
To also throw an exit code 9 whenever the Slice
isn't completely empty, use Slice.endParse()
.
// Preparations
let someCell = beginCell().storeUint(42, 7).endCell();
let someBuilder = beginCell().storeRef(someCell);
// Obtaining our Slices
let slice1 = someCell.asSlice();
let slice2 = someBuilder.asSlice();
// .dataEmpty()
slice1.dataEmpty(); // false
slice2.dataEmpty(); // true
// .refsEmpty()
slice1.refsEmpty(); // true
slice2.refsEmpty(); // false
// .empty()
slice1.empty(); // false
slice2.empty(); // false
// .endParse()
try {
slice1.endParse();
slice2.endParse();
} catch (e) {
e; // 9
}
Check if equal
Values of type Builder
cannot be compared directly using binary equality ==
or inequality !=
operators. However, values of type Cell
and Slice
can.
Direct comparisons:
let a = beginCell().storeUint(123, 8).endCell();
let aSlice = a.asSlice();
let b = beginCell().storeUint(123, 8).endCell();
let bSlice = b.asSlice();
let areCellsEqual = a == b; // true
let areCellsNotEqual = a != b; // false
let areSlicesEqual = aSlice == bSlice; // true
let areSlicesNotEqual = aSlice != bSlice; // false
Note, that direct comparison via ==
or !=
operators implicitly uses SHA-256 (opens in a new tab) hashes of standard Cell
representation under the hood.
Explicit comparisons using .hash()
are also available:
let a = beginCell().storeUint(123, 8).endCell();
let aSlice = a.asSlice();
let b = beginCell().storeUint(123, 8).endCell();
let bSlice = b.asSlice();
let areCellsEqual = a.hash() == b.hash(); // true
let areCellsNotEqual = a.hash() != b.hash(); // false
let areSlicesEqual = aSlice.hash() == bSlice.hash(); // true
let areSlicesNotEqual = aSlice.hash() != bSlice.hash(); // false