Skip to main content
All data in TON, such as messages, storage, etc., is represented as cells. The Tolk type system is designed to express cell contents, enabling auto-serialization through fromCell and toCell:

Type serialization

A struct can define a serialization prefix. 32-bit prefixes are commonly used as opcodes for incoming and outgoing messages:
A prefix is not required to be 32 bits: 0x000F is a 16-bit prefix, and 0b010 is a 3-bit one.

Cell references and its types

Fields of a struct are serialized one by one. The compiler does not reorder fields or insert implicit references. When data should be stored in a ref, it is done explicitly. A developer controls exactly when each ref is loaded. There are two types of references – typed and untyped:
  • Cell<T> – typed reference; the cell has a known internal structure;
  • cell – untyped reference; the cell content is not described.
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>

Cell<T> must be loaded to get T. Consider the royalty field:
Since it is a cell, storage.royalty.numerator is not valid:
To access numerator and other fields, load the reference:
When composing an instance, assign a cell, not an object:
Summary:
Cell<address> or Cell<int32 | int64> is supported, T is not restricted to structures.

Custom serializers for custom types

Tolk allows overriding serialization behavior when the required encoding cannot be represented using existing types. Custom serializers can be defined for type aliases, structures, enums, and generic types:
Custom serializers also work for generic structures and type aliases: fun GenericStruct<T>.packToBuilder and fun GenericAlias<T>.unpackFromSlice are allowed. The method names packToBuilder and unpackFromSlice are reserved for this purpose. Their signatures must match exactly as shown, with only the receiver type varying.

Behavior with corrupted input

Point.fromCell(c) throws an exception if the cell does not contain the required data. The function expects at least 16 bits.
Typical failure cases include:
  • not enough bits or refs, unless lazy fromCell is used;
  • extra data after the expected fields; can be enabled;
  • address has an invalid format;
  • enum has an invalid value;
  • a mismatched struct prefix;
  • other format inconsistencies.
Common exception codes: Some behaviors are configurable. For example, to allow extra data:

Cell packing and unpacking options

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

Functions

Functions such as fromCell(), fromSlice(), etc., are designed to integrate with low-level features. For deserialization, each of the functions can be controlled by UnpackOptions:
  1. T.fromCell(c) parses a cell using c.beginParse() + fromSlice:
  2. T.fromSlice(s) parses a slice; the slice is not mutated:
  3. slice.loadAny<T>() mutates the slice:
    options.assertEndAfterReading is ignored because the function reads from the middle of the slice.
  4. slice.skipAny<T>(), such as skipBits() and similar:
For serialization, each of the functions can be controlled by PackOptions.
  1. T.toCell() works as beginCell() + serialize + endCell():
  2. builder.storeAny<T>(v), such as storeUint() and similar:

RemainingBitsAndRefs

RemainingBitsAndRefs is a built-in type to get remaining part of a slice when reading. Example:
After JettonMessage.fromCell, forwardPayload contains everything left in the slice after reading the preceding fields. It is an alias for a slice that the compiler treats specially:

What if data exceeds 1023 bits?

The Tolk compiler issues a warning if a serializable struct may exceed 1023 bits. This can happen because many types have variable size. For example, int8? can be 1 or 9 bits, coins can range from 4 to 124 bits, etc. Consider a struct:
Serializing this struct produces a compiler error:
Consider one of the following actions:
  1. Suppress the error. If coins values are expected to be relatively small and the struct will fit in practice, suppress the error using an annotation:
  2. Reorganize the struct by splitting into multiple cells. If coins values are expected to be relatively large and the data may exceed 1023 bits, extract some fields into a separate cell. For example, store 800 bits as a ref or extract the other two fields:
Frequently used fields should remain in the struct; less-frequent fields can be moved to a nested ref.

What if serialization is unavailable?

A common mistake is using int. It cannot be serialized; instead, use int32, uint64, etc.
The compiler reports:

Integration with message sending

Auto-serialization can be applied when sending messages to other contracts:

lazy for deserialization

Tolk provides a special keyword lazy for use with auto-deserialization. The compiler loads only the requested fields, rather than the entire struct.