fromCell and toCell:
How each type is serialized
To dig into binary data, follow Overall: serialization. Astruct can provide its “serialization prefix”.
32-bit ones are typically called opcodes and used for messages (incoming and outgoing):
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
NftCollectionStorage.fromCell() is processed as follows:
- read
address - read
uint64 - 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:
storage.royalty.xxx is NOT valid:
numerator and other fields, manually load that ref:
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:What if input is corrupted
How willPoint.fromCell(c) work if c is less than 16 bits?
- input is too small — not enough bits or refs, unless
lazy fromCell - input is too big — contains extra data (can be turned off)
addresshas incorrect formatenumhas an invalid value- a struct prefix does not match
- etc.
UnpackOptions and PackOptions
Behavior offromCell and toCell can be controlled by options:
fromCell and similar), there are two options:
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.
T.fromCell(c)— parse a cell: “c.beginParse() + fromSlice”:
T.fromSlice(s)— parse a slice (a slice is not mutated):
slice.loadAny<T>()— mutate the slice:
options.assertEndAfterReading is ignored by this function because it is intended to read data from the middle.
slice.skipAny<T>()— likeskipBits()and similar:
PackOptions.
T.toCell()— works as “beginCell() + serialize + endCell()”:
builder.storeAny<T>(v)— likestoreUint()and similar:
Special type: RemainingBitsAndRefs
It’s a built-in type to get “all the rest” slice tail on reading. Example: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:- to suppress the error; it means “okay, I understand”
- or reorganize a struct by splitting into multiple cells
int8? is either one or nine bits, coins is 4..124 bits, etc.
So, given a struct:
- if
coinsvalues are expected to be relatively small, and this struct will 100% fit in reality; then, suppress the error using an annotation:
- or
coinsare 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:
What if serialization is unavailable
A common mistake: usingint (it cannot be serialized; use int32, uint64, etc.; see numeric types).
Integration with message sending
Auto-serialization is integrated natively with message sending to other contracts:Not “fromCell” but “lazy fromCell”
Tolk provides a special keywordlazy combined with auto-deserialization.
The compiler loads only the fields requested, rather than the entire struct.