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 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.
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
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: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.
- 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 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:
-
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 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.