fromCell and toCell:
Type serialization
Astruct can define a serialization prefix. 32-bit prefixes are commonly used as opcodes for incoming and outgoing messages:
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βs 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.
NftCollectionStorage.fromCell() is processed as follows:
- read
address; - read
uint64; - 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:
storage.royalty.numerator is not valid:
numerator and other fields, load the reference:
Cell<address> or Cell<int32 | int64> is supported, T is not restricted to structures.
Custom serializers for custom types
Type aliases allow overriding serialization behavior when the required encoding cannot be represented using existing types: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.
- not enough bits or refs, unless
lazy fromCellis used; - extra data after the expected fields; can be enabled;
addresshas an invalid format;enumhas an invalid value;- a mismatched struct prefix;
- other format inconsistencies.
Cell packing and unpacking options
Behavior offromCell and toCell can be controlled by an option object:
Functions
Functions such asfromCell(), fromSlice(), etc., are designed to integrate with low-level features.
For deserialization, each of the functions can be controlled by UnpackOptions:
-
T.fromCell(c)parses a cell usingc.beginParse() + fromSlice: -
T.fromSlice(s)parses a slice; the slice is not mutated: -
slice.loadAny<T>()mutates the slice:options.assertEndAfterReadingis ignored because the function reads from the middle of the slice. -
slice.skipAny<T>(), such asskipBits()and similar:
PackOptions.
-
T.toCell()works asbeginCell() + serialize + endCell(): -
builder.storeAny<T>(v), such asstoreUint()and similar:
RemainingBitsAndRefs
RemainingBitsAndRefs is a built-in type to get remaining part of a slice when reading. Example:
JettonMessage.fromCell, forwardPayload contains everything left in the slice after reading the preceding fields. Itβs 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:
-
Suppress the error.
If
coinsvalues are expected to be relatively small and the struct will fit in practice, suppress the error using an annotation: -
Reorganize the struct by splitting into multiple cells.
If
coinsvalues 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:
What if serialization is unavailable?
A common mistake is usingint. It cannot be serialized; instead, use int32, uint64, etc.
Integration with message sending
Auto-serialization is integrated when messages sending 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.