Skip to main content

Documentation Index

Fetch the complete documentation index at: https://companyname-a7d5b98e-3-gasless.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

In TON, all data is stored in cells. Cells opened for reading are called slices. Cells being constructed are called builders. Having a builder, only writing is possible. Having a slice, only reading is possible. Tolk provides low-level capabilities to construct and parse cells manually, as well as automatic packing structures to/from cells.

Cells

A cell is the fundamental data structure in TON. It’s a container that holds up to 1023 bits of data and up to 4 references to other cells. Everything in TON (contracts, messages, storage) is represented using cells. They are read-only and immutable once created. Read more about cells. In Tolk, the basic type cell describes “some cell”.
struct SomeMessage {
    // ...
    customPayload: cell
}

Typed cells: Cell<T>

Besides “some cell”, Tolk has a “cell with known shape” Cell<T>. Since one cell can store only 1023 bits, when storage exceeds this limit, the solution is to split it into multiple cells, so they become referencing each other.
struct Demo {
    ref1: cell          // untyped ref
    ref2: Cell<Inner>   // typed ref
    ref3: Cell<int256>? // maybe ref
}
A typed cell can be assigned to cell implicitly.

Slices: cells opened for reading

To manually read data from a cell, use 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()) {
   // ...
}
An IDE will suggest applicable methods after a dot.

Builders: cells at the moment of writing

To manually construct a cell, create a builder, write some data, and finalize this builder:
var b = beginCell();
b.storeUint(123, 8);
b.storeAddress(dest);
val c = b.endCell();
Since methods storeXXX return self, these calls can be chained:
val c = beginCell()
    .storeUint(123, 8)
    .storeAddress(dest)
    .endCell();

How to read from a builder

The only way to access already written bits is to convert a builder into a slice:
var s = b.asSlice();
// or (absolutely the same)
var s = b.endCell().beginParse();
Constructing a cell is generally expensive in terms of gas, but b.endCell().beginParse() is optimized to a cheap asm instruction BTOS without intermediate cell creation.

Auto packing to/from cells

Tolk type system is designed to avoid cumbersome manual work with slices and builders. Almost every practical use case can be represented with an auto-serializable structure.
struct Something {
    // ...
}

fun parseAndModify(c: cell): cell {
    var smth = Something.fromCell(c);
    // ...
    return smth.toCell();
}
Having Cell<T>, just call load() to get T:
fun parsePoint(c: Cell<Point>) {
    // same as `Point.fromCell(c)`
    var p = c.load();
}
Read a detailed article automatic serialization. Internally, fromCell() does beginParse() and reads data from a slice.

Auto packing to/from builders/slices

A struct can be parsed not only from a cell but also from a slice:
val smth = Something.fromSlice(s);
Auto-serialization works at low-level also: by analogy with loadUint() and others, there is a loadAny<T>() method:
val smth = s.loadAny<Something>();
// or even
val smth: Something = s.loadAny();  // T=Something deduced
Similarly, storeAny<T>() for a builder accepts any serializable value:
beginCell()
    .storeAddress(dest)
    .storeAny(smth)         // T=Something deduced
    .storeUint(123, 8);
Furthermore, it works not only with structures but also with arbitrary types.
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)
This approach allows both low-level and high-level intentions to be expressed uniformly.

Builders and slices can NOT be serialized

Builders and slices are low-level primitives used for constructing and parsing cells. They contain raw binary data. For this reason, attempting to read an arbitrary slice from another slice is impossible: how many bits should be read?
struct CantBeRead {
    before: int8
    s: slice
    after: int8
}
An attempt to call CantBeRead.fromCell(c) will fire an error “Can not be deserialized, because CantBeRead.s is slice. Express shape of data using the type system to make serialization distinct. For example, s: bits100 if it’s exactly 100 bits.

Type bitsN: fixed-size slices

By analogy: int can not be serialized, but int32 and int64 can. The same: slice can not be serialized, but bits32 and bytes8 can. At runtime, bitsN is a TVM SLICE, like int32 is a TVM INT.
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. It’s intentional, because slices may have refs, so explicit casting forces a programmer to think whether this transformation is valid. At runtime, it’s no-op.
fun calcHash(raw: bits512) {
    // ...
}

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

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

”The remaining” slice when reading

A common pattern is to read a portion of data and then retrieve the remainder. With manual parsing, it 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 the type system use a special type RemainingBitsAndRefs:
struct WithPayload {
    ownerId: uint32
    dest: address
    payload: RemainingBitsAndRefs
}
Then, obj = WithPayload.fromSlice(s) will return an object, where obj.payload contains “all bits/refs left”. This is a special type:
// declared in stdlib, handled specially by the compiler
type RemainingBitsAndRefs = slice
Naturally, such a field must appear last in a struct: no more data exists after reading it.

Embedding constant slices into a contract

A string literal is represented as a slice:
// `slice` with 4 bytes: 97,98,99,100 (0x61626364)
const SLICE1 = "abcd"
Also, use stringHexToSlice("...") to embed hexadecimal binary data:
// `slice` with 2 bytes: 16,32 (0x1020)
const SLICE2 = stringHexToSlice("1020")
TVM does not have string types; it operates solely on slices. Read about emulating strings.

Stack layout and serialization

Both cell and Cell<T> are backed by TVM CELL. Serialized as a reference; nullable are “maybe reference”. The primitive types builder and slice cannot be serialized. Use bitsN and RemainingBitsAndRefs. For details, follow TVM representation and Serialization.