Skip to content

Commit

Permalink
docs: clarify interactions of message sending functions and their modes
Browse files Browse the repository at this point in the history
Also listed functions with implicit mode, plus made some minor fixes on
related pages
  • Loading branch information
novusnota committed Jan 30, 2025
1 parent f937337 commit 93cdfae
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 14 deletions.
1 change: 1 addition & 0 deletions dev-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Document how storage variables get updated in relation to the `init()` function: PR [#1311](https://github.com/tact-lang/tact/pull/1311)
- Document compiler upgrades in Blueprint and other Tact projects: PR [#1560](https://github.com/tact-lang/tact/pull/1560)
- Illustrate how nested maps can be created: PR [#1593](https://github.com/tact-lang/tact/pull/1593)
- Listed functions with implicit mode and further clarified the interactions of message sending functions and their modes: PR [#1634](https://github.com/tact-lang/tact/pull/1634)

### Release contributors

Expand Down
11 changes: 7 additions & 4 deletions docs/src/content/docs/book/message-mode.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,19 @@ send(SendParameters{
});
```

Note that there can be only **one** [base mode](#base-modes), but number of [optional flags](#optional-flags) may vary: you can use them all, none or just some.

:::caution

Note, that while adding ([`+{:tact}`](/book/operators#binary-add)) base modes together with optional flags is possible, it is discouraged due to the possibility of excess values. Use the bitwise OR ([`|{:tact}`](/book/operators#binary-bitwise-or)) instead, as it's designed to work with such flag and bit manipulations of the `mode`.
While adding ([`+{:tact}`](/book/operators#binary-add)) base modes together with optional flags is possible, it is discouraged due to the possibility of excess values. Use the [bitwise OR `|{:tact}`](/book/operators#binary-bitwise-or) instead, as it's designed to work with such flag and bit manipulations of the `mode`.

:::

:::note
## Functions with implicit mode

Also note, that there can be only one [base mode](#base-modes), but number of [optional flags](#optional-flags) may vary: you can use them all, none or just some.
Some [message sending functions](/book/send#message-sending-functions) do not allow to set a mode by passing an argument. That's because their internal logic requires specific fixed set of modes to be used instead:

:::
* [`emit(){:tact}`](/ref/core-common#emit) sends a message with the `SendDefaultMode{:tact}` ($0$).
* [`self.reply(){:tact}`](/ref/core-base#self-reply), [`self.notify(){:tact}`](/ref/core-base#self-notify), and [`self.forward(){:tact}`](/ref/core-base#self-forward) all use the `SendRemainingValue{:tact}` mode unless the [`self.storageReserve{:tact}`](/ref/core-base#self-storagereserve) constant is overwritten to be greater than $0$, in which case they attempt to use the `SendRemainingBalance{:tact}` mode.

[int]: /book/integers
52 changes: 46 additions & 6 deletions docs/src/content/docs/book/send.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Sending messages
description: "TON Blockchain is message-based — to communicate with other contracts and to deploy new ones you need to send messages."
---

TON blockchain is message-based — to communicate with other contracts and to deploy new ones you need to send messages.
TON Blockchain is message-based — to communicate with other contracts and to deploy new ones you need to send messages.

Messages in Tact are commonly composed using a built-in [Struct](/book/structs-and-messages#structs) `SendParameters{:tact}`, which consists of:

Expand Down Expand Up @@ -112,30 +112,69 @@ Consider the following example:
contract FailureIsNothingButAnotherStep {
// And all the funds it gets are obtained from inbound internal messages
receive() {
// 1st outbound message evaluated and queued (but not sent yet)
// 1st outbound message evaluated and queued (but not yet sent)
send(SendParameters{
to: sender(),
value: ton("0.042"), // plus forward fee due to SendPayGasSeparately
mode: SendIgnoreErrors | SendPayGasSeparately,
// body is null by default
});
// 2nd outbound message evaluated and queued (but not sent yet, and never will be!)
// 2nd outbound message evaluated and queued,
// but not yet sent, and never will be!
send(SendParameters{
to: sender(),
value: 0,
mode: SendRemainingValue | SendIgnoreErrors,
// body is null by default
});
}
} // exit code 37 during action phase!
}
```

There, the second message won't actually be sent:

* After finishing the [compute phase][compute], the remaining value $\mathrm{R}$ of the contract is computed.

* During the outbound message processing and assuming that there was enough value provided in the inbound message, the first message leaves $\mathrm{R} - (0.042 + \mathrm{forward\_fees})$ [nanoToncoins](/book/integers#nanotoncoin) on the balance.
* During the outbound message processing and assuming that there was enough value provided in the inbound message, the first message leaves $\mathrm{R} - (0.042 + \mathrm{forward\_fees})$ [nanoToncoins][nano] on the balance.

* When the second message is processed, contract tries to send $\mathrm{R}$ [nanoToncoins](/book/integers#nanotoncoin), but fails to do so because there is already a smaller amount left.
* When the second message is processed, contract tries to send $\mathrm{R}$ [nanoToncoins][nano], but fails to do so because there is already a smaller amount left.

* Thus, an error with [exit code 37](/book/exit-codes#37) is thrown: `Not enough Toncoin`.

Note that such failures are not exclusive to the [`send(){:tact}`](/ref/core-common#send) function and may also occur when using other [message sending functions](#message-sending-functions).

For instance, let's replace the first call to the [`send(){:tact}`](/ref/core-common#send) function in the previous example with the [`emit(){:tact}`](/ref/core-common#emit) function. The latter queues the message using the default mode, i.e. $0$, and spends some [nanoToncoins][nano] to pay the [forward fees][fwdfee].

If a subsequent message is then sent with a [`SendRemainingValue{:tact}`](/book/message-mode#base-modes) base mode, it will cause the same error as before:

```tact
// This contract initially has 0 nanoToncoins on the balance
contract IfItDiesItDies {
// And all the funds it gets are obtained from inbound internal messages
receive() {
// 1st outbound message evaluated and queued (but not yet sent)
// with the mode 0, which is the default
emit("Have you seen this message?".asComment());
// 2nd outbound message evaluated and queued,
// but not yet sent, and never will be!
send(SendParameters{
to: sender(),
value: 0,
bounce: false, // brave and bold
mode: SendRemainingValue,
body: "Not this again!".asComment(),
});
} // exit code 37 during action phase!
}
```

:::note

To avoid dealing with similar cases and to simplify future [debugging sessions](/book/debug), consider having only one call to one of the [message sending functions](#message-sending-functions) per [receiver function](/book/receive), preferably at the end of the function body.

:::

## Message sending limits

Expand All @@ -158,6 +197,7 @@ Read more about all message sending functions in the Reference:
[int]: /book/integers
[cell]: /book/cells#cells
[opt]: /book/optionals
[nano]: /book/integers#nanotoncoin

[phases]: https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases
[compute]: https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase
Expand Down
6 changes: 3 additions & 3 deletions docs/src/content/docs/ref/core-base.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ virtual fun forward(to: Address, body: Cell?, bounce: Bool, init: StateInit?);

[Queues the message](/book/send#outbound-message-processing) (bounceable or non-bounceable) to be sent to the specified address `to`. Optionally, you may provide a `body` of the message and the [`init` package](/book/expressions#initof).

When [`self.storageReserve{:tact}`](#self-storagereserve) constant is overwritten to be $> 0$, before sending a message it also tries to reserve the `self.storageReserve{:tact}` amount of [nanoToncoins][nano] from the remaining balance before making the send in the [`SendRemainingBalance{:tact}`](https://docs.tact-lang.org/book/message-mode#base-modes) ($128$) mode.
When [`self.storageReserve{:tact}`](#self-storagereserve) constant is overwritten to be greater than $0$, before sending a message it also tries to reserve the `self.storageReserve{:tact}` amount of [nanoToncoins][nano] from the remaining balance before making the send in the [`SendRemainingBalance{:tact}`](/book/message-mode#base-modes) ($128$) mode.

In case reservation attempt fails and in the default case without the attempt, the message is sent with the [`SendRemainingValue{:tact}`](https://docs.tact-lang.org/book/message-mode#base-modes) ($64$) mode instead.
In case reservation attempt fails and in the default case without the attempt, the message is sent with the [`SendRemainingValue{:tact}`](/book/message-mode#base-modes) ($64$) mode instead.

:::note

Note, that `self.forward(){:tact}` never sends additional [nanoToncoins][nano] on top of what's available on the balance.\
To be able to send more [nanoToncoins][nano] with a single message, use the the [`send(){:tact}`](/ref/core-common#send) function.
To be able to send more [nanoToncoins][nano] with a single message, use the [`send(){:tact}`](/ref/core-common#send) function.

:::

Expand Down
4 changes: 3 additions & 1 deletion docs/src/content/docs/ref/core-common.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,9 @@ send(SendParameters{
fun emit(body: Cell);
```

[Queues the message](/book/send#outbound-message-processing) `body` to be sent to the outer world with the purpose of logging and analyzing it later off-chain. The message does not have a recipient and is gas-efficient compared to using any other message sending functions of Tact.
[Queues the message](/book/send#outbound-message-processing) `body` to be sent to the outer world with the purpose of logging and analyzing it later off-chain. The message does not have a recipient and is gas-efficient compared to using any other [message sending functions](/book/send#message-sending-functions) of Tact.

The message is sent with the default mode: [`SendDefaultMode`](/book/message-mode#base-modes) ($0$).

Attempts to queue more than $255$ messages throw an exception with an [exit code 33](/book/exit-codes#33): `Action list is too long`.

Expand Down

0 comments on commit 93cdfae

Please sign in to comment.