Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

feat: explanation of remaining serialization format #335

Merged
merged 4 commits into from
Aug 14, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 81 additions & 6 deletions pages/book/cells.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,95 @@ While you may use them for [manual parsing](#cnp-manually) of the cells, it's st

Similar to serialization options of [`Int{:tact}`](/book/integers) type, `Cell{:tact}`, `Builder{:tact}` and `Slice{:tact}` also have various representations for encoding their values in the following cases:

* as fields of [contracts](/book/contracts) and [traits](/book/types#traits),
* as fields of [Structs](/book/structs-and-messages#structs) and [Messages](/book/structs-and-messages#messages),
* and as key/value types of [maps](/book/maps).
* as [storage variables](/book/contracts#variables) of [contracts](/book/contracts) and [traits](/book/types#traits),
* and as fields of [Structs](/book/structs-and-messages#structs) and [Messages](/book/structs-and-messages#messages).

```tact
```tact {2-3}
contract SerializationExample {
someCell: Cell as remaining;
someSlice: Slice as bytes32;

// Constructor function,
// necessary for this example contract to compile
init() {
self.someCell = emptyCell();
self.someSlice = beginCell().storeUint(42, 256).asSlice();
}
}
```

### `remaining` [#serialization-remaining]

The `remaining{:tact}` serialization option can be applied to values of [`Cell{:tact}`](#cells), [`Builder{:tact}`](#builders) and [`Slice{:tact}`](#slices) types.

It affects the process of constructing and parsing cell values by causing them to be stored and loaded directly rather than as a reference. To draw parallels with [cell manipulation instructions](#cells-immutability), specifying `remaining{:tact}` is like using [`Builder.storeSlice(){:tact}`][b-5] and [`Slice.loadSlice(){:tact}`][s-5] instead of [`Builder.storeRef(){:tact}`][b-8] and [`Slice.loadRef(){:tact}`][s-8], which are to be used by default.

In addition, the [TL-B][tlb] representation produced by Tact changes too:

```tact {3-5, 8-10}
contract SerializationExample {
novusnota marked this conversation as resolved.
Show resolved Hide resolved
// By default
cRef: Cell; // ^cell in TL-B
bRef: Builder; // ^builder in TL-B
sRef: Slice; // ^slice in TL-B

// With `remaining`
cRem: Cell as remaining; // remainder<cell> in TL-B
bRem: Builder as remaining; // remainder<builder> in TL-B
sRem: Slice as remaining; // remainder<slice> in TL-B
novusnota marked this conversation as resolved.
Show resolved Hide resolved

// Constructor function,
// necessary for this example contract to compile
init() {
self.cRef = emptyCell();
self.bRef = beginCell();
self.sRef = emptySlice();
self.cRem = emptyCell();
self.bRem = beginCell();
self.sRem = emptySlice();
}
}
```

There, `^cell`, `^builder` and `^slice` in [TL-B][tlb] syntax mean the reference to [`Cell{:tact}`](#cells), [`Builder{:tact}`](#builders) and [`Slice{:tact}`](#slices) values respectively, while the `remainder<…>` of `cell`, `builder` or `slice` tells that the given value would be stored as a `Slice{:tact}` directly and not as a reference.

Now, to give a real-world example, imagine that you need to notice and react to inbound [jetton][jetton] transfers in your smart contract. The appropriate [Message][message] structure for doing so would look something like this:

```tact /remaining/
message(0x7362d09c) JettonTransferNotification {
queryId: Int as uint64; // arbitrary request number to prevent replay attacks
amount: Int as coins; // amount of jettons transferred
sender: Address; // address of the sender of the jettons
forwardPayload: Slice as remaining; // optional custom payload
}
```

And the [receiver][recv] in the contract would look like this:

```tact
receive(msg: JettonTransferNotification) {
// ... you do you ...
}
```

### `remaining` [#serialization-bytes64]
Upon receiving a [jetton][jetton] transfer notification message, its cell body is converted into a `Slice{tact}` and then parsed as a `JettonTransferNotification{:tact}` [Message][message]. At the end of this process, the `forwardPayload` will have all the remaining data of the original message cell.

If we were to violate the [jetton][jetton] standard and place the `forwardPayload: Slice as remaining` field in any other position in the `JettonTransferNotification{:tact}` [Message][message], the `forwardPayload` would have the remaining data of the message cell at the moment of parsing it, which would include all the subsequent fields of the [Message][message].

Therefore, to prevent misuse of the contract storage and reduce gas consumption, make sure to specify `remaining{:tact}` serialization option only on the last field of the given [Message][message], as it will store all the remaining data of the [`Slice{:tact}`](#slices) at the moment it was parsed into said [Message][message] in [receiver functions][recv].

<Callout>

To be resolved by [#26](https://github.com/tact-lang/tact-docs/issues/26).
Note, that the cell serialized via `as remaining{:tact}` cannot be [optional](/book/optional). That is, specifying something like `Cell? as remaining{:tact}`, `Builder{:tact}` or `Slice? as remaining{:tact}` would cause a compilation error.

Also note, that as of now, specifying `remaining{:tact}` for the `Cell{:tact}` as the [map](/book/maps) value type does nothing — the cell would still be stored as a reference:

```tact
struct Nope {
m: map<Int, Cell as remaining>; // dict<uint8, ^cell> in TL-B notation,
anton-trunov marked this conversation as resolved.
Show resolved Hide resolved
// despite the `remaining` specified
}
```

</Callout>

Expand Down Expand Up @@ -373,9 +446,11 @@ let areSlicesNotEqual = aSlice.hash() != bSlice.hash(); // false
[p]: /book/types#primitive-types
[struct]: /book/structs-and-messages#structs
[message]: /book/structs-and-messages#messages
[recv]: /book/contracts#receiver-functions

[tvm]: https://docs.ton.org/learn/tvm-instructions/tvm-overview
[tlb]: https://docs.ton.org/develop/data-formats/tl-b-language
[jetton]: https://docs.ton.org/develop/dapps/asset-processing/jettons
[sha-2]: https://en.wikipedia.org/wiki/SHA-2#Hash_standard

[quadtree]: https://en.wikipedia.org/wiki/Quadtree
Expand Down
Loading