Skip to main content
In TON, all data is stored in and represented as cells. Tolk provides low-level functions for constructing and parsing cells manually, as well as automatic serialization of structures to and from cells.

Cells

Cells are a data structure that can hold up to 1023 bits of data and up to 4 references to other cells. They are read-only and immutable once created.

Untyped cells

The basic type cell describes an untyped cell:
struct SomeMessage {
    // ...
    customPayload: cell
}

Typed cells

Cell<T> represents a cell with known internal structure. Since a single cell stores at most 1023 bits, larger data is split across multiple cells that sequentially reference each other. Circular references are not allowed.
struct Demo {
    ref1: cell //          untyped reference
    ref2: Cell<Inner> //   typed ref
    ref3: Cell<int256>? // maybe ref
}
The toCell() method on struct instances produces values of type Cell<STRUCT_NAME>. However, any typed cell can be assigned to an untyped cell directly.

Slices

A cell opened for reading is called a slice. They allow loading data from cells. To read data from a cell manually, call beginParse() to get a slice:
var s = cell.beginParse();
Then load data incrementally: integers, coins, sub-slices, references, etc.
val mode = s.loadUint(8);
val dest = s.loadAddress();
val firstRef = s.loadRef();   // cell

if (s.remainingBitsCount()) {
   // ...
}

Prefer distinct slice types

Builders and slices are low-level primitives for constructing and parsing cells. The values of builder and slice types contain raw binary data. As such, reading an arbitrary slice or builder from another slice is impossible:
struct CantBeRead {
    before: int8
    s: slice
    after: int8
}
An attempt to call CantBeRead.fromCell(c) fires an error: “Can not be deserialized, because CantBeRead.s is slice”. Express the shape of data using the type system and fixed-size slices to make serialization distinct. For example, use s: bits100 to hold a slice containing at most 100 bits.

Fixed-size slices

The slice type cannot be serialized, but the bitsN types, such as bits32 or bytes8, can. At runtime, bitsN is a regular slice, just how int32 are regular TVM integers.
struct OkayToRead {
    before: int8
    s: bits100
    after: int8
}

fun read(c: cell) {
    // a cell `c` is expected to be 116 bits
    val r = OkayToRead.fromCell(c);
    // on the stack: INT, SLICE, INT
}
To cast slice to bitsN, use the unsafe as operator. This is intentional: slices may have refs, so explicit casting forces consideration of whether the transformation is valid. At runtime, this is a no-op:
fun calcHash(raw: bits512) {
    // ...
}

fun demo() {
    calcHash(someSlice);                   // error
    calcHash(someSlice as bits512);        // ok

    someBytes.loadAddress();               // error
    (someBytes as slice).loadAddress();    // ok
}

Embed constant slices into a contract

TVM does not have a dedicated string type: they are emulated using slices.
A string literal is represented as a slice:
// a slice with 4 bytes: 97,98,99,100 (0x61626364)
const SLICE1 = "abcd"
Use stringHexToSlice("<HEX_BYTES>") to embed hexadecimal binary data:
// a slice with 2 bytes: 16,32 (0x1020)
const SLICE2 = stringHexToSlice("1020")

Builders

A cell under construction is called a builder. They allow composing new cells. To construct a cell manually, create a builder, write data to it, then finalize:
var b = beginCell();
b.storeUint(123, 8);
b.storeAddress(dest);
val c = b.endCell();
Methods named storeXYZ return self, which is a builder, so calls can be chained:
val c = beginCell()
    .storeUint(123, 8)
    .storeAddress(dest)
    .endCell();

Read from a builder

The only way to access bits already written to a builder is to convert it into a slice:
var s = b.asSlice();
// or (exactly the same)
var s = b.endCell().beginParse();
Constructing a cell is generally expensive in terms of gas, but b.endCell().beginParse() is optimized and does not create intermediate cells.

Automatic construction and parsing

Structures are auto-serializable to and from cells when their fields are well-typed. Prefer using structures over manual work with builders and slices.

Marshall to and from cells

struct Something {
    // ...
}

fun parseAndModify(c: cell): cell {
    var smth = Something.fromCell(c)
    // ...
    return smth.toCell()
}
For Cell<T>, call load() to get a value of type T:
fun parsePoint(c: Cell<Point>) {
    // same as `Point.fromCell(c)`
    var p = c.load();
}
Internally, fromCell() calls beginParse() and reads data from a slice.

Marshall to and from builders and slices

A struct can be parsed not only from a cell but also from a slice:
val smth = Something.fromSlice(s);
In addition to loadUint() and similar methods, there is loadAny<T>():
val smth = s.loadAny<Something>();
// or
val smth: Something = s.loadAny(); // with T deduced as Something
Notice that T.fromSlice(s) does not mutate a slice s, while s.loadAny<T>() does. This is due to how Tolk handles mutability: all s.loadXYZ() methods mutate a slice, but functions in the form doSomething(s) do not.
Similarly, storeAny<T>() for a builder accepts any serializable value:
beginCell()
    .storeAddress(dest)
    .storeAny(smth)         // T=Something deduced
    .storeUint(123, 8);
Both loadAny<T>() and storeAny<T>() work with arbitrary types, not only with structures:
s.loadAny<int32>();           // same as loadInt(32)
s.loadAny<(coins, bool?)>();  // read a pair (a tensor)

b.storeAny(someAddress);      // same as storeAddress
b.storeAny(0xFF as uint8);    // same as storeUint(0xFF, 8)

Remaining slice when reading

A common pattern is to read a portion of data and then retrieve the remainder. With manual parsing, this happens naturally:
val ownerId = s.loadUint(32);
val dest = s.loadAddress();
// `s` contains all bits/refs still unread
val payload = s;
To express the same with structures, use the special type RemainingBitsAndRefs:
struct WithPayload {
    ownerId: uint32
    dest: address
    payload: RemainingBitsAndRefs
}
Then, obj = WithPayload.fromSlice(s) is a structure instance where obj.payload contains all bits and refs left as a slice. A field with this type must appear last in a struct: no more data exists after reading it.

Stack layout and serialization

Structure fields of cell and Cell<T> types which are serialized as references to corresponding TVM cells, with nullable types ? serialized as 0 when the value is null and 1, followed by the cell reference otherwise. The primitive types builder and slice cannot be serialized. Use bitsN and RemainingBitsAndRefs types instead.