Skip to main content

Basic structure

The TON Virtual Machine (TVM) memory, persistent storage, and smart contract code consist of cells. In turn, each cell consists of
  • up to 1023 bits;
  • up to 4 references to other cells.
Nota bene: Circular references are forbidden and cannot be created by means of TVM. In this way, all cells kept in TVM memory and persistent storage constitute a directed acyclic graph (DAG). From the perspective of low-level cell operations, these data bits and cell references are not intermixed. In other words, a cell essentially is a couple consisting of a list of up to 1023 bits and of a list of up to four cell references, without prescribing an order in which the references and the data bits should be deserialized, even though TL-B schemes appear to suggest such an order.

Kinds of cells

There are two kinds of cells: ordinary and exotic. The former are the simplest and most commonly used flavor of cells, which can only contain data and references, while the latter are used for special purposes only. They sometimes appear in actual representations of blocks and other data structures on TON Blockchain. Their memory layouts and purposes differ significantly from ordinary cells. From the low-level perspective, ordinary and exotic cells can be distinguished by a special 1-bit flag stored outside the main 1,023 bits and read by TVM at runtime. Users cannot read this flag directly. TVM can support up to 256 different types of exotic cells that differ by a special 8-bit type identifier stored in the first byte of the cell data. TVM supports four types of exotic cells:
  • pruned branch (type identifier 0x01);
  • library reference (type identifier 0x02);
  • Merkle proof (type identifier 0x03);
  • Merkle update (type identifier 0x04).

Level of a cell

Every cell c has an attribute Lvl(c)Lvl(c) called its level, which takes integer values in the range 0…3. A cell’s level affects the number of higher hashes it has, see the section on hashes for details. The level of an ordinary cell cc is equal to the maximum of the levels of all its children cic_i: Lvl(c)=maxiLvl(ci).Lvl(c) = \max_i Lvl(c_i). For instance, each cell in a tree of cells that does not contain any exotic cell has level 0. Exotic cells may have different rules for setting their level.

Standard cell representation and its hash

Before a cell can be transferred over the network or stored on disk, it must be serialized. A common way to do this is to use the so-called standard cell representation, CellRepr(c). The standard representation of a cell c is a byte sequence that is constructed as follows:
  • Two descriptor bytes d1d_1 and d2d_2 are serialized first. Byte d1d_1 equals r+8s+32l, where 0 ≤ r ≤ 4 is the quantity of cell references contained in the cell, 0 ≤ l ≤ 3 is the level of the cell, and 0 ≤ s ≤ 1 is 1 for exotic cells and 0 for ordinary cells. Byte d2d_2 equals b8+b8\lfloor\frac{b}{8}\rfloor + \lceil\frac{b}{8}\rceil, where 0 ≤ b ≤ 1023 is the quantity of data bits in c.
  • Then the data bits are serialized as b8\lceil\frac{b}{8}\rceil bytes. If b is not a multiple of eight, a binary 1 and up to six binary 0s are appended to the data bits. After that, the data is split into b8\lceil\frac{b}{8}\rceil 8-bit groups, and each group is interpreted as an unsigned big-endian integer 0 … 255 and stored into an byte.
  • Next, for every referenced cell, 2 bytes in big-endian format store the depth of the refs, i.e. the number of cells between the root of the
    cell tree (the current cell) and the deepest reference, including it. For example, a cell containing only one reference and no further references would have a depth of 1, while the referenced cell would have a depth of 0.
  • Finally, for every referenced cell, the SHA-256 hash of its standard representation is stored, occupying 32 bytes per referenced cell, recursively repeating the said algorithm. Note that cyclic cell references are not allowed, so this algorithm always terminates. If there are no referenced cells, neither depths nor hashes are stored.
In this way, 2+b8+2r+32r2 + \lceil\frac{b}{8}\rceil + 2r + 32r bytes of CellRepr(c) are obtained. Thus, we got the serialization of c. However, the serialization of trees formed by cells is arranged differently, see bag of cells for details.

Cell manipulation

Cells are read-only and immutable, but there are two major sets of ordinary cell manipulation instructions in TVM:
  • Cell creation (or serialization) instructions, which are used to construct new cells from previously stored values and cells.
  • Cell parsing (or deserialization) instructions, which are used to extract or load data previously stored into cells via serialization instructions.
All cell manipulation instructions require transforming values of Cell type into either builder or slice types before such cells can be modified or inspected. Libraries like @ton/core and @ton-community/assets-sdk provide efficient cell handling. Below are examples of sequential cell creation, populating it with data, and then parsing.

Create a cell and store data

To build a cell, you use the beginCell() function. While the cell is open, you can store various data types with store...() functions. When you’re done, you close the cell with the endCell() function.
import { Address, beginCell } from "@ton/core";

const cell = beginCell()
  .storeUint(99, 64) // Stores uint 99 in 64 bits
  .storeAddress(Address.parse("EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c")) // Stores an address
  .storeCoins(123) // Stores 123 as coins
  .endCell(); // Closes the cell
Each cell has a 1023-bit limit. If you exceed this, an error occurs:
// This will fail due to overflow
const cell = beginCell()
  .storeUint(1, 256)
  .storeUint(2, 256)
  .storeUint(3, 256)
  .storeUint(4, 256) // Exceeds 1023-bit limit (256 + 256 + 256 + 256 = 1024)
  .endCell();
To store more data, cells can reference up to four other cells. You can use the storeRef() function to create nested cells:
const cell = beginCell()
  .storeUint(1, 256)
  .storeUint(2, 256)
  .storeRef(beginCell().storeUint(3, 256).storeUint(4, 256).endCell())
  .endCell();
You can store optional (nullable) values in cells by using the storeMaybe...() helpers:
const cell = beginCell()
  .storeMaybeInt(null, 64) // Optionally stores an int
  .storeMaybeInt(1, 64)
  .storeMaybeRef(null) // Optionally stores a reference
  .storeMaybeRef(beginCell().storeCoins(123).endCell())
  .endCell();

Load data from a cell

To read data from a cell, you first convert it into a slice using the beginParse() function. Then, you can extract various data types with load...() functions. You read data in the same order it was stored.
const slice = cell.beginParse();
const uint = slice.loadUint(64);
const address = slice.loadAddress();
const coins = slice.loadCoins();
To load a referenced (nested) cell, use loadRef():
const slice = cell.beginParse();
const uint1 = slice.loadUint(256);
const uint2 = slice.loadUint(256);
const innerSlice = slice.loadRef().beginParse(); // Load and parse nested cell
const uint3 = innerSlice.loadUint(256);
const uint4 = innerSlice.loadUint(256);
You can parse optional values using the corresponding loadMaybe...() functions. Returned values are nullable, so do not forget to check them for null.
const slice = cell.beginParse();
const maybeInt = slice.loadMaybeInt(64);
const maybeInt1 = slice.loadMaybeInt(64);
const maybeRef = slice.loadMaybeRef();
const maybeRef1 = slice.loadMaybeRef();
if (maybeRef1) {
  const coins = maybeRef1.beginParse().loadCoins();
}

References

I