From a90b7af2c3117410187cc22b9990d0bc07707645 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:55:35 +0400 Subject: [PATCH 1/7] chore(deps-dev): bump @types/node from 22.7.5 to 22.7.9 (#981) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.7.5 to 22.7.9. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index b1f867fa8..5431e2da9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1442,9 +1442,9 @@ integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/node@*", "@types/node@>=13.7.0", "@types/node@^22.5.0": - version "22.7.5" - resolved "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" - integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== + version "22.7.9" + resolved "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz#2bf2797b5e84702d8262ea2cf843c3c3c880d0e9" + integrity sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg== dependencies: undici-types "~6.19.2" From 458f9968a162980ebfbe54dc85986730bbcbedb9 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Thu, 24 Oct 2024 20:58:59 +0200 Subject: [PATCH 2/7] refactor(docs): exit codes page overhaul (#978) --- CHANGELOG.md | 1 + cspell-fift-words-adjusted.txt | 1 + cspell.json | 2 + docs/README.md | 4 + docs/astro.config.mjs | 3 +- docs/cspell.json | 38 +- docs/src/content/docs/book/exit-codes.mdx | 732 +++++++++++++----- src/generator/createABI.ts | 4 +- .../local-type-inference.spec.ts.snap | 4 +- .../contracts/compute-phase-errors.tact | 2 +- 10 files changed, 566 insertions(+), 225 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7adda2edc..ed59a4c82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `parseImports` function now returns AST import nodes instead of raw strings: PR [#966](https://github.com/tact-lang/tact/pull/966) - Optional types for `self` argument in `extends mutates` functions are now allowed: PR [#854](https://github.com/tact-lang/tact/pull/854) +- Docs: complete overhaul of the exit codes page: PR [#978](https://github.com/tact-lang/tact/pull/978) ### Fixed diff --git a/cspell-fift-words-adjusted.txt b/cspell-fift-words-adjusted.txt index 3ec53abab..53d734641 100644 --- a/cspell-fift-words-adjusted.txt +++ b/cspell-fift-words-adjusted.txt @@ -29,6 +29,7 @@ Pos Split abort abs +addop allot and anon diff --git a/cspell.json b/cspell.json index 382c2ef3a..fdbd9dac6 100644 --- a/cspell.json +++ b/cspell.json @@ -106,6 +106,8 @@ "Tarjan", "testdata", "Topup", + "Toncoin", + "Toncoins", "Trunov", "typechecker", "uintptr", diff --git a/docs/README.md b/docs/README.md index b78cb8fc1..4b626ddbd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -101,6 +101,10 @@ All commands are run from the root of the project, from a terminal: | `yarn astro ...` | Run CLI commands like `astro add`, `astro check`, etc. | `yarn astro -- --help` | Get help using the Astro CLI. +### ⚠️ Gotchas + +- When updating TextMate grammars in `grammars/` (for example, `grammar-tact.json`), make sure that the value for the `"name"` property is written all lowercase, otherwise highlighting will break. + ### πŸ‘€ Want to learn more about the framework behind Tact docs? Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat). diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 30d746bf8..a2c581828 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -33,7 +33,8 @@ export default defineConfig({ behavior: "append", properties: { class: "autolink-header", - ariaHidden: true, + ariaHidden: "true", + ariaLabel: "Link to this header", tabIndex: -1, }, }], diff --git a/docs/cspell.json b/docs/cspell.json index 1f53904be..006f966d6 100644 --- a/docs/cspell.json +++ b/docs/cspell.json @@ -2,55 +2,39 @@ "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", "version": "0.2", "language": "en", + "dictionaryDefinitions": [ + { + "name": "fift-words", + "path": "../cspell-fift-words-adjusted.txt" + }, + { + "name": "tvm-instructions", + "path": "../cspell-tvm-instructions.txt" + } + ], + "dictionaries": ["fift-words", "tvm-instructions"], "words": [ "ADDRAND", "BBITS", "BREFS", "Brujin", - "CHKSIGNS", - "CHKSIGNU", - "CONFIGOPTPARAM", - "CTOS", "Cheatsheet", "Cheatsheets", "Comptime", - "DEBUGSTR", - "DUEPAYMENT", "Daniil", "Decompilation", "Decompiled", "Descr", "DomΓ­nguez", - "ENDC", "Epva", - "Fift", - "Fift", - "GASCONSUMED", - "GETFORWARDFEE", - "GETFORWARDFEESIMPLE", - "GETGASFEE", - "GETGASFEESIMPLE", - "GETORIGINALFWDFEE", - "GETSTORAGEFEE", "Georgiy", - "HASHCU", - "HASHEXT", - "HASHSU", "HΓ©ctor", "IPFS", "JesΓΊs", "Jetton", "Jettons", - "KECCAK", "Komarov", "Korshakov", - "LDDICT", - "LDIX", - "LDREF", - "LDSLICEX", - "LDUX", - "LDVARUINT", - "LTIME", "Laika", "MYADDR", "Masterchain", diff --git a/docs/src/content/docs/book/exit-codes.mdx b/docs/src/content/docs/book/exit-codes.mdx index dc5c78744..8ede3e76a 100644 --- a/docs/src/content/docs/book/exit-codes.mdx +++ b/docs/src/content/docs/book/exit-codes.mdx @@ -3,323 +3,671 @@ title: Exit codes description: "An exit code is a 32-bit signed integer, which indicates whether the compute or action phase of the transaction was successful, and if not β€” signals the code of the exception occurred" --- -:::caution - THis page is under re-construction as per [#106](https://github.com/tact-lang/tact-docs/issues/106). All anchor links (`#`) may change in the future! +Each transaction on TON Blockchain consists of [multiple phases](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases). An _exit code_ is a $32$-bit signed integer, which indicates whether the [compute](#compute) or [action](#action) phase of the transaction was successful, and if not β€” holds the code of the exception occurred. Each exit code represents its own exception or resulting state of the transaction. + +Exit codes $0$ and $1$ indicate normal (successful) execution of the [compute phase](#compute). Exit (or [result](#action)) code $0$ indicates normal (successful) execution of the [action phase](#action). Any other exit code indicates that a certain exception has occurred and that the transaction wasn't successful in one way or another, i.e. transaction was reverted or the inbound message has bounced back. + +TON Blockchain reserves exit code values from $0$ to $127$, while Tact utilizes exit codes from $128$ to $255$. Note, that exit codes used by Tact indicate contract errors which can occur when using Tact-generated FunC code, and are therefore thrown in the transaction's [compute phase](#compute) and not during the compilation. + +The range from $256$ to $65535$ is free for developer-defined exit codes. + +:::note + + While an exit (or [result](#action)) code is a $32$-bit signed integer on TON Blockchain, an attempt to [throw](/ref/core-debug) an exit code out of bounds of the $16$-bit unsigned integer ($0 - 65535$) will cause an error with [exit code 5](#5). That's done intentionally to prevent some exit codes from being produced artificially, such as the [exit code -14](#-14). + ::: -An exit code is a $16$-bit unsigned integer which ranges between $0$ to $65535$ (or $2_{16} - 1$). - -Codes from $0$ to $127$ are allocated for FunC (TVM), $128$ to $255$ for Tact. The range from $256$ to $65535$ is free for developer-defined exit codes. - -List of pre-allocated exit codes: - -Exit Code | phase | Description -:--------- | :----------------- | -------------------------------------------------------------------------------------------------------------------------- -$0$ | [Compute phase][c] | Standard successful execution exit code -$2$ | [Compute phase][c] | Stack underflow. Last op-code consumed more elements than there are on the stacks -$3$ | [Compute phase][c] | Stack overflow. More values have been stored on a stack than allowed by this version of TVM -$4$ | [Compute phase][c] | Integer overflow. Integer does not fit into βˆ’2256 ≀ x < 2256 or a division by zero has occurred -$5$ | [Compute phase][c] | Integer out of expected range -$6$ | [Compute phase][c] | Invalid opcode. Instruction is unknown in the current TVM version -$7$ | [Compute phase][c] | Type check error. An argument to a primitive is of an incorrect value type -$8$ | [Compute phase][c] | Cell overflow. Writing to builder is not possible since after operation there would be more than 1023 bits or 4 references -$9$ | [Compute phase][c] | Cell underflow. Read from slice primitive tried to read more bits or references than there are -$10$ | [Compute phase][c] | Dictionary error. Error during manipulation with dictionary (hashmaps) -$13$ | [Compute phase][c] | Out of gas error. Thrown by TVM when the remaining gas becomes negative -$-14$ | [Compute phase][c] | It means out of gas error, same as $13$. Negative, because it cannot be faked -$32$ | [Action phase][a] | Action list is invalid. Set during action phase if c5 register after execution contains unparsable object -$34$ | [Action phase][a] | Action is invalid or not supported. Set during action phase if current action cannot be applied -$37$ | [Action phase][a] | Not enough TON. Message sends too much TON (or there is not enough TON after deducting fees) -$38$ | [Action phase][a] | Not enough extra-currencies -$128$ | Tact (Compiler) | Null reference exception β€” compiler expects an integer or cell but a null value has been passed -$129$ | Tact (Compiler) | Invalid serialization prefix β€” if there is any inconsistency with the previous op-code check, this exit code will be thrown -$130$ | Tact (Compiler) | Invalid incoming message β€” no suitable operation is found -$131$ | Tact (Compiler) | Constraints error -$132$ | Tact (Compiler) | Access denied β€” someone other than the owner sent a message to the contract -$133$ | Tact (Compiler) | Contract stopped β€” a message has been sent to a stopped contract -$134$ | Tact (Compiler) | Invalid argument β€” invalid Base64 string -$135$ | Tact (Compiler) | Code of a contract was not found β€” false flag for a dictionary call -$136$ | Tact (Compiler) | Invalid Address β€” Non $267$-bit Address or invalid chain id (other than 0 or -1) -$137$ | Tact (Compiler) | Masterchain support is not enabled for this contract +## Table of exit codes {#table} + +The following table lists exit codes with an origin (where it can occur) and a short description for each. + +The table doesn't list the exit code of the [`require()`](/ref/core-debug#require), as it generates it depending on the concrete `error` message [String][p]. + +Exit code | Origin | Brief description +:------------ | :---------------------------------- | :---------------- +[$0$](#0) | [Compute][c] and [action][a] phases | Standard successful execution exit code. +[$1$](#1) | [Compute phase][c] | Alternative successful execution exit code. Reserved, but doesn't occur. +[$2$](#2) | [Compute phase][c] | Stack underflow. +[$3$](#3) | [Compute phase][c] | Stack overflow. +[$4$](#4) | [Compute phase][c] | Integer overflow. +[$5$](#5) | [Compute phase][c] | Range check error β€” some integer is out of its expected range. +[$6$](#6) | [Compute phase][c] | Invalid [TVM][tvm] opcode. +[$7$](#7) | [Compute phase][c] | Type check error. +[$8$](#8) | [Compute phase][c] | Cell overflow. +[$9$](#9) | [Compute phase][c] | Cell underflow. +[$10$](#10) | [Compute phase][c] | Dictionary error. +[$11$](#11) | [Compute phase][c] | Described in [TVM][tvm] docs as "Unknown error, may be thrown by user programs". +[$12$](#12) | [Compute phase][c] | Fatal error. Thrown by [TVM][tvm] in situations deemed impossible. +[$13$](#13) | [Compute phase][c] | Out of gas error. +[$-14$](#-14) | [Compute phase][c] | Same as $13$. Negative, so that it [cannot be faked](#13). +[$14$](#14) | [Compute phase][c] | VM virtualization error. Reserved, but never thrown. +[$32$](#32) | [Action phase][a] | Action list is invalid. +[$33$](#33) | [Action phase][a] | Action list is too long. +[$34$](#34) | [Action phase][a] | Action is invalid or not supported. +[$35$](#35) | [Action phase][a] | Invalid source address in outbound message. +[$36$](#36) | [Action phase][a] | Invalid destination address in outbound message. +[$37$](#37) | [Action phase][a] | Not enough Toncoin. +[$38$](#38) | [Action phase][a] | Not enough extra currencies. +[$39$](#39) | [Action phase][a] | Outbound message does not fit into a cell after rewriting. +[$40$](#40) | [Action phase][a] | Cannot process a message β€” not enough funds, the message is too large or its Merkle depth is too big. +[$41$](#41) | [Action phase][a] | Library reference is null during library change action. +[$42$](#42) | [Action phase][a] | Library change action error β€” error during an attempt of the library change action. +[$43$](#43) | [Action phase][a] | Exceeded maximum number of cells in the library or the maximum depth of the Merkle tree. +[$50$](#50) | [Action phase][a] | Account state size exceeded limits. +[$128$](#128) | Tact compiler ([Compute phase][c]) | Null reference exception. +[$129$](#129) | Tact compiler ([Compute phase][c]) | Invalid serialization prefix. +[$130$](#130) | Tact compiler ([Compute phase][c]) | Invalid incoming message β€” there's no receiver for the opcode of the received message. +[$131$](#131) | Tact compiler ([Compute phase][c]) | Constraints error. Reserved, but never thrown. +[$132$](#132) | Tact compiler ([Compute phase][c]) | Access denied β€” someone other than the owner sent a message to the contract. +[$133$](#133) | Tact compiler ([Compute phase][c]) | Contract stopped. Reserved, but never thrown. +[$134$](#134) | Tact compiler ([Compute phase][c]) | Invalid argument. +[$135$](#135) | Tact compiler ([Compute phase][c]) | Code of a contract was not found. +[$136$](#136) | Tact compiler ([Compute phase][c]) | Invalid address. +[$137$](#137) | Tact compiler ([Compute phase][c]) | Masterchain support is not enabled for this contract. + +:::note + + Often enough you might encounter the exit code $65535$ (or `0xffff`), which usually means the same as the [exit code 130](#130) β€” the received opcode is unknown to the contract as there were no receivers expecting it. When writing contracts, the exit code $65535$ is set by the developers and not by [TVM][tvm]or the Tact compiler. + +::: [c]: https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase [a]: https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases -Q: **Where to observe the list of all auto-generated exit codes in your project?**\ -A: The Tact Compiler collects all exit codes at the end of a *.md file and you can track them in the directory along -the path "./ProjectFolder/build/ProjectName/tact_ProjectName.md" +## Exit codes in Blueprint projects {#blueprint} -Q: **How to observe a thrown exit code?**\ -A: In Tact, it's not wise to print the transactions to see the results because they are not easy to read. If you want to see the exit code of a transaction, -use the below template in your Typescript local tests: +In [Blueprint][bp] tests, exit codes from the [compute phase](#compute) are specified in the `exitCode` field of the object argument for `toHaveTransaction(){:typescript}` method of `expect(){:typescript}` matcher. The field for the [result](#action) codes (exit codes from the [action phase](#action)) in the same `toHaveTransaction(){:typescript}` method is called `actionResultCode`. -```typescript -const sender = await blockchain.treasury('sender'); -const result = await contractName.send(sender.getSender(), { value: toNano('0.05'), }, { transactionData }); +:::note + + Read more about expecting specific exit codes: [Transactions with intentional errors](/book/debug#tests-errors). + +::: + +Additionally, one can take a look at the result of [sending a message to a contract](/book/debug#tests-send) and discover the phases of each transaction and their values, including exit (or result) codes for [compute phase](#compute) (or [action phase](#action)). -expect(result.transactions).toHaveTransaction( - { from: sender.address, to: contractName.address, exitCode: YOUR_DESIRED_EXIT_CODE } -); +Note, that in order to do so, you'll have to do a couple of type checks before that: + +```typescript +it('tests something, you name it', async () => { + // Send a specific message to our contract and store the results + const res = await your_contract_name.send(…); + + // Now, we have an access to array of executed transactions, + // with the second one (index 1) being the one that we look for + const tx = res.transactions[1]!; + + // To do something useful with it, let's ensure that it's type is 'generic' + // and that the compute phase in it wasn't skipped + if (tx.description.type === "generic" + && tx.description.computePhase.type === "vm") { + // Finally, we're able to freely peek into the transaction for general details, + // such as printing out the exit code of the compute phase if we so desire + console.log(tx.description.computePhase.exitCode); + } + + // ... +}); ``` -* First line defines the sender. -* Second line sends the transaction. -* In the third line, you check if the result has a transaction from sender to your contract with your desired exit code. -## Compute phase +## Compute and action phases + +### 0: Normal termination {#0} + +This exit (or [result](#action)) code indicates a successful completion of the [compute](#compute) (or [action](#action)) phase of the transaction. + +## Compute phase {#compute} + +[TVM][tvm] initialization and all computations occur in the [compute phase][c]. -### $0$: Successful execution {#0} +If the compute phase fails (the resulting exit code isn't [$0$](#0) or [$1$](#1)), the transaction skips the [action phase](#action) and goes to the bounce phase. In it, the bounce message is formed for the transactions initiated by the inbound message. -This exit code means that the Compute phase of the transaction was completed successfully. +### 1: Alternative termination {#1} -### $4$: Integer overflow {#4} +This is an alternative exit code for the successful execution of the [compute phase](#compute). Reserved, but never occurs. -In TVM, integer can be in the range -2256 < x < 2256. -If the value during the calculation went beyond this range, then 4 exit code is thrown. +### 2: Stack underflow {#2} -Example: +If some operation consumed more elements than there were on the stacks, the error with exit code $2$ is thrown: `Stack underflow`. ```tact -self.id = 1; // force not to ignore it by using storage variables -repeat(256) { - self.id = 2 * self.id; +asm fun drop() { DROP } + +contract Loot { + receive("I solemnly swear that I'm up to no good") { + try { + // Removes 100 elements from the stack, causing an underflow + repeat (100) { drop() } + } catch (exitCode) { + // exitCode is 2 + } + } } ``` -### $5$: Integer out of expected range {#5} +:::note[Useful links:] + + [TVM is a stack machine](https://docs.ton.org/learn/tvm-instructions/tvm-overview#tvm-is-a-stack-machine) in TON Docs. + +::: -If the integer value went beyond the expected range, then 5 exit code is thrown. -For example, if a negative value was used in the .store_uint() function. In Tact, there are some other new situations such as:\ -1- As you know, you can define more limited integers in Tact (integers with less than 257 bits). -If you try to store a number in this kind of integers and the number doesn't fit to this limited range, you will face this exit code.\ -2- according to ```storeUint(self: Builder, value: Int, bits: Int)``` function, it's not possible to use ```storeUint(0, 257)``` because ```0 ≀ bits ≀ 256```. +### 3: Stack overflow {#3} -Example: +If there are too many elements copied into a closure continuation or stored on the stack, an error with exit code $3$ is thrown: `Stack overflow`. Occurs rarely, unless you're deep in [Fift and TVM assembly](https://docs.ton.org/develop/fift/fift-and-tvm-assembly) trenches: ```tact -// option 1 -> id: Int as uint32 -self.id = 1; // force not to ignore it by using storage variables -repeat(32) { - self.id = 2 * self.id; +// Remember kids, don't try to overflow the stack at home! +asm fun stackOverflow() { + <{ + }>CONT // c + 0 SETNUMARGS // c' + 2 PUSHINT // c' 2 + SWAP // 2 c' + 1 -1 SETCONTARGS // <- this blows up } -// option 2 -> according to storeUint(self: Builder, value: Int, bits: Int) function, it's not possible to use storeUint(0, 1024) because 0 ≀ bits ≀ 256 -let s: Slice = beginCell().storeUint(0, 257).asSlice(); +contract ItsSoOver { + receive("I solemnly swear that I'm up to no good") { + try { + stackOverflow(); + } catch (exitCode) { + // exitCode is 3 + } + } +} ``` -### $8$: Cell overflow {#8} -A cell has the capacity to store 1023 bits of data and 4 references to other cells. -If you try to write more than 1023 bits or more than 4 references, 8 exit code is thrown. +:::note[Useful links:] -Example: + [TVM is a stack machine](https://docs.ton.org/learn/tvm-instructions/tvm-overview#tvm-is-a-stack-machine) in TON Docs. + +::: + +### 4: Integer overflow {#4} + +If the value in calculation goes beyond the range from $-2^{256}$ to $2^{256} - 1$ inclusive, or there's an attempt to [divide](/book/operators#binary-divide) or [modulo](/book/operators#binary-modulo) by zero, an error with exit code $4$ is thrown: `Integer overflow`. ```tact -// according to storeUint(self: Builder, value: Int, bits: Int) function, it's not possible to use storeUint(0, 1024) because 0 ≀ bits ≀ 256 -let s: Slice = beginCell().storeUint(0, 256).storeUint(0, 256).storeUint(0, 256).storeUint(0, 256).asSlice(); +let x = -pow(2, 255) - pow(2, 255); // -2^{256} + +try { + -x; // integer overflow by negation + // since the max positive value is 2^{256} - 1 +} catch (exitCode) { + // exitCode is 4 +} + +try { + x / 0; // division by zero! +} catch (exitCode) { + // exitCode is 4 +} + +try { + x * x * x; // integer overflow! +} catch (exitCode) { + // exitCode is 4 +} + +// There can also be an integer overflow when doing: +// addition (+), +// subtraction (-), +// division (/) by a negative number or modulo (%) by zero ``` -### $9$: Cell underflow {#9} +### 5: Integer out of range {#5} -If you try to read more data from a slice than it contains, then 9 exit code is thrown. +Range check error β€” some integer is out of its expected range. I.e. any attempt to store an unexpected amount of data or specify an out-of-bounds value throws an error with exit code $5$: `Integer out of range`. -Example: +Examples of specifying an out-of-bounds value: ```tact -let s: Slice = emptySlice(); -self.id = s.loadUint(1); // force not to ignore it by using storage variables +try { + // Repeat only operates on inclusive range from 1 to 2^{31} - 1 + // and any valid integer value greater than that causes an error with exit code 5 + repeat (pow(2, 55)) { + dump("smash. logs. I. must."); + } +} catch (exitCode) { + // exitCode is 5 +} + +try { + // Builder.storeUint() function can only use up to 256 bits, so 512 is too much: + let s: Slice = beginCell().storeUint(-1, 512).asSlice(); +} catch (exitCode) { + // exitCode is 5 +} ``` -### $13$: Out of gas error {#13} +### 6: Invalid opcode {#6} -If there isn't enough TON to handle compute phase, this error is thrown. +If you specify an instruction that is not defined in the current [TVM][tvm] version, an error with exit code $6$ is thrown: `Invalid opcode`. -During processing, the NOT operation is applied to this value, which changes this value to -14. This is done so that this exit code cannot be faked using the throw function, since all such functions accept only positive values for the exit code as it was discussed previously. +```tact +// No such thing +asm fun invalidOpcode() { x{D7FF} @addop } + +contract OpOp { + receive("I solemnly swear that I'm up to no good") { + try { + invalidOpcode(); + } catch (exitCode) { + // exitCode is 6 + } + } +} +``` -Example: +### 7: Type check error {#7} + +If an argument to a primitive is of an incorrect value type or there's any other mismatch in types during the [compute phase](#compute), an error with exit code $7$ is thrown: `Type check error`. ```tact -repeat(10000) { - self.id += 1; +// The actual returned value type doesn't match the declared one +asm fun typeCheckError(): map { 42 PUSHINT } + +contract VibeCheck { + receive("I solemnly swear that I'm up to no good") { + try { + // The 0th index doesn't exist + typeCheckError().get(0)!!; + } catch (exitCode) { + // exitCode is 7 + } + } } ``` -## Action phase +### 8: Cell overflow {#8} + +From [Cells, Builders and Slices page](/book/cells#cells) of the Book: -### $34$: Action is invalid or not supported {#34} +> [`Cell{:tact}`][cell] is a [primitive][p] and a data structure, which [ordinarly](/book/cells#cells-kinds) consists of up to $1023$ continuously laid out bits and up to $4$ references (refs) to other cells. -This exit code is responsible for most of the errors when working with actions: invalid message, incorrect action, and so on. +To construct a [`Cell{:tact}`][cell], a [`Builder{:tact}`][builder] is used. If you try to store more than $1023$ bits of data or more than $4$ references to other cells, an error with exit code $8$ is thrown: `Cell overflow`. -Example: +This error can be triggered by [manual construction](/book/cells#cnp-manually) of the cells via [relevant `.loadSomething()` methods](/ref/core-cells) or when [using Structs and Messages and their convenience methods](/book/cells#cnp-structs). ```tact -nativeSendMessage(emptyCell(), 0); +// Too much bits +try { + let data = beginCell() + .storeInt(0, 250) + .storeInt(0, 250) + .storeInt(0, 250) + .storeInt(0, 250) + .storeInt(0, 24) // 1024 bits! + .endCell(); +} catch (exitCode) { + // exitCode is 8 +} + +// Too much refs +try { + let data = beginCell() + .storeRef(emptyCell()) + .storeRef(emptyCell()) + .storeRef(emptyCell()) + .storeRef(emptyCell()) + .storeRef(emptyCell()) // 5 refs! + .endCell(); +} catch (exitCode) { + // exitCode is 8 +} ``` -### $37$: Not enough TON {#37} +### 9: Cell underflow {#9} + +From [Cells, Builders and Slices page](/book/cells#cells) of the Book: + +> `Cell{:tact}` is a [primitive][p] and a data structure, which [ordinarly](/book/cells#cells-kinds) consists of up to $1023$ continuously laid out bits and up to $4$ references (refs) to other cells. -It means that there isn't enough TON to send the specified amount of it. +To parse a [`Cell{:tact}`][cell], a [`Slice{:tact}`][slice] is used. If you try to load more data or references than `Slice{:tact}` contains, an error with exit code $9$ is thrown: `Cell underflow`. -Example: +The most common cause of this error is a mismatch between the expected and actual memory layouts of the cells, so it's recommended to [use Structs and Messages for parsing](/book/cells#cnp-structs) of the cells instead of [manual parsing](/book/cells#cnp-manually) via [relevant `.loadSomething()` methods](/ref/core-cells). ```tact -send(SendParameters{to: context().sender, value: ton("10")}); +// Too few bits +try { + emptySlice().loadInt(1); // 0 bits! +} catch (exitCode) { + // exitCode is 9 +} + +// Too few refs +try { + emptySlice().loadRef(); // 0 refs! +} catch (exitCode) { + // exitCode is 9 +} ``` -## Tact (Compiler) +### 10: Dictionary error {#10} -### 128: Null reference exception {#128} +In Tact, the [`map{:tact}`](/book/maps) type is an abstraction over the ["hash" map dictionaries of FunC](https://docs.ton.org/develop/func/dictionaries#hashmap) and underlying [`HashmapE` type](https://docs.ton.org/develop/data-formats/tl-b-types#hashmap) of [TL-B][tlb] and [TVM][tvm]. -If there's a non-null assertion, such as the [`!!{:tact}`](/book/operators#unary-non-null-assert) operator, and the checked value is [`null{:tact}`](/book/optionals), an error with exit code $128$ is thrown: `Null reference exception`. +If there is an incorrect manipulation of dictionaries, such as improper assumptions about their memory layout, an error with exit code $10$ is thrown: `Dictionary error`. Note, that Tact prevents you from getting this error unless you do [Fift and TVM assembly](https://docs.ton.org/develop/fift/fift-and-tvm-assembly) work yourself: ```tact -let gotcha: String? = null; +/// Pre-computed Int to Int dictionary with two entries β€” 0: 0 and 1: 1 +const cellWithDictIntInt: Cell = cell("te6cckEBBAEAUAABAcABAgPQCAIDAEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMLMbT1U="); + +/// Tries to preload a dictionary from a Slice as a map +asm fun toMapIntCell(x: Slice): map { PLDDICT } + +contract DictPic { + receive("I solemnly swear that I'm up to no good") { + try { + // The Int to Int dictionary is being misinterpreted as a map + let m: map = toMapIntCell(cellWithDictIntInt.beginParse()); + + // And the error happens only when we touch it + m.get(0)!!; + } catch (exitCode) { + // exitCode is 10 + } + } +} +``` + +### 11: "Unknown" error {#11} + +Described in [TVM][tvm] docs as "Unknown error, may be thrown by user programs", although most commonly used for problems with queueing a message send or problems with [get-methods](https://docs.ton.org/develop/smart-contracts/guidelines/get-methods). +```tact try { - // Asserting that the value isn't null, which isn't the case! - dump(gotcha!!); + // Unlike nativeSendMessage which uses SENDRAWMSG, this one uses SENDMSG, + // and therefore fails in Compute phase when the message is ill-formed + nativeSendMessageReturnForwardFee(emptyCell(), 0); } catch (exitCode) { - exitCode; // 128 + // exitCode is 11 } ``` -### $130$: Invalid incoming message {#130} +### 12: Fatal error {#12} -When you send a message to a contract, the first 32 bits of message body is the op code. It determines the operation that must be done. -In FunC, if no op code is found, 0xffff will be thrown. In Tact, 130 exit code will be thrown. +Fatal error. Thrown by TVM in situations deemed impossible. -Example: +### 13: Out of gas error {#13} -1. First, define an empty contract like below: +If there isn't enough gas to end computations in the [compute phase](#compute), the error with exit code $13$ is thrown: `Out of gas error`. + +But this code isn't immediately shown as is β€” instead, the bitwise NOT operation is applied, which changes the value from $13$ to $-14$. And only then the code is shown. + +That's done in order to prevent the resulting code ($-14$) from being produced artificially in user contracts, since all functions that can [throw an exit code](/ref/core-debug) can only specify integers in the range from $0$ to $65535$ inclusive. ```tact -contract Fireworks {} +try { + repeat (pow(2, 31) - 1) {} +} catch (exitCode) { + // exitCode is -14 +} ``` -2. Then, send a message to this contract. Because no suitable operation is found, you will get this exit code. +### -14: Out of gas error {#-14} -### $132$: Access denied {#132} +See [exit code 13](#13). -First, you should import and inherit from Ownable Trait. After it, your contract will have an owner. -You can ask for a check by calling ```self.requireOwner();``` in your functions. It will ensure that only the owner can send message to your contract. +### 14: Virtualization error {#14} -Example: +Virtualization error, related to [prunned branch cells](/book/cells#cells-kinds). Reserved, but never thrown. -```tact -import "@stdlib/deploy"; -import "@stdlib/ownable"; +## Action phase {#action} -message FakeLaunch { +The [action phase][a] is processed after the successful execution of the [compute phase](#compute). It attempts to perform the actions stored into the action list by [TVM][tvm] during the compute phase. -} +Some actions may fail during processing, in which case those actions may be skipped or the whole transaction may revert depending on the mode of actions. The code indicating the resulting state of the [action phase][a] is called a _result code_. Since it's also a $32$-bit signed integer that essentially serves the same purpose as _exit code_ of [compute phase](#compute), it's common to call the result code an exit code too. -contract Fireworks with - Deployable, - Ownable, -{ - owner: Address; +### 32: Action list is invalid {#32} - init(){ - self.owner = sender(); - } +If the list of actions contains [exotic cells](/book/cells#cells-kinds), an action entry cell does not have references or some action entry cell couldn't be parsed, an error with exit code $32$ is thrown: `Action list is invalid`. - receive(msg: FakeLaunch){ - self.requireOwner(); - } -} +:::note + + Aside from this exit code there's a boolean flag `valid`, which you can find under `description.actionPhase.valid` in the transaction results when working with [Sandbox and Blueprint](#blueprint). Transaction can set this flag to `false` even when there is some other exit code thrown from the action phase. -fun requireOwner() { - nativeThrowUnless(132, sender() == self.owner); +::: + +### 33: Action list is too long {#33} + +If there are more than $255$ actions queued for execution, the [action phase](#action) will throw an error with an exit code $33$: `Action list is too long`. + +```tact +// For example, let's attempt to queue reservation of specific amount of nanoToncoins +// This won't fail in compute phase, but will result in exit code 33 in Action phase +repeat (256) { + nativeReserve(ton("0.001"), ReserveAtMost); } ``` -### $133$: Contract stopped {#133} - -The stoppable trait allows to stop the contract. -If you send a message to a stopped contract, and the contract asks for a check by running ```self.requireNotStopped();```, this exit code will be thrown. -In the current version of Tact, 40368 exit code will be thrown instead of 133. +### 34: Invalid or unsupported action {#34} -Example: +There are only four supported actions at the moment: changing the contract code, sending a message, reserving a specific amount of [nanoToncoins](/book/integers#nanotoncoin) and changing the library cell. If there's any issue with the specified action (invalid message, unsupported action, etc.), an error with exit code $34$ is thrown: `Invalid or unsupported action`. ```tact -import "@stdlib/deploy"; -import "@stdlib/ownable"; -import "@stdlib/stoppable"; +// For example, let's try to send an ill-formed message: +nativeSendMessage(emptyCell(), 0); // won't fail in compute phase, + // but will result in exit code 34 in Action phase +``` -message FakeLaunch {} +### 35: Invalid source address in outbound message {#35} -contract Fireworks with - Deployable, - Ownable, - Stoppable, -{ - owner: Address; - stopped: Bool; +If the source address in the outbound message isn't equal to [`addr_none`](https://docs.ton.org/develop/data-formats/msg-tlb#addr_none00) or to the address of the contract that initiated this message, an error with exit code $35$ is thrown: `Invalid source address in outbound message`. - init() { - self.owner = sender(); - self.stopped = false; - } +### 36: Invalid destination address in outbound message {#36} - receive(msg: FakeLaunch) { - self.stopped = true; - self.requireNotStopped(); - } -} +If the destination address in the outbound message is invalid, e.g. it doesn't conform to the relevant [TL-B][tlb] schemas, contains unknown workchain ID or it has invalid length for the given workchain, an error with exit code $36$ is thrown: `Invalid destination address in outbound message`. + +:::note + + If the [optional flag +2](/book/message-mode#optional-flags) is set, this error won't be thrown and the given message won't be sent. + +::: + +### 37: Not enough Toncoin {#37} + +If all funds of the inbound message with [base mode 64](/book/message-mode#base-modes) set had been already consumed and there's not enough funds to pay for the failed action, or the [TL-B][tlb] layout of the provided value ([`CurrencyCollection`](https://docs.ton.org/develop/data-formats/msg-tlb#currencycollection)) is invalid, or there's not enough funds to pay [forward fees](https://docs.ton.org/develop/smart-contracts/guidelines/processing) or not enough funds after deducting fees, an error with exit code $37$ is thrown: `Not enough Toncoin`. + +:::note + + If the [optional flag +2](/book/message-mode#optional-flags) is set, this error won't be thrown and the given message won't be sent. + +::: + +### 38: Not enough extra currencies {#38} + +Besides the native currency, Toncoin, TON Blockchain supports up to $2^{32}$ extra currencies. They differ from making new [Jettons](/cookbook/jettons) because extra currencies are natively supported β€” one can potentially just specify an extra [`HashmapE`](https://docs.ton.org/develop/data-formats/tl-b-types#hashmap) of extra currency amounts in addition to the Toncoin amount in the internal message to another contract. Unlike Jettons, extra currencies can only be stored and transferred and do not have any other functionality. + +At the moment, **there are no extra currencies** on TON Blockchain, but the exit code $38$ in cases when there is not enough extra currency to send the specified amount of it is already reserved: `Not enough extra currencies`. + +:::note[Useful links:] + + [Extra currencies](https://docs.ton.org/develop/dapps/defi/coins) in TON Docs.\ + [Extra currency mining](https://docs.ton.org/develop/research-and-development/minter-flow) in TON Docs. + +::: + +### 39: Outbound message doesn't fit into a cell {#39} + +When processing the message, TON Blockchain tries to pack it according to the [relevant TL-B schemas](https://docs.ton.org/develop/data-formats/msg-tlb), and if it cannot an error with exit code $39$ is thrown: `Outbound message doesn't fit into a cell`. + +:::note + + If attempts at sending the message fail multiple times and the [optional flag +2](/book/message-mode#optional-flags) is set, this error won't be thrown and the given message won't be sent. + +::: + +### 40: Cannot process a message {#40} + +If there would not be enough funds to process all the cells in a message, the message is too large or its Merkle depth is too big, an error with exit code $40$ is thrown: `Cannot process a message`. + +### 41: Library reference is null {#41} + +If the library reference was required during library change action, but it was null, an error with exit code $41$ is thrown: `Library reference is null`. + +### 42: Library change action error {#42} + +If there's an error during an attempt at library change action, an error with exit code $42$ is thrown: `Library change action error`. + +### 43: Library limits exceeded {#43} + +If the maximum number of cells in the library is exceeded or the maximum depth of the Merkle tree is exceeded, an error with exit code $43$ is thrown: `Library limits exceeded`. + +### 50: Account state size exceeded limits {#50} + +If the account state (contract storage, essentially) exceeds any of the limits specified in [config param 43 of TON Blockchain](https://docs.ton.org/develop/howto/blockchain-configs#param-43) by the end of the [action phase](#action), an error with exit code $50$ is thrown: `Account state size exceeded limits`. + +If the configuration is absent, default values are: + +* `max_msg_bits` is equal to $2^{21}$ β€” maximum message size in bits. +* `max_msg_cells` is equal to $2^{13}$ β€” maximum number of [cells][cell] a message can occupy. +* `max_library_cells` is equal to $1000$ β€” maximum number of [cells][cell] that can be used as [library reference cells](/book/cells#cells-kinds). +* `max_vm_data_depth` is equal to $2^{9}$ β€” maximum [cells][cell] depth in messages and account state. +* `ext_msg_limits.max_size` is equal to $65535$ β€” maximum external message size in bits. +* `ext_msg_limits.max_depth` is equal to $2^{9}$ β€” maximum external message [depth](/book/cells#cells-representation). +* `max_acc_state_cells` is equal to $2^{16}$ β€” maximum number of [cells][cell] that an account state can occupy. +* `max_acc_state_bits` is equal to $2^{16} * 1023$ β€” maximum account state size in bits. +* `max_acc_public_libraries` is equal to $2^{8}$ β€” maximum number of [library reference cells](/book/cells#cells-kinds) that an account state can use on the [masterchain](/book/masterchain). +* `defer_out_queue_size_limit` is equal to $2^{8}$ β€” maximum number of outbound messages to be queued (regards validators and collators). + +## Tact compiler -fun requireNotStopped() { - require(!self.stopped, "Contract stopped"); +Tact utilizes exit codes from $128$ to $255$. Note, that exit codes used by Tact indicate contract errors which can occur when using Tact-generated FunC code, and are therefore thrown in the transaction's [compute phase](#compute) and not during the compilation. + +### 128: Null reference exception {#128} + +If there's a non-null assertion, such as the [`!!{:tact}`](/book/operators#unary-non-null-assert) operator, and the checked value is [`null{:tact}`](/book/optionals), an error with exit code $128$ is thrown: `Null reference exception`. + +```tact +let gotcha: String? = null; + +try { + // Asserting that the value isn't null, which isn't the case! + dump(gotcha!!); +} catch (exitCode) { + // exitCode is 128 } ``` -### $134$: Invalid argument {#134} +### 129: Invalid serialization prefix {#129} + +Reserved, but due to a number of prior checks it cannot be thrown unless one hijacks the contract code before deployment and changes the opcodes of the [Messages][message] expected to be received in the contract. -This will be thrown by the below FunC function(in the last part of a bunch of if conditions). This function reads something from Base64. +### 130: Invalid incoming message {#130} -If the input characters don't fit into base64 chars, you will encounter this exit code. +If the received internal or external message isn't handled by the contract, an error with exit code $130$ is thrown: `Invalid incoming message`. It usually happens when the contract doesn't have a receiver for the particular message and its opcode prefix (32-bit integer header). -Example: +Consider the following contract: ```tact -let code: Slice = beginCell().storeUint(0, 8).asSlice().fromBase64(); -// 0 is not a valid ASCII code so it cannot be converted to Base64 +import "@stdlib/deploy"; + +contract Dummy with Deployable {} ``` -### $135$: Code of a contract was not found {#135} +If you try to send any message, except for [`Deploy{:tact}`](/ref/stdlib-deploy#deploy) provided by [`@stdlib/deploy`](/ref/stdlib-deploy), the contract won't have a receiver for it and thus would throw an error with exit code $130$. + +### 131: Constraints error {#131} -It will check the return flag of a search on the dictionary keys. +Constraints error. Reserved, but never thrown. -Example: +### 132: Access denied {#132} + +If you use the [`Ownable{:tact}`](/ref/stdlib-ownable#ownable) [trait][ct] from the [`@stdlib/ownable`](/ref/stdlib-ownable) library, the helper function `requireOwner(){:tact}` provided by it will throw an error with exit code $132$ if the sender of the inbound message won't match the specified owner: `Access denied`. ```tact -// copy & paste the below line in wrapper file(../build/ContractName/tact_ContractName.ts) instead of the second line of ContractName_init() function - this is a dictionary containing another smart contract code which leads to 135 exit code -// const __system = Cell.fromBase64('te6cckECIwEAB1EAAQHAAQEFodSXAgEU/wD0pBP0vPLICwMCAWIPBAIBIA0FAgEgDAYCAUgLBwIBIAkIAHWs3caGrS4MzmdF5eotqc1vCmiu5ihm5iaqaEpGiYzo5syoyYptJmhuDSoKamwmziqo5spNKy0NLapwQAIRrt7tnm2eNijAIAoAAiQAEbCvu1E0NIAAYACVu70YJwXOw9XSyuex6E7DnWSoUbZoJwndY1LStkfLMi068t/fFiOYJwIFXAG4BnY5TOWDquRyWyw4JwnZdOWrNOy3M6DpZtlGbopIAhG+KO7Z5tnjYowgDgACIwN+0AHQ0wMBcbCjAfpAASDXSYEBC7ry4Igg1wsKIIEE/7ry0ImDCbry4IhUUFMDbwT4YQL4Yts8VRTbPPLggts8IBIQARbI+EMBzH8BygBVQBEA8lBUINdJgQELuvLgiCDXCwoggQT/uvLQiYMJuvLgiM8WWCDXSYEBC7ry4Igg1wsKIIEE/7ry0ImDCbry4IjPFgEgbpUwcAHLAY4eINdJgQELuvLgiCDXCwoggQT/uvLQiYMJuvLgiM8W4hL0AAHIgQEBzwDJAczJ7VQC9gGSMH/gcCHXScIflTAg1wsf3iCCEIQwhou6jtYw0x8BghCEMIaLuvLggfpAASDXSYEBC7ry4Igg1wsKIIEE/7ry0ImDCbry4IgBgQEB1wD6QAEg10mBAQu68uCIINcLCiCBBP+68tCJgwm68uCIQzBsE+AgghAF6DTmuhkTAvyO0DDTHwGCEAXoNOa68uCB+kABINdJgQELuvLgiCDXCwoggQT/uvLQiYMJuvLgiAH6QAEg10mBAQu68uCIINcLCiCBBP+68tCJgwm68uCIEmwS4CCCEHKDsbi6jpQw0x8BghByg7G4uvLggdQBMds8f+DAAAHXScEhsJF/4HAXFATw+EFvJBAjXwMkbrOOF4ERTVNxxwWSMX+ZJSBu8tCAWMcF4vL0mSaBEU0CxwXy9OL4ACDIAYIQcoOxuFjLH8zJI9s8kyBus48kICBu8tCAbyIxggkxLQAjfwNwQwNtbds8IG7y0IBvIjBSQNs86FtwgwYmA39VMG1tFh4dFQEE2zweADSBAQH0hG+lwP+dIG7y0IABIG7y0IBvAuBbbQLQNPhBbyQQI18D+ENUECfbPAGBEU0CcFnIcAHLAXMBywFwAcsAEszMyfkAyHIBywFwAcsAEsoHy//J0CDXSYEBC7ry4Igg1wsKIIEE/7ry0ImDCbry4IgixwXy9ANwgEBwVSBtbW3bPH8YHgDaAtD0BDBtAYIA6ksBgBD0D2+h8uCHAYIA6ksiAoAQ9BfIAcj0AMkBzHABygBAA1kg10mBAQu68uCIINcLCiCBBP+68tCJgwm68uCIzxYBINdJgQELuvLgiCDXCwoggQT/uvLQiYMJuvLgiM8WyQKi+EFvJDAyJ26zjheBEU1ToccFkjF/mSggbvLQgFjHBeLy9JkpgRFNAscF8vTiJYEBASRZ9AxvoZIwbd9ujo8TXwNwgEBwVSBtbW3bPAHjDQF/HhoC+iTBFI72FYEBAVQQNCBulTBZ9FowlEEz9BTiA6QBggr68IChJnAGyFmCEAXoNOZQA8sfASDXSYEBC7ry4Igg1wsKIIEE/7ry0ImDCbry4IjPFgEg10mBAQu68uCIINcLCiCBBP+68tCJgwm68uCIzxbJQVBDMHABbW3bPOMOHhsD6jBTQds8IG6OhDAk2zzeIG7y0IBvIjFwUEOAQAPIVSCCEIQwhotQBMsfWCDXSYEBC7ry4Igg1wsKIIEE/7ry0ImDCbry4IjPFoEBAc8AASDXSYEBC7ry4Igg1wsKIIEE/7ry0ImDCbry4IjPFsl/VTBtbds8AR0cHgA0gQEB9IxvpcD/nSBu8tCAASBu8tCAbwLgW20ANgGBAQH0eG+lwP+dIG7y0IABIG7y0IBvAuBbbQHKyHEBygFQBwHKAHABygJQBSDXSYEBC7ry4Igg1wsKIIEE/7ry0ImDCbry4IjPFlAD+gJwAcpoI26zkX+TJG6z4pczMwFwAcoA4w0hbrOcfwHKAAEgbvLQgAHMlTFwAcoA4skB+wAfAJh/AcoAyHABygBwAcoAJG6znX8BygAEIG7y0IBQBMyWNANwAcoA4iRus51/AcoABCBu8tCAUATMljQDcAHKAOJwAcoAAn8BygACyVjMArjtRNDUAfhj0gAB4wL4KNcLCoMJuvLgifpAASDXSYEBC7ry4Igg1wsKIIEE/7ry0ImDCbry4IgB+kABINdJgQELuvLgiCDXCwoggQT/uvLQiYMJuvLgiBIC0QHbPCIhAAgBbW1wAPr6QAEg10mBAQu68uCIINcLCiCBBP+68tCJgwm68uCIAfpAASDXSYEBC7ry4Igg1wsKIIEE/7ry0ImDCbry4IgB+kAh1wsBwwCOHQEg10mBAQu68uCIINcLCiCBBP+68tCJgwm68uCIkjFt4gH0BNQB0IEBAdcAMBUUQzBsFUhhij0='); -let ctx: Context = context(); -let fireworks_init: StateInit = initOf Fireworks(0); +import "@stdlib/ownable"; + +contract Hal9k with Ownable { + owner: Address; + + init(owner: Address) { + self.owner = owner; // set the owner address upon deployment + } + + receive("I'm sorry Dave, I'm afraid I can't do that.") { + // Checks that the message sender's address equals to the owner address, + // and if not β€” throws an error with exit code 132. + self.requireOwner(); + + // ... you do you ... + } +} ``` -### $136$: Invalid address {#136} +### 133: Contract stopped {#133} + +A message has been sent to a stopped contract. Reserved, but never thrown. -In TON, all addresses are 267 bits. If you violate this rule, you will face this exit code. +### 134: Invalid argument {#134} -Currently, TON only supports two chain id. 0 for basechain and -1 for masterchain. If you address isn't from basechain, 136 exit code will be thrown. +If there is an invalid or unexpected argument value, an error with exit code $134$ is thrown: `Invalid argument`. -Example: +Here are some of the functions in Tact which can throw an error with this exit code: + +1. [`Int.toFloatString(digits){:tact}`](/ref/core-strings#inttofloatstring): if the `digits` is not in the interval: $0 <$ `digits` $< 78$. + +2. [`String.fromBase64(){:tact}`](/ref/core-strings#stringfrombase64) and [`Slice.fromBase64(){:tact}`](/ref/core-strings#slicefrombase64): if the given [`String{:tact}`][p] or [`Slice{:tact}`][slice] contains non-Base64 characters. ```tact -// fun newAddress(chain: Int, hash: Int): Address; -// creates a new address from chain and hash values. -let zeroAddress: Address = newAddress(1, 0); // invalid chain zero address +try { + // 0 is code of NUL in ASCII and it is not valid Base64 + let code: Slice = beginCell().storeUint(0, 8).asSlice().fromBase64(); +} catch (exitCode) { + // exitCode is 134 +} ``` -### $137$: Masterchain support is not enabled for this contract {#137} +### 135: Code of a contract was not found {#135} + +If the code of the contract doesn't match the one saved in TypeScript wrappers, the error with exit code $135$ will be thrown: `Code of a contract was not found`. + +### 136: Invalid address {#136} + +A value of type [`Address{:tact}`][p] is valid in Tact when: + +* It occupies $267$ bits: $11$ bits for the chain ID prefix and $256$ bits for the [address itself](https://docs.ton.org/learn/overviews/addresses#address-of-smart-contract). +* It belongs to either: basechain (ID $0$) or masterchain (ID $-1$), with the latter requiring [masterchain support](/book/masterchain#support) to be enabled. -Currently, TON only supports two chain id. 0 for basechain and -1 for masterchain. +If the [`Address{:tact}`][p] isn't valid, the error with exit code $136$ will be thrown: `Invalid address`. -Tact only supports basechain and if you address is from masterchain, 137 exit code will be thrown. +```tact +// Only basechain (ID 0) or masterchain (ID -1) are supported by Tact +let unsupportedChainID = 1; + +try { + // Zero address in unsupported workchain + dump(newAddress(unsupportedChainID, 0)); +} catch (exitCode) { + // exitCode is 136 +} +``` -Example: +### 137: Masterchain support is not enabled for this contract {#137} + +Any attempts to point to masterchain (ID $-1$) or otherwise interact with it without [enabling masterchain support](/book/masterchain#support) throw an exception with exit code $137$: `Masterchain support is not enabled for this contract`. ```tact -// fun newAddress(chain: Int, hash: Int): Address; -// creates a new address from chain and hash values. -let zeroAddress: Address = newAddress(-1, 0); // masterchain zero address +let masterchainID = -1; + +try { + // Zero address in masterchain without the config option set + dump(newAddress(masterchainID, 0)); +} catch (exitCode) { + // exitCode is 137 +} ``` + +[p]: /book/types#primitive-types +[ct]: /book/types#composite-types +[cell]: /book/cells +[builder]: /book/cells#builders +[slice]: /book/cells#slices +[message]: /book/structs-and-messages#messages + +[tlb]: https://docs.ton.org/develop/data-formats/tl-b-language +[tvm]: https://docs.ton.org/develop/func/statements#function-application +[bp]: https://github.com/ton-org/blueprint +[sb]: https://github.com/ton-org/sandbox +[jest]: https://jestjs.io diff --git a/src/generator/createABI.ts b/src/generator/createABI.ts index 503edfa1c..4aa7b4ff8 100644 --- a/src/generator/createABI.ts +++ b/src/generator/createABI.ts @@ -158,8 +158,8 @@ export function createABI(ctx: CompilerContext, name: string): ContractABI { errors["36"] = { message: "Invalid destination address in outbound message", }; - errors["37"] = { message: "Not enough TON" }; - errors["38"] = { message: "Not enough extra-currencies" }; + errors["37"] = { message: "Not enough Toncoin" }; + errors["38"] = { message: "Not enough extra currencies" }; errors["39"] = { message: "Outbound message does not fit into a cell after rewriting", }; diff --git a/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap b/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap index 33a4b617f..5573c4ad8 100644 --- a/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap +++ b/src/test/e2e-emulated/__snapshots__/local-type-inference.spec.ts.snap @@ -70,10 +70,10 @@ exports[`local-type-inference should automatically set types for let statements "message": "Invalid destination address in outbound message", }, "37": { - "message": "Not enough TON", + "message": "Not enough Toncoin", }, "38": { - "message": "Not enough extra-currencies", + "message": "Not enough extra currencies", }, "39": { "message": "Outbound message does not fit into a cell after rewriting", diff --git a/src/test/exit-codes/contracts/compute-phase-errors.tact b/src/test/exit-codes/contracts/compute-phase-errors.tact index e27f8395c..aa9e68463 100644 --- a/src/test/exit-codes/contracts/compute-phase-errors.tact +++ b/src/test/exit-codes/contracts/compute-phase-errors.tact @@ -204,7 +204,7 @@ contract ComputePhaseErrorsTester { /// Exit code 11 receive("11") { // Unlike nativeSendMessage which uses SENDRAWMSG, this one uses SENDMSG, - // and therefore fails in Compute time when the message is ill-formed + // and therefore fails in Compute phase when the message is ill-formed nativeSendMessageReturnForwardFee(emptyCell(), 0); } From f92c266347bb23b3513cdb42be55239fc4c5185c Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:51:17 +0200 Subject: [PATCH 3/7] feat(docs): `Builder.storeMaybeRef`, `parseStdAddress` and `parseVarAddress` (#950) --- CHANGELOG.md | 2 +- docs/src/content/docs/ref/core-advanced.mdx | 80 ++++++++++++++++ docs/src/content/docs/ref/core-cells.mdx | 35 +++++++ src/imports/stdlib.ts | 101 +++++++++++++------- stdlib/std/cells.tact | 20 +++- stdlib/std/contract.tact | 50 +++++++++- 6 files changed, 250 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed59a4c82..79e411b99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,7 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `asm` bodies for module-level functions: PR [#769](https://github.com/tact-lang/tact/pull/769), PR [#825](https://github.com/tact-lang/tact/pull/825) - Corresponding stdlib functions for new TVM instructions from 2023.07 and 2024.04 upgrades: PR [#331](https://github.com/tact-lang/tact/pull/331). Added the `storeBuilder` extension function and `gasConsumed`, `getComputeFee`, `getStorageFee`, `getForwardFee`, `getSimpleComputeFee`, `getSimpleForwardFee`, `getOriginalFwdFee`, `myStorageDue` functions. - `slice`, `rawSlice`, `ascii` and `crc32` built-in functions: PR [#787](https://github.com/tact-lang/tact/pull/787), PR [#799](https://github.com/tact-lang/tact/pull/799) -- `Builder.storeMaybeRef`, `parseStdAddress` and `parseVarAddress` stdlib functions: PR [#793](https://github.com/tact-lang/tact/pull/793) +- `Builder.storeMaybeRef`, `parseStdAddress` and `parseVarAddress` stdlib functions: PR [#793](https://github.com/tact-lang/tact/pull/793), PR [#950](https://github.com/tact-lang/tact/pull/950) - The compiler development guide: PR [#833](https://github.com/tact-lang/tact/pull/833) - Constant evaluator now uses an interpreter: PR [#664](https://github.com/tact-lang/tact/pull/664). This allows calls to user-defined functions and references to declared global constants. diff --git a/docs/src/content/docs/ref/core-advanced.mdx b/docs/src/content/docs/ref/core-advanced.mdx index e52ba2aad..8bbdcd726 100644 --- a/docs/src/content/docs/ref/core-advanced.mdx +++ b/docs/src/content/docs/ref/core-advanced.mdx @@ -3,6 +3,8 @@ title: Advanced description: "Advanced, niche or dangerous functions from the Core library of Tact" --- +import { Badge } from '@astrojs/starlight/components'; + Various niche, dangerous or unstable features which can produce unexpected results and are meant to be used by the more experienced users. :::caution @@ -251,6 +253,84 @@ nativeReserve(ton("0.1"), ReserveExact | ReserveBounceIfActionFail); // amount of nanoToncoins to reserve ``` +## parseStdAddress + +

+ +```tact +fun parseStdAddress(slice: Slice): StdAddress; +``` + +Converts a [`Slice{:tact}`][slice] containing an address into the `StdAddress{:tact}` [Struct][s] and returns it. The `StdAddress{:tact}` is a built-in [Struct][s] that consists of: + +Field | Type | Description +:---------- | :----------------------------- | :---------- +`workchain` | [`Int as int8{:tact}`][int] | Workchain ID of the address, usually $0$ (basechain) or $-1$ (masterchain) +`address` | [`Int as uint256{:tact}`][int] | Address in the specified `workchain` + +Attempts to pass a [`Slice{:tact}`][slice] with layout different from the `StdAddress{:tact}` or to load more data than a given [`Slice{:tact}`][slice] contains throw an exception with [exit code 9](/book/exit-codes#9): `Cell underflow`. + +Usage example: + +```tact +let addr = address("EQDtFpEwcFAEcRe5mLVh2N6C0x-_hJEM7W61_JLnSF74p4q2"); +let parsedAddr = parseStdAddress(addr.asSlice()); + +parsedAddr.workchain; // 0 +parsedAddr.address; // 107...lots of digits...287 + +// Using newAddress() function with the contents of StdAddress will yield the initial Address: +let addr2: Address = newAddress(parsedAddr.workchain, parsedAddr.address); +addr2 == addr; // true +``` + +:::note + + For parsing addresses of variable length, see the [`parseVarAddress(){:tact}`](#parsevaraddress) function. + +::: + +## parseVarAddress + +

+ +```tact +fun parseVarAddress(slice: Slice): VarAddress; +``` + +Converts a [`Slice{:tact}`][slice] containing an address of variable length into the `VarAddress{:tact}` [Struct][s] and returns it. The `VarAddress{:tact}` is a built-in [Struct][s] consisting of: + +Field | Type | Description +:---------- | :--------------------------- | :---------- +`workchain` | [`Int as int32{:tact}`][int] | Workchain ID of the variable length address +`address` | [`Slice{:tact}`][slice] | Address in the specified `workchain` + +Attempts to pass a [`Slice{:tact}`][slice] with layout different from the `VarAddress{:tact}` or to load more data than a given [`Slice{:tact}`][slice] contains throw an exception with [exit code 9](/book/exit-codes#9): `Cell underflow`. + +Usage example: + +```tact +let varAddrSlice = beginCell() + .storeUint(6, 3) // to recognize the following as a VarAddress + .storeUint(123, 9) // make address occupy 123 bits + .storeUint(234, 32) // specify workchain ID of 234 + .storeUint(345, 123) // specify address of 345 + .asSlice(); +let parsedVarAddr = parseVarAddress(varAddrSlice); + +parsedVarAddr.workchain; // 234 +parsedVarAddr.address; // CS{Cell{002...2b3} bits: 44..167; refs: 0..0} +parsedVarAddr.address.loadUint(123); // 345 +``` + +:::caution + + Variable-length addresses are intended for future extensions, and while validators must be ready to accept them in inbound messages, the standard (non-variable) addresses are used whenever possible. + +::: + [p]: /book/types#primitive-types [bool]: /book/types#booleans [int]: /book/integers +[slice]: /book/cells#slices +[s]: /book/structs-and-messages#structs diff --git a/docs/src/content/docs/ref/core-cells.mdx b/docs/src/content/docs/ref/core-cells.mdx index 7b8e3309f..c4e051ec6 100644 --- a/docs/src/content/docs/ref/core-cells.mdx +++ b/docs/src/content/docs/ref/core-cells.mdx @@ -3,6 +3,8 @@ title: Cells, Builders and Slices description: "Various Cell, Builder and Slice functions from the Core library of Tact" --- +import { Badge } from '@astrojs/starlight/components'; + [`Cell{:tact}`][cell] is a low-level [primitive][p] that represents data in TON Blockchain. Cells consist of $1023$ bits of data with up to $4$ references to another cells. They are read-only and immutable, and cannot have cyclic references. [`Builder{:tact}`][builder] is an immutable [primitive][p] to construct cells, and [`Slice{:tact}`][slice] is a mutable [primitive][p] to parse them. @@ -191,6 +193,8 @@ let buzz: Builder = b.storeBool(false); // writes 0 ## Builder.storeBit +

+ ```tact extends fun storeBit(self: Builder, value: Bool): Builder; ``` @@ -286,6 +290,31 @@ let b: Builder = beginCell(); let fizz: Builder = b.storeRef(emptyCell()); ``` +## Builder.storeMaybeRef + +

+ +```tact +extends fun storeMaybeRef(self: Builder, cell: Cell?): Builder; +``` + +Extension function for the [`Builder{:tact}`][builder]. + +If the `cell` is not `null{:tact}`, stores $1$ as a single bit and then reference `cell` into the copy of the [`Builder{:tact}`][builder]. Returns that copy. + +If the `cell` is `null{:tact}`, only stores $0$ as a single bit into the copy of the [`Builder{:tact}`][builder]. Returns that copy. + +As a single [`Cell{:tact}`][cell] can store up to $4$ references, attempts to store more throw an exception with [exit code 8](/book/exit-codes#8): `Cell overflow`. + +Usage example: + +```tact +let b: Builder = beginCell(); +let fizz: Builder = b + .storeMaybeRef(emptyCell()) // stores a single 1 bit, then an empty cell + .storeMaybeRef(null); // stores only a single 0 bit +``` + ## Builder.refs ```tact @@ -525,6 +554,8 @@ let fizz: Bool = s.loadBool(); // true ## Slice.loadBit +

+ ```tact extends mutates fun loadBit(self: Slice): Bool; ``` @@ -817,6 +848,8 @@ fun coinCell(): Cell { ## Struct.toSlice +

+ ```tact extends fun toSlice(self: Struct): Slice; ``` @@ -939,6 +972,8 @@ fun coinCell(): Cell { ## Message.toSlice +

+ ```tact extends fun toSlice(self: Message): Slice; ``` diff --git a/src/imports/stdlib.ts b/src/imports/stdlib.ts index d4f5b8d91..c50def19b 100644 --- a/src/imports/stdlib.ts +++ b/src/imports/stdlib.ts @@ -136,37 +136,45 @@ files['std/cells.tact'] = 'eyBTVFZBUlVJTlQxNiB9Cgphc20oY2VsbCBzZWxmKSBleHRlbmRzIGZ1biBzdG9yZVJlZihzZWxmOiBCdWlsZGVyLCBjZWxsOiBDZWxsKTogQnVpbGRlciB7IFNUUkVG' + 'IH0KCmFzbSBleHRlbmRzIGZ1biBzdG9yZVNsaWNlKHNlbGY6IEJ1aWxkZXIsIGNlbGw6IFNsaWNlKTogQnVpbGRlciB7IFNUU0xJQ0VSIH0KCmFzbSBleHRlbmRzIGZ1' + 'biBzdG9yZUJ1aWxkZXIoc2VsZjogQnVpbGRlciwgY2VsbDogQnVpbGRlcik6IEJ1aWxkZXIgeyBTVEJSIH0KCkBuYW1lKF9fdGFjdF9zdG9yZV9hZGRyZXNzKQpleHRl' + - 'bmRzIG5hdGl2ZSBzdG9yZUFkZHJlc3Moc2VsZjogQnVpbGRlciwgYWRkcmVzczogQWRkcmVzcyk6IEJ1aWxkZXI7Cgphc20oY2VsbCBzZWxmKSBleHRlbmRzIGZ1biBz' + - 'dG9yZU1heWJlUmVmKHNlbGY6IEJ1aWxkZXIsIGNlbGw6IENlbGw/KTogQnVpbGRlciB7IFNUT1BUUkVGIH0KCmFzbSBleHRlbmRzIGZ1biBlbmRDZWxsKHNlbGY6IEJ1' + - 'aWxkZXIpOiBDZWxsIHsgRU5EQyB9Cgphc20gZXh0ZW5kcyBmdW4gcmVmcyhzZWxmOiBCdWlsZGVyKTogSW50IHsgQlJFRlMgfQoKYXNtIGV4dGVuZHMgZnVuIGJpdHMo' + - 'c2VsZjogQnVpbGRlcik6IEludCB7IEJCSVRTIH0KCi8vCi8vIFNsaWNlCi8vCgphc20gZXh0ZW5kcyBmdW4gYmVnaW5QYXJzZShzZWxmOiBDZWxsKTogU2xpY2UgeyBD' + - 'VE9TIH0KCmFzbSgtPiAxIDApIGV4dGVuZHMgbXV0YXRlcyBmdW4gbG9hZFJlZihzZWxmOiBTbGljZSk6IENlbGwgeyBMRFJFRiB9Cgphc20gZXh0ZW5kcyBmdW4gcHJl' + - 'bG9hZFJlZihzZWxmOiBTbGljZSk6IENlbGwgeyBQTERSRUYgfQoKLy8gc3BlY2lhbCB0cmVhdG1lbnQgaW4gRnVuYyBjb21waWxlciwgc28gbm90IHJlcGxhY2VkIHdp' + - 'dGggYXNtICJMRFNMSUNFWCIKQG5hbWUobG9hZF9iaXRzKQpleHRlbmRzIG11dGF0ZXMgbmF0aXZlIGxvYWRCaXRzKHNlbGY6IFNsaWNlLCBsOiBJbnQpOiBTbGljZTsK' + - 'Ci8vIHNwZWNpYWwgdHJlYXRtZW50IGluIEZ1bmMgY29tcGlsZXIsIHNvIG5vdCByZXBsYWNlZCB3aXRoIGFzbSAiUExEU0xJQ0VYIgpAbmFtZShwcmVsb2FkX2JpdHMp' + - 'CmV4dGVuZHMgbmF0aXZlIHByZWxvYWRCaXRzKHNlbGY6IFNsaWNlLCBsOiBJbnQpOiBTbGljZTsKCi8vIHNwZWNpYWwgdHJlYXRtZW50IGluIEZ1bmMgY29tcGlsZXIs' + - 'IHNvIG5vdCByZXBsYWNlZCB3aXRoIGFzbSAiTERJWCIKQG5hbWUobG9hZF9pbnQpCmV4dGVuZHMgbXV0YXRlcyBuYXRpdmUgbG9hZEludChzZWxmOiBTbGljZSwgbDog' + - 'SW50KTogSW50OwoKLy8gc3BlY2lhbCB0cmVhdG1lbnQgaW4gRnVuYyBjb21waWxlciwgc28gbm90IHJlcGxhY2VkIHdpdGggYXNtICJQTERJWCIKQG5hbWUocHJlbG9h' + - 'ZF9pbnQpCmV4dGVuZHMgbmF0aXZlIHByZWxvYWRJbnQoc2VsZjogU2xpY2UsIGw6IEludCk6IEludDsKCi8vIHNwZWNpYWwgdHJlYXRtZW50IGluIEZ1bmMgY29tcGls' + - 'ZXIsIHNvIG5vdCByZXBsYWNlZCB3aXRoIGFzbSAiTERVWCIKQG5hbWUobG9hZF91aW50KQpleHRlbmRzIG11dGF0ZXMgbmF0aXZlIGxvYWRVaW50KHNlbGY6IFNsaWNl' + - 'LCBsOiBJbnQpOiBJbnQ7CgovLyBzcGVjaWFsIHRyZWF0bWVudCBpbiBGdW5jIGNvbXBpbGVyLCBzbyBub3QgcmVwbGFjZWQgd2l0aCBhc20gIlBMRFVYIgpAbmFtZShw' + - 'cmVsb2FkX3VpbnQpCmV4dGVuZHMgbmF0aXZlIHByZWxvYWRVaW50KHNlbGY6IFNsaWNlLCBsOiBJbnQpOiBJbnQ7Cgphc20oLT4gMSAwKSBleHRlbmRzIG11dGF0ZXMg' + - 'ZnVuIGxvYWRCb29sKHNlbGY6IFNsaWNlKTogQm9vbCB7IDEgTERJIH0KCi8vLyBFeHRlbnNpb24gbXV0YXRpb24gZnVuY3Rpb24gZm9yIHRoZSBgU2xpY2VgLiBBbGlh' + - 'cyB0byBgU2xpY2UubG9hZEJvb2woKWAuCi8vLwovLy8gYGBgdGFjdAovLy8gbGV0IHM6IFNsaWNlID0gYmVnaW5DZWxsKCkuc3RvcmVCb29sKHRydWUpLmFzU2xpY2Uo' + - 'KTsKLy8vIGxldCBmaXp6OiBCb29sID0gcy5sb2FkQml0KCk7IC8vIHRydWUKLy8vIGBgYAovLy8KLy8vIEBzaW5jZSBUYWN0IDEuNS4wCi8vLyBAc2VlIGh0dHBzOi8v' + - 'ZG9jcy50YWN0LWxhbmcub3JnL3JlZi9jb3JlLWNlbGxzI3NsaWNlbG9hZGJpdAovLy8KYXNtKC0+IDEgMCkgZXh0ZW5kcyBtdXRhdGVzIGZ1biBsb2FkQml0KHNlbGY6' + - 'IFNsaWNlKTogQm9vbCB7IDEgTERJIH0KCmFzbSggLT4gMSAwKSBleHRlbmRzIG11dGF0ZXMgZnVuIGxvYWRDb2lucyhzZWxmOiBTbGljZSk6IEludCB7IExEVkFSVUlO' + - 'VDE2IH0KCkBuYW1lKF9fdGFjdF9sb2FkX2FkZHJlc3MpCmV4dGVuZHMgbXV0YXRlcyBuYXRpdmUgbG9hZEFkZHJlc3Moc2VsZjogU2xpY2UpOiBBZGRyZXNzOwoKYXNt' + - 'IGV4dGVuZHMgbXV0YXRlcyBmdW4gc2tpcEJpdHMoc2VsZjogU2xpY2UsIGw6IEludCkgeyBTRFNLSVBGSVJTVCB9Cgphc20gZXh0ZW5kcyBmdW4gZW5kUGFyc2Uoc2Vs' + - 'ZjogU2xpY2UpIHsgRU5EUyB9CgovLwovLyBTbGljZSBzaXplCi8vCgphc20gZXh0ZW5kcyBmdW4gcmVmcyhzZWxmOiBTbGljZSk6IEludCB7IFNSRUZTIH0KCmFzbSBl' + - 'eHRlbmRzIGZ1biBiaXRzKHNlbGY6IFNsaWNlKTogSW50IHsgU0JJVFMgfQoKYXNtIGV4dGVuZHMgZnVuIGVtcHR5KHNlbGY6IFNsaWNlKTogQm9vbCB7IFNFTVBUWSB9' + - 'Cgphc20gZXh0ZW5kcyBmdW4gZGF0YUVtcHR5KHNlbGY6IFNsaWNlKTogQm9vbCB7IFNERU1QVFkgfQoKYXNtIGV4dGVuZHMgZnVuIHJlZnNFbXB0eShzZWxmOiBTbGlj' + - 'ZSk6IEJvb2wgeyBTUkVNUFRZIH0KCi8vCi8vIENvbnZlcnNpb25zCi8vCgppbmxpbmUgZXh0ZW5kcyBmdW4gYXNTbGljZShzZWxmOiBCdWlsZGVyKTogU2xpY2Ugewog' + - 'ICAgcmV0dXJuIHNlbGYuZW5kQ2VsbCgpLmJlZ2luUGFyc2UoKTsKfQoKaW5saW5lIGV4dGVuZHMgZnVuIGFzU2xpY2Uoc2VsZjogQ2VsbCk6IFNsaWNlIHsKICAgIHJl' + - 'dHVybiBzZWxmLmJlZ2luUGFyc2UoKTsKfQoKaW5saW5lIGV4dGVuZHMgZnVuIGFzQ2VsbChzZWxmOiBTbGljZSk6IENlbGwgewogICAgcmV0dXJuIGJlZ2luQ2VsbCgp' + - 'CiAgICAgICAgLnN0b3JlU2xpY2Uoc2VsZikKICAgICAgICAuZW5kQ2VsbCgpOwp9CgppbmxpbmUgZXh0ZW5kcyBmdW4gYXNDZWxsKHNlbGY6IEJ1aWxkZXIpOiBDZWxs' + - 'IHsKICAgIHJldHVybiBzZWxmLmVuZENlbGwoKTsKfQoKaW5saW5lIGZ1biBlbXB0eUNlbGwoKTogQ2VsbCB7CiAgICByZXR1cm4gYmVnaW5DZWxsKCkuZW5kQ2VsbCgp' + - 'Owp9CgppbmxpbmUgZnVuIGVtcHR5U2xpY2UoKTogU2xpY2UgewogICAgcmV0dXJuIGVtcHR5Q2VsbCgpLmFzU2xpY2UoKTsKfQ=='; + 'bmRzIG5hdGl2ZSBzdG9yZUFkZHJlc3Moc2VsZjogQnVpbGRlciwgYWRkcmVzczogQWRkcmVzcyk6IEJ1aWxkZXI7CgovLy8gRXh0ZW5zaW9uIGZ1bmN0aW9uIGZvciB0' + + 'aGUgYEJ1aWxkZXJgLgovLy8KLy8vIElmIHRoZSBgY2VsbGAgaXMgbm90IGBudWxsYCwgc3RvcmVzIDEgYXMgYSBzaW5nbGUgYml0IGFuZCB0aGVuIHJlZmVyZW5jZSBg' + + 'Y2VsbGAgaW50byB0aGUgY29weSBvZiB0aGUgYEJ1aWxkZXJgLiBSZXR1cm5zIHRoYXQgY29weS4KLy8vCi8vLyBJZiB0aGUgYGNlbGxgIGlzIGBudWxsYCwgb25seSBz' + + 'dG9yZXMgMCBhcyBhIHNpbmdsZSBiaXQgaW50byB0aGUgY29weSBvZiB0aGUgYEJ1aWxkZXJgLiBSZXR1cm5zIHRoYXQgY29weS4KLy8vCi8vLyBBcyBhIHNpbmdsZSBg' + + 'Q2VsbGAgY2FuIHN0b3JlIHVwIHRvIDQgcmVmZXJlbmNlcywgYXR0ZW1wdHMgdG8gc3RvcmUgbW9yZSB0aHJvdyBhbiBleGNlcHRpb24gd2l0aCBleGl0IGNvZGUgODog' + + 'YENlbGwgb3ZlcmZsb3dgLgovLy8KLy8vIGBgYHRhY3QKLy8vIGxldCBiOiBCdWlsZGVyID0gYmVnaW5DZWxsKCk7Ci8vLyBsZXQgZml6ejogQnVpbGRlciA9IGIKLy8v' + + 'ICAgICAuc3RvcmVNYXliZVJlZihlbXB0eUNlbGwoKSkgLy8gMSwgdGhlbiBlbXB0eSBjZWxsCi8vLyAgICAgLnN0b3JlTWF5YmVSZWYobnVsbCk7ICAgICAgIC8vIDAK' + + 'Ly8vIGBgYAovLy8KLy8vIEBzaW5jZSBUYWN0IDEuNS4wCi8vLyBAc2VlIGh0dHBzOi8vZG9jcy50YWN0LWxhbmcub3JnL3JlZi9jb3JlLWNlbGxzI2J1aWxkZXJzdG9y' + + 'ZW1heWJlcmVmCi8vLwphc20oY2VsbCBzZWxmKSBleHRlbmRzIGZ1biBzdG9yZU1heWJlUmVmKHNlbGY6IEJ1aWxkZXIsIGNlbGw6IENlbGw/KTogQnVpbGRlciB7IFNU' + + 'T1BUUkVGIH0KCmFzbSBleHRlbmRzIGZ1biBlbmRDZWxsKHNlbGY6IEJ1aWxkZXIpOiBDZWxsIHsgRU5EQyB9Cgphc20gZXh0ZW5kcyBmdW4gcmVmcyhzZWxmOiBCdWls' + + 'ZGVyKTogSW50IHsgQlJFRlMgfQoKYXNtIGV4dGVuZHMgZnVuIGJpdHMoc2VsZjogQnVpbGRlcik6IEludCB7IEJCSVRTIH0KCi8vCi8vIFNsaWNlCi8vCgphc20gZXh0' + + 'ZW5kcyBmdW4gYmVnaW5QYXJzZShzZWxmOiBDZWxsKTogU2xpY2UgeyBDVE9TIH0KCmFzbSgtPiAxIDApIGV4dGVuZHMgbXV0YXRlcyBmdW4gbG9hZFJlZihzZWxmOiBT' + + 'bGljZSk6IENlbGwgeyBMRFJFRiB9Cgphc20gZXh0ZW5kcyBmdW4gcHJlbG9hZFJlZihzZWxmOiBTbGljZSk6IENlbGwgeyBQTERSRUYgfQoKLy8gc3BlY2lhbCB0cmVh' + + 'dG1lbnQgaW4gRnVuYyBjb21waWxlciwgc28gbm90IHJlcGxhY2VkIHdpdGggYXNtICJMRFNMSUNFWCIKQG5hbWUobG9hZF9iaXRzKQpleHRlbmRzIG11dGF0ZXMgbmF0' + + 'aXZlIGxvYWRCaXRzKHNlbGY6IFNsaWNlLCBsOiBJbnQpOiBTbGljZTsKCi8vIHNwZWNpYWwgdHJlYXRtZW50IGluIEZ1bmMgY29tcGlsZXIsIHNvIG5vdCByZXBsYWNl' + + 'ZCB3aXRoIGFzbSAiUExEU0xJQ0VYIgpAbmFtZShwcmVsb2FkX2JpdHMpCmV4dGVuZHMgbmF0aXZlIHByZWxvYWRCaXRzKHNlbGY6IFNsaWNlLCBsOiBJbnQpOiBTbGlj' + + 'ZTsKCi8vIHNwZWNpYWwgdHJlYXRtZW50IGluIEZ1bmMgY29tcGlsZXIsIHNvIG5vdCByZXBsYWNlZCB3aXRoIGFzbSAiTERJWCIKQG5hbWUobG9hZF9pbnQpCmV4dGVu' + + 'ZHMgbXV0YXRlcyBuYXRpdmUgbG9hZEludChzZWxmOiBTbGljZSwgbDogSW50KTogSW50OwoKLy8gc3BlY2lhbCB0cmVhdG1lbnQgaW4gRnVuYyBjb21waWxlciwgc28g' + + 'bm90IHJlcGxhY2VkIHdpdGggYXNtICJQTERJWCIKQG5hbWUocHJlbG9hZF9pbnQpCmV4dGVuZHMgbmF0aXZlIHByZWxvYWRJbnQoc2VsZjogU2xpY2UsIGw6IEludCk6' + + 'IEludDsKCi8vIHNwZWNpYWwgdHJlYXRtZW50IGluIEZ1bmMgY29tcGlsZXIsIHNvIG5vdCByZXBsYWNlZCB3aXRoIGFzbSAiTERVWCIKQG5hbWUobG9hZF91aW50KQpl' + + 'eHRlbmRzIG11dGF0ZXMgbmF0aXZlIGxvYWRVaW50KHNlbGY6IFNsaWNlLCBsOiBJbnQpOiBJbnQ7CgovLyBzcGVjaWFsIHRyZWF0bWVudCBpbiBGdW5jIGNvbXBpbGVy' + + 'LCBzbyBub3QgcmVwbGFjZWQgd2l0aCBhc20gIlBMRFVYIgpAbmFtZShwcmVsb2FkX3VpbnQpCmV4dGVuZHMgbmF0aXZlIHByZWxvYWRVaW50KHNlbGY6IFNsaWNlLCBs' + + 'OiBJbnQpOiBJbnQ7Cgphc20oLT4gMSAwKSBleHRlbmRzIG11dGF0ZXMgZnVuIGxvYWRCb29sKHNlbGY6IFNsaWNlKTogQm9vbCB7IDEgTERJIH0KCi8vLyBFeHRlbnNp' + + 'b24gbXV0YXRpb24gZnVuY3Rpb24gZm9yIHRoZSBgU2xpY2VgLiBBbGlhcyB0byBgU2xpY2UubG9hZEJvb2woKWAuCi8vLwovLy8gYGBgdGFjdAovLy8gbGV0IHM6IFNs' + + 'aWNlID0gYmVnaW5DZWxsKCkuc3RvcmVCb29sKHRydWUpLmFzU2xpY2UoKTsKLy8vIGxldCBmaXp6OiBCb29sID0gcy5sb2FkQml0KCk7IC8vIHRydWUKLy8vIGBgYAov' + + 'Ly8KLy8vIEBzaW5jZSBUYWN0IDEuNS4wCi8vLyBAc2VlIGh0dHBzOi8vZG9jcy50YWN0LWxhbmcub3JnL3JlZi9jb3JlLWNlbGxzI3NsaWNlbG9hZGJpdAovLy8KYXNt' + + 'KC0+IDEgMCkgZXh0ZW5kcyBtdXRhdGVzIGZ1biBsb2FkQml0KHNlbGY6IFNsaWNlKTogQm9vbCB7IDEgTERJIH0KCmFzbSggLT4gMSAwKSBleHRlbmRzIG11dGF0ZXMg' + + 'ZnVuIGxvYWRDb2lucyhzZWxmOiBTbGljZSk6IEludCB7IExEVkFSVUlOVDE2IH0KCkBuYW1lKF9fdGFjdF9sb2FkX2FkZHJlc3MpCmV4dGVuZHMgbXV0YXRlcyBuYXRp' + + 'dmUgbG9hZEFkZHJlc3Moc2VsZjogU2xpY2UpOiBBZGRyZXNzOwoKYXNtIGV4dGVuZHMgbXV0YXRlcyBmdW4gc2tpcEJpdHMoc2VsZjogU2xpY2UsIGw6IEludCkgeyBT' + + 'RFNLSVBGSVJTVCB9Cgphc20gZXh0ZW5kcyBmdW4gZW5kUGFyc2Uoc2VsZjogU2xpY2UpIHsgRU5EUyB9CgovLwovLyBTbGljZSBzaXplCi8vCgphc20gZXh0ZW5kcyBm' + + 'dW4gcmVmcyhzZWxmOiBTbGljZSk6IEludCB7IFNSRUZTIH0KCmFzbSBleHRlbmRzIGZ1biBiaXRzKHNlbGY6IFNsaWNlKTogSW50IHsgU0JJVFMgfQoKYXNtIGV4dGVu' + + 'ZHMgZnVuIGVtcHR5KHNlbGY6IFNsaWNlKTogQm9vbCB7IFNFTVBUWSB9Cgphc20gZXh0ZW5kcyBmdW4gZGF0YUVtcHR5KHNlbGY6IFNsaWNlKTogQm9vbCB7IFNERU1Q' + + 'VFkgfQoKYXNtIGV4dGVuZHMgZnVuIHJlZnNFbXB0eShzZWxmOiBTbGljZSk6IEJvb2wgeyBTUkVNUFRZIH0KCi8vCi8vIENvbnZlcnNpb25zCi8vCgppbmxpbmUgZXh0' + + 'ZW5kcyBmdW4gYXNTbGljZShzZWxmOiBCdWlsZGVyKTogU2xpY2UgewogICAgcmV0dXJuIHNlbGYuZW5kQ2VsbCgpLmJlZ2luUGFyc2UoKTsKfQoKaW5saW5lIGV4dGVu' + + 'ZHMgZnVuIGFzU2xpY2Uoc2VsZjogQ2VsbCk6IFNsaWNlIHsKICAgIHJldHVybiBzZWxmLmJlZ2luUGFyc2UoKTsKfQoKaW5saW5lIGV4dGVuZHMgZnVuIGFzQ2VsbChz' + + 'ZWxmOiBTbGljZSk6IENlbGwgewogICAgcmV0dXJuIGJlZ2luQ2VsbCgpCiAgICAgICAgLnN0b3JlU2xpY2Uoc2VsZikKICAgICAgICAuZW5kQ2VsbCgpOwp9Cgppbmxp' + + 'bmUgZXh0ZW5kcyBmdW4gYXNDZWxsKHNlbGY6IEJ1aWxkZXIpOiBDZWxsIHsKICAgIHJldHVybiBzZWxmLmVuZENlbGwoKTsKfQoKaW5saW5lIGZ1biBlbXB0eUNlbGwo' + + 'KTogQ2VsbCB7CiAgICByZXR1cm4gYmVnaW5DZWxsKCkuZW5kQ2VsbCgpOwp9CgppbmxpbmUgZnVuIGVtcHR5U2xpY2UoKTogU2xpY2UgewogICAgcmV0dXJuIGVtcHR5' + + 'Q2VsbCgpLmFzU2xpY2UoKTsKfQo='; files['std/config.tact'] = 'YXNtIGZ1biBnZXRDb25maWdQYXJhbShpZDogSW50KTogQ2VsbD8geyBDT05GSUdPUFRQQVJBTSB9Cg=='; files['std/context.tact'] = @@ -203,10 +211,33 @@ files['std/contract.tact'] = 'ZGVkIHZhbHVlcykgZnJvbSBgZndkX2ZlZWAgcGFyc2VkIGZyb20gaW5jb21pbmcgbWVzc2FnZS4gYGlzX21hc3RlcmNoYWluYCBpcyB0cnVlIGlmIHRoZSBzb3VyY2Ug' + 'b3IgdGhlIGRlc3RpbmF0aW9uIGlzIGluIG1hc3RlcmNoYWluLCBmYWxzZSBpZiBib3RoIGFyZSBpbiBiYXNlY2hhaW4uCmFzbSBmdW4gZ2V0T3JpZ2luYWxGd2RGZWUo' + 'ZndkX2ZlZTogSW50LCBpc19tYXN0ZXJjaGFpbjogQm9vbCk6IEludCB7IEdFVE9SSUdJTkFMRldERkVFIH0KCi8vIEN1cnJlbnQgZGVidCBmb3Igc3RvcmFnZSBmZWUg' + - 'aW4gbmFub3RvbnMuCmFzbSBmdW4gbXlTdG9yYWdlRHVlKCk6IEludCB7IERVRVBBWU1FTlQgfQoKc3RydWN0IFN0ZEFkZHJlc3MgewogICAgd29ya2NoYWluOiBJbnQg' + - 'YXMgaW50ODsKICAgIGFkZHJlc3M6IEludCBhcyB1aW50MjU2Owp9CgpzdHJ1Y3QgVmFyQWRkcmVzcyB7CiAgICB3b3JrY2hhaW46IEludCBhcyBpbnQzMjsKICAgIGFk' + - 'ZHJlc3M6IFNsaWNlOwp9Cgphc20gZnVuIHBhcnNlU3RkQWRkcmVzcyhzbGljZTogU2xpY2UpOiBTdGRBZGRyZXNzIHsgUkVXUklURVNUREFERFIgfQoKYXNtIGZ1biBw' + - 'YXJzZVZhckFkZHJlc3Moc2xpY2U6IFNsaWNlKTogVmFyQWRkcmVzcyB7IFJFV1JJVEVWQVJBRERSIH0='; + 'aW4gbmFub3RvbnMuCmFzbSBmdW4gbXlTdG9yYWdlRHVlKCk6IEludCB7IERVRVBBWU1FTlQgfQoKLy8vIFN0cnVjdCByZXByZXNlbnRpbmcgdGhlIHN0YW5kYXJkIGFk' + + 'ZHJlc3Mgb24gVE9OIEJsb2NrY2hhaW4gd2l0aCBzaWduZWQgOC1iaXQgYHdvcmtjaGFpbmAgSUQgYW5kIGFuIHVuc2lnbmVkIDI1Ni1iaXQgYGFkZHJlc3NgIGluIHRo' + + 'ZSBzcGVjaWZpZWQgYHdvcmtjaGFpbmAuCi8vLwovLy8gQXQgdGhlIG1vbWVudCwgb25seSBgd29ya2NoYWluYCBJRHMgdXNlZCBvbiBUT04gYXJlIDAgb2YgdGhlIGJh' + + 'c2VjaGFpbiBhbmQgLTEgb2YgdGhlIG1hc3RlcmNoYWluLgovLy8KLy8vIEBzaW5jZSBUYWN0IDEuNS4wCi8vLyBAc2VlIGh0dHBzOi8vZG9jcy50YWN0LWxhbmcub3Jn' + + 'L3JlZi9jb3JlLWFkdmFuY2VkI3BhcnNlc3RkYWRkcmVzcwovLy8gQHNlZSBodHRwczovL2dpdGh1Yi5jb20vdG9uLWJsb2NrY2hhaW4vdG9uL2Jsb2IvbWFzdGVyL2Ny' + + 'eXB0by9ibG9jay9ibG9jay50bGIjTDEwNS1MMTA2Ci8vLwpzdHJ1Y3QgU3RkQWRkcmVzcyB7CiAgICB3b3JrY2hhaW46IEludCBhcyBpbnQ4OwogICAgYWRkcmVzczog' + + 'SW50IGFzIHVpbnQyNTY7Cn0KCi8vLyBTdHJ1Y3QgcmVwcmVzZW50aW5nIHRoZSBhZGRyZXNzIG9mIHZhcmlhYmxlIGxlbmd0aCB3aXRoIHNpZ25lZCAzMi1iaXQgYHdv' + + 'cmtjaGFpbmAgSUQgYW5kIGEgYFNsaWNlYCBjb250YWluaW5nIHVuc2lnbmVkIGBhZGRyZXNzYCBpbiB0aGUgc3BlY2lmaWVkIGB3b3JrY2hhaW5gLgovLy8KLy8vIFZh' + + 'cmlhYmxlLWxlbmd0aCBhZGRyZXNzZXMgYXJlIGludGVuZGVkIGZvciBmdXR1cmUgZXh0ZW5zaW9ucywgYW5kIHdoaWxlIHZhbGlkYXRvcnMgbXVzdCBiZSByZWFkeSB0' + + 'byBhY2NlcHQgdGhlbSBpbiBpbmJvdW5kIG1lc3NhZ2VzLCB0aGUgc3RhbmRhcmQgKG5vbi12YXJpYWJsZSkgYWRkcmVzc2VzIGFyZSB1c2VkIHdoZW5ldmVyIHBvc3Np' + + 'YmxlLgovLy8KLy8vIEBzaW5jZSBUYWN0IDEuNS4wCi8vLyBAc2VlIGh0dHBzOi8vZG9jcy50YWN0LWxhbmcub3JnL3JlZi9jb3JlLWFkdmFuY2VkI3BhcnNldmFyYWRk' + + 'cmVzcwovLy8gQHNlZSBodHRwczovL2dpdGh1Yi5jb20vdG9uLWJsb2NrY2hhaW4vdG9uL2Jsb2IvbWFzdGVyL2NyeXB0by9ibG9jay9ibG9jay50bGIjTDEwNy1MMTA4' + + 'Ci8vLwpzdHJ1Y3QgVmFyQWRkcmVzcyB7CiAgICB3b3JrY2hhaW46IEludCBhcyBpbnQzMjsKICAgIGFkZHJlc3M6IFNsaWNlOwp9CgovLy8gQ29udmVydHMgYSBgU2xp' + + 'Y2VgIGNvbnRhaW5pbmcgYW4gYWRkcmVzcyBpbnRvIHRoZSBgU3RkQWRkcmVzc2AgU3RydWN0IGFuZCByZXR1cm5zIGl0LgovLy8KLy8vIGBgYHRhY3QKLy8vIGxldCBh' + + 'ZGRyID0gYWRkcmVzcygiRVFEdEZwRXdjRkFFY1JlNW1MVmgyTjZDMHgtX2hKRU03VzYxX0pMblNGNzRwNHEyIik7Ci8vLyBsZXQgcGFyc2VkQWRkciA9IHBhcnNlU3Rk' + + 'QWRkcmVzcyhhZGRyLmFzU2xpY2UoKSk7Ci8vLwovLy8gcGFyc2VkQWRkci53b3JrY2hhaW47IC8vIDAKLy8vIHBhcnNlZEFkZHIuYWRkcmVzczsgICAvLyAxMDcuLi4y' + + 'ODcKLy8vIGBgYAovLy8KLy8vIEBzaW5jZSBUYWN0IDEuNS4wCi8vLyBAc2VlIGh0dHBzOi8vZG9jcy50YWN0LWxhbmcub3JnL3JlZi9jb3JlLWFkdmFuY2VkI3BhcnNl' + + 'c3RkYWRkcmVzcwovLy8KYXNtIGZ1biBwYXJzZVN0ZEFkZHJlc3Moc2xpY2U6IFNsaWNlKTogU3RkQWRkcmVzcyB7IFJFV1JJVEVTVERBRERSIH0KCi8vLyBDb252ZXJ0' + + 'cyBhIGBTbGljZWAgY29udGFpbmluZyBhbiBhZGRyZXNzIG9mIHZhcmlhYmxlIGxlbmd0aCBpbnRvIHRoZSBgVmFyQWRkcmVzc2AgU3RydWN0IGFuZCByZXR1cm5zIGl0' + + 'LgovLy8KLy8vIGBgYHRhY3QKLy8vIGxldCB2YXJBZGRyU2xpY2UgPSBiZWdpbkNlbGwoKQovLy8gICAgIC5zdG9yZVVpbnQoNiwgMykgICAgIC8vIHRvIHJlY29nbml6' + + 'ZSB0aGUgZm9sbG93aW5nIGFzIGEgVmFyQWRkcmVzcwovLy8gICAgIC5zdG9yZVVpbnQoMTIzLCA5KSAgIC8vIG1ha2UgYWRkcmVzcyBvY2N1cHkgMTIzIGJpdHMKLy8v' + + 'ICAgICAuc3RvcmVVaW50KDIzNCwgMzIpICAvLyBzcGVjaWZ5IHdvcmtjaGFpbiBJRCBvZiAyMzQKLy8vICAgICAuc3RvcmVVaW50KDM0NSwgMTIzKSAvLyBzcGVjaWZ5' + + 'IGFkZHJlc3Mgb2YgMzQ1Ci8vLyAgICAgLmFzU2xpY2UoKTsKLy8vIGxldCBwYXJzZWRWYXJBZGRyID0gcGFyc2VWYXJBZGRyZXNzKHZhckFkZHJTbGljZSk7Ci8vLwov' + + 'Ly8gcGFyc2VkVmFyQWRkci53b3JrY2hhaW47ICAgICAgICAgICAgIC8vIDIzNAovLy8gcGFyc2VkVmFyQWRkci5hZGRyZXNzOyAgICAgICAgICAgICAgIC8vIENTe0Nl' + + 'bGx7MDAyLi4uMmIzfSBiaXRzOiA0NC4uMTY3OyByZWZzOiAwLi4wfQovLy8gcGFyc2VkVmFyQWRkci5hZGRyZXNzLmxvYWRVaW50KDEyMyk7IC8vIDM0NQovLy8gYGBg' + + 'Ci8vLwovLy8gQHNpbmNlIFRhY3QgMS41LjAKLy8vIEBzZWUgaHR0cHM6Ly9kb2NzLnRhY3QtbGFuZy5vcmcvcmVmL2NvcmUtYWR2YW5jZWQjcGFyc2V2YXJhZGRyZXNz' + + 'Ci8vLwphc20gZnVuIHBhcnNlVmFyQWRkcmVzcyhzbGljZTogU2xpY2UpOiBWYXJBZGRyZXNzIHsgUkVXUklURVZBUkFERFIgfQo='; files['std/crypto.tact'] = 'YXNtIGV4dGVuZHMgZnVuIGhhc2goc2VsZjogQ2VsbCk6IEludCB7IEhBU0hDVSB9Cgphc20gZXh0ZW5kcyBmdW4gaGFzaChzZWxmOiBTbGljZSk6IEludCB7IEhBU0hT' + 'VSB9Cgphc20gZnVuIGNoZWNrU2lnbmF0dXJlKGhhc2g6IEludCwgc2lnbmF0dXJlOiBTbGljZSwgcHVibGljX2tleTogSW50KTogQm9vbCB7IENIS1NJR05VIH0KCmFz' + diff --git a/stdlib/std/cells.tact b/stdlib/std/cells.tact index f3c8e1429..eb9b1c561 100644 --- a/stdlib/std/cells.tact +++ b/stdlib/std/cells.tact @@ -40,6 +40,24 @@ asm extends fun storeBuilder(self: Builder, cell: Builder): Builder { STBR } @name(__tact_store_address) extends native storeAddress(self: Builder, address: Address): Builder; +/// Extension function for the `Builder`. +/// +/// If the `cell` is not `null`, stores 1 as a single bit and then reference `cell` into the copy of the `Builder`. Returns that copy. +/// +/// If the `cell` is `null`, only stores 0 as a single bit into the copy of the `Builder`. Returns that copy. +/// +/// As a single `Cell` can store up to 4 references, attempts to store more throw an exception with exit code 8: `Cell overflow`. +/// +/// ```tact +/// let b: Builder = beginCell(); +/// let fizz: Builder = b +/// .storeMaybeRef(emptyCell()) // 1, then empty cell +/// .storeMaybeRef(null); // 0 +/// ``` +/// +/// @since Tact 1.5.0 +/// @see https://docs.tact-lang.org/ref/core-cells#builderstoremayberef +/// asm(cell self) extends fun storeMaybeRef(self: Builder, cell: Cell?): Builder { STOPTREF } asm extends fun endCell(self: Builder): Cell { ENDC } @@ -147,4 +165,4 @@ inline fun emptyCell(): Cell { inline fun emptySlice(): Slice { return emptyCell().asSlice(); -} \ No newline at end of file +} diff --git a/stdlib/std/contract.tact b/stdlib/std/contract.tact index 1c3cd4fe1..5a782dfa8 100644 --- a/stdlib/std/contract.tact +++ b/stdlib/std/contract.tact @@ -44,16 +44,64 @@ asm fun getOriginalFwdFee(fwd_fee: Int, is_masterchain: Bool): Int { GETORIGINAL // Current debt for storage fee in nanotons. asm fun myStorageDue(): Int { DUEPAYMENT } +/// Struct representing the standard address on TON Blockchain with signed 8-bit `workchain` ID and an unsigned 256-bit `address` in the specified `workchain`. +/// +/// At the moment, only `workchain` IDs used on TON are 0 of the basechain and -1 of the masterchain. +/// +/// @since Tact 1.5.0 +/// @see https://docs.tact-lang.org/ref/core-advanced#parsestdaddress +/// @see https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L105-L106 +/// struct StdAddress { workchain: Int as int8; address: Int as uint256; } +/// Struct representing the address of variable length with signed 32-bit `workchain` ID and a `Slice` containing unsigned `address` in the specified `workchain`. +/// +/// Variable-length addresses are intended for future extensions, and while validators must be ready to accept them in inbound messages, the standard (non-variable) addresses are used whenever possible. +/// +/// @since Tact 1.5.0 +/// @see https://docs.tact-lang.org/ref/core-advanced#parsevaraddress +/// @see https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L107-L108 +/// struct VarAddress { workchain: Int as int32; address: Slice; } +/// Converts a `Slice` containing an address into the `StdAddress` Struct and returns it. +/// +/// ```tact +/// let addr = address("EQDtFpEwcFAEcRe5mLVh2N6C0x-_hJEM7W61_JLnSF74p4q2"); +/// let parsedAddr = parseStdAddress(addr.asSlice()); +/// +/// parsedAddr.workchain; // 0 +/// parsedAddr.address; // 107...287 +/// ``` +/// +/// @since Tact 1.5.0 +/// @see https://docs.tact-lang.org/ref/core-advanced#parsestdaddress +/// asm fun parseStdAddress(slice: Slice): StdAddress { REWRITESTDADDR } -asm fun parseVarAddress(slice: Slice): VarAddress { REWRITEVARADDR } \ No newline at end of file +/// Converts a `Slice` containing an address of variable length into the `VarAddress` Struct and returns it. +/// +/// ```tact +/// let varAddrSlice = beginCell() +/// .storeUint(6, 3) // to recognize the following as a VarAddress +/// .storeUint(123, 9) // make address occupy 123 bits +/// .storeUint(234, 32) // specify workchain ID of 234 +/// .storeUint(345, 123) // specify address of 345 +/// .asSlice(); +/// let parsedVarAddr = parseVarAddress(varAddrSlice); +/// +/// parsedVarAddr.workchain; // 234 +/// parsedVarAddr.address; // CS{Cell{002...2b3} bits: 44..167; refs: 0..0} +/// parsedVarAddr.address.loadUint(123); // 345 +/// ``` +/// +/// @since Tact 1.5.0 +/// @see https://docs.tact-lang.org/ref/core-advanced#parsevaraddress +/// +asm fun parseVarAddress(slice: Slice): VarAddress { REWRITEVARADDR } From 1adf5d5fa2f9b1d8dae3bc6cd2d2bcca90d2812a Mon Sep 17 00:00:00 2001 From: Aliaksandr Bahdanau <122269567+a-bahdanau@users.noreply.github.com> Date: Fri, 25 Oct 2024 20:39:56 +0300 Subject: [PATCH 4/7] feat(docs): add NFT cookbook (#958) * feat(docs): add NFT cookbook * chore(docs): update CHANGELOG.md * Apply suggestions from code review --------- Co-authored-by: Novus Nota <68142933+novusnota@users.noreply.github.com> --- CHANGELOG.md | 1 + docs/src/content/docs/cookbook/nfts.mdx | 237 +++++++++++++++++++++++- 2 files changed, 236 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79e411b99..56a03ad1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New CSpell dictionaries: TVM instructions and adjusted list of Fift words: PR [#881](https://github.com/tact-lang/tact/pull/881) - Docs: the `description` property to the frontmatter of the each page for better SEO: PR [#916](https://github.com/tact-lang/tact/pull/916) - Docs: Google Analytics tags per every page: PR [#921](https://github.com/tact-lang/tact/pull/921) +- Docs: Added NFTs cookbook: PR [#958](https://github.com/tact-lang/tact/pull/958) - Ability to specify a compile-time method ID expression for getters: PR [#922](https://github.com/tact-lang/tact/pull/922) and PR [#932](https://github.com/tact-lang/tact/pull/932) - Destructuring of structs and messages: PR [#856](https://github.com/tact-lang/tact/pull/856) diff --git a/docs/src/content/docs/cookbook/nfts.mdx b/docs/src/content/docs/cookbook/nfts.mdx index c1617d52a..e21c46a57 100644 --- a/docs/src/content/docs/cookbook/nfts.mdx +++ b/docs/src/content/docs/cookbook/nfts.mdx @@ -3,8 +3,241 @@ title: Non-Fungible Tokens (NFTs) description: "Common examples of working with Non-Fungible Tokens (NFTs) in Tact" --- -:::danger[Not implemented] +This page lists common examples of working with [NFTs](https://docs.ton.org/develop/dapps/asset-processing/nfts). - This page is a stub. [Contributions are welcome!](https://github.com/tact-lang/tact/issues) +## Accepting NFT ownership assignment + +Notification message of assigned NFT ownership has the following structure: + +```tact +message(0x05138d91) NFTOwnershipAssigned { + previousOwner: Address; + forwardPayload: Slice as remaining; +} +``` + +Use [receiver](/book/receive) function to accept notification message. + +:::caution + + Sender of notification must be validated! + +:::caution + +Validation can be done in two ways: + +1. Directly store the NFT item address and validate against it. + +```tact +contract Example with Deployable { + nftItemAddress: Address; + + init(nftItemAddress: Address) { + self.nftItemAddress = nftItemAddress; + } + + receive(msg: NFTOwnershipAssigned) { + require(nftItemAddress == sender(), "NFT contract is not the sender"); + + // your logic of processing nft ownership assign notification + } +} +``` + +2. Use [`StateInit{:tact}`](/book/expressions#initof) and derived address of the NFT item. + +```tact +struct NFTItemInitData { + index: Int as uint64; + collectionAddress: Address; +} + +inline fun calculateNFTAddress(index: Int, collectionAddress: Address, nftCode: Cell): Address { + let initData = NFTItemInitData{ + index, + collectionAddress, + }; + + return contractAddress(StateInit{code: nftCode, data: initData.toCell()}); +} + +contract Example with Deployable { + nftCollectionAddress: Address; + nftItemIndex: Int as uint64; + nftCode: Cell; + + init(nftCollectionAddress: Address, nftItemIndex: Int, nftCode: Cell) { + self.nftCollectionAddress = nftCollectionAddress; + self.nftItemIndex = nftItemIndex; + self.nftCode = nftCode; + } + + receive(msg: NFTOwnershipAssigned) { + let expectedNftAddress = calculateNFTAddress(self.nftItemIndex, self.nftCollectionAddress, self.nftCode); // or you can even store expectedNftAddress + require(expectedNftAddress == sender(), "NFT contract is not the sender"); + + // your logic of processing nft ownership assign notification + } +} +``` + +Since the initial data layout of the NFT item can vary, the first approach is often more suitable. + +## Transferring NFT item + +To send NFT item transfer use [`send(){:tact}`](/book/send) function. + +```tact +message(0x5fcc3d14) NFTTransfer { + queryId: Int as uint64; + newOwner: Address; // address of the new owner of the NFT item. + responseDestination: Address; // address where to send a response with confirmation of a successful transfer and the rest of the incoming message coins. + customPayload: Cell? = null; // optional custom data. In most cases should be null + forwardAmount: Int as coins; // the amount of nanotons to be sent to the new owner. + forwardPayload: Slice as remaining; // optional custom data that should be sent to the new owner. +} + +receive("transfer") { + send(SendParameters{ + to: self.nftItemAddress, + value: ton("0.1"), + body: NFTTransfer{ + queryId: 42, + newOwner: address("NEW_OWNER_ADDRESS"), // NOTE: Modify it to your liking + responseDestination: myAddress(), + customPayload: null, + forwardAmount: 1, + forwardPayload: rawSlice("F"), // precomputed beginCell().storeUint(0xF, 4).endCell().beginParse() + }.toCell(), + }); +} +``` + +## Get NFT static info + +Note, that TON Blockchain does not allow contracts to call each other [getters](https://docs.tact-lang.org/book/contracts#getter-functions). +In order to receive data from another contract, you must exchange messages. + +```tact +message(0x2fcb26a2) NFTGetStaticData { + queryId: Int as uint64; +} + +message(0x8b771735) NFTReportStaticData { + queryId: Int as uint64; + index: Int as uint256; + collection: Address; +} + +receive("get static data") { + let nftAddress = address("[NFT_ADDRESS]"); + send(SendParameters{ + to: nftAddress, + value: ton("0.1"), + body: NFTGetStaticData{ + queryId: 42, + }.toCell(), + }); +} + +receive(msg: NFTReportStaticData) { + let expectedNftAddress = calculateNFTAddress(msg.index, msg.collection, self.nftCode); + require(self.nftItemAddress == sender(), "NFT contract is not the sender"); + + // Save nft static data or do something +} +``` + +## Get NFT royalty params + +NFT royalty params are described [here](https://github.com/ton-blockchain/TEPs/blob/master/text/0066-nft-royalty-standard.md). + +```tact +message(0x693d3950) NFTGetRoyaltyParams { + queryId: Int as uint64; +} + +message(0xa8cb00ad) NFTReportRoyaltyParams { + queryId: Int as uint64; + numerator: Int as uint16; + denominator: Int as uint16; + destination: Address; +} + +receive("get royalty params") { + send(SendParameters{ + to: self.nftCollectionAddress, + value: ton("0.1"), + body: NFTGetRoyaltyParams{ + queryId: 42, + }.toCell(), + }); +} + +receive(msg: NFTReportRoyaltyParams) { + require(self.nftCollectionAddress == sender(), "NFT collection contract is not the sender"); + + // Do something with msg +} +``` + +## NFT Collection methods + + +:::caution + + These methods are not part of any standard, and they will only work with [this specific implementation](https://github.com/ton-blockchain/token-contract/blob/main/nft/nft-collection.fc). Please keep this in mind before using them. + +::: + +Note that only NFT owners are allowed to use these methods. + +### Deploy NFT + +```tact +message(0x1) NFTDeploy { + queryId: Int as uint64; + itemIndex: Int as uint64; + amount: Int as coins; // amount to sent when deploying nft + nftContent: Cell; +} + +receive("deploy") { + send(SendParameters{ + to: self.nftCollectionAddress, + value: ton("0.14"), + body: NFTDeploy{ + queryId: 42, + itemIndex: 42, + amount: ton("0.1"), + content: beginCell().endCell() // Should be your content, mostly generated offchain + }.toCell(), + }); +} +``` + +### Change owner + +```tact +message(0x3) NFTChangeOwner { + queryId: Int as uint64; + newOwner: Address; +} + +receive("change owner") { + send(SendParameters{ + to: self.nftCollectionAddress, + value: ton("0.05"), + body: NFTChangeOwner{ + queryId: 42, + newOwner: address("NEW_OWNER_ADDRESS"), // NOTE: Modify it to your liking + }.toCell(), + }); +} +``` + +:::tip[Hey there!] + + Didn't find your favorite example of a NFT communication? Have cool implementations in mind? [Contributions are welcome!](https://github.com/tact-lang/tact/issues) ::: From 980f68a067eb0ccb6ce13730bd5e1535899d7343 Mon Sep 17 00:00:00 2001 From: Aliaksandr Bahdanau <122269567+a-bahdanau@users.noreply.github.com> Date: Sat, 26 Oct 2024 02:16:17 +0300 Subject: [PATCH 5/7] feat(docs): enhance Jettons Cookbook page (#944) * feat(docs): enhance jetton page * chore(docs): add pr info * update docs/src/content/docs/cookbook/jettons.mdx * fix: remove mermaid until it gets a proper support in Starlight or when we would really need inlined diagrams Perhaps, we can pre-compute the mermaid diagrams using it's CLI, then embed those onto the page via a remark plugin. Unless, of course, there would be a simpler solution available if/when we need lots of diagrams in the future! * chore: editing busywork * chore: final editing touches --------- Co-authored-by: Novus Nota <68142933+novusnota@users.noreply.github.com> --- CHANGELOG.md | 1 + docs/astro.config.mjs | 2 +- docs/package.json | 4 +- docs/src/content/docs/cookbook/jettons.mdx | 307 +++++++++++++++++---- 4 files changed, 262 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56a03ad1d..2db77fca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `parseImports` function now returns AST import nodes instead of raw strings: PR [#966](https://github.com/tact-lang/tact/pull/966) - Optional types for `self` argument in `extends mutates` functions are now allowed: PR [#854](https://github.com/tact-lang/tact/pull/854) - Docs: complete overhaul of the exit codes page: PR [#978](https://github.com/tact-lang/tact/pull/978) +- Docs: enhanced Jettons Cookbook page: PR [#944](https://github.com/tact-lang/tact/pull/944) ### Fixed diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index a2c581828..69e3b3ad2 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -67,7 +67,7 @@ export default defineConfig({ // Per-page Google tag setup tag: "script", content: "window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','G-ZJ3GZHJ0Z5');", - } + }, ], social: { github: 'https://github.com/tact-lang/tact', diff --git a/docs/package.json b/docs/package.json index 98075aac9..abbf585c6 100644 --- a/docs/package.json +++ b/docs/package.json @@ -18,6 +18,8 @@ "@astrojs/markdown-remark": "^5.2.0", "@astrojs/starlight": "^0.28.2", "astro": "^4.16.1", + "cspell": "^8.14.4", + "hast-util-to-string": "^3.0.0", "rehype-autolink-headings": "7.1.0", "rehype-katex": "7.0.1", "remark-custom-heading-id": "2.0.0", @@ -25,8 +27,6 @@ "sharp": "^0.32.5", "starlight-links-validator": "^0.12.1", "typescript": "^5.6.2", - "cspell": "^8.14.4", - "hast-util-to-string": "^3.0.0", "unist-util-visit": "^5.0.0" }, "packageManager": "yarn@1.22.22" diff --git a/docs/src/content/docs/cookbook/jettons.mdx b/docs/src/content/docs/cookbook/jettons.mdx index 31002826e..c29b28cda 100644 --- a/docs/src/content/docs/cookbook/jettons.mdx +++ b/docs/src/content/docs/cookbook/jettons.mdx @@ -3,9 +3,42 @@ title: Fungible Tokens (Jettons) description: "Common examples of working with Fungible Tokens (Jettons) in Tact" --- -This page lists common examples of working with [jettons](https://docs.ton.org/develop/dapps/asset-processing/jettons). +This page lists common examples of working with [Fungible Tokens (Jettons)](https://docs.ton.org/develop/dapps/asset-processing/jettons). -## Accepting jetton transfer +Jettons are token standards on the TON Blockchain, designed to create fungible tokens (similar to ERC-20 on Ethereum) with a decentralized approach. They are implemented as a pair of smart contracts, typically consisting of two core components: + +* Jetton Master Contract (Jetton master) +* Jetton Wallet Contract (Jetton wallet) + +These contracts interact with each other to manage token supply, distribution, transfers, and other operations related to the Jetton. + +## Jetton Master Contract + +The Jetton Master Contract serves as the central entity for a given Jetton. It maintains critical information about the Jetton itself. Key responsibilities and data stored in the Jetton Master Contract include: + +* Jetton metadata: Information such as the token's name, symbol, total supply, and decimals. + +* Minting and Burning: When new Jettons are minted (created), the Jetton Master manages the creation process and distributes them to the appropriate wallets. It also manages the burning (destruction) of tokens as needed. + +* Supply Management: The Jetton Master keeps track of the total supply of Jettons and ensures proper accounting of all issued Jettons. + +## Jetton Wallet Contract + +The Jetton Wallet Contract represents an individual holder's token wallet and is responsible for managing the balance and token-related operations for a specific user. Each user or entity holding Jettons will have its own unique Jetton Wallet Contract. Key features of the Jetton Wallet Contract include: + +* Balance tracking: The wallet contract stores the user's token balance. + +* Token Transfers: The wallet is responsible for handling token transfers between users. When a user sends Jettons, the Jetton Wallet Contract ensures proper transfer and communication with the recipient's wallet. The Jetton Master is not involved in this activity and does not create a bottleneck. Wallets can use TON's sharding capability in a great way + +* Token burning: The Jetton Wallet interacts with the Jetton Master to burn tokens. + +* Owner control: The wallet contract is owned and controlled by a specific user, meaning that only the owner of the wallet can initiate transfers or other token operations. + +## Examples + +Common examples of working with Jettons. + +### Accepting Jetton transfer Transfer notification message have the following structure. @@ -26,12 +59,31 @@ Use [receiver](/book/receive) function to accept token notification message. ::: -Validation can be done using jetton wallet state init and calculating jetton address. -Note, that notifications are coming from YOUR contract's jetton wallet, so [`myAddress()`](/ref/core-common#myaddress) should be used in owner address field. -Wallet initial data layout is shown below, but sometimes it can differ. -Note that `myJettonWalletAddress` may also be stored in contract storage to use less gas in every transaction. +The sender of a transfer notification must be validated because malicious actors could attempt to spoof notifications from an unauthorized account. +If this validation is not done, the contract may accept unauthorized transactions, leading to potential security vulnerabilities. + +Validation is done using the Jetton address from the contract: + +1. Sender sends message with `0xf8a7ea5` as its 32-bit header (opcode) to his Jetton wallet. +2. Jetton wallet transfers funds to contract's Jetton wallet. +3. After successful transfer accept, contract's Jetton wallet sends transfer notification to his owner contract. +4. Contract validates the Jetton message. + +You may obtain contract's Jetton wallet is done using the [`contractAddress(){:tact}`](/ref/core-common#contractaddress) function or calculate this address offchain. + +To obtain the Jetton wallet's state init, you need the wallet's data and code. While there is a common structure for the initial data layout, it may differ in some cases, such as with [USDT](#usdt-jetton-operations). + +Since notifications originate from your contract's Jetton wallet, the function [`myAddress(){:tact}`](/ref/core-common#myaddress) should be used in `ownerAddress` field. + +:::caution + + Notifications are not always guaranteed to be sent. By default, the implementation does not send a notification if the `forwardAmount` is set to zero. Therefore, in such cases, you cannot rely on notifications being sent. + +::: ```tact +import "@stdlib/deploy"; + struct JettonWalletData { balance: Int as coins; ownerAddress: Address; @@ -39,7 +91,12 @@ struct JettonWalletData { jettonWalletCode: Cell; } -fun calculateJettonWalletAddress(ownerAddress: Address, jettonMasterAddress: Address, jettonWalletCode: Cell): Address { +fun calculateJettonWalletAddress( + ownerAddress: Address, + jettonMasterAddress: Address, + jettonWalletCode: Cell +): Address { + let initData = JettonWalletData{ balance: 0, ownerAddress, @@ -47,33 +104,54 @@ fun calculateJettonWalletAddress(ownerAddress: Address, jettonMasterAddress: Add jettonWalletCode, }; - return contractAddress(StateInit{code: jettonWalletCode, data: initData.toCell()}); + return contractAddress(StateInit{ + code: jettonWalletCode, + data: initData.toCell(), + }); } -contract Sample { - jettonWalletCode: Cell; - jettonMasterAddress: Address; +message(0x7362d09c) JettonTransferNotification { + queryId: Int as uint64; + amount: Int as coins; + sender: Address; + forwardPayload: Slice as remaining; +} + +contract Example with Deployable { + myJettonWalletAddress: Address; + myJettonAmount: Int as coins = 0; init(jettonWalletCode: Cell, jettonMasterAddress: Address) { - self.jettonWalletCode = jettonWalletCode; - self.jettonMasterAddress = jettonMasterAddress; + self.myJettonWalletAddress = calculateJettonWalletAddress( + myAddress(), + jettonMasterAddress, + jettonWalletCode, + ); } receive(msg: JettonTransferNotification) { - let myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), self.jettonMasterAddress, self.jettonWalletCode); - require(sender() == myJettonWalletAddress, "Notification not from your jetton wallet!"); + require( + sender() == self.myJettonWalletAddress, + "Notification not from your jetton wallet!", + ); + + self.myJettonAmount += msg.amount; - // your logic of processing token notification + // Forward excesses + self.forward(msg.sender, null, false, null); } } ``` -## Sending jetton transfer +### Sending Jetton transfer + +A Jetton transfer is the process of sending a specified amount of Jettons from one wallet (contract) to another. -To send jetton transfer use [`send(){:tact}`](/book/send) function. -Note that `myJettonWalletAddress` may also be stored in contract storage to use less gas in every transaction. +To send Jetton transfer use [`send(){:tact}`](/book/send) function. ```tact +import "@stdlib/deploy"; + message(0xf8a7ea5) JettonTransfer { queryId: Int as uint64; amount: Int as coins; @@ -84,27 +162,94 @@ message(0xf8a7ea5) JettonTransfer { forwardPayload: Slice as remaining; } -receive("send") { - let myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), self.jettonMasterAddress, self.jettonWalletCode); - send(SendParameters{ - to: myJettonWalletAddress, - value: ton("0.05"), - body: JettonTransfer{ - queryId: 42, - amount: jettonAmount, // jetton amount you want to transfer - destination: msg.userAddress, // address you want to transfer jettons. Note that this is address of jetton wallet owner, not jetton wallet itself - responseDestination: msg.userAddress, // address where to send a response with confirmation of a successful transfer and the rest of the incoming message Toncoins - customPayload: null, // in most cases will be null and can be omitted. Needed for custom logic on Jetton Wallets itself - forwardTonAmount: 1, // amount that will be transferred with JettonTransferNotification. Needed for custom logic execution like in example below. If the amount is 0 notification won't be sent - forwardPayload: rawSlice("F") // precomputed beginCell().storeUint(0xF, 4).endCell().beginParse(). This works for simple transfer, if needed any struct can be used as `forwardPayload` - }.toCell(), +const JettonTransferGas: Int = ton("0.05"); + +struct JettonWalletData { + balance: Int as coins; + ownerAddress: Address; + jettonMasterAddress: Address; + jettonWalletCode: Cell; +} + +fun calculateJettonWalletAddress( + ownerAddress: Address, + jettonMasterAddress: Address, + jettonWalletCode: Cell, +): Address { + + let initData = JettonWalletData{ + balance: 0, + ownerAddress, + jettonMasterAddress, + jettonWalletCode, + }; + + return contractAddress(StateInit{ + code: jettonWalletCode, + data: initData.toCell(), }); } + +message Withdraw { + amount: Int as coins; +} + +contract Example with Deployable { + myJettonWalletAddress: Address; + myJettonAmount: Int as coins = 0; + + init(jettonWalletCode: Cell, jettonMasterAddress: Address) { + self.myJettonWalletAddress = calculateJettonWalletAddress( + myAddress(), + jettonMasterAddress, + jettonWalletCode, + ); + } + + receive(msg: Withdraw) { + require( + msg.amount <= self.myJettonAmount, + "Not enough funds to withdraw" + ); + + send(SendParameters{ + to: self.myJettonWalletAddress, + value: JettonTransferGas, + body: JettonTransfer{ + // To prevent replay attacks + queryId: 42, + // Jetton amount to transfer + amount: msg.amount, + // Where to transfer Jettons: + // this is an address of the Jetton wallet + // owner and not the Jetton wallet itself + destination: sender(), + // Where to send a confirmation notice of a successful transfer + // and the rest of the incoming message value + responseDestination: sender(), + // Can be used for custom logic of Jettons themselves, + // and without such can be set to null + customPayload: null, + // Amount to transfer with JettonTransferNotification, + // which is needed for the execution of custom logic + forwardTonAmount: 1, // if its 0, the notification won't be sent! + // Compile-time way of expressing: + // beginCell().storeUint(0xF, 4).endCell().beginParse() + // For more complicated transfers, adjust accordingly + forwardPayload: rawSlice("F") + }.toCell(), + }); + } +} ``` -## Burning jetton +### Burning Jetton + +Jetton burning is the process of permanently removing a specified amount of Jettons from circulation, with no possibility of recovery. ```tact +import "@stdlib/deploy"; + message(0x595f07bc) JettonBurn { queryId: Int as uint64; amount: Int as coins; @@ -112,23 +257,79 @@ message(0x595f07bc) JettonBurn { customPayload: Cell? = null; } -receive("burn") { - let myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), self.jettonMasterAddress, self.jettonWalletCode); - send(SendParameters{ - to: myJettonWalletAddress, - body: JettonBurn{ - queryId: 42, - amount: jettonAmount, // jetton amount you want to burn - responseDestination: someAddress, // address where to send a response with confirmation of a successful burn and the rest of the incoming message coins - customPayload: null, // in most cases will be null and can be omitted. Needed for custom logic on jettons itself - }.toCell(), +const JettonBurnGas: Int = ton("0.05"); + +struct JettonWalletData { + balance: Int as coins; + ownerAddress: Address; + jettonMasterAddress: Address; + jettonWalletCode: Cell; +} + +fun calculateJettonWalletAddress( + ownerAddress: Address, + jettonMasterAddress: Address, + jettonWalletCode: Cell, +): Address { + + let initData = JettonWalletData{ + balance: 0, + ownerAddress, + jettonMasterAddress, + jettonWalletCode, + }; + + return contractAddress(StateInit{ + code: jettonWalletCode, + data: initData.toCell(), }); } + +message ThrowAway { + amount: Int as coins; +} + +contract Example with Deployable { + myJettonWalletAddress: Address; + myJettonAmount: Int as coins = 0; + + init(jettonWalletCode: Cell, jettonMasterAddress: Address) { + self.myJettonWalletAddress = calculateJettonWalletAddress( + myAddress(), + jettonMasterAddress, + jettonWalletCode, + ); + } + + receive(msg: ThrowAway) { + require( + msg.amount <= self.myJettonAmount, + "Not enough funds to throw away", + ); + + send(SendParameters{ + to: self.myJettonWalletAddress, + value: JettonBurnGas, + body: JettonBurn{ + // To prevent replay attacks + queryId: 42, + // Jetton amount you want to burn + amount: msg.amount, + // Where to send a confirmation notice of a successful burn + // and the rest of the incoming message value + responseDestination: sender(), + // Can be used for custom logic of Jettons themselves, + // and without such can be set to null + customPayload: null, + }.toCell(), + }); + } +} ``` -## USDT jetton operations +### USDT Jetton operations -Operations with USDT (on TON) remain the same, except that the `JettonWalletData` will have the following structure: +Operations with USDT (on TON) remain the same, except that the `JettonWalletData` will have the following structure: ```tact struct JettonWalletData { @@ -142,7 +343,12 @@ struct JettonWalletData { Function to calculate wallet address will look like this: ```tact -fun calculateJettonWalletAddress(ownerAddress: Address, jettonMasterAddress: Address, jettonWalletCode: Cell): Address { +fun calculateJettonWalletAddress( + ownerAddress: Address, + jettonMasterAddress: Address, + jettonWalletCode: Cell +): Address { + let initData = JettonWalletData{ status: 0, balance: 0, @@ -150,12 +356,15 @@ fun calculateJettonWalletAddress(ownerAddress: Address, jettonMasterAddress: Add jettonMasterAddress, }; - return contractAddress(StateInit{code: jettonWalletCode, data: initData.toCell()}); + return contractAddress(StateInit{ + code: jettonWalletCode, + data: initData.toCell(), + }); } ``` :::tip[Hey there!] - Didn't find your favorite example of jetton usage? Have cool implementations in mind? [Contributions are welcome!](https://github.com/tact-lang/tact/issues) +Didn't find your favorite example of Jetton usage? Have cool implementations in mind? [Contributions are welcome!](https://github.com/tact-lang/tact/issues) ::: From 9ed1c94abede47cc79f5b40104915d020e5c94b2 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:38:12 +0100 Subject: [PATCH 6/7] fix(docs/ci): compilation of examples in `data-structures.mdx` and across Cookbook (#917) * feat(CI): a script to check cookbook examples --- .github/workflows/tact-docs-prod.yml | 6 +- .github/workflows/tact-docs-test.yml | 4 + CHANGELOG.md | 1 + docs/scripts/check-cookbook-examples.js | 177 +++++++++++++++ docs/src/content/docs/book/exit-codes.mdx | 10 +- docs/src/content/docs/book/send.mdx | 20 +- .../content/docs/cookbook/data-structures.mdx | 62 ++--- docs/src/content/docs/cookbook/jettons.mdx | 7 +- docs/src/content/docs/cookbook/misc.mdx | 6 +- docs/src/content/docs/cookbook/nfts.mdx | 211 +++++++++++++----- docs/src/content/docs/cookbook/random.mdx | 2 +- .../docs/cookbook/single-communication.mdx | 102 +++++---- docs/src/content/docs/ref/core-common.mdx | 8 +- 13 files changed, 451 insertions(+), 165 deletions(-) create mode 100644 docs/scripts/check-cookbook-examples.js diff --git a/.github/workflows/tact-docs-prod.yml b/.github/workflows/tact-docs-prod.yml index e039fb7d5..3a68be831 100644 --- a/.github/workflows/tact-docs-prod.yml +++ b/.github/workflows/tact-docs-prod.yml @@ -6,6 +6,7 @@ on: # The check for the 'main' branch is done on the job level push: tags: ["v[0-9]+.[0-9]+.[0-9]+"] + branches: ["main"] # Allows to run this workflow manually from the Actions tab workflow_dispatch: @@ -29,8 +30,9 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - # with: - # fetch-depth: 0 + with: + fetch-depth: 0 + - name: Install, build and store site artifact uses: withastro/action@v3 with: diff --git a/.github/workflows/tact-docs-test.yml b/.github/workflows/tact-docs-test.yml index 20e279081..622b666fa 100644 --- a/.github/workflows/tact-docs-test.yml +++ b/.github/workflows/tact-docs-test.yml @@ -42,6 +42,10 @@ jobs: cache: "yarn" cache-dependency-path: "docs/yarn.lock" + - name: Perform syntax and type checking of the Cookbook + working-directory: docs + run: node scripts/check-cookbook-examples.js + - name: Install dependencies working-directory: docs run: yarn deps diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db77fca2..9717e6b7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Non-null struct fields after null ones are treated correctly in Sandbox tests after updating `@ton/core` to 0.59.0: PR [#933](https://github.com/tact-lang/tact/pull/933) - Prevent inline code snippets from changing their background color: PR [#935](https://github.com/tact-lang/tact/pull/935) - Docs: correctly handle next and previous page links at the bottom of the pages when there's a separator item in the sidebar: PR [#949](https://github.com/tact-lang/tact/pull/949) +- Docs: compilation of examples in `data-structures.mdx` and across Cookbook: PR [#917](https://github.com/tact-lang/tact/pull/917) ### Release contributors diff --git a/docs/scripts/check-cookbook-examples.js b/docs/scripts/check-cookbook-examples.js new file mode 100644 index 000000000..713ee8977 --- /dev/null +++ b/docs/scripts/check-cookbook-examples.js @@ -0,0 +1,177 @@ +/*─────────────────────────────────────────────────────────────────────────────╗ +β”‚ IMPORTANT: β”‚ +β”‚ Run this script from the root of the docs, not from the scripts directory! β”‚ +β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•‘ +β”‚ The script: β”‚ +β”‚ 1. Goes over every file in Cookbook β”‚ +β”‚ 2. Extracts the Tact code blocks from them β”‚ +β”‚ 3. For every code block, it runs the latest publicly available version β”‚ +β”‚ of the Tact compiler, performing the syntax and type checking (--check) β”‚ +β”‚ 4. If there are any errors, outputs them and exits β”‚ +β”‚ β”‚ +β”‚ Checks take ~0.5 seconds per code block, so lets use it for Cookbook only β”‚ +β•šβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€*/ + +import { spawnSync } from 'node:child_process'; +import { tmpdir } from 'node:os'; +import { + mkdtempSync, + mkdtemp, + readFileSync, + readdirSync, + statSync, + writeFileSync, + existsSync, +} from 'node:fs'; +import { chdir, cwd } from 'node:process'; +// TODO(?): check the proper dir (from git) and automatically change the working dir + +/*******************/ +/* Utility helpers */ +/*******************/ + +/** Default directory for temporary files with / separator (because even PowerShell can use direct slash) */ +const globalTmpDir = tmpdir() + '/'; + +/** + * Obtains the list of files with target extension in the target directory and its + * sub-directories as a flat array of names. + * + * @param dir {string | undefined} defaults to "." (current directory) + * @param extension {string | undefined} defaults to any file + * @returns {string[]} + */ +const getFileNames = (dir, extension) => { + /** + * @param dir {string | undefined} + * @param extension {string | undefined} + * @returns {string[]} + */ + const recGetFileNames = (dir, extension, _files) => { + _files = _files || []; + let files = readdirSync(dir); + for (let i in files) { + let name = dir + '/' + files[i]; + if (statSync(name).isDirectory()) { + recGetFileNames(name, extension, _files); + continue; + } + if (extension === undefined || name.endsWith(extension)) { + _files.push(name.trim()); + } + } + return _files; + }; + + return recGetFileNames(dir ?? ".", extension); +}; + +/** + * @param src {string} source of the .md or .mdx file to extract code blocks from + * @returns {string[]} all Tact code blocks on the page ready to be processed + */ +const extractTactCodeBlocks = (src) => { + /** @type RegExpExecArray[] */ + const regexMatches = [...src.matchAll(/```(\w*).*?\n([\s\S]*?)```/gm)]; + /** @type string[] */ + let res = []; + + for (let i = 0; i < regexMatches.length; i += 1) { + // Skip non-Tact matches + if (regexMatches[i].at(1)?.trim() !== "tact") { + continue; + } + + // Guard the contents + let code = regexMatches[i].at(2)?.trim(); + if (code === undefined || code.length === 0) { + console.log(`Error: regex failed when processing code blocks of:\n\n${src}`); + process.exit(1); + } + + // See if the `code` needs additional wrapping in a global function or not + // i.e. if it doesn't contain any module-level items (implicit convention in Tact docs): + const moduleItems = code.split('\n').filter((line) => { + const matchRes = line.match(/^\s*(?:import|primitive|const|asm|fun|extends|mutates|virtual|override|inline|abstract|@name|@interface|contract|trait|struct|message)\b/); + + if (matchRes === null) { return false; } + else { return true; } + }); + + if (moduleItems.length === 0) { + code = `fun _() {\n${code}\n}`; + } + + // Save the code + res.push(code); + } + + return res; +}; + +/** + * @requires Node.js 22+ with npm installed + * @param filepath {string} a path to Tact file + * @returns {{ ok: true } | { ok: false, error: string }} + */ +const checkTactFile = (filepath) => { + // Using the latest publicly available compiler to ensure that current users + // can compile and run the code, not just the compiler developers + const res = spawnSync('npx', + ['-y', '@tact-lang/compiler@latest', '--check', filepath], + { encoding: 'utf8' } + ); + + if (res.status !== 0) { + return { + ok: false, + error: res.stdout + res.stderr, + } + } + + return { ok: true }; +}; + +/**********/ +/* Script */ +/**********/ + +/** @type string */ +const cookbookPath = "src/content/docs/cookbook"; + +if (!existsSync(cookbookPath)) { + console.log(`Error: path ${cookbookPath} doesn't exist, ensure that you're in the right directory!`); + process.exit(1); +} + +/** @type string[] */ +const mdxFileNames = getFileNames(cookbookPath, ".mdx"); + +for (let i = 0; i < mdxFileNames.length; i += 1) { + const file = readFileSync(mdxFileNames[i], { encoding: 'utf8' }); + const codeBlocks = extractTactCodeBlocks(file); + const tmpDirForCurrentPage = mkdtempSync(globalTmpDir); + const pageName = mdxFileNames[i].slice( + mdxFileNames[i].lastIndexOf('/') + 1, + mdxFileNames[i].lastIndexOf('.mdx'), + ); + + for (let j = 0; j < codeBlocks.length; j += 1) { + const tactFile = `${tmpDirForCurrentPage}/${pageName}-block-${(j + 1).toString()}.tact`; + writeFileSync(tactFile, codeBlocks[j], { encoding: 'utf8', mode: '644' }); + console.log(`Checking ${tactFile}`); + + // TODO(?): Alternative solution would be to prepare a tact.config.json on the fly + const savedCwd = cwd(); + chdir(tmpDirForCurrentPage); + + // Perform individual checks (see TODO above) + const checkRes = checkTactFile(tactFile); + chdir(savedCwd); + + if (checkRes.ok === false) { + console.log(`Error: check of ${tactFile} has failed:\n\n${checkRes.error}`); + process.exit(1); + } + } +} diff --git a/docs/src/content/docs/book/exit-codes.mdx b/docs/src/content/docs/book/exit-codes.mdx index 8ede3e76a..bfb6486b7 100644 --- a/docs/src/content/docs/book/exit-codes.mdx +++ b/docs/src/content/docs/book/exit-codes.mdx @@ -1,6 +1,6 @@ --- title: Exit codes -description: "An exit code is a 32-bit signed integer, which indicates whether the compute or action phase of the transaction was successful, and if not β€” signals the code of the exception occurred" +description: "An exit code is a 32-bit signed integer, which indicates whether the compute or action phase of the transaction was successful, and if not β€” holds the code of the exception occurred" --- Each transaction on TON Blockchain consists of [multiple phases](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases). An _exit code_ is a $32$-bit signed integer, which indicates whether the [compute](#compute) or [action](#action) phase of the transaction was successful, and if not β€” holds the code of the exception occurred. Each exit code represents its own exception or resulting state of the transaction. @@ -19,9 +19,7 @@ The range from $256$ to $65535$ is free for developer-defined exit codes. ## Table of exit codes {#table} -The following table lists exit codes with an origin (where it can occur) and a short description for each. - -The table doesn't list the exit code of the [`require()`](/ref/core-debug#require), as it generates it depending on the concrete `error` message [String][p]. +The following table lists exit codes with an origin (where it can occur) and a short description for each. The table doesn't list the exit code of the [`require()`](/ref/core-debug#require), as it generates it depending on the concrete `error` message [String][p]. Exit code | Origin | Brief description :------------ | :---------------------------------- | :---------------- @@ -51,7 +49,7 @@ Exit code | Origin | Brief description [$39$](#39) | [Action phase][a] | Outbound message does not fit into a cell after rewriting. [$40$](#40) | [Action phase][a] | Cannot process a message β€” not enough funds, the message is too large or its Merkle depth is too big. [$41$](#41) | [Action phase][a] | Library reference is null during library change action. -[$42$](#42) | [Action phase][a] | Library change action error β€” error during an attempt of the library change action. +[$42$](#42) | [Action phase][a] | Library change action error. [$43$](#43) | [Action phase][a] | Exceeded maximum number of cells in the library or the maximum depth of the Merkle tree. [$50$](#50) | [Action phase][a] | Account state size exceeded limits. [$128$](#128) | Tact compiler ([Compute phase][c]) | Null reference exception. @@ -667,7 +665,7 @@ try { [message]: /book/structs-and-messages#messages [tlb]: https://docs.ton.org/develop/data-formats/tl-b-language -[tvm]: https://docs.ton.org/develop/func/statements#function-application +[tvm]: https://docs.ton.org/learn/tvm-instructions/tvm-overview [bp]: https://github.com/ton-org/blueprint [sb]: https://github.com/ton-org/sandbox [jest]: https://jestjs.io diff --git a/docs/src/content/docs/book/send.mdx b/docs/src/content/docs/book/send.mdx index f7ad772e8..315ef5435 100644 --- a/docs/src/content/docs/book/send.mdx +++ b/docs/src/content/docs/book/send.mdx @@ -24,9 +24,7 @@ Fields `code` and `data` are what's called an [init package](/book/expressions#i The simplest message is a reply to the incoming message returning all excess value of a message: ```tact -receive() { - self.reply("Hello, World!".asComment()); // asComment converts a String to a Cell with a comment -} +self.reply("Hello, World!".asComment()); // asComment converts a String to a Cell with a comment ``` ## Send message @@ -36,15 +34,13 @@ If you need more advanced logic you can use the `send(){:tact}` function and `Se In fact, the previous example with [`.reply(){:tact}`](#send-simple-reply) can be made using the following call to `send(){:tact}` function: ```tact -receive() { - send(SendParameters{ - // bounce is set to true by default - to: sender(), // sending message back to the sender - value: 0, // don't add Toncoins to the message... - mode: SendRemainingValue | SendIgnoreErrors, // ...except for ones received from the sender due to SendRemainingValue - body: "Hello, World".asComment(), // asComment converts a String to a Cell with a comment - }); -} +send(SendParameters{ + // bounce is set to true by default + to: sender(), // sending message back to the sender + value: 0, // don't add Toncoins to the message... + mode: SendRemainingValue | SendIgnoreErrors, // ...except for ones received from the sender due to SendRemainingValue + body: "Hello, World".asComment(), // asComment converts a String to a Cell with a comment +}); ``` Another example sends a message to the specified [`Address{:tact}`][p] with a `value` of $1$ TON and the `body` of a comment with a [`String{:tact}`][p] `"Hello, World!"{:tact}`: diff --git a/docs/src/content/docs/cookbook/data-structures.mdx b/docs/src/content/docs/cookbook/data-structures.mdx index 174ed39bc..2ebcb2384 100644 --- a/docs/src/content/docs/cookbook/data-structures.mdx +++ b/docs/src/content/docs/cookbook/data-structures.mdx @@ -31,8 +31,8 @@ const MaxArraySize: Int = 5_000; // 5,000 entries max, to stay reasonably far fr extends mutates fun append(self: Array, item: Int) { require(self.length + 1 <= MaxArraySize, "No space in the array left for new items!"); - self.map.set(self.length, item); // set the entry (key-value pair) - self.length += 1; // increase the length field + self.m.set(self.length, item); // set the entry (key-value pair) + self.length += 1; // increase the length field } // Extension mutation function for inserting new entries at the given index @@ -45,13 +45,13 @@ extends mutates fun insert(self: Array, item: Int, idx: Int) { let i: Int = self.length; // not a typo, as we need to start from the non-existing place while (i > idx) { // Note, that we use !! operator as we know for sure that the value would be there - self.map.set(i, self.map.get(i - 1)!!); + self.m.set(i, self.m.get(i - 1)!!); i -= 1; } // And put the new item in - self.map.set(idx, item); // set the entry (key-value pair) - self.length += 1; // increase the length field + self.m.set(idx, item); // set the entry (key-value pair) + self.length += 1; // increase the length field } // Extension function for getting the value at the given index @@ -61,7 +61,7 @@ extends fun getIdx(self: Array, idx: Int): Int { require(idx < self.length, "Index is out of array bounds!"); // Note, that we use !! operator as we know for sure that the value would be there - return self.map.get(idx)!!; + return self.m.get(idx)!!; } // Extension function for returning the last value @@ -69,7 +69,7 @@ extends fun getLast(self: Array): Int { require(self.length > 0, "No items in the array!"); // Note, that we use !! operator as we know for sure that the value would be there - return self.map.get(self.length - 1)!!; + return self.m.get(self.length - 1)!!; } // Extension mutation function for deleting and entry at the given index and returning its value @@ -79,18 +79,18 @@ extends mutates fun deleteIdx(self: Array, idx: Int): Int { require(idx < self.length, "Index is out of array bounds!"); // Remember the value, which is going to be deleted - let memorized: Int = self.map.get(idx)!!; + let memorized: Int = self.m.get(idx)!!; // Move all items from idx and including to the left let i: Int = idx; while (i + 1 < self.length) { // Note, that we use !! operator as we know for sure that the value would be there - self.map.set(i, self.map.get(i + 1)!!); + self.m.set(i, self.m.get(i + 1)!!); i += 1; } - self.map.set(self.length - 1, null); // delete the last entry - self.length -= 1; // decrease the length field + self.m.set(self.length - 1, null); // delete the last entry + self.length -= 1; // decrease the length field return memorized; } @@ -100,16 +100,16 @@ extends fun deleteLast(self: Array): Int { require(self.length > 0, "No items in the array!"); // Note, that we use !! operator as we know for sure that the value would be there - let lastItem: Int = self.map.get(self.length - 1)!!; - self.map.set(self.length - 1, null); // delete the entry - self.length -= 1; // decrease the length field + let lastItem: Int = self.m.get(self.length - 1)!!; + self.m.set(self.length - 1, null); // delete the entry + self.length -= 1; // decrease the length field return lastItem; } // Extension function for deleting all items in the Array extends mutates fun deleteAll(self: Array) { - self.map = emptyMap(); + self.m = emptyMap(); self.length = 0; } @@ -184,8 +184,8 @@ const MaxStackSize: Int = 5_000; // 5,000 entries max, to stay reasonably far fr extends mutates fun push(self: Stack, item: Int) { require(self.length + 1 <= MaxStackSize, "No space in the stack left for new items!"); - self.map.set(self.length, item); // set the entry (key-value pair) - self.length += 1; // increase the length field + self.m.set(self.length, item); // set the entry (key-value pair) + self.length += 1; // increase the length field } // Extension mutation function for deleting the last entry and returning its value @@ -193,9 +193,9 @@ extends mutates fun pop(self: Stack): Int { require(self.length > 0, "No items in the stack to delete!"); // Note, that we use !! operator as we know for sure that the value would be there - let lastItem: Int = self.map.get(self.length - 1)!!; - self.map.set(self.length - 1, null); // delete the entry - self.length -= 1; // decrease the length field + let lastItem: Int = self.m.get(self.length - 1)!!; + self.m.set(self.length - 1, null); // delete the entry + self.length -= 1; // decrease the length field return lastItem; } @@ -205,12 +205,12 @@ extends fun peek(self: Stack): Int { require(self.length > 0, "No items in the stack!"); // Note, that we use !! operator as we know for sure that the value would be there - return self.map.get(self.length - 1)!!; + return self.m.get(self.length - 1)!!; } // Extension function for deleting all items in the Stack extends mutates fun deleteAll(self: Stack) { - self.map = emptyMap(); + self.m = emptyMap(); self.length = 0; } @@ -252,8 +252,8 @@ contract MapAsStack with Deployable { } // Getter function for obtaining the stack - get fun map(): map { - return self.stack.map; + get fun stack(): map { + return self.stack.m; } // Getter function for obtaining the current length of the stack @@ -285,10 +285,10 @@ const MaxCircularBufferSize: Int = 5; // Extension mutation function for putting new items to the circular buffer extends mutates fun put(self: CircularBuffer, item: Int) { if (self.length < MaxCircularBufferSize) { - self.map.set(self.length, item); // store the item - self.length += 1; // increase the length field + self.m.set(self.length, item); // store the item + self.length += 1; // increase the length field } else { - self.map.set(self.start, item); // store the item, overriding previous entry + self.m.set(self.start, item); // store the item, overriding previous entry self.start = (self.start + 1) % MaxCircularBufferSize; // update starting position } } @@ -300,25 +300,25 @@ extends mutates fun getIdx(self: CircularBuffer, idx: Int): Int { if (self.length < MaxCircularBufferSize) { // Note, that we use !! operator as we know for sure that the value would be there - return self.map.get(idx % self.length)!!; + return self.m.get(idx % self.length)!!; } // Return the value rotating around the circular buffer, also guaranteed to be there - return self.map.get((self.start + idx) % MaxCircularBufferSize)!!; + return self.m.get((self.start + idx) % MaxCircularBufferSize)!!; } // Extension function for iterating over all items in the circular buffer and dumping them to the console extends fun printAll(self: CircularBuffer) { let i: Int = self.start; repeat (self.length) { - dump(self.map.get(i)!!); // !! tells the compiler this can't be null + dump(self.m.get(i)!!); // !! tells the compiler this can't be null i = (i + 1) % MaxCircularBufferSize; } } // Extension function for deleting all items in the CircularBuffer extends mutates fun deleteAll(self: CircularBuffer) { - self.map = emptyMap(); + self.m = emptyMap(); self.length = 0; self.start = 0; } diff --git a/docs/src/content/docs/cookbook/jettons.mdx b/docs/src/content/docs/cookbook/jettons.mdx index c29b28cda..329421f75 100644 --- a/docs/src/content/docs/cookbook/jettons.mdx +++ b/docs/src/content/docs/cookbook/jettons.mdx @@ -333,16 +333,13 @@ Operations with USDT (on TON) remain the same, except that the `JettonWalletData ```tact struct JettonWalletData { - status: Ins as uint4; + status: Int as uint4; balance: Int as coins; ownerAddress: Address; jettonMasterAddress: Address; } -``` - -Function to calculate wallet address will look like this: -```tact +// And the function to calculate the wallet address may look like this: fun calculateJettonWalletAddress( ownerAddress: Address, jettonMasterAddress: Address, diff --git a/docs/src/content/docs/cookbook/misc.mdx b/docs/src/content/docs/cookbook/misc.mdx index c68a9ca7b..18e8f16ec 100644 --- a/docs/src/content/docs/cookbook/misc.mdx +++ b/docs/src/content/docs/cookbook/misc.mdx @@ -15,13 +15,13 @@ It allows intentional exception or error handling, which leads to the terminatio let number: Int = 198; // the error will be triggered anyway -throw(36); +try { throw(36); } catch (exitCode) {} // the error will be triggered only if the number is greater than 50 -nativeThrowIf(35, number > 50); +try { nativeThrowIf(35, number > 50); } catch (exitCode) {} // the error will be triggered only if the number is NOT EQUAL to 198 -nativeThrowUnless(39, number == 198); +try { nativeThrowUnless(39, number == 198); } catch (exitCode) {} ``` :::note[Useful links:] diff --git a/docs/src/content/docs/cookbook/nfts.mdx b/docs/src/content/docs/cookbook/nfts.mdx index e21c46a57..613e83459 100644 --- a/docs/src/content/docs/cookbook/nfts.mdx +++ b/docs/src/content/docs/cookbook/nfts.mdx @@ -22,14 +22,21 @@ Use [receiver](/book/receive) function to accept notification message. Sender of notification must be validated! -:::caution +::: Validation can be done in two ways: 1. Directly store the NFT item address and validate against it. ```tact -contract Example with Deployable { +import "@stdlib/deploy"; + +message(0x05138d91) NFTOwnershipAssigned { + previousOwner: Address; + forwardPayload: Slice as remaining; +} + +contract SingleNft with Deployable { nftItemAddress: Address; init(nftItemAddress: Address) { @@ -37,7 +44,7 @@ contract Example with Deployable { } receive(msg: NFTOwnershipAssigned) { - require(nftItemAddress == sender(), "NFT contract is not the sender"); + require(self.nftItemAddress == sender(), "NFT contract is not the sender"); // your logic of processing nft ownership assign notification } @@ -47,6 +54,13 @@ contract Example with Deployable { 2. Use [`StateInit{:tact}`](/book/expressions#initof) and derived address of the NFT item. ```tact +import "@stdlib/deploy"; + +message(0x05138d91) NFTOwnershipAssigned { + previousOwner: Address; + forwardPayload: Slice as remaining; +} + struct NFTItemInitData { index: Int as uint64; collectionAddress: Address; @@ -61,7 +75,7 @@ inline fun calculateNFTAddress(index: Int, collectionAddress: Address, nftCode: return contractAddress(StateInit{code: nftCode, data: initData.toCell()}); } -contract Example with Deployable { +contract NftInCollection with Deployable { nftCollectionAddress: Address; nftItemIndex: Int as uint64; nftCode: Cell; @@ -88,6 +102,8 @@ Since the initial data layout of the NFT item can vary, the first approach is of To send NFT item transfer use [`send(){:tact}`](/book/send) function. ```tact +import "@stdlib/deploy"; + message(0x5fcc3d14) NFTTransfer { queryId: Int as uint64; newOwner: Address; // address of the new owner of the NFT item. @@ -97,19 +113,30 @@ message(0x5fcc3d14) NFTTransfer { forwardPayload: Slice as remaining; // optional custom data that should be sent to the new owner. } -receive("transfer") { - send(SendParameters{ - to: self.nftItemAddress, - value: ton("0.1"), - body: NFTTransfer{ - queryId: 42, - newOwner: address("NEW_OWNER_ADDRESS"), // NOTE: Modify it to your liking - responseDestination: myAddress(), - customPayload: null, - forwardAmount: 1, - forwardPayload: rawSlice("F"), // precomputed beginCell().storeUint(0xF, 4).endCell().beginParse() - }.toCell(), - }); +contract Example { + nftItemAddress: Address; + + init(nftItemAddress: Address) { + self.nftItemAddress = nftItemAddress; + } + + // ... add more code from previous examples ... + + receive("transfer") { + send(SendParameters{ + to: self.nftItemAddress, + value: ton("0.1"), + body: NFTTransfer{ + queryId: 42, + // FIXME: Change this according to your needs + newOwner: sender(), + responseDestination: myAddress(), + customPayload: null, + forwardAmount: 1, + forwardPayload: rawSlice("F"), // precomputed beginCell().storeUint(0xF, 4).endCell().beginParse() + }.toCell(), + }); + } } ``` @@ -129,22 +156,51 @@ message(0x8b771735) NFTReportStaticData { collection: Address; } -receive("get static data") { - let nftAddress = address("[NFT_ADDRESS]"); - send(SendParameters{ - to: nftAddress, - value: ton("0.1"), - body: NFTGetStaticData{ - queryId: 42, - }.toCell(), - }); +struct NFTItemInitData { + index: Int as uint64; + collectionAddress: Address; } -receive(msg: NFTReportStaticData) { - let expectedNftAddress = calculateNFTAddress(msg.index, msg.collection, self.nftCode); - require(self.nftItemAddress == sender(), "NFT contract is not the sender"); +inline fun calculateNFTAddress(index: Int, collectionAddress: Address, nftCode: Cell): Address { + let initData = NFTItemInitData{ + index, + collectionAddress, + }; - // Save nft static data or do something + return contractAddress(StateInit{code: nftCode, data: initData.toCell()}); +} + +contract Example { + nftCollectionAddress: Address; + nftItemIndex: Int as uint64; + nftCode: Cell; + + init(nftCollectionAddress: Address, nftItemIndex: Int, nftCode: Cell) { + self.nftCollectionAddress = nftCollectionAddress; + self.nftItemIndex = nftItemIndex; + self.nftCode = nftCode; + } + + // ... add more code from previous examples ... + + receive("get static data") { + // FIXME: Put proper address("[NFT_ADDRESS]") here + let nftAddress = sender(); + send(SendParameters{ + to: nftAddress, + value: ton("0.1"), + body: NFTGetStaticData{ + queryId: 42, + }.toCell(), + }); + } + + receive(msg: NFTReportStaticData) { + let expectedNftAddress = calculateNFTAddress(msg.index, msg.collection, self.nftCode); + require(expectedNftAddress == sender(), "NFT contract is not the sender"); + + // Save nft static data or do something + } } ``` @@ -164,20 +220,30 @@ message(0xa8cb00ad) NFTReportRoyaltyParams { destination: Address; } -receive("get royalty params") { - send(SendParameters{ - to: self.nftCollectionAddress, - value: ton("0.1"), - body: NFTGetRoyaltyParams{ - queryId: 42, - }.toCell(), - }); -} +contract Example { + nftCollectionAddress: Address; + + init(nftCollectionAddress: Address) { + self.nftCollectionAddress = nftCollectionAddress; + } + + // ... add more code from previous examples ... -receive(msg: NFTReportRoyaltyParams) { - require(self.nftCollectionAddress == sender(), "NFT collection contract is not the sender"); + receive("get royalty params") { + send(SendParameters{ + to: self.nftCollectionAddress, + value: ton("0.1"), + body: NFTGetRoyaltyParams{ + queryId: 42, + }.toCell(), + }); + } - // Do something with msg + receive(msg: NFTReportRoyaltyParams) { + require(self.nftCollectionAddress == sender(), "NFT collection contract is not the sender"); + + // Do something with msg + } } ``` @@ -202,17 +268,27 @@ message(0x1) NFTDeploy { nftContent: Cell; } -receive("deploy") { - send(SendParameters{ - to: self.nftCollectionAddress, - value: ton("0.14"), - body: NFTDeploy{ - queryId: 42, - itemIndex: 42, - amount: ton("0.1"), - content: beginCell().endCell() // Should be your content, mostly generated offchain - }.toCell(), - }); +contract Example { + nftCollectionAddress: Address; + + init(nftCollectionAddress: Address) { + self.nftCollectionAddress = nftCollectionAddress; + } + + // ... add more code from previous examples ... + + receive("deploy") { + send(SendParameters{ + to: self.nftCollectionAddress, + value: ton("0.14"), + body: NFTDeploy{ + queryId: 42, + itemIndex: 42, + amount: ton("0.1"), + nftContent: beginCell().endCell() // FIXME: Should be your content, mostly generated offchain + }.toCell(), + }); + } } ``` @@ -224,15 +300,26 @@ message(0x3) NFTChangeOwner { newOwner: Address; } -receive("change owner") { - send(SendParameters{ - to: self.nftCollectionAddress, - value: ton("0.05"), - body: NFTChangeOwner{ - queryId: 42, - newOwner: address("NEW_OWNER_ADDRESS"), // NOTE: Modify it to your liking - }.toCell(), - }); +contract Example { + nftCollectionAddress: Address; + + init(nftCollectionAddress: Address) { + self.nftCollectionAddress = nftCollectionAddress; + } + + // ... add more code from previous examples ... + + receive("change owner") { + send(SendParameters{ + to: self.nftCollectionAddress, + value: ton("0.05"), + body: NFTChangeOwner{ + queryId: 42, + // FIXME: Put proper address("NEW_OWNER_ADDRESS") here + newOwner: sender(), + }.toCell(), + }); + } } ``` diff --git a/docs/src/content/docs/cookbook/random.mdx b/docs/src/content/docs/cookbook/random.mdx index 524a0ebb3..c54d8d298 100644 --- a/docs/src/content/docs/cookbook/random.mdx +++ b/docs/src/content/docs/cookbook/random.mdx @@ -9,7 +9,7 @@ This page lists examples of working with random numbers, uncertainty and randomn ```tact // Declare a variable to store the random number -let number: Int; +let number: Int = 0; // Generate a new random number, which is an unsigned 256-bit integer number = randomInt(); diff --git a/docs/src/content/docs/cookbook/single-communication.mdx b/docs/src/content/docs/cookbook/single-communication.mdx index 8b53d9a3e..5feaf4a3c 100644 --- a/docs/src/content/docs/cookbook/single-communication.mdx +++ b/docs/src/content/docs/cookbook/single-communication.mdx @@ -13,20 +13,26 @@ For examples of communication between multiple deployed contracts see: [Multi-co ## How to make a basic reply ```tact -receive() { - self.reply("Hello, World!".asComment()); // asComment converts a String to a Cell with a comment +contract Example { + receive() { + self.reply("Hello, World!".asComment()); // asComment converts a String to a Cell with a comment + } } ``` ## How to send a simple message ```tact -send(SendParameters{ - bounce: true, // default - to: destinationAddress, - value: ton("0.01"), // attached amount of Tons to send - body: "Hello from Tact!".asComment(), // comment (optional) -}); +contract Example { + receive() { + send(SendParameters{ + bounce: true, // default + to: sender(), // or another destination address + value: ton("0.01"), // attached amount of Tons to send + body: "Hello from Tact!".asComment(), // comment (optional) + }); + } +} ``` ## How to send a message with the entire balance @@ -34,13 +40,17 @@ send(SendParameters{ If we need to send the whole balance of the smart contract, then we should use the `SendRemainingBalance{:tact}` send mode. Alternatively, we can use `mode: 128{:tact}`, which has the same meaning. ```tact -send(SendParameters{ - // bounce = true by default - to: sender(), // send the message back to the original sender - value: 0, - mode: SendRemainingBalance, // or mode: 128 - body: "Hello from Tact!".asComment(), // comment (optional) -}); +contract Example { + receive() { + send(SendParameters{ + // bounce = true by default + to: sender(), // send the message back to the original sender + value: 0, + mode: SendRemainingBalance, // or mode: 128 + body: "Hello from Tact!".asComment(), // comment (optional) + }); + } +} ``` ## How to send a message with the remaining value @@ -48,25 +58,33 @@ send(SendParameters{ If we want to make a reply to the same sender, we can use the mode `SendRemainingValue{:tact}` (i.e. `mode: 64{:tact}`), which carries all the remaining value of the inbound message in addition to the value initially indicated in the new message. ```tact -send(SendParameters{ - // bounce = true by default - to: sender(), // send the message back to the original sender - value: 0, - mode: SendRemainingValue, - body: "Hello from Tact!".asComment(), // comment (optional) -}); +contract Example { + receive() { + send(SendParameters{ + // bounce = true by default + to: sender(), // send the message back to the original sender + value: 0, + mode: SendRemainingValue, + body: "Hello from Tact!".asComment(), // comment (optional) + }); + } +} ``` It's often useful to add the `SendIgnoreErrors{:tact}` flag too, in order to ignore any errors arising while processing this message during the action phaseL ```tact -send(SendParameters{ - // bounce = true by default - to: sender(), // send the message back to the original sender - value: 0, - mode: SendRemainingValue | SendIgnoreErrors, // prefer using | over + for the mode - body: "Hello from Tact!".asComment(), // comment (optional) -}); +contract Example { + receive() { + send(SendParameters{ + // bounce = true by default + to: sender(), // send the message back to the original sender + value: 0, + mode: SendRemainingValue | SendIgnoreErrors, // prefer using | over + for the mode + body: "Hello from Tact!".asComment(), // comment (optional) + }); + } +} ``` The latter example is identical to using a [`.reply(){:tact}` function](#how-to-make-a-basic-reply). @@ -76,17 +94,21 @@ The latter example is identical to using a [`.reply(){:tact}` function](#how-to- If we need to send a message with a lengthy text comment, we should create a [`String{:tact}`](/book/types#primitive-types) that consists of more than $127$ characters. To do this, we can utilize the [`StringBuilder{:tact}`](/book/types#primitive-types) primitive type and its methods called `beginComment(){:tact}` and `append(){:tact}`. Prior to sending, we should convert this string into a cell using the `toCell(){:tact}` method. ```tact -let comment: StringBuilder = beginComment(); -let longString = "..."; // Some string with more than 127 characters. -comment.append(longString); - -send(SendParameters{ - // bounce = true by default - to: sender(), - value: 0, - mode: SendIgnoreErrors, - body: comment.toCell(), -}); +contract Example { + receive() { + let comment: StringBuilder = beginComment(); + let longString = "..."; // Some string with more than 127 characters. + comment.append(longString); + + send(SendParameters{ + // bounce = true by default + to: sender(), + value: 0, + mode: SendIgnoreErrors, + body: comment.toCell(), + }); + } +} ``` :::note[Useful links:] diff --git a/docs/src/content/docs/ref/core-common.mdx b/docs/src/content/docs/ref/core-common.mdx index c3f541eab..64c28fdc3 100644 --- a/docs/src/content/docs/ref/core-common.mdx +++ b/docs/src/content/docs/ref/core-common.mdx @@ -66,14 +66,16 @@ Returns the [`Address{:tact}`][p] of the sender of the current message. Usage example: ```tact -receive() { - let whoSentMeMessage: Address = sender(); +contract MeSee { + receive() { + let whoSentMeMessage: Address = sender(); + } } ``` :::caution - Behavior is undefined for [getter functions](/book/contracts#getter-functions), as they cannot have a sender nor they can send messages. + Behavior is undefined for [getter functions](/book/contracts#getter-functions), because they cannot have a sender nor can they send messages. ::: From 757e62b96a5e644550d770fd2ac14335a82a9223 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:01:39 +0100 Subject: [PATCH 7/7] feat(docs): comptime functions `slice`, `rawSlice`, `ascii` and `crc32` (#951) * chore(CI): try using `pnpm setup` as advised that may or may not require sourcing proper files on the system, like .profile/.bashrc for systems using Bash (Linux in GitHub Actions), .zprofile/.zshrc for systems using Zsh (macOS in GitHub Actions), etc. Because of some bug in macos-latest, which overrides the used version of Node.js (from 22 specified to 20 installed), which then causes compilation errors with Tact (`.isSubsetOf()` stuff) --- .github/workflows/tact.yml | 12 +- CHANGELOG.md | 2 +- docs/src/content/docs/ref/core-comptime.mdx | 138 ++++++++++++++++++-- 3 files changed, 141 insertions(+), 11 deletions(-) diff --git a/.github/workflows/tact.yml b/.github/workflows/tact.yml index bddd162b1..3fbd596be 100644 --- a/.github/workflows/tact.yml +++ b/.github/workflows/tact.yml @@ -231,7 +231,17 @@ jobs: - name: (pnpm) Test creation of new Blueprint projects if: ${{ matrix.package-manager == 'pnpm' }} run: | - npm i -g pnpm + # Installing the specific pnpm version to work around https://github.com/pnpm/pnpm/issues/5000 + # and not fail suddenly when a release comes that fixes this + # NOTE: revisit and simplify this step if/when those bugs with pnpm are resolved + npm i -g pnpm@9.12.2 + # To help pnpm recognize the shell (they cannot do it themselves sometimes...) + ${{ matrix.os != 'windows-latest' && 'export SHELL=bash' || 'echo windows_noop' }} + pnpm setup -f + # To source .bashrc on Linux: + ${{ matrix.os == 'ubuntu-latest' && 'source ~/.bashrc' || 'echo noop' }} + # To expose stuff for pnpm directly on macOS (because otherwise the pre-installed Node.js version gets used): + ${{ matrix.os == 'macos-latest' && 'export PNPM_HOME=~/Library/pnpm; export PATH=$PNPM_HOME:$PATH' || 'echo noop' }} pnpm link -g cd .. pnpm create ton@latest test-project --type tact-counter --contractName Counter diff --git a/CHANGELOG.md b/CHANGELOG.md index 9717e6b7c..e55390853 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,7 +73,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `deepEquals` method for the `Map` type: PR [#637](https://github.com/tact-lang/tact/pull/637), PR [#939](https://github.com/tact-lang/tact/pull/939) - `asm` bodies for module-level functions: PR [#769](https://github.com/tact-lang/tact/pull/769), PR [#825](https://github.com/tact-lang/tact/pull/825) - Corresponding stdlib functions for new TVM instructions from 2023.07 and 2024.04 upgrades: PR [#331](https://github.com/tact-lang/tact/pull/331). Added the `storeBuilder` extension function and `gasConsumed`, `getComputeFee`, `getStorageFee`, `getForwardFee`, `getSimpleComputeFee`, `getSimpleForwardFee`, `getOriginalFwdFee`, `myStorageDue` functions. -- `slice`, `rawSlice`, `ascii` and `crc32` built-in functions: PR [#787](https://github.com/tact-lang/tact/pull/787), PR [#799](https://github.com/tact-lang/tact/pull/799) +- `slice`, `rawSlice`, `ascii` and `crc32` built-in functions: PR [#787](https://github.com/tact-lang/tact/pull/787), PR [#799](https://github.com/tact-lang/tact/pull/799), PR [#951](https://github.com/tact-lang/tact/pull/951) - `Builder.storeMaybeRef`, `parseStdAddress` and `parseVarAddress` stdlib functions: PR [#793](https://github.com/tact-lang/tact/pull/793), PR [#950](https://github.com/tact-lang/tact/pull/950) - The compiler development guide: PR [#833](https://github.com/tact-lang/tact/pull/833) - Constant evaluator now uses an interpreter: PR [#664](https://github.com/tact-lang/tact/pull/664). This allows calls to user-defined functions and references to declared global constants. diff --git a/docs/src/content/docs/ref/core-comptime.mdx b/docs/src/content/docs/ref/core-comptime.mdx index 5ab3ff4f0..8c1f2cb5f 100644 --- a/docs/src/content/docs/ref/core-comptime.mdx +++ b/docs/src/content/docs/ref/core-comptime.mdx @@ -3,6 +3,8 @@ title: Compile-time description: "Various compile-time global functions from the Core library of Tact" --- +import { Badge } from '@astrojs/starlight/components'; + This page lists all the built-in [global static functions](/book/functions#global-static-functions), which are evaluated at the time of building the Tact project and cannot work with non-constant, run-time data. These functions are commonly referred to as "compile-time functions". ## address @@ -11,7 +13,7 @@ This page lists all the built-in [global static functions](/book/functions#globa fun address(s: String): Address; ``` -A compile-time function that converts a [`String{:tact}`][p] with an address into the [`Address{:tact}`][p] type. +A compile-time function that converts a [`String{:tact}`][p] with an address into the [`Address{:tact}`][p] type and embeds it into the contract. Usage example: @@ -23,28 +25,141 @@ contract Example { } ``` +:::note + + The `address("...Address..."){:tact}` in Tact is equivalent to `"...Address..."a{:func}` in FunC. + +::: + ## cell ```tact fun cell(bocBase64: String): Cell; ``` -A compile-time function that embeds a base64-encoded [BoC](https://docs.ton.org/develop/data-formats/cell-boc#bag-of-cells) `bocBase64` as a [`Cell{:tact}`][cell] into the contract. +A compile-time function that embeds a base64-encoded [BoC][boc] `bocBase64` as a [`Cell{:tact}`][cell] into the contract. -Usage example: +Usage examples: ```tact contract Example { // Persistent state variables - storedCell: Cell = + stored: Cell = // Init package for Wallet V3R1 as a base64-encoded BoC cell("te6cckEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVD++buA="); // works at compile-time! } ``` -:::note[Useful links:] +## slice + +

+ +```tact +fun slice(bocBase64: String): Slice; +``` + +A compile-time function that embeds a base64-encoded [BoC][boc] `bocBase64` as a [`Slice{:tact}`][slice] into the contract. + +Usage examples: + +```tact +contract Example { + // Persistent state variables + stored: Slice = + // Works at compile-time! + slice("te6cckEBAQEADgAAGEhlbGxvIHdvcmxkIXgtxbw="); // Hello world! +} +``` + +## rawSlice + +

+ +```tact +fun rawSlice(hex: String): Slice; +``` + +A compile-time function that converts `hex` [`String{:tact}`][p] with hex-encoded and optionally bit-padded contents as a [`Slice{:tact}`][slice] into the contract. + +Contents are bit-padded if there's an underscore `_` at the very end of [`String{:tact}`][p]. The padding removes all the trailing zeros and the last $1$ bit before them: + +```tact +// Not bit-padded +rawSlice("4a").loadUint(8); // 74, or 1001010 in binary + +// Bit-padded +rawSlice("4a_").loadUint(6); // 18, or 10010 in binary +``` + +Note, that this function is limited and only allows to specify up to $1023$ bits. + +Usage example: + +```tact +contract Example { + // Persistent state variables + stored: Slice = + rawSlice("000DEADBEEF000"); // CS{Cell{03f...430} bits: 588..644; refs: 1..1} + bitPadded: Slice = + rawSlice("000DEADBEEF000_"); // CS{Cell{03f...e14} bits: 36..79; refs: 0..0} +} +``` + +:::note - [Bag of Cells in TON Docs](https://docs.ton.org/develop/data-formats/cell-boc#bag-of-cells) + The `rawSlice("...Hex contents..."){:tact}` in Tact is equivalent to `"...Hex contents..."s{:func}` in FunC. + +::: + +## ascii + +

+ +```tact +fun ascii(str: String): Int; +``` + +A compile-time function that concatenates the hexadecimal values of the characters in `str` into one and embeds the resulting [`Int{:tact}`][int] into the contract. Only works for strings that occupy up to $32$ bytes, which allows to represent up to $32$ [ASCII codes](https://en.wikipedia.org/wiki/ASCII#Control_code_chart) or up to $8$ $4$-byte [Unicode code points](https://en.wikipedia.org/wiki/List_of_Unicode_characters). + +Usage example: + +```tact +contract Example { + // Persistent state variables + a: Int = ascii("a"); // 97 or 0x61, one byte in total + zap: Int = ascii("⚑"); // 14850721 or 0xE29AA1, 3 bytes in total + doubleZap: Int = ascii("⚑⚑"); // 249153768823457 or 0xE29AA1E29AA1, 6 bytes in total +} +``` + +:::note + + The `ascii("...String contents..."){:tact}` in Tact is equivalent to `"...String contents..."u{:func}` in FunC. + +::: + +## crc32 + +

+ +```tact +fun crc32(str: String): Int; +``` + +A compile-time function that computes a checksum using [CRC-32](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) algorithm and embeds the resulting [`Int{:tact}`][int] value into the contract. + +Usage example: + +```tact +contract Example { + // Persistent state variables + checksum: Int = crc32("000DEADBEEF000"); // 1821923098 +} +``` + +:::note + + The `crc32("...String contents..."){:tact}` in Tact is equivalent to `"...String contents..."c{:func}` in FunC. ::: @@ -61,9 +176,9 @@ Usage example: ```tact contract Example { // Persistent state variables - one: Int = ton("1"); // 10^9 nanoToncoins, which is equal to one Toncoin - pointOne: Int = ton("0.1"); // 10^8 nanoToncoins, which is equal to 0.1 Toncoin - nano: Int = ton("0.000000001"); // 1 nanoToncoin, which is equal to 10^-9 Toncoins + one: Int = ton("1"); // one Toncoin, which is equivalent to 10^9 nanoToncoins + pointOne: Int = ton("0.1"); // 0.1 Toncoin, which is equivalent to 10^8 nanoToncoins + nano: Int = ton("0.000000001"); // 10^-9 Toncoins, which is equivalent to 1 nanoToncoin // works at compile-time! } ``` @@ -72,3 +187,8 @@ contract Example { [bool]: /book/types#booleans [int]: /book/integers [cell]: /book/cells#cells +[slice]: /book/cells#slices + +[boc]: /book/cells#cells-boc + +[crc]: https://en.wikipedia.org/wiki/Cyclic_redundancy_check