Skip to content

Commit 65ae172

Browse files
authored
feat(docs): how the storage is handled with regards to return and throw(0) patterns (#3419)
1 parent e078ce7 commit 65ae172

File tree

4 files changed

+75
-8
lines changed

4 files changed

+75
-8
lines changed

docs/src/content/docs/book/bounced.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,8 @@ contract MyContract {
7676

7777
On TON, bounced message bodies have a 32 bits prefix, where all bits are set, i.e., with `0xFFFFFFFF{:tact}`. However, since that prefix is always the same for all bounced messages, Tact cuts it off for your convenience, keeping only the remaining 256 bits of the payload, which usually starts with a 32-bit message opcode.
7878

79+
## Contract storage handling
80+
81+
Bounced message receivers handle contract storage just as [internal message receivers](/book/receive#contract-storage-handling) do. In addition, the empty [`return{:tact}` statement](/book/statements#return) and the [`throw(0){:tact}`](/ref/core-debug#throw) patterns [work the same](/book/receive#contract-storage-handling).
82+
7983
[message]: /book/structs-and-messages#messages

docs/src/content/docs/book/external.mdx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,22 @@ After accepting a message, the contract can use as much gas as it wants. This is
4646

4747
When processing an external message, the `context` and `sender` functions are not available. This is because there is no context available for external messages. Therefore, you cannot use the `context` and `sender` functions in external messages. You need to carefully test your contract to ensure that it does not use the `context` and `sender` functions.
4848

49-
## Enable External Messages Support
49+
## Enable external message receivers support
5050

51-
To enable external message support, please enable it in the project configuration file:
51+
You can enable the external message support in the [`tact.config.json`](/book/config) by setting the [`external`](/book/config#options-external) option to `true{:json}`.
5252

53-
```json
53+
```json title="tact.config.json" {8}
5454
{
55-
"options": {
56-
"external": true
57-
}
55+
"projects": [
56+
{
57+
"name": "some_prefix",
58+
"path": "./contract.tact",
59+
"output": "./contract_output",
60+
"options": {
61+
"external": true
62+
}
63+
}
64+
]
5865
}
5966
```
6067

@@ -78,4 +85,8 @@ contract SampleContract {
7885
}
7986
```
8087

81-
External receivers follow the same execution order conventions as [internal receivers](/book/receive).
88+
External receivers follow the same execution order conventions as [internal receivers](/book/receive).
89+
90+
## Contract storage handling
91+
92+
External message receivers handle contract storage just as [internal message receivers](/book/receive#contract-storage-handling) do. In addition, the empty [`return{:tact}` statement](/book/statements#return) and the [`throw(0){:tact}`](/ref/core-debug#throw) patterns [work the same](/book/receive#contract-storage-handling).

docs/src/content/docs/book/functions.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ extends fun doWith(self: Slice) {}
928928

929929
All functions return a value. If the function does not have an explicit return type specified, it has an implicit return type of `void` — a pseudo-value that represents "nothing" in the Tact compiler. This also applies to the functions that do not allow specifying a return type, such as [receivers](#receivers) or [`init(){:tact}`](#init).
930930

931-
Moreover, execution of any function can be ended prematurely with the [`return{:tact}` statement](/book/statements#return). This is also true for the [receiver functions](#receivers) and [`init(){:tact}`], where empty `return{:tact}` statements are allowed.
931+
Moreover, execution of any function can be ended prematurely with the [`return{:tact}` statement](/book/statements#return). This is also true for receiver functions and `init(){:tact}` function, where empty `return{:tact}` statements are allowed.
932932

933933
```tact
934934
contract GreedyCashier(owner: Address) {

docs/src/content/docs/book/receive.mdx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,55 @@ contract JettonWallet(
236236
}
237237
}
238238
```
239+
240+
## Contract storage handling
241+
242+
Blockchain keeps each smart contract's code and data in its state. After receiving a message, the contract's storage data is loaded, and once processing in the receiver is complete, a new state of the contract is saved.
243+
244+
The loads and stores are managed by Tact, which implicitly adds relevant code during compilation. That way, the user only needs to think of using receivers to handle message bodies.
245+
246+
As Tact writes relevant code to automatically save the contract's state after the end of each receiver's logic, you can safely skip some of the steps in your code and jump to the end of processing the receiver with the [`return{:tact}` statement](/book/statements#return).
247+
248+
```tact {5}
249+
contract GreedyCashier(owner: Address) {
250+
receive() {
251+
// Stop the execution if the message is not from an `owner`.
252+
if (sender() != self.owner) {
253+
return;
254+
}
255+
// Otherwise, forward excesses back to the sender.
256+
cashback(sender());
257+
}
258+
}
259+
```
260+
261+
If the Tact compiler can deduce that a certain receiver does not modify the contract's state, then the storage-saving logic is omitted, and some extra gas is saved as a result.
262+
263+
Furthermore, to make an early exit from the receiver without saving the new contract's state, use the [`throw(0){:tact}`](/ref/core-debug#throw) idiom. It will immediately and successfully terminate the execution of the compute phase of the contract and skip the code generated by Tact to save the contract's state. Conversely, changes to data made before calling `throw(){:tact}` in with this function will be lost unless you manually save them beforehand.
264+
265+
That said, when using the `throw(0){:tact}` idiom, make sure to double-check and test cover your every move so that the contract's data won't become corrupt or inadvertently gone.
266+
267+
```tact {18-20}
268+
// This function manually saves contract data Cell
269+
asm fun customSetData(data: Cell) { c4 POP }
270+
271+
contract WalletV4(
272+
seqno: Int as uint32,
273+
// ...other parameters...
274+
) {
275+
// ...
276+
external(_: Slice) {
277+
// ...various prior checks...
278+
279+
acceptMessage();
280+
self.seqno += 1;
281+
282+
// Manually saving the contract's state
283+
customSetData(self.toCell());
284+
285+
// And halting the transaction to prevent a secondary save implicitly
286+
// added by Tact after the main execution logic of the receiver
287+
throw(0);
288+
}
289+
}
290+
```

0 commit comments

Comments
 (0)