Skip to main content
All data in TON (messages, storage, etc.) is represented with cells. Tolk type system is designed to express cell contents, enabling auto-serialization via fromCell and toCell:

How each type is serialized

To dig into binary data, follow Overall: serialization. A struct can provide its “serialization prefix”. 32-bit ones are typically called opcodes and used for messages (incoming and outgoing):
But prefixes are not restricted to be 32-bit: 0x000F is a 16-bit prefix, 0b010 is 3-bit (binary). Serialization of structures is also on the “overall” page.

Controlling cell references. Typed cells

Fields of a struct are serialized one by one. The compiler does not reorder fields, create implicit references, etc. When data should be stored in a ref, it’s done explicitly. A developer controls exactly when each ref is loaded. There are two types of references — typed and untyped:
  • cell — untyped ref — just “some cell”, “arbitrary content”
  • Cell<T> — typed ref — a cell, which internal structure is known
A call NftCollectionStorage.fromCell() is processed as follows:
  1. read address
  2. read uint64
  3. read two refs without unpacking them: only their pointers are loaded

Cell<T> must be loaded to get T

Let’s look at the royalty above:
Since it is a cell, storage.royalty.xxx is NOT valid:
To access numerator and other fields, manually load that ref:
And conversely, when composing an instance, assign a cell, not an object:
The following snippet summarizes the behavior:
Note that Cell<address> or even Cell<int32 | int64> is also okay, T is not restricted to structures.

Custom serializers for custom types

Using type aliases, it is possible to override serialization behavior when it cannot be expressed using existing types:
See Serialization of type aliases for examples.

What if input is corrupted

How will Point.fromCell(c) work if c is less than 16 bits?
The answer: an exception is thrown. In multiple cases, actually:
  • input is too small — not enough bits or refs, unless lazy fromCell
  • input is too big — contains extra data (can be turned off)
  • address has incorrect format
  • enum has an invalid value
  • a struct prefix does not match
  • etc.
An exception code is typically 9 (“cell underflow”) or 5 (“out of range”). Some aspects of this behavior can be controlled. For example, if “input is too big” is okay, use an option:

UnpackOptions and PackOptions

Behavior of fromCell and toCell can be controlled by options:
For deserialization (fromCell and similar), there are two options:
For serialization (toCell and similar), there is one option:

Not only fromCell, but fromSlice and more

This API is also designed to integrate with low-level features. Each of these functions can be controlled by UnpackOptions.
  1. T.fromCell(c) — parse a cell: “c.beginParse() + fromSlice”:
  1. T.fromSlice(s) — parse a slice (a slice is not mutated):
  1. slice.loadAny<T>()mutate the slice:
Note: options.assertEndAfterReading is ignored by this function because it is intended to read data from the middle.
  1. slice.skipAny<T>() — like skipBits() and similar:
Same for serialization. Each of these functions can be controlled by PackOptions.
  1. T.toCell() — works as “beginCell() + serialize + endCell()”:
  1. builder.storeAny<T>(v) — like storeUint() and similar:

Special type: RemainingBitsAndRefs

It’s a built-in type to get “all the rest” slice tail on reading. Example:
After JettonMessage.fromCell, forwardPayload contains everything left after reading the fields above. Essentially, it’s an alias to a slice which is handled specially by the compiler:

What if data exceeds 1023 bits

Tolk compiler warns if a serializable struct potentially exceeds 1023 bits. A developer should take one of the following actions:
  1. to suppress the error; it means “okay, I understand”
  2. or reorganize a struct by splitting into multiple cells
Why “potentially exceeds”? Because for many types, their size can vary. For example, int8? is either one or nine bits, coins is 4..124 bits, etc. So, given a struct:
And trying to serialize it, the compiler prints an error:
Actually, two choices are available:
  1. if coins values are expected to be relatively small, and this struct will 100% fit in reality; then, suppress the error using an annotation:
  1. or coins are expected to be billions of billions, so data really can exceed; in this case, extract some fields into a separate cell; for example, store 800 bits as a ref or extract the other two fields:
A general guideline: leave frequently used fields directly and place less-frequent fields into a nested ref. Overall, the compiler reports potential overflow, and it is the developer’s responsibility to resolve it.

What if serialization is unavailable

A common mistake: using int (it cannot be serialized; use int32, uint64, etc.; see numeric types).
The compiler reports a reasonable error:

Integration with message sending

Auto-serialization is integrated natively with message sending to other contracts:
See: sending messages.

Not “fromCell” but “lazy fromCell”

Tolk provides a special keyword lazy combined with auto-deserialization. The compiler loads only the fields requested, rather than the entire struct.
See: lazy loading.