From 93cdfae880fda358097aaa4df31d4344ad0c2339 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Fri, 24 Jan 2025 12:28:25 +0100 Subject: [PATCH] docs: clarify interactions of message sending functions and their modes Also listed functions with implicit mode, plus made some minor fixes on related pages --- dev-docs/CHANGELOG.md | 1 + docs/src/content/docs/book/message-mode.mdx | 11 +++-- docs/src/content/docs/book/send.mdx | 52 ++++++++++++++++++--- docs/src/content/docs/ref/core-base.mdx | 6 +-- docs/src/content/docs/ref/core-common.mdx | 4 +- 5 files changed, 60 insertions(+), 14 deletions(-) diff --git a/dev-docs/CHANGELOG.md b/dev-docs/CHANGELOG.md index 3c0ed84db..6a7daee0f 100644 --- a/dev-docs/CHANGELOG.md +++ b/dev-docs/CHANGELOG.md @@ -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 diff --git a/docs/src/content/docs/book/message-mode.mdx b/docs/src/content/docs/book/message-mode.mdx index b3478f9c6..b03caba5f 100644 --- a/docs/src/content/docs/book/message-mode.mdx +++ b/docs/src/content/docs/book/message-mode.mdx @@ -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 diff --git a/docs/src/content/docs/book/send.mdx b/docs/src/content/docs/book/send.mdx index b6ba343e4..c4ec19085 100644 --- a/docs/src/content/docs/book/send.mdx +++ b/docs/src/content/docs/book/send.mdx @@ -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: @@ -112,20 +112,23 @@ 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! } ``` @@ -133,9 +136,45 @@ 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 @@ -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 diff --git a/docs/src/content/docs/ref/core-base.mdx b/docs/src/content/docs/ref/core-base.mdx index 8ccfbfe2e..25fb71cad 100644 --- a/docs/src/content/docs/ref/core-base.mdx +++ b/docs/src/content/docs/ref/core-base.mdx @@ -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. ::: diff --git a/docs/src/content/docs/ref/core-common.mdx b/docs/src/content/docs/ref/core-common.mdx index e93ee2955..47acf0d72 100644 --- a/docs/src/content/docs/ref/core-common.mdx +++ b/docs/src/content/docs/ref/core-common.mdx @@ -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`.