From d50e24cf9b39751bec2e6a6e5e158acaf2cc6b5e Mon Sep 17 00:00:00 2001 From: sansx Date: Wed, 30 Oct 2024 22:52:47 +0800 Subject: [PATCH] feat(docs): initial semi-automated Chinese translation (#942) Co-authored-by: Novus Nota <68142933+novusnota@users.noreply.github.com> --- CHANGELOG.md | 1 + crowdin.yml | 8 + docs/src/content/docs/index.mdx | 4 +- docs/src/content/docs/zh-cn/book/bounced.mdx | 37 + docs/src/content/docs/zh-cn/book/cells.mdx | 444 ++++++++ docs/src/content/docs/zh-cn/book/config.mdx | 414 ++++++++ .../src/content/docs/zh-cn/book/constants.mdx | 69 ++ .../src/content/docs/zh-cn/book/contracts.mdx | 347 +++++++ .../content/docs/zh-cn/book/cs/from-func.mdx | 12 + .../docs/zh-cn/book/cs/from-solidity.mdx | 12 + docs/src/content/docs/zh-cn/book/debug.mdx | 725 +++++++++++++ docs/src/content/docs/zh-cn/book/deploy.mdx | 52 + .../content/docs/zh-cn/book/exit-codes.mdx | 324 ++++++ .../content/docs/zh-cn/book/expressions.mdx | 256 +++++ docs/src/content/docs/zh-cn/book/external.mdx | 75 ++ docs/src/content/docs/zh-cn/book/func.mdx | 307 ++++++ .../src/content/docs/zh-cn/book/functions.mdx | 172 ++++ docs/src/content/docs/zh-cn/book/import.mdx | 54 + docs/src/content/docs/zh-cn/book/index.mdx | 39 + docs/src/content/docs/zh-cn/book/integers.mdx | 186 ++++ .../src/content/docs/zh-cn/book/lifecycle.mdx | 28 + docs/src/content/docs/zh-cn/book/maps.mdx | 362 +++++++ .../content/docs/zh-cn/book/masterchain.mdx | 49 + .../content/docs/zh-cn/book/message-mode.mdx | 59 ++ .../src/content/docs/zh-cn/book/operators.mdx | 642 ++++++++++++ .../src/content/docs/zh-cn/book/optionals.mdx | 42 + .../content/docs/zh-cn/book/programmatic.mdx | 47 + docs/src/content/docs/zh-cn/book/receive.mdx | 54 + docs/src/content/docs/zh-cn/book/send.mdx | 159 +++ .../content/docs/zh-cn/book/statements.mdx | 416 ++++++++ .../docs/zh-cn/book/structs-and-messages.mdx | 301 ++++++ docs/src/content/docs/zh-cn/book/types.mdx | 176 ++++ docs/src/content/docs/zh-cn/book/upgrades.mdx | 5 + .../content/docs/zh-cn/cookbook/access.mdx | 52 + docs/src/content/docs/zh-cn/cookbook/algo.mdx | 11 + .../docs/zh-cn/cookbook/data-structures.mdx | 377 +++++++ .../docs/zh-cn/cookbook/dexes/dedust.mdx | 13 + .../docs/zh-cn/cookbook/dexes/stonfi.mdx | 13 + .../src/content/docs/zh-cn/cookbook/index.mdx | 84 ++ .../content/docs/zh-cn/cookbook/jettons.mdx | 160 +++ docs/src/content/docs/zh-cn/cookbook/misc.mdx | 37 + .../zh-cn/cookbook/multi-communication.mdx | 9 + docs/src/content/docs/zh-cn/cookbook/nfts.mdx | 9 + .../content/docs/zh-cn/cookbook/random.mdx | 31 + .../zh-cn/cookbook/single-communication.mdx | 101 ++ docs/src/content/docs/zh-cn/cookbook/time.mdx | 30 + .../docs/zh-cn/cookbook/type-conversion.mdx | 150 +++ .../content/docs/zh-cn/ecosystem/index.mdx | 36 + .../docs/zh-cn/ecosystem/jetbrains.mdx | 25 + .../content/docs/zh-cn/ecosystem/misti.mdx | 20 + .../docs/zh-cn/ecosystem/typescript.mdx | 9 + .../content/docs/zh-cn/ecosystem/vscode.mdx | 63 ++ docs/src/content/docs/zh-cn/index.mdx | 197 ++++ .../content/docs/zh-cn/ref/core-advanced.mdx | 256 +++++ docs/src/content/docs/zh-cn/ref/core-base.mdx | 127 +++ .../src/content/docs/zh-cn/ref/core-cells.mdx | 963 ++++++++++++++++++ .../content/docs/zh-cn/ref/core-common.mdx | 245 +++++ .../content/docs/zh-cn/ref/core-comptime.mdx | 73 ++ .../src/content/docs/zh-cn/ref/core-debug.mdx | 217 ++++ docs/src/content/docs/zh-cn/ref/core-math.mdx | 279 +++++ .../content/docs/zh-cn/ref/core-random.mdx | 44 + .../content/docs/zh-cn/ref/core-strings.mdx | 354 +++++++ .../docs/zh-cn/ref/evolution/otp-001.mdx | 57 ++ .../docs/zh-cn/ref/evolution/otp-002.mdx | 78 ++ .../docs/zh-cn/ref/evolution/otp-003.mdx | 19 + .../docs/zh-cn/ref/evolution/otp-004.mdx | 36 + .../docs/zh-cn/ref/evolution/otp-005.mdx | 35 + .../docs/zh-cn/ref/evolution/otp-006.mdx | 54 + .../docs/zh-cn/ref/evolution/overview.mdx | 50 + docs/src/content/docs/zh-cn/ref/index.mdx | 57 ++ docs/src/content/docs/zh-cn/ref/spec.mdx | 306 ++++++ .../docs/zh-cn/ref/standard-libraries.mdx | 44 + .../content/docs/zh-cn/ref/stdlib-config.mdx | 55 + .../content/docs/zh-cn/ref/stdlib-content.mdx | 38 + .../content/docs/zh-cn/ref/stdlib-deploy.mdx | 96 ++ .../src/content/docs/zh-cn/ref/stdlib-dns.mdx | 194 ++++ .../content/docs/zh-cn/ref/stdlib-ownable.mdx | 114 +++ .../docs/zh-cn/ref/stdlib-stoppable.mdx | 105 ++ 78 files changed, 11199 insertions(+), 2 deletions(-) create mode 100644 crowdin.yml create mode 100644 docs/src/content/docs/zh-cn/book/bounced.mdx create mode 100644 docs/src/content/docs/zh-cn/book/cells.mdx create mode 100644 docs/src/content/docs/zh-cn/book/config.mdx create mode 100644 docs/src/content/docs/zh-cn/book/constants.mdx create mode 100644 docs/src/content/docs/zh-cn/book/contracts.mdx create mode 100644 docs/src/content/docs/zh-cn/book/cs/from-func.mdx create mode 100644 docs/src/content/docs/zh-cn/book/cs/from-solidity.mdx create mode 100644 docs/src/content/docs/zh-cn/book/debug.mdx create mode 100644 docs/src/content/docs/zh-cn/book/deploy.mdx create mode 100644 docs/src/content/docs/zh-cn/book/exit-codes.mdx create mode 100644 docs/src/content/docs/zh-cn/book/expressions.mdx create mode 100644 docs/src/content/docs/zh-cn/book/external.mdx create mode 100644 docs/src/content/docs/zh-cn/book/func.mdx create mode 100644 docs/src/content/docs/zh-cn/book/functions.mdx create mode 100644 docs/src/content/docs/zh-cn/book/import.mdx create mode 100644 docs/src/content/docs/zh-cn/book/index.mdx create mode 100644 docs/src/content/docs/zh-cn/book/integers.mdx create mode 100644 docs/src/content/docs/zh-cn/book/lifecycle.mdx create mode 100644 docs/src/content/docs/zh-cn/book/maps.mdx create mode 100644 docs/src/content/docs/zh-cn/book/masterchain.mdx create mode 100644 docs/src/content/docs/zh-cn/book/message-mode.mdx create mode 100644 docs/src/content/docs/zh-cn/book/operators.mdx create mode 100644 docs/src/content/docs/zh-cn/book/optionals.mdx create mode 100644 docs/src/content/docs/zh-cn/book/programmatic.mdx create mode 100644 docs/src/content/docs/zh-cn/book/receive.mdx create mode 100644 docs/src/content/docs/zh-cn/book/send.mdx create mode 100644 docs/src/content/docs/zh-cn/book/statements.mdx create mode 100644 docs/src/content/docs/zh-cn/book/structs-and-messages.mdx create mode 100644 docs/src/content/docs/zh-cn/book/types.mdx create mode 100644 docs/src/content/docs/zh-cn/book/upgrades.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/access.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/algo.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/data-structures.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/dexes/dedust.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/dexes/stonfi.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/index.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/jettons.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/misc.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/multi-communication.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/nfts.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/random.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/single-communication.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/time.mdx create mode 100644 docs/src/content/docs/zh-cn/cookbook/type-conversion.mdx create mode 100644 docs/src/content/docs/zh-cn/ecosystem/index.mdx create mode 100644 docs/src/content/docs/zh-cn/ecosystem/jetbrains.mdx create mode 100644 docs/src/content/docs/zh-cn/ecosystem/misti.mdx create mode 100644 docs/src/content/docs/zh-cn/ecosystem/typescript.mdx create mode 100644 docs/src/content/docs/zh-cn/ecosystem/vscode.mdx create mode 100644 docs/src/content/docs/zh-cn/index.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/core-advanced.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/core-base.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/core-cells.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/core-common.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/core-comptime.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/core-debug.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/core-math.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/core-random.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/core-strings.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/evolution/otp-001.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/evolution/otp-002.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/evolution/otp-003.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/evolution/otp-004.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/evolution/otp-005.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/evolution/otp-006.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/evolution/overview.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/index.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/spec.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/standard-libraries.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/stdlib-config.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/stdlib-content.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/stdlib-deploy.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/stdlib-dns.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/stdlib-ownable.mdx create mode 100644 docs/src/content/docs/zh-cn/ref/stdlib-stoppable.mdx diff --git a/CHANGELOG.md b/CHANGELOG.md index ce98a5326..23a8e9164 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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) - Docs: automatic links to Web IDE from all code blocks: PR [#994](https://github.com/tact-lang/tact/pull/994) +- Docs: initial semi-automated Chinese translation of the documentation: PR [#942](https://github.com/tact-lang/tact/pull/942) ### Changed diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..cac5a617c --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,8 @@ +project_id: "723773" +api_token_env: CROWDIN_PERSONAL_TOKEN +preserve_hierarchy: 1 +files: + - source: /docs/src/content/docs/**/* + translation: /docs/src/content/docs/%two_letters_code%/**/%original_file_name% + ignore: + - /docs/src/content/docs/%two_letters_code% diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index d9ef1cf22..bb2c77793 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -5,8 +5,8 @@ template: splash hero: tagline: Tact is a new programming language for TON Blockchain that is focused on efficiency and simplicity. It is designed to be easy to learn and use, and to be a good fit for smart contracts. Tact is a statically typed language with a simple syntax and a powerful type system. image: - dark: ../../../public/logomark-dark.svg - light: ../../../public/logomark-light.svg + dark: /public/logomark-dark.svg + light: /public/logomark-light.svg alt: Tact logo actions: - text: 📚 Book diff --git a/docs/src/content/docs/zh-cn/book/bounced.mdx b/docs/src/content/docs/zh-cn/book/bounced.mdx new file mode 100644 index 000000000..f9c900d2c --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/bounced.mdx @@ -0,0 +1,37 @@ +--- +title: 退信 +--- + +当一个合约发送的信息的反弹标志设置为 true 时,如果信息没有被正确处理,它就会反弹回发送者。当你想确认信息是否被正确处理,如果没有,就可以恢复更改,这个功能非常有用。 + +## 注意事项 + +目前,在 TON 中,被退回的报文只有 224 个可用数据位,没有引用。 这意味着您无法从被退回的邮件中恢复大部分数据。 这是 TON 区块链的局限性,将来会得到修复。 Tact helps you to check if your message fits the limit and if not - it will suggest to use a special type modifier `bounced` for the receiver that would construct a partial representation that fits into the required limits. + +## 退信接收器 + +:::caution + + 目前暂不支持文本消息回退 + +::: + +要接收退回的信息,您需要在合同或特性中定义一个 “退回 ”接收器: + +```tact {2-4} +contract MyContract { + bounced(src: bounced) { + // ... + } +} +``` + +要手动处理被退回的信息,您可以使用回退定义,直接处理原始的 [`Slice{:tact}`](/book/cells#slices)。请注意,这样的接收器将获得由您的合约产生的**所有**被退回的信息: + +```tact /rawMsg: Slice/ +contract MyContract { + bounced(src: Slice) { + // ... + } +} +``` diff --git a/docs/src/content/docs/zh-cn/book/cells.mdx b/docs/src/content/docs/zh-cn/book/cells.mdx new file mode 100644 index 000000000..841856951 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/cells.mdx @@ -0,0 +1,444 @@ +--- +title: 细胞、建造者和切片 +--- + +[单元](#cells)、[构建器](#builders)和[切片](#slices)是 TON 区块链的底层[基元][p]。 TON 区块链的虚拟机 [TVM][tvm]使用单元格来表示持久存储中的所有数据结构,以及内存中的大部分数据结构。 + +## Cells + +`Cell{:tact}`是一种[基元][p]和数据结构,它[通常](#cells-kinds)由多达 $1023$ 个连续排列的比特和多达 $4$ 个指向其他单元格的引用(refs)组成。 禁止循环引用,也不能通过[TVM][tvm]的方式创建循环引用,这意味着单元格可以被视为[四叉树][quadtree]或[有向无环图(DAG)](https://en.wikipedia.org/wiki/Directed_acyclic_graph)。 合同代码本身由单元格树形结构表示。 + +单元和 [单元基元](#cells-immutability) 是面向比特的,而不是面向字节的:[TVM][tvm] 将保存在单元中的数据视为最多为 $1023$ 比特的序列(字符串或数据流),而不是字节。 如有必要,合约可以自由使用 $21$-bit 整数字段,将其序列化为 [TVM][tvm] 单元,从而使用更少的持久存储字节来表示相同的数据。 + +### Kinds {#cells-kinds} + +虽然 [TVM][tvm] 类型 [`单元格{:tact}`](#cells)指的是所有单元格,但有不同的单元格类型,其内存布局也各不相同。 前面描述的单元格(#cells)通常被称为_ordinary_(或 simple)单元格--这是最简单、最常用的单元格,只能包含数据。 绝大多数关于细胞及其用法的描述、指南和[参考文献](/ref/core-cells)都假定细胞是普通的。 + +其他类型的细胞统称为_外来细胞_(或特殊细胞)。 它们有时会出现在 TON 区块链上的区块和其他数据结构的实际表示中。 它们的内存布局和用途与普通电池大不相同。 + +所有细胞的种类(或亚型)都由 $-1$ 和 $255$之间的整数编码。 普通单元格用 $-1$编码,特殊单元格可用该范围内的任何其他整数编码。 奇异单元的子类型存储在其数据的前 $8$ 位,这意味着有效的奇异单元总是至少有 $8$ 个数据位。 + +[TVM][tvm]目前支持以下奇异细胞子类型: + +- [剪枝单元格][c-pruned],子类型编码为 $1$ - 它们代表删除的单元格子树。 +- [图书馆引用单元][c-library],子类型编码为 $2$ - 它们用于存储图书馆,通常在[masterchain](/book/masterchain)上下文中使用。 +- [梅克尔证明单元][c-mproof],子类型编码为 $3$ - 它们用于验证其他单元的树数据的某些部分是否属于完整树。 +- [梅克尔更新单元][c-mupdate],子类型编码为 $4$ - 它们总是有两个引用,对这两个引用的行为类似于[梅克尔证明][mproof]。 + +:::note[Useful links:] + + [TON Docs 中的剪枝单元][c-pruned] + [TON Docs 中的 Merkle 证明单元][c-mproof] + [TON Docs 中的 Merkle 更新单元][c-mupdate] + [TON Docs 中的简单证明验证示例][mproof] + +::: + +[c-pruned]: https://docs.ton.org/develop/data-formats/exotic-cells#pruned-branch +[c-library]: https://docs.ton.org/develop/data-formats/library-cells +[c-mproof]: https://docs.ton.org/develop/data-formats/exotic-cells#merkle-proof +[c-mupdate]: https://docs.ton.org/develop/data-formats/exotic-cells#merkle-update +[mproof]: https://docs.ton.org/develop/data-formats/exotic-cells#simple-proof-verifying-example + +### Levels {#cells-levels} + +作为 [四叉树][quadtree],每个单元格都有一个名为 _level_ 的属性,它由 $0$ 和 $3$之间的整数表示。 [普通](#cells-kinds) 单元格的级别总是等于其所有引用级别的最大值。 也就是说,没有引用的普通单元格的电平等于 $0$。 + +[外来](#cells-kinds)细胞有不同的规则来决定它们的等级,这些规则在[TON Docs 的本页](https://docs.ton.org/develop/data-formats/exotic-cells)上有描述。 + +### Serialization {#cells-serialization} + +在通过网络传输单元格或在磁盘上存储单元格之前,必须对其进行序列化。 有几种常用格式,如[标准 `Cell{:tact}` 表示法](#cells-representation)和[BoC](#cells-boc)。 + +#### Standard representation {#cells-representation} + +标准[`单元格{:tact}`](#cells)表示法是[tvm.pdf](https://docs.ton.org/tvm.pdf)中首次描述的单元格通用序列化格式。 它的算法以八进制(字节)序列表示单元,首先将称为描述符的第一个 $2$ 字节序列化: + +- _Refs descriptor_ 的计算公式如下: $r + 8 * k + 32 * l$,其中 $r$ 是单元格中包含的引用数(介于 $0$ 和 $4$之间), $k$ 是单元格类型标志($0$ 表示 [ordinary](#cells-kinds) 和 $1$ 表示 [exotic](#cells-kinds) ), $l$ 是单元格的 [level](#cells-levels) (介于 $0$ 和 $3$之间)。 +- _Bits descriptor_ 的计算公式为 $\lfloor\frac{b}{8}\rfloor + \lceil\frac{b}{8}\rceil$,其中 $b$ 是单元格中的位数(介于 $0$ 和 $1023$之间)。 + +然后,单元本身的数据位被序列化为 $\lceil\frac{b}{8}\rceil$ $8$-bit octets(字节)。 如果 $b$ 不是 8 的倍数,则在数据位上附加一个二进制 $1$ 和最多六个二进制 $0$s。 + +接下来, $2$ 字节存储了引用的深度,即单元格树根(当前单元格)和最深引用(包括它)之间的单元格数。 例如,如果一个单元格只包含一个引用而没有其他引用,则其深度为 $1$,而被引用单元格的深度为 $0$。 + +最后,为每个参考单元存储其标准表示的[SHA-256][sha-2] 哈希值,每个参考单元占用 $32$ 字节,并递归重复上述算法。 请注意,不允许循环引用单元格,因此递归总是以定义明确的方式结束。 + +如果我们要计算这个单元格的标准表示的哈希值,就需要将上述步骤中的所有字节连接在一起,然后使用 [SHA-256][sha-2] 哈希值进行散列。 这是[TVM][tvm]的[`HASHCU`和`HASHSU`指令](https://docs.ton.org/learn/tvm-instructions/instructions)以及 Tact 的[`Cell.hash(){:tact}`](/ref/core-cells#cellhash)和[`Slice.hash(){:tact}`](/ref/core-cells#slicehash)函数背后的算法。 + +#### Bag of Cells {#cells-boc} + +如 [boc.tlb](https://github.com/ton-blockchain/ton/blob/24dc184a2ea67f9c47042b4104bbb4d82289fac1/crypto/tl/boc.tlb#L25) [TL-B schema][tlb] 所述,Bag of Cells(简称 _BoC_)是一种将单元格序列化和去序列化为字节数组的格式。 + +在 TON Docs 中阅读有关 BoC 的更多信息:[细胞袋](https://docs.ton.org/develop/data-formats/cell-boc#bag-of-cells)。 + +:::note + + 关于[`单元格{:tact}`](#cells)序列化的高级信息:[Canonical `Cell{:tact}` 序列化](https://docs.ton.org/develop/research-and-development/boc)。 + +::: + +### Immutability {#cells-immutability} + +单元格是只读和不可变的,但 [TVM][tvm] 中有两组主要的 [ordinary](#cells-kinds) 单元格操作指令: + +- 单元格创建(或序列化)指令,用于根据先前保存的值和单元格构建新单元格; +- 单元格解析(或反序列化)指令,用于提取或加载之前通过序列化指令存储到单元格中的数据。 + +此外,[exotic](#cells-kinds)单元格有专门的指令来创建它们并预期它们的值。不过,[普通](#cells-kinds) 单元格解析指令仍可用于[奇异](#cells-kinds) 单元格,在这种情况下,它们会在反序列化尝试中被自动替换为[普通](#cells-kinds) 单元格。 + +所有单元操作指令都需要将 [`Cell{:tact}`](#cells) 类型的值转换为 [`Builder{:tact}`](#builders)或 [`Slice{:tact}`](#slices)类型,然后才能修改或检查这些单元。 + +## Builders + +`Builder{:tact}` 是一种用于使用单元格创建指令的单元格操作[基元][p]。 它们就像单元格一样不可改变,可以用以前保存的值和单元格构建新的单元格。 与单元格不同,`Builder{:tact}`类型的值只出现在[TVM][tvm]堆栈中,不能存储在持久存储区中。 举例来说,这意味着类型为 `Builder{:tact}`的持久存储字段实际上是以单元格的形式存储的。 + +`Builder{:tact}` 类型表示部分组成的单元格,为其定义了追加整数、其他单元格、引用其他单元格等快速操作: + +- [核心库中的 `Builder.storeUint(){:tact}`][b-2] +- [核心库中的 `Builder.storeInt(){:tact}`][b-3] +- [核心库中的 `Builder.storeBool(){:tact}`][b-4] +- [核心库中的 `Builder.storeSlice(){:tact}`][b-5] +- [核心库中的 `Builder.storeCoins(){:tact}`][b-6] +- [核心库中的 `Builder.storeAddress(){:tact}`][b-7] +- [核心库中的 `Builder.storeRef(){:tact}`][b-8] + +虽然您可以使用它们来[手动构建](#cnp-manually)单元格,但强烈建议使用[结构体][structs]:[使用结构体构建单元格](#cnp-structs)。 + +## Slices + +`Slice{:tact}` 是使用单元格解析指令的单元格操作[基元][p]。 与单元格不同,它们是可变的,可以通过序列化指令提取或加载之前存储在单元格中的数据。 此外,与单元格不同,`Slice{:tact}`类型的值只出现在[TVM][tvm]堆栈中,不能存储在持久存储区中。 举例来说,这就意味着类型为 `Slice{:tact}`的持久存储字段实际上是以单元格的形式存储的。 + +`Slice{:tact}` 类型表示部分解析单元格的剩余部分,或位于此类单元格内并通过解析指令从中提取的值(子单元格): + +- [核心库中的`Slice.loadUint(){:tact}`][s-2] +- [核心库中的`Slice.loadInt(){:tact}`][s-3] +- [核心库中的`Slice.loadBool(){:tact}`][s-4] +- [核心库中的`Slice.loadBits(){:tact}`][s-5] +- [核心库中的`Slice.loadCoins(){:tact}`][s-6] +- [核心库中的`Slice.loadAddress(){:tact}`][s-7] +- [核心库中的`Slice.loadRef(){:tact}`][s-8] + +虽然您可以将它们用于单元格的 [手动解析](#cnp-manually),但强烈建议使用 [结构体][structs]:[使用结构体解析单元格](#cnp-structs)。 + +## Serialization types + +与 [`Int{:tact}`](/book/integers)类型的序列化选项类似,`Cell{:tact}`、`Builder{:tact}` 和`Slice{:tact}` 在以下情况下也有不同的值编码方式: + +- 作为 [contracts](/book/contracts) 和 [traits](/book/types#traits) 的 [storage variables](/book/contracts#variables) 、 +- 以及 [Structs](/book/structs and-messages#structs) 和 [Messages](/book/structs and-messages#messages) 的字段。 + +```tact {2-3} +contract SerializationExample { + someCell: Cell as remaining; + someSlice:Slice as bytes32; + + // 构造函数, + // 本示例合同编译所必需的 + init() { + self.someCell = emptyCell(); + self.someSlice = beginCell().storeUint(42, 256).asSlice(); + } +} +``` + +### `remaining` {#serialization-remaining} + +`remaining{:tact}` 序列化选项可应用于 [`Cell{:tact}`](#cells)、[`Builder{:tact}`](#builders)和 [`Slice{:tact}`](#slices)类型的值。 + +它通过直接存储和加载单元格值而不是作为引用来影响单元格值的构建和解析过程。 与 [单元操作指令](#cells-immutability) 相似,指定 `remaining{:tact}` 就像使用 [`Builder.storeSlice(){:tact}`][b-5] 和 [`Slice.loadBits(){:tact}`][s-5] 而不是 [`Builder.storeRef(){:tact}`][b-8] 和 [`Slice.loadRef(){:tact}`][s-8],后者是默认使用的。 + +此外,Tact 产生的 [TL-B][tlb] 表示也会发生变化: + +```tact {3-5, 8-10} +contract SerializationExample { + // 默认情况下 + cRef: Cell; // ^cell in TL-B + bRef: Builder; // ^builder in TL-B + sRef: Slice; // ^slice in TL-B + + // With `remaining` + cRem:Cell as remaining; // remainder in TL-B + bRem:Builder as remaining; // remainder in TL-B + sRem:Slice as remaining; // remainder in TL-B + + // 构造函数, + // 本示例合同编译所必需 + init() { + self.cRef = emptyCell(); + self.bRef = beginCell(); + self.sRef = emptySlice(); + self.cRem = emptyCell(); + self.bRem = beginCell(); + self.sRem = emptySlice(); + } +} +``` + +其中,[TL-B][tlb] 语法中的 `^cell`、`^builder` 和 `^slice` 分别表示对 [`cell{:tact}`](#cells)、[`builder{:tact}`](#builders)和 [`slice{:tact}`](#slices)值的引用、而 `cell`、`builder` 或 `slice` 的 `remainder<…>` 则表示给定值将直接存储为 `Slice{:tact}`,而不是作为引用。 + +现在,举一个真实世界的例子,想象一下你需要注意到智能合约中的入站 [jetton][jetton] 传输并做出反应。 相应的 [信息][消息] 结构如下: + +```tact /remaining/ +message(0x7362d09c) JettonTransferNotification { + queryId:Int as uint64; // 任意请求编号,以防止重放攻击 + amount:Int as coins; // 传输的捷通数量 + sender:地址; // 净币发送方的地址 + forwardPayload:Slice as remaining; // 可选自定义有效载荷 +} +``` + +合同中的 [receiver][recv] 应该是这样的: + +```tact +receive(msg: JettonTransferNotification) { + // ... you do you ... +} +``` + +收到 [jetton][jetton] 传输通知消息后,其单元体会被转换为 [`Slice{:tact}`](#slices),然后解析为 `JettonTransferNotification{:tact}` [消息][消息]。在此过程结束时,`forwardPayload` 将包含原始信息单元的所有剩余数据。 + +在这里,将 `forwardPayload: Slice as remaining` 字段放在 `JettonTransferNotification{:tact}` [消息][消息]中的任何其他位置都不会违反 [jetton][jetton] 标准。这是因为 Tact 禁止在 [Structs][结构] 和 [Messages][消息] 的最后一个字段之外的任何字段中使用 `as remaining{:tact}`,以防止滥用合约存储空间并减少 gas 消耗。 + +:::note + + 注意,通过 `as remaining{:tact}` 序列化的单元格不能是 [可选](/book/optionals)。 也就是说,指定类似 `Cell? as remaining{:tact}`, `Builder? 作为剩余{:tact}` 或 `切片? 剩余的{:tact}` 会导致编译错误。 + + 另外请注意,将 `Cell{:tact}` 指定为[map](/book/maps) 值类型的 `remaining{:tact}` 会被视为错误,无法编译。 + +::: + +### `bytes32` {#serialization-bytes32} + +:::note + + 由 [#94](https://github.com/tact-lang/tact-docs/issues/94) 解决。 + +::: + +### `bytes64` {#serialization-bytes64} + +:::note + + 由 [#94](https://github.com/tact-lang/tact-docs/issues/94) 解决。 + +::: + +## Operations + +### Construct and parse {#operations-cnp} + +在 Tact 中,至少有两种构建和解析单元格的方法: + +- [手动](#cnp-manually),其中涉及积极使用[`Builder{:tact}`](#builders)、[`Slice{:tact}`](#slices)和[相关方法](/ref/core-cells)。 +- [使用结构体](#cnp-structs),这是一种值得推荐且更加方便的方法。 + +#### Manually {#cnp-manually} + +| 通过 `Builder{:tact}`进行建造 | 通过 `切片{:tact}` 进行解析 | +| :------------------------------------- | :------------------------------------ | +| [`beginCell(){:tact}`][b-1] | [`Cell.beginParse(){:tact}`][s-1]。 | +| [`.storeUint(42, 7){:tact}`][b-2] | [`Slice.loadUint(7){:tact}`][s-2] | +| [`.storeInt(42, 7){:tact}`][b-3] | [`Slice.loadInt(7){:tact}`][s-3] | +| [`.storeBool(true){:tact}`][b-4] | [`Slice.loadBool(true){:tact}`][s-4] | +| [`.storeSlice(slice){:tact}`][b-5] | [`Slice.loadBits(slice){:tact}`][s-5] | +| [`.storeCoins(42){:tact}`][b-6] | [`Slice.loadCoins(42){:tact}`][s-6] | +| [`.storeAddress(address){:tact}`][b-7] | [`Slice.loadAddress(){:tact}`][s-7] | +| [`.storeRef(cell){:tact}`][b-8] | [`Slice.loadRef(){:tact}`][s-8] | +| [`.endCell(){:tact}`][b-9] | [`Slice.endParse(){:tact}`][s-9] | + +[b-1]: /ref/core-cells#begincell +[b-2]: /ref/core-cells#builderstoreuint +[b-3]: /ref/core-cells#builderstoreint +[b-4]: /ref/core-cells#builderstorebool +[b-5]: /ref/core-cells#builderstoreslice +[b-6]: /ref/core-cells#builderstorecoins +[b-7]: /ref/core-cells#builderstoreaddress +[b-8]: /ref/core-cells#builderstoreref +[b-9]: /ref/core-cells#builderendcell +[s-1]: /ref/core-cells#cellbeginparse +[s-2]: /ref/core-cells#sliceloaduint +[s-3]: /ref/core-cells#sliceloadint +[s-4]: /ref/core-cells#sliceloadbool +[s-5]: /ref/core-cells#sliceloadbits +[s-6]: /ref/core-cells#sliceloadcoins +[s-7]: /ref/core-cells#sliceloadaddress +[s-8]: /ref/core-cells#sliceloadref +[s-9]: /ref/core-cells#sliceendparse + +#### Using Structs {#cnp-structs} + +[结构][struct]和[消息][messages]几乎就是活生生的[TL-B 模式][tlb]。 也就是说,它们本质上是用可维护、可验证和用户友好的 Tact 代码表达的[TL-B 模式][tlb]。 + +强烈建议使用它们及其 [方法](/book/functions#extension-function),如 [`Struct.toCell(){:tact}`][st-tc]和 [`Struct.fromCell(){:tact}`][st-fc],而不是手动构造和解析单元格,因为这样可以得到更多声明性和不言自明的合约。 + +[上文](#cnp-manually)的手动解析示例可以使用[Structs][struct]重新编写,如果愿意,还可以使用字段的描述性名称: + +```tact /fromCell/ /toCell/ +// First Struct +struct Showcase { + id: Int as uint8; + someImportantNumber: Int as int8; + isThatCool:Bool; + payload:Slice; + nanoToncoins:Int as coins; + wackyTacky: Address; + jojoRef: Adventure; // another Struct +} + +// Here it is +struct Adventure { + bizarre: Bool = true; + time:Bool = false; +} + +fun example() { + // Basics + let s = Showcase.fromCell( + Showcase{ + id: 7, + someImportantNumber: 42, + isThatCool: true, + payload: emptySlice(), + nanoToncoins: 1330 + 7, + wackyTacky: myAddress(), + jojoRef: Adventure{ bizarre: true, time: false }, + }.toCell()); + s.isThatCool; // true +} +``` + +请注意,Tact 的自动布局算法是贪婪的。例如,`struct Adventure{:tact}` 占用的空间很小,它不会以引用 [`Cell{:tact}`](#cells) 的形式存储,而是直接以 [`Slice{:tact}`](#slices) 的形式提供。 + +通过使用 [结构][struct] 和 [消息][messages],而不是手动 [`Cell{:tact}`](#cells) 组成和解析,这些细节将被简化,在优化布局发生变化时也不会造成任何麻烦。 + +:::note[Useful links:] + + [Convert serialization](/book/func#convert-serialization) + [`Struct.toCell(){:tact}` 在核心库中][st-tc] + [`Struct.fromCell(){:tact}` 在核心库中][st-fc] + [`Struct.fromSlice(){:tact}` 在核心库中][st-fs] + [`Message.toCell(){:tact}` 在核心库中][msg-tc] + [`Message.fromCell(){:tact}` 在核心库中][msg-fc] + [`Message.fromSlice(){:tact}` 在核心库中][msg-fs] + +::: + +[st-tc]: /ref/core-cells#structtocell +[st-fc]: /ref/core-cells#structfromcell +[st-fs]: /ref/core-cells#structfromslice +[msg-tc]: /ref/core-cells#messagetocell +[msg-fc]: /ref/core-cells#messagefromcell +[msg-fs]: /ref/core-cells#messagefromslice + +### Check if empty {#operations-empty} + +[`Cell{:tact}`](#cells)和[`Builder{:tact}`](#builders)都不能直接检查空性,需要先将它们转换为[`Slice{:tact}`](#slices)。 + +要检查是否有任何位,请使用[`Slice.dataEmpty(){:tact}`][s-de]。要检查是否存在引用,请使用[`Slice.refsEmpty(){:tact}`][s-re]。要同时检查这两项,请使用[`Slice.empty(){:tact}`][s-e]。 + +如果[`Slice{:tact}`](#slices)不完全为空,也要抛出[exit code 9](/book/exit-codes#9),请使用[`Slice.endParse(){:tact}`][s-ep]。 + +```tact +// 准备工作 +let someCell = beginCell().storeUint(42, 7).endCell(); +let someBuilder = beginCell().storeRef(someCell); + +// 获取切片 +let slice1 = someCell.asSlice(); +let slice2 = someBuilder.asSlice(); + +// .dataEmpty() +slice1.dataEmpty(); // false +slice2.dataEmpty(); // true + +// .refsEmpty() +slice1.refsEmpty(); // true +slice2.refsEmpty(); // false + +// .empty() +slice1.empty(); // false +slice2.empty(); // false + +// .endParse() +try { + slice1.endParse(); + slice2.endParse(); +} catch (e) { + e; // 9 +} +``` + +:::note[Useful links:] + + [`Cell.asSlice(){:tact}` 在核心库中](/ref/core-cells#cellasslice)\ + [`Builder.asSlice(){:tact}` 在核心库中](/ref/core-cells#builderasslice)\ + [`Slice.dataEmpty(){:tact}` 在核心库中][s-de]\ + [`Slice.refsEmpty(){:tact}` 在核心库中][s-re]\ + [`Slice.empty(){:tact}` 在核心库中][s-e]\ + [`Slice.endParse(){:tact}` 在核心库中][s-ep] + +::: + +[咝--咝]: /ref/core-cells#slicedataempty +[re]: /ref/core-cells#slicerefsempty +[s-e]: /ref/core-cells#sliceempty +[s-ep]: /ref/core-cells#sliceendparse + +### Check if equal {#operations-equal} + +不能使用二进制相等 [`=={:tact}`][bin-eq] 或不等式 [`!={:tact}`][bin-eq] 操作符直接比较 [`Builder{:tact}`](#builders) 类型的值。但是,[`Cell{:tact}`](#cells) 和 [`Slice{:tact}`](#slices) 类型的值可以。 + +直接比较: + +```tact +让 a = beginCell().storeUint(123, 8).endCell(); +让 aSlice = a.asSlice(); + +让 b = beginCell().storeUint(123, 8).endCell(); +让 bSlice = b. asSlice(); 让 areCellsEqual = a == b; // true 让 areCellsNotEqual = a !asSlice(); + +let areCellsEqual = a == b; // true +let areCellsNotEqual = a != b; // false + +let areSlicesEqual = aSlice == bSlice; // true +let areSlicesNotEqual = aSlice != bSlice; // false +``` + +请注意,通过 `=={:tact}` 或 `!={:tact}` 操作符进行的直接比较隐含地使用了[标准 `Cell{:tact}` 表示法](#cells-representation)的 [SHA-256](https://en.wikipedia.org/wiki/SHA-2#Hash_standard) 哈希值。 + +还可使用 `.hash(){:tact}` 进行显式比较: + +```tact +let a = beginCell().storeUint(123, 8).endCell(); +let aSlice = a.asSlice(); + +let b = beginCell().storeUint(123, 8).endCell(); +let bSlice = b.asSlice(); + +let areCellsEqual = a.hash() == b.hash(); // true let areCellsNotEqual = a.hash() != b.hash(); // false let areSliceEqual = aSlice.asSlice(); // truehash(); // true +let areCellsNotEqual = a.hash() != b.hash(); // false + +let areSlicesEqual = aSlice.hash() == bSlice.hash(); // true +let areSlicesNotEqual = aSlice.hash() != bSlice.hash(); // false +``` + +:::note[Useful links:] + + [核心库中的 `Cell.hash(){:tact}`](/ref/core-cells#cellhash)/ + [核心库中的 `Slice.hash(){:tact}`](/ref/core-cells#slicehash)/ + [`=={:tact}`和`!={:tact}`][bin-eq]。 + +::: + +[p]: /book/types#primitive-types +[struct]: /book/structs-and-messages#structs +[message]: /book/structs-and-messages#messages +[recv]: /book/contracts#receiver-functions + +[tvm]: https://docs.ton.org/learn/tvm-instructions/tvm-overview +[tlb]: https://docs.ton.org/develop/data-formats/tl-b-language +[jetton]: https://docs.ton.org/develop/dapps/asset-processing/jettons +[sha-2]: https://en.wikipedia.org/wiki/SHA-2#Hash_standard + +[quadtree]: https://en.wikipedia.org/wiki/Quadtree +[bin-eq]: /book/operators#binary-equality diff --git a/docs/src/content/docs/zh-cn/book/config.mdx b/docs/src/content/docs/zh-cn/book/config.mdx new file mode 100644 index 000000000..f12a4bacf --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/config.mdx @@ -0,0 +1,414 @@ +--- +title: 配置 +--- + +`tact.config.json` 是 Tact 项目的入口点。它是一个 JSON 文件,包含所有项目和编译器参数的列表。 + +本页列出了 [模式](#schema)中的所有配置选项。 请查看右侧的目录,以方便浏览。 + +:::note + + 对该文件的唯一要求是它是一个有效的 JSON 文件,包含 [适当的字段](#schema),因此可以任意命名。 不过,将配置文件命名为 `tact.config.json` 是所有使用 Tact 的工具都鼓励和支持的通用约定。 + +::: + +## `$schema` {#schema} + +编辑器可使用[JSON 模式](https://json-schema.org/) 文件提供自动完成和悬停提示:[configSchema.json](http://raw.githubusercontent.com/tact-lang/tact/main/schemas/configSchema.json)。 + +只需在配置文件顶部添加 `$schema` 字段即可: + +```json filename="tact.config.json" {2} +{ + "$schema": "http://raw.githubusercontent.com/tact-lang/tact/main/schemas/configSchema.json", + "projects":[] +} +``` + +## 项目 {#projects} + +带有相应编译选项的 Tact 项目列表。 每个 `.tact` 文件都代表自己的 Tact 项目。 + +```json filename="tact.config.json" {3,4} +{ + "projects":[ + { }, + { } + ] +} +``` + +### `name` {#projects-name} + +`name` 是项目名称。所有生成的文件都以此为前缀。 + +在 [Blueprint][bp] 中,"name "指合同本身的名称。 + +```json filename="tact.config.json" {4,7} +{ + "projects":[ + { + "name":"some_prefix" + }, + { + "name":"ContractUnderBlueprint" + } + ] +} +``` + +### `path` {#projects-path} + +项目 Tact 文件的路径。 每个项目只能指定一个 Tact 文件。 + +在 [Blueprint][bp] 中,`path` 被 `wrappers/ContractName.compile.ts` 中的 `target` 字段取代。 + +```json filename="tact.config.json" {5} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact" + } + ] +} +``` + +### `output` {#projects-output} + +`output` 是放置所有生成文件的目录路径。 + +在 [蓝图][bp] 中,不使用 `output` ,所有生成的文件始终放在 `build/ProjectName/` 中。 + +```json filename="tact.config.json" {6} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact", + "output": "./contract_output" + } + ] +} +``` + +### `options` {#projects-options} + +项目的编译选项。 + +在 [Blueprint][bp] 中,除非在`wrappers/ContractName.compile.ts`中进行修改,否则它们将作为默认设置运行。 + +```json filename="tact.config.json" {7,11} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact", + "output": "./contract_output", + "options": {} + }, + { + "name": "ContractUnderBlueprint", + "options": {} + } + ] +} +``` + +#### `debug` {#options-debug} + +默认为 `false{:json}`。 + +`debug: true`. 启用合约的调试输出。这对调试非常有用。启用此合约将报告它是在调试模式下使用 `supported_interfaces` 方法编译的。 + +```json filename="tact.config.json" {8,14} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact", + "output": "./contract_output", + "options": { + "debug": true + } + }, + { + "name": "ContractUnderBlueprint", + "options": { + "debug": true + } + } + ] +} +``` + +:::note + + 更多信息,请访问专用页面:[调试](/book/debug)。 + +::: + +#### `masterchain` {#options-masterchain} + +默认为 `false{:json}`。 + +如果设置为 `true{:json}`,则启用 [masterchain](/book/masterchain) 支持。 + +```json filename="tact.config.json" {8,14} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact", + "output": "./contract_output", + "options": { + "masterchain": true + } + }, + { + "name": "ContractUnderBlueprint", + "options": { + "masterchain": true + } + } + ] +} +``` + +:::note + + 更多信息,请访问专用页面:[大师链](/book/masterchain)。 + +::: + +#### `external` {#options-external} + +默认为 `false{:json}`。 + +如果设置为 `true{:json}`,则启用对 [external](/book/external) 消息接收器的支持。 + +```json filename="tact.config.json" {8,14} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact", + "output": "./contract_output", + "options": { + "external": true + } + }, + { + "name": "ContractUnderBlueprint", + "options": { + "external": true + } + } + ] +} +``` + +:::note + + 更多信息,请访问专用页面:[外部信息](/book/external)。 + +::: + +#### `ipfsAbiGetter` {#options-ipfsabigetter} + +默认为 `false{:json}`。 + +如果设置为 `true{:json}`,则可生成带有描述合同 ABI 的 IPFS 链接的[getter](/book/contracts#getter-functions)。 + +```json filename="tact.config.json" {8,14} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact", + "output": "./contract_output", + "options": { + "ipfsAbiGetter": true + } + }, + { + "name": "ContractUnderBlueprint", + "options": { + "ipfsAbiGetter": true + } + } + ] +} +``` + +:::note + + 在专用网页上阅读更多信息:[OTP-003:自我 ABI 报告](/ref/evolution/otp-003)。 + +::: + +#### `interfacesGetter` {#options-interfacesgetter} + +默认为 `false{:json}`。 + +如果设置为 `true{:json}`,则可生成包含合约所提供接口列表的 [getter](/book/contracts#getter-functions)。 + +```json filename="tact.config.json" {8,14} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact", + "output": "./contract_output", + "options": { + "interfacesGetter": true + } + }, + { + "name": "ContractUnderBlueprint", + "options": { + "interfacesGetter": true + } + } + ] +} +``` + +:::note + + 了解更多信息:[支持的接口](/book/contracts#interfaces)。 + +::: + +#### Experimental + +将来可能会取消的试验性选项。 谨慎使用! + +```json filename="tact.config.json" {8,14} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact", + "output": "./contract_output", + "options": { + "experimental": {} + } + }, + { + "name": "ContractUnderBlueprint", + "options": { + "experimental": {} + } + } + ] +} +``` + +##### `inline` {#experimental-inline} + +默认为 `false{:json}`。 + +`inline: true`. 启用合同中所有函数的内联。这可以减少gas的使用,但代价是需要更大的合约。 + +```json filename="tact.config.json" {9,17} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact", + "output": "./contract_output", + "options": { + "experimental": { + "inline": true + } + } + }, + { + "name": "ContractUnderBlueprint", + "options": { + "experimental": { + "inline": true + } + } + } + ] +} +``` + +### `mode` {#projects-mode} + +项目的编译模式。 有效值为 + +Value | Description +:------------------------------- | :---------- +`"full"{:json}` | (默认) 运行整个编译流水线,并生成 FunC 代码、BoC 和各种实用程序文件,包括 TypeScript 的封装器。 +`"fullWithDecompilation"{:json}` | 运行整个编译管道,如 `“full”{:json}`,并以 BoC 格式反编译生成的二进制代码。 +`"funcOnly"{:json}` | 只输出中间 FunC 代码,阻止进一步编译。 +`"checkOnly"{:json}` | 仅执行语法和类型检查,阻止进一步编译。 + +在 [Blueprint][bp] 中,"mode "始终设置为`"full"{:json}`,且不可覆盖。 + +```json filename="tact.config.json" {7,13} +{ + "projects": [ + { + "name": "some_prefix", + "path": "./contract.tact", + "output": "./contract_output", + "mode": "full" + }, + { + "name": "func_only", + "path": "./contract.tact", + "output": "./contract_output", + "mode": "funcOnly" + } + ] +} +``` + +## Full example + +```json filename="tact.config.json" copy=false +{ + "$schema": "http://raw.githubusercontent.com/tact-lang/tact/main/schemas/configSchema.json", + "projects": [ + { + "name": "basic", + "path": "./basic.tact", + "output": "./basic_output", + "mode": "full" + }, + { + "name": "func_only", + "path": "./basic.tact", + "output": "./basic_output", + "mode": "funcOnly" + }, + { + "name": "debugPrefix", + "path": "./contracts/contract.tact", + "output": "./contracts/output", + "options": { + "debug": true + } + }, + { + "name": "ContractUnderBlueprint", + "options": { + "debug": false, + "masterchain": false, + "external": false, + "ipfsAbiGetter": true, + "interfacesGetter": true, + "experimental": { + "inline": false + } + } + } + ] +} +``` + +[bp]: https://github.com/ton-org/blueprint diff --git a/docs/src/content/docs/zh-cn/book/constants.mdx b/docs/src/content/docs/zh-cn/book/constants.mdx new file mode 100644 index 000000000..0a378befb --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/constants.mdx @@ -0,0 +1,69 @@ +--- +title: 常数 +--- + +Tact 中的常量可以比流行语言中的常量更先进一些:它们可以是虚拟的、抽象的。 智能合约通常需要实现多个特征,有时您需要在编译时配置其中的一些特征。 禁止使用特质中的构造函数,因为它们的行为是无法预测的。 因此,我们必须使用常量和字段来传递值。 主合同的职责是为所有特性实现值和常量。 + +## 简单常数 + +让我们从一个简单的常数开始。 这是一个在编译时定义的值,不能更改。 您可以在顶层或合约/特质中定义常量。 让我们在顶层定义一个常量: + +```tact +const MY_CONSTANT: Int = 42; +``` + +特征和合同也类似: + +```tact +trait MyTrait { + const MY_CONSTANT: Int = 42; +} + +contract MyContract { + const MY_CONSTANT: Int = 42; +} +``` + +## 虚拟常量和抽象常量 + +虚拟常量是可以在特质中定义但在合约中改变的常量。 当您需要在编译时配置某些特征时,它非常有用。 让我们定义一个虚拟常量和一个抽象常量: + +```tact +trait MyTrait { + virtual const MY_FEE: Int = ton("1.0"); +} + +trait MyAbstractTrait { + abstract const MY_DEVFEE: Int; +} +``` + +现在,您可以覆盖合同中的默认设置: + +```tact +contract MyContract with MyTrait, MyAbstractTrait { + override const MY_FEE: Int = ton("0.5"); + override const MY_DEVFEE: Int = ton("1000"); +} +``` + +这对于帮助编译器在编译时获得某些值可能非常有用,例如,您可以启用或禁用功能,而无需修改代码,也不会浪费 gas 。 + +```tact +trait Treasure { + virtual const ENABLE_TIMELOCK: Bool = true; + + receive("Execute") { + if (self.ENABLE_TIMELOCK) { + // + // This branch would be removed in compile time if ENABLE_TIMELOCK is false + // + } + } +} + +contract MyContract with Treasure { + override const ENABLE_TIMELOCK: Bool = false; +} + +``` diff --git a/docs/src/content/docs/zh-cn/book/contracts.mdx b/docs/src/content/docs/zh-cn/book/contracts.mdx new file mode 100644 index 000000000..f9692c109 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/contracts.mdx @@ -0,0 +1,347 @@ +--- +title: 合同 +--- + +Tact 中的合约类似于流行的面向对象语言中的类,只是它们的实例部署在区块链上,不能像 [Structs and Messages](/book/structs-and-messages) 那样被传递。 + +## Self-references {#self} + +契约和[特质][trait]有一个内置的[标识符](/book/expressions#identifiers) `self{:tact}`,用于引用它们的字段(持久状态[变量](#variables)和[常量](#constants))和方法([内部函数](#internal-functions)): + +```tact +contract Example { + // 持久状态变量 + foo:Int; + + init() { + self.foo = 42; // <- 通过 self 引用变量 foo。 + } +} +``` + +## Structure + +每份合同可包括: + +* [Inherited traits](#traits) +* [Supported interfaces](#interfaces) +* [Persistent state variables](#variables) +* [Constructor function `init(){:tact}`](#init-function) +* [Contract constants](#constants) +* [Getter functions](#getter-functions) +* [Receiver functions](#receiver-functions) +* [Internal functions](#internal-functions) + +### Inherited traits, `with{:tact}` {#traits} + +契约可以继承[traits][trait]的所有声明和定义,并覆盖它们的某些默认行为。除此之外,每个契约和特质都隐式继承了特殊的[`BaseTrait{:tact}` trait](/ref/core-base)。 + +要继承[trait][trait],请在合约声明中的关键字`with{:tact}`后指定其名称。要同时继承多个特质,请在逗号分隔的列表中指定它们的名称,并在后面加上逗号。 + +```tact /with/ +trait InheritMe {} +trait InheritMeToo {} + +// 继承单个特质的合约 +合约 Single with InheritMe {} + +// 继承多个特质的合约 +合约 Plural with + InheritMe, + InheritMeToo, // 允许使用逗号结尾 +{} +``` + +由于[traits][trait]不允许有[`init(){:tact}`函数](#init-function),因此继承了声明了任何[持久状态变量](#variables)的trait的合约必须通过提供自己的[`init(){:tact}`函数](#init-function)来初始化这些变量。 + +```tact +trait Supe { omelander: Bool } + +contract Vot with Supe { + init() { + self.omelander = true; + } +} +``` + +如果在特质中声明或定义了内部函数和常量,则可将其标记为[虚拟或抽象](/book/functions#virtual-and-abstract-functions),并在从特质继承的合约中重写。 + +### Supported interfaces, `@interface(…)` {#interfaces} + +如果不查看源代码,就很难弄清一个合约是做什么的,有哪些[接收器](#receiver-functions)和[获取器](#getter-functions)。有时,无法获得或无法访问源代码,剩下的办法就是尝试拆解合同,并以这种方式对其进行反省,这是一种非常混乱且容易出错的方法,其收益会递减,而且没有真正的可重复性。 + +为了解决这个问题,创建了[OTP-001:支持的接口](/ref/evolution/otp-001)。据此,Tact 合约[可以报告](/book/config#options-interfacesgetter)支持的接口列表,作为特殊的`supported_interfaces` [getter](#getter-functions)的返回值。使用任何 TON 区块链浏览器都可以在链外访问该获取器--只需指定"supported_interfaces"作为要执行的方法,即可获得十六进制值列表。 + +这些十六进制值被截断为受支持接口的原始[`String{:tact}`][p]值的[SHA-256](https://en.wikipedia.org/wiki/SHA-2#Hash_standard)哈希值的前 128 位。该列表中的第一个值**必须**等于$\mathrm{0x5cec3d5d2cae7b1e84ec39d64a851b66}$([十六进制符号](/book/integers#hexadecimal)),即`"org.ton.introspection.v0"{:tact}`的 SHA-256 哈希值的前半部分。如果第一个值是错误的,就必须停止反省合同,因为它不符合[支持的接口](/ref/evolution/otp-001)建议。 + +要声明支持某个接口,可在 contract 和[trait][trait]声明之前添加一个或多个`@interface("…"){:tact}`属性: + +```tact +@interface("His name is") +@interface("John") +contract SeeNah with Misc { + // ... +} + +@interface("name_of_your_org - miscellaneous") +trait Misc { + // ... +} +``` + +Tact 有一小套在特定条件下提供的接口: + +- `"org.ton.abi.ipfs.v0"{:tact}`,根据 [OTP-003: Self-ABI Reporting](/ref/evolution/otp-003) - 通过 [`ipfsAbiGetter`](/book/config#options-ipfsabigetter)配置属性选择加入 +- `"org.ton.deploy.lazy.v0"{:tact}`,符合[OTP-005:参数可寻址合约](/ref/evolution/otp-005) +- `"org.ton.debug.v0"{:tact}`,但只有在启用了[调试模式](/book/debug#debug-mode)时才会这样做 +- `"org.ton.chain.any.v0"{:tact}` 如果启用了 [masterchain](/book/masterchain) 支持,否则为 `"org.ton.chain.workchain.v0"{:tact}` + +[标准库](/ref/standard-libraries)中的一些[traits][trait]也定义了它们的接口: + +- [`Ownable{:tact}`](/ref/stdlib-ownable#ownable) trait 指定`"org.ton.ownable"{:tact}` +- [`OwnableTransferable{:tact}`](/ref/stdlib-ownable#ownabletransferable) trait 指定`"org.ton.ownable.transferable.v2"{:tact}` +- [`Stoppable{:tact}`](/ref/stdlib-stoppable#stoppable) trait 指定`"org.ton.stoppable"{:tact}` +- [`Resumable{:tact}`](/ref/stdlib-stoppable#resumable) trait 指定`"org.ton.resumable"{:tact}` + +要启用 `supported_interfaces` [getter](#getter-functions) 生成并在 Tact 合约中使用 `@interface(){:tact}` 属性,请修改项目根目录下的 [`tact.config.json`](/book/config) 文件(如果该文件不存在,则创建该文件),并 [将 `interfacesGetter` 属性设置为 `true{:json}`](/book/config#options-interfacesgetter)。 + +如果您的项目基于 [Blueprint][bp],您可以在合约的编译配置中启用`supported_interfaces`,这些配置位于名为`wrappers/`的目录中: + +```typescript title="wrappers/YourContractName.compile.ts" {7} +import { CompilerConfig } from '@ton/blueprint'; + +export const compile:CompilerConfig = { + lang: 'tact', + target: 'contracts/your_contract_name.tact', + options:{ + interfacesGetter: true, // ← that's the stuff! + } +}; +``` + +除此之外,[蓝图][bp] 项目中仍可使用 [`tact.config.json`](/book/config)。 在这种情况下,除非在 `wrappers/` 中进行修改,否则 [`tact.config.json`](/book/config)中指定的值将作为默认值。 + +:::caution + 请注意,添加接口并不能保证合约实际实现任何特定功能,也不能保证以任何特定方式实现这些功能。 这只是一种可验证的链外承诺,即合同中可能包含某些特定代码。 您应该相信但要核实这些说法。 + + 此外,不同接口之间也不能保证不会发生名称冲突,尽管这种情况不太可能发生,因为即使是 SHA-256 的前 128 位也能提供足够的[抗碰撞性](https://en.wikipedia.org/wiki/Collision_resistance)。 +::: + +### Persistent state variables {#variables} + +合约可以定义在合约调用之间持续存在的状态变量。 TON 中的合约[支付租金](https://docs.ton.org/develop/smart-contracts/fees#storage-fee) 与它们消耗的持久空间成正比,因此鼓励[通过序列化进行紧凑表示](/book/integers#serialization)。 + +```tact +contract Example { + // 持久状态变量 + val: Int; // Int + val32: Int as uint32; // Int 序列化为 32 位无符号 + mapVal: map; // Int keys to Int values + optVal: Int?; // Int or null +} +``` + +状态变量必须有默认值或在 [`init(){:tact}`](#init-function) 函数中初始化,该函数在部署合约时运行。唯一的例外是 [`map{:tact}`](/book/maps) 类型的持久状态变量,因为它们默认初始化为空。 + +:::note + + 请注意,Tact 也支持非持续状态的局部变量,请参阅:[变量声明](/book/statements#let)。 + +::: + +### Contract constants {#constants} + +与 [变量](#variables) 不同,常量不能更改。 它们的值是在*编译时*计算的,在执行过程中不会改变。 + +在合约外定义的常量(全局常量)和在合约内定义的常量(合约常量)没有太大区别。 项目中的其他合同可以使用外部定义的常量。 + +常量初始化必须相对简单,并且只依赖于编译时已知的值。 例如,如果您将两个数字相加,编译器会在编译过程中计算出结果,并将结果放入已编译的代码中。 + +您可以在 [接收器](#receiver-functions) 和 [获取器](#getter-functions) 中读取常量。 + +与[合约变量](#variables)不同,**合约常量不会占用持久状态**的空间。 它们的值直接存储在合约的代码 [`单元格`](/book/cells#cells)中。 + +```tact +// 全局常量在编译时计算,不能更改 +const GlobalConst1:Int = 1000 + ton("42") + pow(10, 9); + +contract Example { + // 合同常量也是在编译时计算的,不能更改 + const ContractConst1:Int = 2000 + ton("43") + pow(10, 9); + + // 合同常量可以轻松替代枚举 + const StateUnpaid:Int = 0; + const StatePaid: Int = 1; + const StateDelivered:Int = 2; + const StateDisputed:Int = 3; + + get fun sum():Int { + // access constants from anywhere + return GlobalConst1 + self.ContractConst1 + self.StatePaid; + } +} +``` + +有关常量的更多信息,请访问其专门页面:[常量](/book/constants)。 + +### Constructor function `init()` {#init-function} + +在部署合约时,会运行构造函数 `init(){:tact}`。 + +如果合约有任何未指定默认值的[持久状态变量](#variables),则必须在此函数中对其进行初始化。 + +```tact +contract Example { + // 持久状态变量 + var1: Int = 0; // 以默认值 0 初始化 + var2: Int; // 必须在 init() 函数中初始化 + + // 构造函数 + init() { + self.var2 = 42; + } +} +``` + +如果合约没有任何持久状态变量,或所有持久状态变量都指定了默认值,则可以完全省略 `init(){:tact}` 函数声明。 这是因为除非明确声明,否则所有合约中都默认存在空的 `init(){:tact}` 函数。 + +下面是一个有效空合同的示例: + +```tact +合同 IamEmptyAndIKnowIt {} +``` + +为方便起见,`init(){:tact}` 的参数列表可以使用逗号: + +```tact +contract TheySeeMeTrailing { + init( + param1: Int, + param2: Int, // trailing comma is allowed + ){ + // ... + } +} +``` + +:::note + + 要获取 [内部函数](#internal-functions)、[接收器](#receiver-functions) 或 [getters](#getter-functions) 中目标合约的初始状态,请使用 [`initOf{:tact}`](/book/expressions#initof)表达式。 + +::: + +### Getter functions + +[获取函数](/book/functions#getter-functions) **不能从其他合约访问,只能导出到链外世界**。 + +此外,**获取器不能修改合约的状态变量**,只能读取它们的值并在表达式中使用。 + +```tact +contract HelloWorld { + foo:Int; + + init() { + self.foo = 0; + } + + // 返回类型为 Int 的 getter 函数 + get fun foo():Int { + return self.foo; // 这里不能更改 self.foo + } +} +``` + +请在其专门章节中阅读更多相关信息:[获取函数](/book/functions#getter-functions) + +### Receiver functions + +Tact 中的[接收器函数](/book/functions#receiver-functions)可以是以下三种之一: + +- [`receive(){:tact}`](/book/receive),用于接收内部消息(来自其他合约)。 +- [`bounced(){:tact}`](/book/bounced),当该合约发出的消息被退回时会调用。 +- [`external(){:tact}`](/book/external),没有发送者,世界上任何人都可以发送。 + +```tact +message CanBounce { + counter:Int; +} + +contract HelloWorld { + counter:Int; + + init() { + self.counter = 0; + } + + get fun counter():Int { + return self.counter; + } + + // 内部消息接收器,响应字符串消息 "increment" + receive("increment") { + self.counter += 1; + + // 发送消息回发送者 + send(SendParameters{ + to: sender(), + value: 0, + mode:SendRemainingValue | SendIgnoreErrors, + body:CanBounce{counter: self.counter}.toCell(), + }); + } + + // 被弹回的消息接收器,当消息弹回本合约时被调用 + bounced(src: bounced) { + self.counter = 0; // 重置计数器以防消息弹回 + } + + // 外部消息接收器,用于回复链外消息 "你好,是我" + external("hello, it's me") { + // 无法回复,因为没有发件人! + self.counter = 0; + } +} +``` + +用下划线`_{:tact}`命名接收函数的参数时,其值将被视为未使用的值并被丢弃。当您不需要检查接收到的信息,而只想让它传达特定的操作码时,这就很有用了: + +```tact +message(42) UniverseCalls {} + +contract Example { + receive(_: UniverseCalls) { + // Got a Message with opcode 42 + } +} +``` + +### Internal functions + +这些函数的行为类似于流行的面向对象语言中的私有方法——它们是合约的内部函数,可以通过在其前缀添加特殊的[标识符 `self{:tact}`](#self)来调用。因此,内部函数有时也被称为"契约方法"。 + +内部函数可以访问合约的[持久状态变量](#variables)和[常量](#constants)。 + +它们只能从[接收器](#receiver-functions)、[获取器](#getter-functions)和其他内部函数中调用,而不能从其他合约或[`init(){:tact}`](#init-function)中调用。 + +```tact +contract Functions { + val: Int = 0; + + // 该契约方法只能在该契约内部调用,并访问其变量 + fun onlyZeros() { + require(self.val == 0, "Only zero are permitted!"); + } + + // 接收函数,调用内部函数 onlyZeros + receive("only zeros") { + self.onlyZeros(); + } +} +``` +:::note + 请注意,Tact 还支持其他类型的函数,请参阅[函数](/book/functions)。 +::: + +[p]: /book/types#primitive-types +[特质]: /book/types#traits +[bp]: https://github.com/ton-org/blueprint diff --git a/docs/src/content/docs/zh-cn/book/cs/from-func.mdx b/docs/src/content/docs/zh-cn/book/cs/from-func.mdx new file mode 100644 index 000000000..2333c36da --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/cs/from-func.mdx @@ -0,0 +1,12 @@ +--- +title: 来自 FunC +description: "从 FunC 快速过渡到 Tact 的小窍门" +sidebar: + order: 1 +--- + +:::danger[Not implemented] + +此页面等待编写 [#54](https://github.com/tact-lang/tact-docs/issues/54) + +::: diff --git a/docs/src/content/docs/zh-cn/book/cs/from-solidity.mdx b/docs/src/content/docs/zh-cn/book/cs/from-solidity.mdx new file mode 100644 index 000000000..adabf0b1a --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/cs/from-solidity.mdx @@ -0,0 +1,12 @@ +--- +title: 来自 Solidity +description: "从 Solidity 快速过渡到 Tact 以及从以太坊快速过渡到 TON 区块链的小抄" +sidebar: + order: 2 +--- + +:::danger[Not implemented] + +此页面等待编写 [#67](https://github.com/tact-lang/tact-docs/issues/67) + +::: diff --git a/docs/src/content/docs/zh-cn/book/debug.mdx b/docs/src/content/docs/zh-cn/book/debug.mdx new file mode 100644 index 000000000..b53790668 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/debug.mdx @@ -0,0 +1,725 @@ +--- +title: 调试 Tact 合约 +--- + +import { LinkCard, CardGrid, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; + +作为智能合约开发人员,我们编写的代码并不总是能实现我们的预期。 有时,它做的事情完全不同! 当意外发生时,接下来的任务就是找出原因。 为此,有多种方法可以揭示代码中的问题或 "错误"。 让我们开始_调试_! + + + + + + + + + + + + + + + + +## 一般方法 {#approach} + +目前,Tact 还没有步进式调试器。 尽管如此,仍然可以使用["printf 调试"](https://en.wikipedia.org/wiki/Debugging#printf_debugging) 方法。 + +这包括在整个代码中主动调用 [`dump(){:tact}`][dump]和 [`dumpStack(){:tact}`](/ref/core-debug#dumpstack)函数,并观察特定时间点的变量状态。 请注意,这些函数只在 [调试模式](#debug-mode)下工作,否则不会执行。 + +:::note + + 请参阅如何使用 [`dump(){:tact}`][dump]进行调试:[使用 `dump() 调试{:tact}`](#tests-dump)。 + +::: + +除了转储值之外,使用 [`require(){:tact}`](/ref/core-debug#require)、[`nativeThrowIf(){:tact}`](/ref/core-debug#nativethrowif)和 [`nativeThrowUnless(){:tact}`](/ref/core-debug#nativethrowunless)等自信的函数通常也很有帮助。 它们有助于明确说明你的假设,并方便设置 "绊线",以便在将来发现问题。 + +如果您没有找到或无法解决您的问题,请尝试在 Tact 的[Telegram 聊天][tg]中询问社区;如果您的问题或疑问与 TON 的关系大于与 Tact 的关系,请进入[TON Dev Telegram 聊天](https://t.me/tondev_eng)。 + +## 常用调试功能 {#debug-functions} + +Tact 提供了大量对调试有用的各种函数:[核心库 → 调试](/ref/core-debug)。 + +## 在编译选项中启用调试模式 {#debug-mode} + +Using `@tact-lang/emulator` + +To make `dump` work you need to enable the feature `debug` in `tact.conf.json`. + +如果您正在处理基于 [Blueprint][bp] 的项目,可以在合约的编译配置中启用调试模式,这些配置位于名为 `wrappers/` 的目录中: + +```typescript title="wrappers/YourContractName.compile.ts" {7} +import { CompilerConfig } from '@ton/blueprint'; + +export const compile:CompilerConfig = { + lang: 'tact', + target: 'contracts/your_contract_name.tact', + options:{ + debug: true, // ← that's the stuff! + } +}; +``` + +请注意,从 0.20.0 开始的 [Blueprint][bp] 版本会自动为新合约启用 `wrappers/` 中的调试模式。 + +除此之外,[蓝图][bp] 项目中仍可使用 [`tact.config.json`](/book/config)。 在这种情况下,除非在 `wrappers/` 中修改,否则 [`tact.config.json`](/book/config)中指定的值将作为默认值。 + +:::note + +::: + +## 在蓝图中编写测试,使用 Sandbox 和 Jest {#tests} + +[蓝图][bp] 是一个流行的开发框架,用于在 TON 区块链上编写、测试和部署智能合约。 + +Ton Emulator allows you to have a small virtual blockchain in your Node.js code. This library is built specifically for testing smart contracts in unit tests. + +无论何时创建一个新的 [Blueprint][bp] 项目,或在现有项目中使用 "blueprint create "命令,都会创建一个新的合同以及测试套件文件。 + +这些文件被放在`tests/`文件夹中,并用[Jest][jest]执行。 默认情况下,除非指定特定组或测试关闭,否则所有测试都会运行。 有关其他选项,请参阅 Jest CLI 中的简要文档:`jest --help`。 + +### 测试文件的结构 {#tests-structure} + +假设我们有一份名为 `Playground` 的合同,写在 `contracts/playground.tact` 文件中。 如果我们通过 [Blueprint][bp] 创建了该合约,那么它也会为我们创建一个 `tests/Playground.spec.ts` 测试套件文件。 + +测试文件包含一个 `describe(){:typescript}` [Jest][jest] 函数调用,表示一个测试组。 + +在该组中,有三个变量在所有测试中都可用: + +- `blockchain` - 由[沙盒][sb]提供的本地区块链实例 +- deployer\` - 一个 TypeScript 封装器,用于部署我们的 Playground 合约或我们希望部署的任何其他合约 +- `playground` - 我们的 `Playground` 合约的 TypeScript 封装器 + +:::note + + 更新 `.tact` 代码和运行测试而不先进行构建是一个常见错误。 这是因为 [Blueprint][bp] 中的测试依赖于 Tact 编译器生成的 TypeScript 封装程序,并与最新的构建程序一起工作。 + + 这就是为什么每次更改 Tact 代码时,都要确保在执行测试套件之前使用 `npx blueprint build` 进行构建。 为方便起见,您可以将构建和测试合并为一条命令,如 [实验性实验室设置](#lab-4) 所示。 + +::: + +然后,调用一个 `beforeEach(){:tact}` [Jest][jest] 函数--它指定了在每个后续测试闭包之前要执行的所有代码。 + +:::note + + 强烈建议不要修改 `beforeEach(){:tact}` 中的内容,除非您确实需要为每个测试闭包设置某些特定行为,或者 [`init(){:tact}`](/book/contracts#init-function)函数的参数发生了变化。 + +::: + +最后,通过调用 `it(){:tact}` [Jest][jest] 函数来描述每个测试闭包--这就是实际编写测试的地方。 + +Example of a minimal test file: + +```typescript +it('should deploy', async () => { + // 检查是在 beforeEach 内部进行的,因此此处可以为空 +}); +``` + +### 使用 `dump()` 调试 {#tests-dump} + +要查看 [`dump(){:tact}`][dump]函数调用的结果,并使用["printf 调试"](#approach) 方法,就必须 + +1. 在代码的相关位置调用 [`dump(){:tact}`][dump]和其他[常用调试函数](#debug-functions)。 +2. 运行 [Jest][jest]测试,这些测试将调用目标函数并向目标接收器发送信息。 + +假设你已经创建了一个 [新计数器合约项目](/#start),让我们来看看它是如何实际运行的。 + +首先,让我们在 `contracts/simple_counter.tact` 中调用 [`dump(){:tact}`][dump],这将把 `msg{:tact}` [Struct][struct] 中传递的 `amount` 输出到合约的调试控制台: + +```tact title="contracts/simple_counter.tact" {3} +// ... +receive(msg: Add) { + dump(msg.amount); + // ... +} +// ... +``` + +接下来,让我们注释掉 `tests/SimpleCounter.spec.ts` 文件中所有现有的 `it(){:typescript}` 测试闭包。 然后再加上下面一条: + +```typescript title="tests/SimpleCounter.spec.ts" +it('should dump', async () => { + await playground.send( + deployer.getSender(), + { value: toNano('0.5') }, + { $$type: 'Add', queryId: 1n, amount: 1n }, + ); +}); +``` + +它向我们合约的 `receive(msg: Add){:tact}` [接收器](/book/receive) 发送信息,而不存储[发送结果](#tests-send)。 + +现在,如果我们使用 `yarn build{:shell}` 构建我们的合约,并使用 `yarn test{:shell}` 运行我们的测试套件,我们将在测试日志中看到以下内容: + +```txt +console.log + #DEBUG#: [DEBUG] File contracts/simple_counter.tact:17:9 + #DEBUG#: 1 + + at SmartContract.runCommon (node_modules/@ton/sandbox/dist/blockchain/SmartContract.js:221:21) +``` + +这是由我们上面的 [`dump(){:tact}`][dump]调用产生的。 + +:::note + + 了解有关在测试中向合约发送消息的更多信息:[向合约发送消息](#tests-send)。 + +::: + +### 使用`expect()`说明期望 {#tests-expect} + +编写测试不可或缺的部分是确保你的期望与观察到的现实相吻合。 为此,[Jest][jest] 提供了一个函数 `expect(){:tact}`,使用方法如下: + +1. 首先,提供一个观测变量。 +2. 然后,调用特定的方法来检查该变量的某个属性。 + +下面是一个更复杂的示例,它使用 `expect(){:tact}` 函数来检查计数器合约是否确实正确地增加了计数器: + +```typescript +it('should increase counter', async () => { + const increaseTimes = 3; + for (let i = 0; i < increaseTimes; i++) { + console.log(`increase ${i + 1}/${increaseTimes}`); + + const increaser = await blockchain.treasury('increaser' + i); + + const counterBefore = await simpleCounter.getCounter(); + console.log('counter before increasing', counterBefore); + + const increaseBy = BigInt(Math.floor(Math.random() * 100)); + console.log('increasing by', increaseBy); + + const increaseResult = await simpleCounter.send( + increaser.getSender(), + { value: toNano('0.05') }, + { $$type: 'Add', queryId: 0n, amount: increaseBy } + ); + + expect(increaseResult.transactions).toHaveTransaction({ + from: increaser.address, + to: simpleCounter.address, + success: true, + }); + + const counterAfter = await simpleCounter.getCounter(); + console.log('counter after increasing', counterAfter); + + expect(counterAfter).toBe(counterBefore + increaseBy); + }. +}); +``` + +:::note + + 查看 [Sandbox][sb] 文档中的更多测试示例: + [Testing flow (FunC)](https://github.com/ton-org/sandbox/blob/main/docs/testing-key-points.md) + [Writing tests for Tact](https://github.com/ton-org/sandbox/blob/main/docs/tact-testing-examples.md) + +::: + +### 实用方法 {#tests-jest-utils} + +由 [Blueprint][bp] 生成的测试文件导入了 `@ton/test-utils` 库,该库为 `expect(){:typescript}` [Jest][jest] 函数的结果类型提供了一些额外的辅助方法。 请注意,`toEqual(){:typescript}`等常规方法仍然存在,随时可以使用。 + +#### 有交易 + +方法 `expect(…).toHaveTransaction(){:typescript}` 检查事务列表中是否有符合你指定的某些属性的事务: + +```typescript {2} +const res = await yourContractName.send(…); +expect(res.transactions).toHaveTransaction({ + // For example, let's check that a transaction to your contract was successful: + to: yourContractName.address, + success: true, +}); +``` + +要了解此类属性的完整列表,请查看编辑器或集成开发环境提供的自动完成选项。 + +#### toEqualCell + +方法 `expect(…).toEqualCell(){:typescript}` 检查两个 [单元格](/book/cells#cells)是否相等: + +```typescript {3} +expect(oneCell).toEqualCell(anotherCell); +``` + +#### 对等切片 + +方法 `expect(…).toEqualSlice(){:typescript}` 检查两个 [slices](/book/cells#slices) 是否相等: + +```typescript {3} +expect(oneSlice).toEqualSlice(anotherSlice); +``` + +#### toEqualAddress + +方法 `expect(…).toEqualAddress(){:typescript}` 检查两个 [地址](/book/types#primitive-types)是否相等: + +```typescript {3} +expect(oneAddress).toEqualAddress(anotherAddress); +``` + +### 发送信息至 {#tests-send} + +要向合约发送消息,请在其 TypeScript 封装器上使用 `.send(){:typescript}` 方法,如下所示: + +```typescript +// 它接受 3 个参数: +await yourContractName.send( + // 1. 消息的发送者 + deployer.getSender(), // 这是一个默认的宝库,可以替换 + + // 2. 值和(可选)反弹,默认为 true + { value: toNano('0.5'), bounce: false }, + + // 3. 消息正文,如果有的话 + 'Look at me!', +); +``` + +消息体可以是简单的字符串,也可以是指定 [消息](/book/structs-and-messages#messages)类型字段的对象: + +```typescript {4-8} +await yourContractName.send( + deployer.getSender(), + { value: toNano('0.5') }, + { + $$type: 'NameOfYourMessageType', + field1: 0n, // bigint zero + field2: 'yay', + }, +); +``` + +通常情况下,存储此类发送的结果非常重要,因为它们包含发生的事件、进行的事务和发送的外部信息: + +```typescript +const res = await yourContractName.send(…); +// res.events - 发生的事件数组 +// res.externals - 外部输出消息数组 +// res.transactions - 完成的交易数组 +``` + +这样,我们就可以轻松地过滤或检查某些交易: + +```typescript +expect(res.transactions).toHaveTransaction(…); +``` + +### 遵守收费和价值 {#tests-fees} + +[沙盒][sb]提供了一个辅助函数 `printTransactionFees(){:typescript}`,它可以漂亮地打印所提供交易的所有值和费用。 它对观察 [纳米通币](/book/integers#nanotoncoin)的流动非常方便。 + +要使用它,请在测试文件顶部修改来自 `@ton/sandbox` 的导入: + +```typescript +import { Blockchain, SandboxContract, TreasuryContract, printTransactionFees } from '@ton/sandbox'; +// ^^^^^^^^^^^^^^^^^^^^ +``` + +然后,提供一个事务数组作为参数,就像这样: + +```typescript +printTransactionFees(res.transactions); +``` + +要处理计算和操作 [阶段](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases)的总费用或费用的单个值,请逐个检查每笔交易: + +```typescript {11,17,21} +// 将接收方处理的交易存储在一个单独的常量中 +const receiverHandledTx = res.transactions[1]; +expect(receiverHandledTx.description.type).toEqual('generic'); + +// 为了满足 TypeScript 的要求 +if (receiverHandledTx.description.type !== 'generic') { + throw new Error('Generic transaction expected'); +} // Total fees + +// 总费用 +console.log('Total fees: ', receiverHandledTx.totalFees); + +// 计算费用 +const computeFee = receiverHandledTx.description.computePhase.type === 'vm' + ?receiverHandledTx.description.computePhase.gasFees + : undefined; +console.log('Compute fee: ', computeFee); + +// Action fee +const actionFee = receiverHandledTx.description.actionPhase?.totalActionFees; +console.log('Action fee: ', actionFee); + +// 现在我们可以进行一些相关检查,比如将费用限制在 1 Toncoin +expect( + (computeFee ?? 0n) + + (actionFee ?? 0n) +).toBeLessThanOrEqual(toNano('1')); +``` + +:::note + + [沙盒][sb]还有很多实用功能,通常都很方便。 例如,它提供了 `prettyLogTransaction(){:typescript}` 和 `prettyLogTransactions(){:typescript}`,分别对单个或多个事务进行操作,并漂亮地打印地址之间的值流。 + +::: + +### 有故意错误的交易 {#tests-errors} + +有时,进行负面测试也很有用,它可以故意出错并抛出特定的[退出代码](/book/exit-codes)。 + +[蓝图][bp]中此类[Jest][jest]测试闭包的示例: + +```typescript title="tests/YourTestFileHere.spec.ts" {9,15} +it('throws specific exit code', async () => { + // 向我们的合约发送特定消息并存储结果 + const res = await your_contract_name.send( + deployer.getSender(), + { + value: toNano('0.5'), // 值以发送的 nanoToncoins 为单位 + bounce: true, // (default) bounceable message + }, + 'the message your receiver expects', // ← change it to yours + ); + + // Expect the transaction to our contract fail with a certain exit code + expect(res.transactions).toHaveTransaction({ + to: your_contract_name.address, + exitCode:5, // ← 更改为您的 + }); +}); +``` + +请注意,要跟踪具有特定退出代码的事务,只需在 `expect(){:typescript}` 方法的 `toHaveTransaction(){:typescript}` 对象参数中指定 `exitCode` 字段即可。 + +不过,通过指定收件人地址 "to "来缩小范围是很有用的,这样 Jest 就只能查看我们发送给合同的消息所引起的事务。 + +### 模拟时间流逝 {#tests-time} + +由 [Sandbox][bp] 提供的本地区块链实例中的 Unix 时间从 `beforeEach(){:typescript}` 块中创建这些实例的时刻开始。 + +```typescript {2} +beforeEach(async () => { + blockchain = await Blockchain.create(); // ← here + // ... +}); +``` + +在此之前,我们曾被警告不要修改 `beforeEach(){:typescript}` 块,除非我们真的需要这样做。 而现在,我们要做的,就是稍稍推翻时间和时空旅行。 + +让我们在末尾添加下面一行,将 `blockchain.now` 明确设置为处理部署消息的时间: + +```typescript {3} +beforeEach(async () => { + // ... + blockchain.now = deployResult.transactions[1].now; +}); +``` + +现在,我们可以在测试子句中操作时间了。 例如,让我们在部署一分钟后进行一次交易,两分钟后再进行一次交易: + +```typescript {2,4} +it('your test clause title', async () => { + blockchain.now += 60; // 60 seconds late + const res1 = await yourContractName.send(…); + blockchain.now += 60; // another 60 seconds late + const res2 = await yourContractName.send(…); +}); +``` + +## 通过 `emit` 记录 {#logging} + +[全局静态函数](/book/functions#global-static-functions) [`emit(){:tact}`](/ref/core-common#emit)向外部世界发送信息--它没有特定的接收者。 + +该功能对于记录和分析链外数据非常方便,只需查看合约生成的 [external messages](/book/external) 即可。 + +### 本地沙箱测试中的日志 {#logging-local} + +在 [Sandbox][sb] 中部署时,您可以从 [receiver function](/book/contracts#receiver-functions) 中调用 [`emit(){:tact}`](/ref/core-common#emit),然后观察已发送的 [external messages](/book/external) 列表: + +```typescript {9-10} +it('emits', async () => { + const res = await simpleCounter.send( + deployer.getSender(), + { value: toNano('0.05') }, + 'emit_receiver', // ← change to the message your receiver handles + ); + + console.log("Address of our contract: " + simpleCounter.address); + console.log(res.externals); // ← 在这里可以看到 emit() 调用的结果, + // 以及所有外部消息 +}); +``` + +### 已部署合同的日志 {#logging-deployed} + +TON 区块链上的每笔交易都[包含`out_msgs`](https://docs.ton.org/develop/data-formats/transaction-layout#transaction) - 这是一个字典,保存着执行交易时创建的传出消息列表。 + +要查看字典中 [`emit(){:tact}`](/ref/core-common#emit)的日志,请查找没有收件人的外部消息。 在各种 TON 区块链探索器中,此类交易将被标记为 "外部输出",目的地指定为"-"或 "空"。 + +请注意,有些探索者会为你反序列化发送的信息体,而有些则不会。 不过,您可以随时在本地[自行解析](#logging-parsing)。 + +### 解析已发送信息的正文 {#logging-parsing} + +Example: + +```tact +// 我们有一个结构 +struct Ballroom { + meme:Bool; + in:Int; + theory:String; +} + +// 还有一个简单的合约, +contract Bonanza { + // 可以接收一个字符串消息, + receive("time to emit") { + // 发送一个字符串 + emit("But to the Supes?Absolutely diabolical.".asComment()); + + // and a Struct + emit(Ballroom{meme: true, in:42, theory: "Duh"}.toCell()); + } +} +``` + +现在,让我们为 "Bonanza "合同制作一个简单的 [测试条款](#tests-structure): + +```typescript /bonanza/ +it('emits', async () => { + const res = await bonanza.send( + deployer.getSender(), + { value: toNano('0.05') }, + 'time to emit', + ); +}); +``` + +在这里,`res` 对象的`externals`字段将包含已发送的[外部信息](/book/external)列表。 让我们访问它,以解析通过调用 Tact 代码中的 [`emit(){:tact}`](/ref/core-common#emit)(或简称 _emitted_)发送的第一条信息: + +```typescript /body/ +it('emits', async () => { + // ... 之前的代码 ... + + // 我们只需要观察到的消息正文: + const firstMsgBody = res.externals[0].body; + + // 现在,我们来解析它,因为我们知道它是一条文本消息。 + // NOTE: In a real-world scenario, + // you'd want to check that first or wrap this in a try...catch + const firstMsgText = firstMsgBody.asSlice().loadStringTail(); + + // "But to the Supes?绝对邪恶。" + console.log(firstMsgText); +}); +``` + +要解析第二条发出的信息,我们可以手动使用一堆 `.loadSomething(){:typescript}` 函数,但这样做太麻烦了--如果 `Ballroom{:tact}` [Struct][struct] 的字段发生变化,就需要重新开始。 当你以这种方式编写大量测试时,可能会适得其反。 + +幸运的是,Tact 编译器会自动为合约生成 TypeScript 绑定(或封装),在测试套件中重新使用它们非常容易。 它们不仅有一个你正在测试的合约的包装器,而且还导出了一堆辅助函数来存储或加载合约中定义的 [Structs][struct] 和 [Messages][message] 。 后者的命名方式与 [Structs][struct] 和 [Messages][message] 一样,只是在前面加上了 `load` 前缀。 + +例如,在我们的例子中,我们需要一个名为 `loadBallroom(){:typescript}` 的函数,用于将 [`Slice{:tact}`][slice]解析为 TypeScript 中的 `Ballroom{:tact}` [Struct][struct] 。 要导入它,要么键入名称,让集成开发环境建议自动导入,要么查看测试套件文件的顶部--应该有类似的一行: + +```typescript +import { Bonanza } from '../wrappers/Bonanza'; +// ^ 这里可以导入 loadBallroom +``` + +现在,让我们来解析第二条发出的信息: + +```typescript +it('emits', async () => { + // ... 之前的代码 ... + + // 我们只需要观察到的消息正文: + const secondMsgBody = res.externals[1].body; + + // 现在,让我们解析它,知道它是 Ballroom Struct。 + // NOTE: In a real-world scenario, + // you'd want to check that first or wrap this in a try...catch + const secondMsgStruct = loadBallroom(secondMsgBody.asSlice()); + + // { '$$type': 'Ballroom', meme: true, in:42n, theory: 'Duh' } + console.log(secondMsgStruct); +}); +``` + +请注意,即使在我们的测试套件之外,也可以解析已部署合约的发射信息。您只需获取发射的消息体,然后像上面的示例一样,在 `@ton/core` 库旁使用自动生成的 Tact 的 TypeScript 绑定。 + +## 处理退回的邮件 {#bounced} + +当 [send](/book/send) 带有 `bounce: true{:tact}` 时,信息会在出错时反弹。确保编写相关的 [`bounced(){:tact}`](/book/bounced) 消息[接收器](/book/contracts#receiver-functions),并优雅地处理被退回的消息: + +```tact +bounced(msg: YourMessage) { + // ...alright squad, let's bounce!... +} +``` + +请记住,在 TON 中被退回的邮件正文中只有 $224$ 个可用数据位,而且没有任何引用,因此无法从中恢复很多数据。 不过,您仍然可以看到邮件是否被退回,从而可以创建更稳健的合同。 + +了解更多有关退信和收信人的信息:[退信](/book/bounced)。 + +## 实验实验室设置 {#lab} + +如果你对 [Blueprint][bp] 的测试设置感到不知所措,或者只是想快速测试一些东西,不用担心--有一种方法可以建立一个简单的游戏场作为实验实验室,来测试你的想法和假设。 + + + +1. #### 创建新的蓝图项目 {#lab-1} + + 这将防止任意代码和测试污染您现有的程序。 + + 新项目可以取任何名字,但我会取名为 "Playground",以表达正确的意图。 + + 要创建它,请运行以下命令: + + + + ```shell + # 推荐 + yarn create ton tact-playground --type tact-empty --contractName Playground + ``` + + + ```shell + npm create ton@latest -- tact-playground --type tact-empty --contractName Playground + ``` + + + ```shell + pnpm create ton@latest tact-playground --type tact-empty --contractName Playground + ``` + + + ```shell + bun create ton@latest tact-playground --type tact-empty --contractName Playground + ``` + + + + 从 0.20.0 开始的 [Blueprint][bp] 版本会自动为新合约启用 `wrappers/` 中的调试模式,因此我们只需调整测试套件并准备好我们的 `Playground` 合约即可进行测试。 + +2. #### 更新测试套件 {#lab-2} + + 移动到新创建的 `tact-playground/` 项目,在 `tests/Playground.spec.ts` 中,将 `"should deploy"{:tact}` 测试闭包改为以下内容: + + ```typescript title="tests/Playground.spec.ts" + it('plays', async () => { + const res = await playground.send( + deployer.getSender(), + { value: toNano('0.5') }, // ← 在这里你可以增加发送的纳币值 + 'plays', + ); + + console.log("Address of our contract: " + playground.address); + console.log(res.externals); // ← 在这里可以看到 emit() 调用的结果 + }); + ``` + +3. #### 修改合同 {#lab-3} + + 用以下代码替换`contracts/playground.tact`中的代码: + + ```tact title="contracts/playground.tact" {4-6} + import "@stdlib/deploy"; + + 合同 Playground with Deployable { + receive("plays") { + // NOTE: write your test logic here! + } + } + ``` + + 此设置的基本思想是将要测试的代码放入 [receiver function](/book/contracts#receiver-functions) 中,以响应 [string](/book/types#primitive-types) 消息 `"plays"{:tact}`。 + + 请注意,您仍然可以在[接收器](/book/contracts#receiver-functions)之外编写任何有效的 Tact 代码。 但为了测试它,你需要在其中编写相关的测试逻辑。 + +4. #### 我们来测试一下! {#lab-4} + + 这样,我们的实验装置就完成了。 要执行我们为 "Playground "合约准备的单个测试,请运行以下程序: + + ```shell + yarn test -t plays + ``` + + 从现在起,您只需修改 Tact 合同文件中已测试的 [receiver function](/book/contracts#receiver-functions)的内容,然后重新运行上述命令,就可以对某些内容进行测试。 冲洗并重复这个过程,直到你测试了你想测试的东西。 + + 为了简化和更干净的输出,您可以在 `package.json` 中为 `scripts` 添加一个新字段,这样您只需运行 `yarn lab{:shell}` 即可在一个字段中完成构建和测试。 + + 在 Linux 或 macOS 上,它看起来就像这样: + + ```json filename="package.json" {3} + { + "脚本":{ + "lab":"blueprint build 1>/dev/null && yarn test -t plays" + } + } + ``` + + 下面是它在 Windows 上的样子: + + ```json filename="package.json" {3-4} + { + "脚本":{ + "build":"blueprint build | out-null", + "lab":"yarn build && yarn test -t plays" + } + } + ``` + + 要运行 + + ```shell + 纱线实验室 + ``` + + + +[转储]: /ref/core-debug#dump +[结构]: +[信息]: +[电池]: /book/cells#cells +[一片]: /book/cells#slices +[tg]: https://t.me/tactlang +[bp]: https://github.com/ton-org/blueprint +[某人]: https://github.com/ton-org/sandbox +[笑话]: https://jestjs.io diff --git a/docs/src/content/docs/zh-cn/book/deploy.mdx b/docs/src/content/docs/zh-cn/book/deploy.mdx new file mode 100644 index 000000000..ba6372e34 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/deploy.mdx @@ -0,0 +1,52 @@ +--- +title: 部署 +--- + +Tact Deployer 是一个与 [TON Verifier](https://verifier.ton.org) 集成的小型库,可让您使用自己喜欢的钱包安全地部署合约,而无需管理密钥或手动部署合约。 Tact Deployer 还能自动验证合同的源代码,确保编译器不受损害。 + +## 要求 + +要使用 Tact Deployer,您的合约必须具有 `@stdlib/deploy` 包中的 `Deployer` 特性。 + +## 安装 + +要在项目中添加 Tact 部署器,只需使用 `yarn`: + +```bash +yarn add @tact-lang/deployer +``` + +## 如何使用 + +当你使用 Tact 构建智能合约时,它会生成一个包(\*.pkg)文件,其中包含所构建智能合约的所有必要信息。要在 TON 中部署智能合约,你需要发送一条附有`init`数据的消息。 + +```typescript +import * as fs from 'fs'; +import * as path from 'path'; +import { Address, contractAddress } from "ton"; +import { SampleTactContract } from "./output/sample_SampleTactContract"; +import { prepareTactDeployment } from "@tact-lang/deployer"; + +// Parameters +let testnet = true; // Flag for testnet or mainnet +let packageName = 'sample_SampleTactContract.pkg'; // 要部署的软件包名称 +let outputPath = path.resolve(__dirname, 'output'); // 输出目录的路径 +let owner = Address.parse(''); // 我们的示例合同有一个所有者 +let init = await SampleTactContract.init(owner); // 为我们的合同创建初始数据 + +// 计算 +let address = contractAddress(0, init); // 计算合同地址。MUST match with the address in the verifier +let data = init.data.toBoc(); // Create init data +let pkg = fs.readFileSync( // Read package file + path.resolve(outputPath, packageName) +); + +// Prepare deploy +let link = await prepareTactDeployment({ pkg, data, testnet }); + +// Present a deployment link and contract address +console.log('Address:' + address.toString({ testOnly: testnet })); +console.log('Deploy link: ' + link); +``` + +点击此链接后,您就可以部署和验证您的智能合约。 diff --git a/docs/src/content/docs/zh-cn/book/exit-codes.mdx b/docs/src/content/docs/zh-cn/book/exit-codes.mdx new file mode 100644 index 000000000..1dd9c39cc --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/exit-codes.mdx @@ -0,0 +1,324 @@ +--- +title: 退出代码 +--- + +:::caution + 根据 [#106] (https://github.com/tact-lang/tact-docs/issues/106),本页正在重建中。 所有锚链接 (`#`) 今后都可能发生变化! +::: + +退出代码是一个 $16$-bit 无符号整数,范围在 $0$ 到 $65535$ 之间(或 2_{16} - 1$)。 + +$0$ 至 $127$ 的代码分配给 FunC (TVM), $128$ 至 $255$ 的代码分配给 Tact。 从 $256$ 到 $65535$ 的范围内,开发人员可自由定义退出代码。 + +预先分配的退出代码列表: + +| 退出代码 | 相位 | 说明 | +| :--- | :--- | :--- | +| $0$ | [计算阶段][c] | 标准成功执行退出代码 | +| $2$ | [计算阶段][c] | 堆栈下溢。最后一个操作码消耗的元素多于堆栈中的元素数量 | +| $3$ | [计算阶段][c] | 堆栈溢出。堆栈中存储的值超过了该版本 TVM 的允许值 | +| $4$ | [计算阶段][c] | 整数溢出。整数不适合 $-2^{256} \leq x < 2^{256}$ 或发生了零除法 | +| $5$ | [计算阶段][c] | 整数超出预期范围 | +| $6$ | [计算阶段][c] | 操作码无效。在当前的 TVM 版本中,指令不详 | +| $7$ | [计算阶段][c] | 类型检查错误。基元参数的值类型不正确 | +| $8$ | [计算阶段][c] | 电池溢出。不可能将数据写入生成器,因为操作后将有超过 1023 位或 4 个引用 | +| $9$ | [计算阶段][c] | 单元下溢。从片原始码读取数据时,试图读取的比特或引用数多于现有比特或引用数 | +| $10$ | [计算阶段][c] | 词典错误。处理字典(哈希图)时出错 | +| $13$ | [计算阶段][c] | gas 耗尽错误。当剩余 gas 变为负值时,由 TVM 抛出 | +| $-14$ | [计算阶段][c] | 这意味着 gas 耗尽错误,与 $13$ 相同。否定,因为无法伪造 | +| $32$ | [行动阶段][a] | 操作列表无效。如果执行后的 c5 寄存器包含不可解析对象,则在操作阶段设置该寄存器 | +| $34$ | [行动阶段][a] | 操作无效或不支持。如果无法执行当前操作,则在操作阶段设置 | +| $37$ | [行动阶段][a] | 不够吨。信息发送的吨数过多(或扣除费用后吨数不足) | +| $38$ | [行动阶段][a] | 额外货币不足 | +| $128$ | 塔克(编译) | 空引用异常--编译器预期是一个整数或单元格,但传递了一个空值 | +| $129$ | 塔克(编译) | 无效序列化前缀--如果与之前的操作码检查有任何不一致,将抛出此退出代码 | +| $130$ | 塔克(编译) | 收到的报文无效 - 未找到合适的操作 | +| $131$ | 塔克(编译) | 制约因素错误 | +| $132$ | 塔克(编译) | 拒绝访问 - 所有者以外的其他人向合同发送了信息 | +| $133$ | 塔克(编译) | 合同已停止 - 已向停止的合同发送信息 | +| $134$ | 塔克(编译) | 无效参数 - 无效 Base64 字符串 | +| $135$ | 塔克(编译) | 未找到合同代码 - 字典调用的假标记 | +| $136$ | 塔克(编译) | 无效地址 - 非 $267$- 位地址或无效链 id(非 0 或 -1) | +| $137$ | 塔克(编译) | 此合约未启用主链支持 | + +[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 + +问:**在哪里可以看到项目中所有自动生成的退出代码列表?** +答:Tact 编译器会在 \*.md 文件末尾收集所有退出代码,您可以在 +路径"./ProjectFolder/build/ProjectName/tact_ProjectName.md "下的目录中跟踪它们。 + +问:** 如何观察抛出的退出代码?** +答:在 Tact 中,打印交易来查看结果是不明智的,因为它们不容易读取。 如果想查看事务的退出代码, +,在 Typescript 本地测试中使用下面的模板: + +```typescript +const sender = await blockchain.treasury('sender'); +const result = await contractName.send(sender.getSender(), { value: toNano('0.05'), }, { transactionData }); + +expect(result.transactions).toHaveTransaction( + { from: sender.address, to: contractName.address, exitCode: YOUR_DESIRED_EXIT_CODE } +); +``` + +- 第一行定义发件人。 +- 第二行发送交易。 +- 在第三行中,您要检查结果中是否有从发送方到您的合约的交易,以及您所需的退出代码。 + +## 计算阶段 + +### $0$:成功执行 {#0} + +该退出代码表示事务的计算阶段已成功完成。 + +### $4$:整数溢出 {#4} + +在 TVM 中,整数的范围可以是 $-2^{256} < x < 2^{256}$。 +如果计算过程中的值超出了这个范围,就会抛出 4 个退出代码。 + +例如 + +```tact +self.id = 1; // 使用存储变量强制不忽略它 +repeat(256) { + self.id = 2 * self.id; +} +``` + +### $5$:超出预期范围的整数 {#5} + +如果整数值超出预期范围,则抛出 5 个退出代码。 +例如,如果在 .store_uint() 函数中使用了负值。 在 Tact 中,还有其他一些新情况,例如: +1- 如你所知,你可以在 Tact 中定义更多有限的整数(小于 257 位的整数)。2- 根据 ````storeUint(self: Builder, value: Int, bits: Int)```函数,不能使用 ````storeUint(0, 257)``` 因为 ``0 ≤ bits ≤ 256```。 + +例如 + +```tact +// 选项 1 -> id: Int as uint32 +self.id = 1; // 强制使用存储变量不忽略它 +repeat(32) { + self.id = 2 * self.id; +} + +// 选项 2 -> 根据 storeUint(self: Builder, value: Int, bits: Int) 函数,不可能使用 storeUint(0, 1024) 因为 0 ≤ bits ≤ 256 +let s:Slice = beginCell().storeUint(0, 257).asSlice(); +``` + +### $8$:单元格溢出 {#8} + +一个单元可存储 1023 位数据和 4 个指向其他单元的引用。 +如果尝试写入超过 1023 位或超过 4 个引用,则会抛出 8 个退出代码。 + +例如 + +```tact +// 根据 storeUint(self: Builder, value: Int, bits: Int) 函数,无法使用 storeUint(0, 1024) 因为 0 ≤ bits ≤ 256 +let s:Slice = beginCell().storeUint(0, 256).storeUint(0, 256).storeUint(0, 256).storeUint(0, 256).asSlice(); +``` + +### $9$:单元下溢 {#9} + +如果尝试从片段中读取的数据多于其包含的数据,则会抛出 9 个退出代码。 + +例如 + +```tact +让 s:Slice = emptySlice(); +self.id = s.loadUint(1); // 使用存储变量强制不忽略它 +``` + +### $13$: 气用尽错误 {#13} + +如果没有足够的 TON 来处理计算阶段,则会抛出此错误。 + +在处理过程中,将对该值执行 NOT 操作,从而将该值更改为 -14。 这样做是为了防止使用 throw 函数伪造退出代码,因为所有此类函数都只接受正值的退出代码,这一点在前面已经讨论过。 + +例如 + +```tact +repeat(10000) { + self.id += 1; +} +``` + +## 行动阶段 + +### $34$行动无效或不支持 {#34} + +在处理操作时,该退出代码会导致大部分错误:无效信息、不正确操作等。 + +例如 + +```tact +nativeSendMessage(emptyCell(), 0); +``` + +### $37$TON {#37} + +这意味着没有足够的 TON 来发送指定的数量。 + +例如 + +```tact +send(SendParameters{to: context().sender, value: ton("10")}); +``` + +## 塔克(编译) + +### 128:空引用异常 {#128} + +如果有一个非空断言,例如 [`!!{:tact}`](/book/operators#unary-non-null-assert)操作符,而检查值是 [`null{:tact}`](/book/optionals),则会抛出一个退出代码为 $128$ 的错误:"空引用异常"。 + +```tact +let gotcha: String? = null; + +try { + // Asserting that the value isn't null, which isn't the case! + dump(gotcha!!); +} catch (exitCode) { + exitCode; // 128 +} +``` + +### $130$来信无效 {#130} + +向合约发送信息时,信息体的前 32 位是操作码。 它决定了必须进行的操作。 +在 FunC 中,如果找不到操作码,就会抛出 0xffff。 在 Tact 中,将抛出 130 个退出代码。 + +例如 + +1. 首先,像下面这样定义一个空合约: + +```tact +合同烟花 {} +``` + +2. 然后,给这份合同发送信息。 由于没有找到合适的操作,您将得到此退出代码。 + +### $132$: 拒绝访问 {#132} + +首先,你应该导入并继承 Ownable Trait。 之后,您的合同就有了所有者。 +您可以在函数中调用 ```self.requireOwner();``` 要求检查。 这将确保只有所有者才能向您的合同发送信息。 + +例如 + +```tact +import "@stdlib/deploy"; +import "@stdlib/ownable"; + +message FakeLaunch { + +} + +contract Fireworks with + Deployable, + Ownable, +{ + owner: Address; + + init(){ + self.owner = sender(); + } + + receive(msg: FakeLaunch){ + self.requireOwner(); + } +} + +fun requireOwner() { + nativeThrowUnless(132, sender() == self.owner); +} +``` + +### $133$: 合同停止 {#133} + +stoppable 特性允许停止合约。 +如果向已停止的合约发送信息,而合约通过运行 \`\`self.requireNotStopped();\`\`\`\` 要求检查,则会抛出此退出代码。 +在当前版本的 Tact 中,将抛出 40368 而不是 133 的退出代码。 + +例如 + +```tact +import "@stdlib/deploy"; +import "@stdlib/ownable"; +import "@stdlib/stoppable"; + +message FakeLaunch {} + +contract Fireworks with + Deployable, + Ownable, + Stoppable, +{ + owner: Address; + stopped:Bool; + + init() { + self.owner = sender(); + self.stopped = false; + } + + receive(msg: FakeLaunch) { + self.stopped = true; + self.requireNotStopped(); + } +} + +fun requireNotStopped() { + require(!self.stopped, "Contract stopped"); +} +``` + +### $134$参数无效 {#134} + +下面的 FunC 函数(在一堆 if 条件的最后一部分)将抛出这个问题。 该函数从 Base64 中读取内容。 + +如果输入字符不符合 base64 字符,就会出现此退出代码。 + +例如 + +```tact +让代码Slice = beginCell().storeUint(0, 8).asSlice().fromBase64(); +// 0 不是有效的 ASCII 码,因此无法转换为 Base64 码。 +``` + +### $135$:未找到合同代码 {#135} + +它将检查字典键搜索的返回标志。 + +例如 + +```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); +``` + +### $136$: 无效地址 {#136} + +在 TON 中,所有地址都是 267 位。 如果您违反了这一规则,您将面临这个退出代码。 + +目前,TON 只支持两个链 id。0 代表基础链,-1 代表主链。 如果地址不是来自 basechain,则会抛出 136 个退出代码。 + +例如 + +```tact +// fun newAddress(chain: Int, hash: Int):Address; +// 从链和哈希值创建一个新地址。 +let zeroAddress:Address = newAddress(1, 0); // 无效的链零地址 +``` + +### $137$:该合约未启用主链支持 {#137} + +目前,TON 只支持两个链 id。0 代表基础链,-1 代表主链。 + +Tact 仅支持 basechain,如果您的地址来自 masterchain,则会抛出 137 个退出代码。 + +例如 + +```tact +// fun newAddress(chain: Int, hash: Int):Address; +// 根据链和哈希值创建新地址。 +let zeroAddress:Address = newAddress(-1, 0); // 主链零地址 +``` diff --git a/docs/src/content/docs/zh-cn/book/expressions.mdx b/docs/src/content/docs/zh-cn/book/expressions.mdx new file mode 100644 index 000000000..2ef28534e --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/expressions.mdx @@ -0,0 +1,256 @@ +--- +title: 表达式 +--- + +Tact 中的每个运算符都能构成一个表达式,但 Tact 还提供了更多的表达式选项供您选择。 + +## Literals + +字面表示 Tact 中的值。 这些是固定值,而不是变量,是您在代码中实际提供的。 Tact 中的所有字面量都是表达式本身。 + +您还可以调用定义在某些 [基元类型][p]上的 [扩展函数](/book/functions#extension-function),这些 [基元类型][p] 与字面值相对应: + +```tact +// 在整数字面量上调用为 Int 定义的 toString() 函数: +42.toString(); + +// 在字符串字面量上调用为 String 定义的 asComment() 函数: +"Tact is awesome!".asComment(); +``` + +### Integer literals + +整数字面可以用[十进制](/book/integers#decimal)(基 $10$)、[十六进制](/book/integers#hexadecimal)(基 $16$)、[八进制](/book/integers#octal)(基 $8$)和[二进制](/book/integers#binary)(基 $2$)符号书写: + +- 一个 [_decimal_ integer](/book/integers#decimal) 字面量是一串数字($\mathrm{0 - 9}$)。 + +- 前导 $\mathrm{0x}$(或 $\mathrm{0X}$)表示[十六进制整数](/book/integers#hexadecimal) 字面量。 它们可以包括数字($\mathrm{0 - 9}$)和字母 $\mathrm{a - f}$ 和 $\mathrm{A - F}$。 请注意,字符的大小写不会改变其值。 因此:$\mathrm{0xa}$ = $\mathrm{0xA}$ = $10$ 和 $\mathrm{0xf}$ = $\mathrm{0xF}$ = $15$。 + +- 前导 $\mathrm{0o}$(或 $\mathrm{0O}$)表示 [octal integer](/book/integers#octal) 字面量。 它们只能包括数字 $\mathrm{0 - 7}$。 + +- 前导 $\mathrm{0b}$(或 $\mathrm{0B}$)表示 [二进制整数](/book/integers#binary) 字面量。 它们只能包括数字 $0$ 和 $1$。 + +:::caution + 需要注意的是,在 Tact 中,以 $0$ 为前导的整数字面量仍被视为小数,而在 JavaScript/TypeScript 中,以 $0$ 为前导的整数字面量表示八进制! +::: + +一些整数字面的例子 + +```tact +// 十进制,基数 10: +0, 42, 1_000, 020 + +// 十六进制,基数 16: +0xABC, 0xF, 0x0011 + +// 八进制,基数 8: +0o777, 0o001 + +// 二进制,基数 2: +0b01111001_01101111_01110101_00100000_01100001_01110010_01100101_00100000_01100001_01110111_01100101_01110011_01101111_01101101_01100101 +``` + +有关整数和 [`Int{:tact}`](/book/integers)类型的更多信息,请访问专门页面:[整数](/book/integers)。 + +### Boolean literals + +[`Bool{:tact}`](/book/types#booleans)类型只有两个字面值:`true{:tact}`和`false{:tact}`。 + +```tact +true == true; +true != false; +``` + +有关布尔和 [`Bool{:tact}`](/book/types#booleans)类型的更多信息,请参阅专门章节:[布尔](/book/types#booleans)。 + +### String literals + +字符串字面量是用双引号(`"`")括起来的零个或多个字符。 所有字符串字面量都是 [`字符串{:tact}`][p]类型的对象。 + +```tact +"foo"; +"1234"; +``` + +Tact 字符串支持一系列从反斜杠字符开始的[转义序列](https://en.wikipedia.org/wiki/Escape_sequence): + +- `\\{:tact}` - 字面反斜线 +- `\"{:tact}` - 双引号 +- `\n{:tact}` - 换行 +- `\r{:tact}` - 回车 +- `\t{:tact}` - tab +- `\v{:tact}` - 垂直标签 +- `\b{:tact}` - 退格 +- `\f{:tact}` - 表格供稿 +- `\x00{:tact}`至`\xFF{:tact}` - [代码点](https://en.wikipedia.org/wiki/Code_point),长度必须正好是两个十六进制数字 +- `\u0000{:tact}`至`\uFFFF{:tact}` - [Unicode 代码点][unicode],长度必须正好是四个十六进制数字 +- `\u{0}{:tact}` 到 `\u{FFFFFF}{:tact}` - [Unicode 代码点][unicode],长度可以是 $1$ 到 $6$ 的十六进制数 + +[unicode]: https://en.wikipedia.org/wiki/Unicode#Codespace_and_code_points + +```tact +// +"escape (escape)if (you)can (can)"; + +// +"this ("litally (") works"; + +// \n +"line \n another line"; + +// \r +"Shutters \r Like \r This \r One"; + +// \t +"spacing \t granted!"; + +// \v +"those \v words \v are \v aligned"; + +// \b +"rm\b\bcreate!"; + +// \f +"form \f feed"; + +// \x00 - \xFF +"this \x22literally\x22 works"; // \x22 表示双引号 + +// \u0000 - \uFFFF +"danger, \u26A1 high voltage \u26A1";// \u26A1 代表⚡表情符号 + +// \u{0} - \u{FFFFFF} +"\u{1F602} LOL \u{1F602}"; // \u{1F602} 代表😂表情符号 +``` + +:::note + + 阅读更多关于字符串和[`字符串{:tact}`][p]类型:\ + [书中的原始类型][p]\ + [参考资料中的字符串和字符串构造器](/ref/core-strings)的内容 + +::: + +### `null` literal + +`空{:tact}`值将以`null{:tact}` 字面形式写入。它**不是[标识符](#identifiers)**,也不指向任何对象。它也**不是[原始类型][p]的实例**。相反,`null{:tact}`表示缺乏标识和故意不存在任何价值。 + +```tact +让 var:= null; // variable, which can hold null value +var = 42; +if (var != null) { + var!!+ var!!; +} +``` + +有关使用 `null{:tact}`的更多信息,请访问专门页面:[选项](/book/optionals)。 + +## Identifiers + +标识符是代码中的一串字符,用于标识[变量](/book/statements#let)、[常量](/book/constants)、[映射](/book/maps)和[函数](/book/functions),以及[结构][s]、[消息][m]、[契约](/book/contracts)、[特质](/book/types#traits)或它们的字段和方法。 标识符区分大小写,不加引号。 + +在 Tact 中,标识符可以包含拉丁小写字母 (`a-z`)、拉丁大写字母 (`A-Z`)、下划线 (`_`)和数字 ($\mathrm{0 - 9}$),但不能以数字开头。 标识符与 [字符串](#string-literals)的区别在于,字符串是数据,而标识符是代码的一部分。 + +请注意,当[基元类型][p]的标识符以大写字母开头时。 已使用定义的 [复合类型](/book/types#composite-types),如 [Structs][s] 和 [Messages][m] 也必须大写。 + +## Instantiation + +您可以创建以下类型的实例: + +- [结构][s] +- [信息][m] + +```tact +struct StExample { + fieldInit:Int = 1; + fieldUninit:Int; +} + +fun example() { + // 带有 fieldInit 默认值的实例 + StExample{ fieldUninit: 2 }; + + // 带有两个字段设置的实例 + StExample{ + fieldInit:0, + fieldUninit: 2, // 允许使用尾部逗号 + }; +} +``` + +## Field access + +您可以直接访问以下类型的字段: + +- [结构][s] +- [信息][m] + +```tact +struct StExample { + fieldInit:Int = 1; + fieldUninit:Int; +} + +fun example():Int { + let struct:StExample = StExample{ fieldUninit: 2 }; // 实例化 + + struct.fieldInit; // 访问字段 + return struct.fieldUninit; // 从函数中返回字段值 +} +``` + +## Extension function call + +[扩展函数](/book/functions#extension-function)仅在特定类型中定义。 它们的调用方式类似于许多其他语言中的方法调用: + +```tact +42.toString(); // toString() 是针对 Int 类型定义的 stdlib 函数。 +``` + +## Static function call + +在函数体的任何位置,都可以调用全局 [static function](/book/functions#global-static-functions) 或 [contract](/book/contracts) 的内部函数: + +```tact +contract ExampleContract { + receive() { + now(); // now() 是 stdlib 的静态函数 + let expiration:Int = now() + 1000; // 运算和变量声明 + expiration = self.answerQuestion(); // 内部函数 + } + fun answerQuestion():Int { + return 42; + } +} +``` + +## `initOf` + +表达式 `initOf{:tact}` 计算 [contract](/book/contracts) 的初始状态 (`StateInit{:tact}`): + +```tact +// 合同的 init() 函数的参数值 +// ↓ ↓ +initOf ExampleContract(42, 100); // 返回一个 Struct StateInit{} +// --------------- +// ↑ +// 合同名称 +// ↓ +// --------------- +initOf ExampleContract( + 42, // 第一个参数 + 100, // 第二个参数,允许使用逗号 +); +``` + +其中,`StateInit{:tact}`是一个内置[结构][s],由以下部分组成: + +| 现场 | 类型 | 说明 | +| :-- | :------------------ | :------------------------------------------------ | +| 代码 | [`单元格{:tact}`][单元格] | [合同](/book/contracts)的初始代码(编译后的字节码 | +| 数据 | [`单元格{:tact}`][单元格] | [合同](/book/contracts)的初始数据(合同的 `init(){:tact}`函数参数 | + +[p]: /book/types#primitive-types +[cell]: /book/cells#cells +[s]: /book/structs-and-messages#structs +[m]: /book/structs-and-messages#messages \ No newline at end of file diff --git a/docs/src/content/docs/zh-cn/book/external.mdx b/docs/src/content/docs/zh-cn/book/external.mdx new file mode 100644 index 000000000..697ae3242 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/external.mdx @@ -0,0 +1,75 @@ +--- +title: 外部信息 +--- + +:::caution + 必须在项目配置中明确启用外部信息支持。 + 如果不启用它,编译工作就会失败。 +::: + +外部信息是没有发件人的信息,世界上任何人都可以发送。 外部信息是与链外系统集成或对合同进行一般维护的良好工具。 处理外部信息与处理内部信息不同。 本节将介绍如何处理外部信息。 + +## 外部信息有何不同 + +外部报文与内部报文有以下不同: + +### 合同自行支付天然气使用费 + +在处理内部信息时,发件人通常会支付燃气使用费。 处理外部报文时,合同支付燃气使用费。 这意味着您需要谨慎使用外部信息中的 gas 。 您应该经常测试合同的燃气使用情况,并确认一切正常。 + +### 信息必须手动接收 + +外部信息不会自动接收。 您需要手动接受它们。 这是通过调用 `acceptMessage` 函数实现的。 如果不调用 `acceptMessage` 函数,信息将被拒绝。 这样做是为了防止外部信息垃圾邮件。 + +### 接受信息前的 10k gas 限值 + +10k gas 是一个非常小的限制,而 Tact 本身在到达你的代码之前就已经消耗了相当数量的 gas 。 您应该经常测试合同的燃气使用情况,并确认一切正常。 + +:::tip[Hey there!] + The 10k gas limit for external messages is based on the parameter we set by the + validator for the whole blockchain of the `gas_limit` field. 您可以在此查阅 + : + - https://docs.ton.org/develop/smart-contracts/guidelines/accept#external-messages + - https://docs.ton.org/develop/howto/blockchain-configs#param-20-and-21 + + ::: + +### 接受信息后的无约束 gas 使用量 + +您接受 gas 后,合同可以随意使用 gas 。 这样做是为了允许合同进行任何类型的处理。 您应该经常测试合同的 gas 使用情况,并验证一切正常,避免出现可能耗尽合同余额的漏洞。 + +### 无背景资料 + +处理外部报文时,"上下文 "和 "发件人 "函数不可用。 这是因为外部信息没有上下文。 这意味着您不能在外部信息中使用 `context` 和 `sender` 函数。 您需要仔细测试您的合约,确保它没有使用 `context` 和 `sender` 函数。 + +## 启用外部信息支持 + +要启用外部信息支持,请在项目配置文件中启用: + +```json +{ + "options": { + "external": true + } +} +``` + +## 外部接收器 + +外部接收器的定义方法与内部接收器相同,但使用的是 "外部 "关键字,而不是 "接收": + +```tact +contract SampleContract { + external("Check Timeout") { + + // Check for contract timeout + require(self.timeout > now(), "Not timeouted"); + + // Accept message + acceptMessage(); + + // Timeout processing + self.onTimeouted(); + } +} +``` diff --git a/docs/src/content/docs/zh-cn/book/func.mdx b/docs/src/content/docs/zh-cn/book/func.mdx new file mode 100644 index 000000000..2daa2ec75 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/func.mdx @@ -0,0 +1,307 @@ +--- +title: 与 func 的兼容性 +--- + +Tact 本身编译为 FunC,并将其所有实体直接映射到各种 FunC 和 TL-B 类型。 + +## 转换类型 + +Tact 中的 [原始类型](/book/types#primitive-types)可直接映射到 FunC 中的类型。 + +所有关于复制变量的规则都是一样的。其中一个最大的不同是,在 Tact 中没有可见的突变操作符,大多数 [`切片{:tact}`](/book/cells#slices)操作都是就地突变变量。 + +## 转换序列化 + +在 Tact 中,[Structs](/book/structs-and-messages#structs)和[Messages](/book/structs-and-messages#messages)的序列化是自动进行的,不像 FunC 需要手动定义序列化逻辑。 + +Tact 的自动布局算法是贪婪的。这意味着它会获取下一个变量,计算其大小,并尝试将其放入当前单元格。如果不合适,它就会创建一个新单元格并继续。所有用于自动布局的内部结构都会在分配前被扁平化。 + +除了 [`Address{:tact}`](/book/types#primitive-types) 以外,所有可选类型在 TL-B 中都序列化为 `Maybe`。 + +没有对 `Either` 的支持,因为它没有定义在某些情况下序列化时应选择什么。 + +### 实例 + +```tact +// _ value1:int257 = SomeValue; +struct SomeValue { + value1: Int; // 默认为 257 位 +} +``` + +```tact +// _ value1:int256 value2:uint32 = SomeValue; +struct SomeValue { + value1: Int as int256; + value2:int as uint32; +} +``` + +```tact +// _ value1:bool value2:Maybe bool = SomeValue; +struct SomeValue { + value1: Bool; + value2:Bool?; +} +``` + +```tact +// _ cell:^cell = SomeValue; +struct SomeValue { + cell:Cell; // 总是作为引用存储 +} +``` + +```tact +// _ cell:^slice = SomeValue; +struct SomeValue { + cell:Slice; // 总是作为引用存储 +} +``` + +```tact +// _ value1:int256 value2:int256 value3:int256 ^[value4:int256] = SomeValue; +struct SomeValue { + value1: Int as int256; + value2: Int as int256; + value3: Int as int256; + value4: Int as int256; +} +``` + +```tact +// _ value1:int256 value2:int256 value3:int256 ^[value4:int256] flag:bool = SomeValue; +struct SomeValue { + value1: Int as int256; + value2: Int as int256; + value3: Int as int256; + flag:Bool; // Flag 写在 value4 之前,以避免自动布局将其分配到下一个单元格 + value4: Int as int256; +} +``` + +```tact +// _ value1:int256 value2:int256 value3:int256 ^[value4:int256 flag:bool] = SomeValue; +struct SomeValue { + value1: Int as int256; + value2: Int as int256; + value3: Int as int256; + value4: Int as int256; + flag: Bool; +} +``` + +```tact +// _ value1:int256 value2:^TailString value3:int256 = SomeValue; +struct SomeValue { + value1: Int as int256; + value2:String; + value3: Int as int256; +} +``` + +## 将收到的信息转换为 `op` 操作 + +Tact 会为每条接收到的键入信息生成一个唯一的 `op`,但它可以被覆盖。 + +下面是 FunC.NET 中的代码 + +```func +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; 传入消息代码... + + ;; 接收 MessageWithGeneratedOp 消息 + if (op == 1180414602) { + ;; 代码... + } + + ;; 接收 MessageWithOverwrittenOP 消息 + if (op == 291) { + ;; 代码... + } + +} +``` + +在 Tact 中变成了这样: + +```tact +message MessageWithGeneratedOp { + amount: Int as uint32; +} + +message(0x123) MessageWithOverwrittenOP { + amount: Int as uint32; +} + +contract Contract { + // Contract Body... + + receive(msg: MessageWithGeneratedOp) { + // code... + } + + receive(msg: MessageWithOverwrittenOP) { + // code... + } + +} + +``` + +## Convert get-methods + +你可以用与 FunC 的 `get` 方法兼容的 Tact 来表达除 `list-style-lists` 以外的所有内容。 + +### 基本返回类型 + +如果一个 `get` 方法在 FunC 中返回一个基元,那么在 Tact 中也可以用同样的方法实现它。 + +下面是 FunC 中的代码 + +```func +int seqno() method_id { + return 0; +} +``` + +在 Tact 中变成了这样: + +```tact +get fun seqno(): Int { + return 0; +} +``` + +### 张量返回类型 + +在 FunC 中,张量类型 `(int, int){:func}` 和 `(int, (int)){:func}` 是有区别的,但对于 TVM 来说没有区别,它们都表示两个整数的堆栈。 + +要转换从 FunC `get` 方法返回的张量,需要定义一个 [Struct](/book/structs-and-messages#structs),它与张量的字段类型相同,顺序也相同。 + +下面是 FunC 中的代码 + +```func +(int, slice, slice, cell) get_wallet_data() method_id { + return ...; +} +``` + +在 Tact 中变成了这样: + +```tact +struct JettonWalletData { + balance: Int; + owner: Address; + master: Address; + walletCode: Cell; +} + +contract JettonWallet { + get fun get_wallet_data(): JettonWalletData { + return ...; + } +} +``` + +### 元组返回类型 + +在 FunC 中,如果返回的是元组而不是张量,则需要遵循张量类型的流程,但要将 “get ”方法的返回类型定义为可选类型。 + +下面是 FunC 中的代码 + +```func +[int, int] get_contract_state() method_id { + return ...; +} +``` + +在 Tact 中变成了这样: + +```tact +struct ContractState { + valueA: Int; + valueB: Int; +} + +contract StatefulContract { + get fun get_contract_state(): ContractState? { + return ...; + } +} +``` + +### 混合元组和张量返回类型 + +如果某些张量是元组,则需要像前面的步骤一样定义结构体,而元组必须定义为单独的 [Struct](/book/structs-and-messages#structs)。 + +下面是 FunC 中的代码 + +```func +(int, [int, int]) get_contract_state() method_id { + return ...; +} +``` + +在 Tact 中变成了这样: + +```tact +struct ContractStateInner { + valueA: Int; + valueB: Int; +} + +struct ContractState { + valueA: Int; + valueB: ContractStateInner; +} + +contract StatefulContract { + get fun get_contract_state(): ContractState { + return ...; + } +} +``` + +### 参数 制图 + +获取 "方法参数的转换非常简单。每个参数都_原样_映射到 FunC one,每个元组都映射到 [结构](/book/structs-and-messages#structs)。 + +下面是 FunC 中的代码 + +```func +(int, [int, int]) get_contract_state(int arg1, [int,int] arg2) method_id { + return ...; +} +``` + +在 Tact 中变成了这样: + +```tact +struct ContractStateArg2 { + valueA: Int; + valueB: Int; +} + +struct ContractStateInner { + valueA: Int; + valueB: Int; +} + +struct ContractState { + valueA: Int; + valueB: ContractStateInner; +} + +contract StatefulContract { + get fun get_contract_state(arg1: Int, arg2: ContractStateArg2): ContractState { + return ContractState{ + valueA: arg1, + valueB: ContractStateInner{ + valueA: arg2.valueA, + valueB: arg2.valueB, // trailing comma is allowed + }, // trailing comma is allowed + }; + } +} +``` diff --git a/docs/src/content/docs/zh-cn/book/functions.mdx b/docs/src/content/docs/zh-cn/book/functions.mdx new file mode 100644 index 000000000..f75355245 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/functions.mdx @@ -0,0 +1,172 @@ +--- +title: 函数及其类型 +--- + +import { Badge } from '@astrojs/starlight/components'; + +Tact 中的功能可以用不同的方式定义: + +- 全局静态函数 +- 扩展功能 +- 可变函数 +- 本地功能 +- 接收器功能 +- 获取器功能 + +除了 [接收器函数](#receiver-functions),所有函数的定义(参数列表)和调用(参数列表)都可以使用逗号: + +```tact +fun foo( + a: Int, // 允许在参数列表中使用逗号拖尾 +) {} + +fun bar() { + foo( + 5, // 也允许在参数列表中使用逗号拖尾! + ); +} +``` + +## 全局静态函数 + +您可以在程序的任何地方定义全局函数: + +```tact +fun pow(a: Int, c: Int): Int { + let res: Int = 1; + repeat(c) { + res = res * a; + } + return res; +} +``` + +## 虚拟和抽象函数 + +如果 [traits](/book/types#traits) 有 `virtual{:tact}` 关键字,则可以使用 `override{:tact}` 允许继承 [traits](/book/types#traits) 的合约修改内部函数。 函数也可以标记为 `抽象{:tact}`,在这种情况下,继承合约必须定义其实现: + +```tact +trait FilterTrait with Ownable { + // Virtual functions can be overridden by users of this trait + virtual fun filterMessage(): Bool { + return sender() != self.owner; + } + + abstract fun specialFilter(): Bool; +} + +contract Filter with FilterTrait { + // Overriding default behavior of the FilterTrait + override fun filterMessage(): Bool { + return true; + } + + override fun specialFilter(): Bool { + return true; + } +} +``` + +## 扩展功能 + +扩展函数允许你为任何可能的类型实现扩展。 + +> **警告** +> 第一个参数的名称必须名为 `self`,该参数的类型必须是你要扩展的类型。 + +```tact +extends fun customPow(self: Int, c: Int): Int { + let res: Int = 1; + repeat(c) { + res *= self; + } + return res; +} +``` + +## 可变函数 + +可变函数是对数值进行变异,用执行结果代替数值。 要执行突变,函数必须改变 `self` 值。 + +```tact +extends mutates fun customPow(self: Int, c: Int) { + let res: Int = 1; + repeat(c) { + res *= self; + } + self = res; +} +``` + +## 本地功能 + +本地函数是 FunC 函数的直接绑定: + +> **注** +> 本地函数也可以是可变函数和扩展函数。 + +```tact +@name(store_uint) +native storeUint(s: Builder, value: Int, bits: Int): Builder; + +@name(load_int) +extends mutates native loadInt(self: Slice, l: Int): Int; +``` + +## Receiver functions + +接收器函数是负责在合约中接收信息的特殊函数,只能在合约或特质中定义。 + +```tact +contract Treasure { + // This means that this contract can receive the comment "Increment" and this function would be called for such messages + receive("Increment") { + self.counter += 1; + } +} +``` + +## 获取器函数 + +获取器函数定义智能合约上的获取器,只能在合约或特征中定义。 + +```tact +contract Treasure { + get fun counter(): Int { + return self.counter; + } +} +``` +### 明确解决方法 ID 碰撞问题 + +

+ +与 TVM 合约中的其他函数一样,getters 也有其*独特*的相关函数选择器,即一些整数 ID(称为*方法 ID*)。 +其中一些整数是为内部目的保留的,例如 -4, -3, -2, -1, 0 是保留 ID,而 +常规函数(合约内部函数,不可从外部调用)通常由从 1 开始的后续(小)整数编号。 +默认情况下,获取器有相关的方法 ID,这些 ID 是使用 [CRC16](https://en.wikipedia.org/wiki/Cyclic_redundancy_check) 算法从名称中导出的,具体如下: +`crc16() & 0xffff) | 0x10000`。 +有时,这可能会使名称不同的获取器获得相同的方法 ID。 +如果出现这种情况,您可以重命名合约中的某些获取器,或 +以编译时表达式的形式手动指定获取器的方法 ID: + +```tact +contract ManualMethodId { + const methodId: Int = 16384 + 42; + + get(self.methodId) fun methodId1(): Int { + return self.methodId; + } + + get(crc32("crc32") + 42 & 0x3ffff | 0x4000) + fun methodId2(): Int { + return 0; + } +} +``` + +请注意,*不能*使用 TVM 保留的方法 ID,也不能使用某些初始正整数,因为编译器会将其用作函数选择器。 + +用户指定的方法 ID 是 19 位有符号整数,因此可以使用从 $-2^{18}$ 到 $-5$ 以及从 $2^{14}$ 到 $2^{18}$ 的整数。- 1$. + +此外,还有一些方法 ID 是为 Tact 编译器在编译过程中插入的获取器保留的,它们是 113617、115390 和 121275。 \ No newline at end of file diff --git a/docs/src/content/docs/zh-cn/book/import.mdx b/docs/src/content/docs/zh-cn/book/import.mdx new file mode 100644 index 000000000..73814226c --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/import.mdx @@ -0,0 +1,54 @@ +--- +title: 导入代码 +--- + +Tact 允许您导入 Tact 和 [FunC](https://docs.ton.org/develop/func/overview) 代码--任何给定的 `.tact` 或 `.fc`/`.func` 文件都可以使用 `import{:tact}` 关键字导入到您的项目中。 + +此外,Tact 编译器还拥有一套通用的标准库,这些标准库捆绑在 Tact 编译器中,但并不立即包含在 Tact 编译器中,请参阅 [标准库概述](/ref/standard-libraries)。 + +:::caution + + 注意:所有导入的代码都会与您的代码合并在一起,因此必须避免名称冲突,并始终仔细检查源代码! + +::: + +## 导入 Tact 代码 + +使用 `import{:tact}` 语句并提供目标 `.tact` 文件的相对路径,可以导入任何 Tact 代码: + +```tact +import "./relative/path/to/the/target/tact/file.tact"; +``` + +也可以指定父目录 (`../`): + +```tact +import "../subfolder/imported/file.tact"; +``` + +## 导入 FunC 代码 + +可以直接导入用 FunC 代码编写的代码,就像 Tact 代码导入一样: + +```tact +// Relative import +import "./relative/path/to/the/target/func/file.fc"; + +// Specifying parent directories +import "../subfolder/imported/func/file.fc"; +``` + +但要使用此类文件中的函数,必须先将它们声明为 "本地 "函数。 For example, when standard library [@stdlib/dns](/ref/stdlib-dns) uses a `dns.fc` FunC file, it maps FunC functions to Tact ones like so: + +```tact +// FunC 代码位于当前 Tact 代码旁边的文件中: +import "./dns.fc"; + +// 从 FunC 到 Tact 的函数签名映射: +@name(dns_string_too_internal) +native dnsStringToInternal(str: String):Slice? +``` + +## 标准图书馆 + +See [Standard libraries overview](/ref/standard-libraries). diff --git a/docs/src/content/docs/zh-cn/book/index.mdx b/docs/src/content/docs/zh-cn/book/index.mdx new file mode 100644 index 000000000..306b5aeb2 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/index.mdx @@ -0,0 +1,39 @@ +--- +title: Overview +--- + +import { LinkCard, CardGrid, Steps } from '@astrojs/starlight/components'; + +欢迎阅读**Tact Book**部分(或简称**The Book**)--一本关于Tact语言的入门书籍。 + +Here are it's main contents: + + + +1. #### Guides + + [Cheatsheets](/book/cs/from-func) 是关于 Tact 语法和习语的快速介绍,并与其他区块链语言(如 FunC(也在 TON 上)和 Solidity(以太坊区块链))进行了比较。 利用这些优势尽快过渡到战术。 + + + + + +2. #### 图书 + + [**Tact Book**](/book/types) 是一套连贯、精简的 Tact 教育资料。 一般来说,它假定您是按从前到后的顺序阅读的。 后面的部分以前面部分的概念为基础,前面的部分可能不会深入探讨某个特定主题的细节,但会在后面的部分重新讨论该主题。 + + 此外,文档的语言部分也有许多参考资料,其中对语言的许多基元进行了更详细的描述。 此外,只要 TON 主文档中对更广泛的 TON 概念已有解释,本书也会尽量参考。 + + 本书还假定你用另一种编程语言编写过代码,但并不假定是哪一种。 我们努力让来自不同编程背景的人都能广泛获取这些材料。 我们没有花很多时间来讨论编程是什么或如何思考编程。 如果你是编程新手,最好阅读专门介绍编程的书籍。 + + + + + + diff --git a/docs/src/content/docs/zh-cn/book/integers.mdx b/docs/src/content/docs/zh-cn/book/integers.mdx new file mode 100644 index 000000000..7471e729e --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/integers.mdx @@ -0,0 +1,186 @@ +--- +title: 整数 +--- + +TON 智能合约中的算术运算始终使用整数,从不使用浮点数,因为浮点数是[不可预测的](https://learn.microsoft.com/en-us/cpp/build/why-floating-point-numbers-may-lose-precision)。 因此,重点应放在整数及其处理上。 + +`Int{:tact}` 是一个 257 位的有符号整数类型。 +它能够存储 $-2^{256}$ 和 $2^{256} - 1$ 之间的整数。 + +## 符号 + +Tact 支持以各种方式将 `Int{:tact}` 的基元值写成 [整数字面量](/book/expressions#integer-literals)。 + +大多数符号允许在数字之间添加下划线 (`_`),但下列符号除外: + +- 字符串表示法,如 [纳吨](#nanotoncoin) 案例所示。 +- 带前导零的十进制数 $0$。一般不鼓励使用,参见 [下文](#十进制)。 + +此外,**不允许**在 $4\_\_2$ 中连续使用多个下划线,或在 $42\_$ 中使用尾部下划线。 + +### 十进制 + +最常见、最常用的数字表示方法,使用[十进制数字系统](https://en.wikipedia.org/wiki/Decimal): $123456789$。 +您可以使用下划线(`_`)来提高可读性: $123\_456\_789$ 等于 $123456789$。 + +:::caution + 或者,您也可以在数字前加上一个 $0$,这样就禁止使用下划线,只允许使用十进制数字:$0123 = 123.$ + 注意,**强烈建议**使用这种带前导零的符号,因为可能会与 TypeScript 中的八进制整数字面混淆,而 TypeScript 通常与 Tact 一起用于开发和测试合约。 +::: + +### 十六进制 + +使用[十六进制数字系统](https://en.wikipedia.org/wiki/Hexadecimal)表示数字,用 $\mathrm{0x}$(或 $\mathrm{0X}$)前缀表示:$\mathrm{0xFFFFFFFFF}$。 +使用下划线(`_`)提高可读性:$\mathrm{0xFFF\_FFFF\_FFF}$ 等于 $\mathrm{0xFFFFFFFFF}$。 + +### 八进制 + +使用[八进制数字系统](https://en.wikipedia.org/wiki/Octal)表示数字,用 $\mathrm{0o}$(或 $\mathrm{0O}$)前缀表示:$\mathrm{0o777777777}$。 +使用下划线(`_`)提高可读性:$\mathrm{0o777\_777\_777}$ 等于 $\mathrm{0o777777777}$。 + +### 二进制 + +使用[二进制数字系统](https://en.wikipedia.org/wiki/Binary_number)表示数字,用 $\mathrm{0b}$(或 $\mathrm{0B}$)前缀表示:$\mathrm{0b111111111}$。 +使用下划线(`_`)提高可读性:$\mathrm{0b111\_111\_111}$ 等于 $\mathrm{0b111111111}$。 + +### NanoToncoin + +美元的算术运算需要在圆点后保留两位小数,这两位小数用于美分的计算。但是,如果我们只能用整数来表示数字 \$$1.25$,我们该如何表示呢?解决的办法是直接使用 _cents_。这样,\$$1.25$ 就变成了 $125$ 美分。我们只需记住最右边的两位数代表小数点后的数字。 + +同样,在使用 TON 区块链的主要货币 Toncoin 时,需要九位小数,而不是两位小数。可以说,纳米通币是通币的 $\frac{1}{10^{9}}$。 + +因此,$1.25$ Toncoin 的数量,可以用 Tact 表示为 [`ton("1.25"){:tact}`](/ref/core-comptime#ton),实际上就是数字 $1250000000$。我们把这些数字称为 _纳米吨币_(或 _纳米吨_),而不是 _美分_。 + +## 序列化 + +将 `Int{:tact}` 值编码为持久状态([contracts](/book/contracts) 和 [traits](/book/types#traits) 的字段)时,通常最好使用比 $257$-bits 更小的表示形式,以降低[存储成本](https://docs.ton.org/develop/smart-contracts/fees#storage-fee)。 这些表示法的使用也被称为 "序列化",因为它们代表了 TON 区块链运行的本地[TL-B][tlb]类型。 + +持久状态大小在状态变量的每个声明中都会在 `as{:tact}`关键字后指定: + +```tact +contract SerializationExample { + // 持久状态变量 + oneByte: Int as int8 = 0; // 范围从 -128 到 127(占用 8 位 = 1 字节) + twoBytes:int as int16; // 范围从 -32,768 到 32,767 (占用 16 位 = 2 个字节) + + init() { + // 需要在 init() 中初始化,因为它没有默认值 + self.twoBytes = 55*55; + } +} +``` + +整数序列化也适用于 [Structs](/book/structs and-messages#structs) 和 [Messages](/book/structs and-messages#messages) 的字段,以及 [maps](/book/maps) 的键/值类型: + +```tact +struct StSerialization { + martin: Int as int8; +} + +message MsgSerialization { + seamus:Int as int8; + mcFly: map; +} +``` + +动机很简单: + +- 在州[成本](https://docs.ton.org/develop/smart-contracts/fees#how-to-calculate-fees)中存储 $1000$ $257$- 位整数每年约需 $0.184$ ton 。 +- 相比之下,存储 $1000$ $32$-bit 整数每年只需花费 $0.023$ ton 。 + +### 序列化类型 {#serialization-types} + +| 名称 | [TL-B][TLB] | 包容性范围 | 占用空间 | +| :--------------: | :----------------------------------------: | :------------------------: | :----------------------------: | +| `uint8{:tact}` | [`uint8`][tlb-构建] | $0$ 到 $2^{8} - 1$ | $8$ 位 = $1$ 字节 | +| `uint16{:tact}` | [`uint16`][tlb-builtin] | $0$ 到 $2^{16} - 1$ | $16$ 位 = $2$ 字节 | +| `uint32{:tact}` | [`uint32`][tlb-builtin] | $0$ 到 $2^{32} - 1$ | $32$ 位 = $4$ 字节 | +| `uint64{:tact}` | [`uint64`][tlb-builtin] | $0$ 到 $2^{64} - 1$ | $64$ 位 = $8$ 字节 | +| `uint128{:tact}` | [`uint128`][tlb-builtin] | $0$ 到 $2^{128} - 1$ | $128$ 位 = $16$ 字节 | +| `uint256{:tact}` | [`uint256`][tlb-builtin] | $0$ 到 $2^{256} - 1$ | $256$ 位 = $32$ 字节 | +| `int8{:tact}` | [`int8`][tlb-构建] | $-2^{7}$ 至 $2^{7} - 1$ | $8$ 位 = $1$ 字节 | +| `int16{:tact}` | [`int16`][tlb-builtin] | $-2^{15}$ 至 $2^{15} - 1$ | $16$ 位 = $2$ 字节 | +| `int32{:tact}` | [`int32`][tlb-builtin] | $-2^{31}$ 至 $2^{31} - 1$ | $32$ 位 = $4$ 字节 | +| `int64{:tact}` | [`int64`][tlb-builtin] | $-2^{63}$ 至 $2^{63} - 1$ | $64$ 位 = $8$ 字节 | +| `int128{:tact}` | [`int128`][tlb-builtin] | $-2^{127}$ 至 $2^{127} - 1$ | $128$ 位 = $16$ 字节 | +| `int256{:tact}` | [`int256`][tlb-builtin] | $-2^{255}$ 至 $2^{255} - 1$ | $256$ 位 = $32$ 字节 | +| `int257{:tact}` | [`int257`][tlb-builtin] | $-2^{256}$ 至 $2^{256} - 1$ | $257$ 位 = $32$ 字节 + $1$ 位 | +| `coins{:tact}` | [`VarUInteger 16`][varuint] | $0$ 到 $2^{120} - 1$ | $4$ 和 $124$ 位之间,[见下文](#serialization-coins) | + +### 变量 `coins` 类型 {#serialization-coins} + +在 Tact 中,`coins{:tact}` 是[TL-B][tlb]表示法中[`VarUInteger 16`][varuint]的别名,即根据存储给定整数所需的最佳字节数,它的位长是可变的,通常用于存储[nanoToncoin](/book/integers#nanotoncoin)金额。 + +这种序列化格式包括两个 [TL-B 字段](https://docs.ton.org/develop/data-formats/tl-b-language#field-definitions): + +- len",一个 $4$位无符号大二进制整数,存储所提供值的字节长度 +- value",即所提供值的 8 \* len$ 位无符号大二进制表示法 + +也就是说,序列化为 `coins{:tact}` 的整数占用 $4$ 至 $124$ 位(`len`为$4$ 位,`value`为 $0$ 至 $15$ 字节),其值范围为 $0$ 至 $2^{120} - 1$ 之间。 + +例如 + +```tact +struct Scrooge { + // len: 0000, 4 bits (always) + // value: none! + // in total: 4 bits + a: Int as coins = 0; // 0000 + + // len: 0001, 4 bits + // value: 00000001, 8 bits + // in total: 12 bits + b: Int as coins = 1; // 0001 00000001 + + // len: 0010, 4 bits + // value: 00000001 00000010, 16 bits + // in total: 20 bits + c: Int as coins = 258; // 0010 00000001 00000010 + + // len: 1111, 4 bits + // value: hundred twenty 1's in binary + // in total: 124 bits + d: Int as coins = pow(2, 120) - 1; // hundred twenty 1's in binary +} +``` + +:::note + + 点击此处阅读有关序列化的更多信息:[与 FunC 兼容](/book/func#convert-serialization) + +::: + +## 业务 + +所有数字的运行时计算都是在 257 位完成的,因此 [溢出](https://en.wikipedia.org/wiki/Integer_overflow) 非常罕见。 不过,如果任何数学运算出现溢出,就会抛出异常,事务也会失败。 可以说,Tact 的数学默认是安全的。 + +请注意,在同一计算中混合使用 [不同状态大小](#序列化)的变量是没有问题的。 在运行时,无论如何,它们都是相同的类型 - $257$-比特签名,因此不会发生溢出。 + +不过,这仍可能导致在事务的[计算阶段](https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase)出现\*\*\*错误。 请看下面的例子: + +```tact +import "@stdlib/deploy"; + +contract ComputeErrorsOhNo with Deployable { + oneByte: Int as uint8; // persistent state variable, max value is 255 + + init() { + self.oneByte = 255; // initial value is 255, everything fits + } + + receive("lets break it") { + let tmp:int = self.oneByte * 256; // 没有运行时溢出 + self.oneByte = tmp; // 哎呀,tmp 值超出了 oneByte 的预期范围 + } +} +``` + +这里,"oneByte" 被序列化为 [`uint8`](#serialization-types),只占用一个字节,范围从 $0$ 到 $2^8 - 1$,即 $255$。在运行时计算中使用时,不会发生溢出,所有计算结果都是 $257$ 位有符号整数。但是,就在我们决定将 `tmp` 的值存储回 `oneByte` 的那一刻,我们收到了一个错误,[退出代码 5](/book/exit-codes#5),错误信息如下:整数超出预期范围。 + +:::caution + 因此,在使用序列化时,对数字要**非常**小心,并始终仔细检查计算结果。 +::: + +[tlb]: https://docs.ton.org/develop/data-formats/tl-b-language +[tlb-builtin]: https://docs.ton.org/develop/data-formats/tl-b-language#built-in-types +[变量]: https://docs.ton.org/develop/data-formats/msg-tlb#varuinteger-n diff --git a/docs/src/content/docs/zh-cn/book/lifecycle.mdx b/docs/src/content/docs/zh-cn/book/lifecycle.mdx new file mode 100644 index 000000000..b7f760ee4 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/lifecycle.mdx @@ -0,0 +1,28 @@ +--- +title: 信息生命周期 +--- + +合同对信息的处理有几个阶段,其中还有更多的阶段,但我们将集中讨论最重要的几个阶段: + +## 接收阶段 + +该阶段结合了多个低级阶段。 + +它首先会在合同余额**中添加一个**信息值。 输入报文的价值是合约为处理该报文的天然气所能支付的最高价格。 合同可以覆盖此限制,但不建议这样做,只有高级开发人员才适合这样做,因为这可能导致合同被耗尽。 1 million of gas is the maximum amount that a contract can spend in a single contract which equals 1 TON (currently). 如果信息值为零,则执行中止。 + +Then some (usually a small amount) of nanotons is subtracted from the contract balance for storage. 这意味着您无法完美预测平衡的变化,必须根据这种不稳定性调整代码。 + +然后,如果合同尚未部署,且信息中包含初始包,则会部署合同。 如果 init 软件包不存在,它将被忽略。 + +## 计算阶段 + +该阶段执行智能合约的代码,并产生一系列操作或异常。 目前只支持两种操作:**发送信息**和**保留**。 + +发送信息可以使用固定值或动态值,如**信息剩余值**--接收信息的剩余值。发送信息时可以使用 “SendIgnoreErrors”(发送忽略错误)标记,这样在发送信息时就会忽略错误,并继续下一个操作。如果有多个操作,这个标志就很有用。在发送带有某个值的信息时,首先会从收到的值中减去该值,然后才从合约余额中减去该值(在处理之前)。 + +## 行动阶段 + +操作是按顺序执行的,但请注意: +处理操作过程中的**异常不会还原事务** + +例如,如果您从客户的余额中减去 1 ton ,然后发送一条无效信息,这可能会导致客户的余额被减去,但他不会收到。 diff --git a/docs/src/content/docs/zh-cn/book/maps.mdx b/docs/src/content/docs/zh-cn/book/maps.mdx new file mode 100644 index 000000000..7b6adcd37 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/maps.mdx @@ -0,0 +1,362 @@ +--- +title: 地图 +--- + +import { Badge } from '@astrojs/starlight/components'; + +[复合类型](/book/types#composite-types) `map{:tact}` 用于将 `K{:tact}` 类型的键与 `V{:tact}` 类型的相应值关联起来。 + +例如,`map{:tact}` 的键和值使用 [`英特{:tact}`][英特] 类型: + +```tact +struct IntToInt { + counters: map; +} +``` + +## 允许的类型 + +允许的密钥类型 + +- [`Int{:tact}`][int]。 +- [`地址{:tact}`][p] + +允许的值类型: + +- [`Int{:tact}`][int]。 +- [`Bool{:tact}`](/book/types#booleans) +- [`单元格{:tact}`][单元格] +- [`地址{:tact}`][p] +- [结构](/book/structs-and-messages#structs) +- [消息](/book/structs-and-messages#messages) + +## 业务 + +### 声明,`emptyMap()` {#emptymap} + +作为[局部变量](/book/statements#let),使用标准库的 `emptyMap(){:tact}` 函数: + +```tact +let fizz: map = emptyMap(); +let fizz: map = null; // 与前一行相同,但描述性较弱 +``` + +作为 [持久状态变量](/book/contracts#variables): + +```tact +contract Example { + fizz: map; // Int keys to Int values + init() { + self.fizz = emptyMap(); // 冗余,可以删除! + } +} +``` + +请注意,类型为 `map{:tact}` 的 [持久状态变量](/book/contracts#variables) 默认为空,不需要默认值,也不需要在 [`init(){:tact}` 函数](/book/contracts#init-function)中进行初始化。 + +### 设置值,`.set()` {#set} + +要设置或替换键下的值,请调用 `.set(){:tact}` [方法](/book/functions#extension-function),所有地图都可以使用该方法。 + +```tact +// 空 map +let fizz: map = emptyMap(); + +// 在不同的键下设置几个值 +fizz.set(7, 7); +fizz.set(42, 42); + +// 覆盖现有键值对中的一个 +fizz.set(7, 68); // 键 7 现在指向值 68 +``` + +### 获取值,`.get()` {#get} + +通过调用 `.get(){:tact}` [方法](/book/functions#extension-function),检查是否在地图中找到了键,所有地图都可以访问该方法。 如果键丢失,则返回 `null{:tact}`;如果键找到,则返回值。 + +```tact +// Empty map +let fizz: map = emptyMap(); + +// Setting a value +fizz.set(68, 0); + +// Getting the value by its key +let gotButUnsure: Int? = fizz.get(68); // returns Int or null, therefore the type is Int? +let mustHaveGotOrErrored:让 mustHaveGotOrErrored: Int = fizz.get(68)!!; // 明确断言值不能为空, + // 如果值实际上为空,运行时可能会崩溃 + +// 或者,我们可以在 if 语句中检查关键字 +if (gotButUnsure != null) { + // 万岁,现在让我们毫无顾忌地使用 !! 并将 Int? 转换为 Int + 让 definitelyGotIt:Int = fizz.get(68)!!; +} else { + // Do something else... +} +``` + +### 删除条目,`.del()` {#del} + +要删除单个键值对(单个条目),请使用 `.del(){:tact}` [方法](/book/functions#extension-function)。 如果删除成功,则返回 `true{:tact}`,否则返回 `false{:tact}`。 + +```tact +// 空 map +let fizz: map = emptyMap(); + +// 在不同的键下设置几个值 +fizz.set(7, 123); +fizz.set(42, 321); + +// 删除其中一个键 +let deletionSuccess:Bool = fizz.del(7); // true,因为 map 包含了键 7 下的条目 +fizz.del(7); // false,因为 map 不再有键 7 下的条目 + +// 注意,在使用 `.set()` 方法 +// 等同于调用 `.del()`,不过这种方法的描述性要差得多 +// 而且一般不推荐使用: +fizz.set(42, null); // 键 42 下的条目现在被删除了 +``` + +要删除映射表中的所有条目,请使用 `emptyMap(){:tact}` 函数重新分配映射表: + +```tact +// 空 map +let fizz: map = emptyMap(); + +// 在不同的键下设置几个值 +fizz.set(7, 123); +fizz.set(42, 321); + +// 一次性删除所有条目 +fizz = emptyMap(); +fizz = null; // 与上一行相同,但描述性较弱 +``` + +采用这种方法后,即使已将映射声明为持久状态变量,映射之前的所有条目也会从合约中完全删除。 因此,将地图分配给 `emptyMap(){:tact}` **不会**产生任何隐藏或突然的[存储费用](https://docs.ton.org/develop/smart-contracts/fees#storage-fee)。 + +### 检查条目是否存在, `.exists()` {#exists} + +

+ +映射上的 `.exists(){:tact}` [方法](/book/functions#extension-function),如果给定键下的值在映射中存在,则返回 `true{:tact}`,否则返回 `false{:tact}`。 + +```tact +let fizz: map = emptyMap(); +fizz.set(0, 0); + +if (fizz.exists(2 + 2)) { // false + dump("Something doesn't add up!"); +} + +if (fizz.exists(1 / 2)) { // true + dump("I told a fraction joke once. It was half funny."); +} + +if (fizz.get(1 / 2) != null) { // also true, but consumes more gas + dump("Gotta pump more!"); +} +``` + +:::note + + 调用`m.exists(key){:tact}`比执行`m.get(key) != null{:tact}` 更省 gas,尽管两种方法产生的结果是一样的。 + +::: + +### 检查是否为空,`.isEmpty()` {#isempty} + +地图上的 `.isEmpty(){:tact}` [方法](/book/functions#extension-function) 如果地图为空,则返回 `true{:tact}`,否则返回 `false{:tact}`: + +```tact +let fizz: map = emptyMap(); + +if (fizz.isEmpty()) { + dump("Empty maps are empty, duh!"); +} + +// 请注意,将地图与 "空 "进行比较的行为与".isEmpty() "方法相同, +// 尽管这种直接比较的描述性要差得多,一般不鼓励使用: +if (fizz == null) { + dump("Empty maps are null, which isn't obvious"); +} +``` + +### 转换为 `Cell`, `.asCell()` {#ascell} + +在地图上使用 `.asCell(){:tact}` [方法](/book/functions#extension-function),将其所有值转换为 [`单元格{:tact}`][单元格] 类型。 请注意,[`Cell{:tact}`][单元格] 类型最多只能存储 1023 位,因此将更大的映射转换为单元格会导致错误。 + +例如,这种方法适用于在回复正文中直接发送小地图: + +```tact +contract Example { + // Persistent state variables + fizz: map; // our map + + // Constructor (initialization) function of the contract + init() { + // Setting a bunch of values + self.fizz.set(0, 3); + self.fizz.set(1, 14); + self.fizz.set(2, 15); + self.fizz.set(3, 926); + self.fizz.set(4, 5_358_979_323_846); + } + + // Internal message receiver, which responds to empty messages + receive() { + // Here we're converting the map to a Cell and making a reply with it + self.reply(self.fizz.asCell()); + } +} +``` + +### 遍历条目 {#traverse} + +要遍历地图条目,有一个 [`foreach{:tact}`](/book/statements#foreach-loop)循环语句: + +```tact +// Empty map +let fizz: map = emptyMap(); + +// 在不同的键下设置几个值 +fizz.set(42, 321); +fizz.set(7, 123); + +// 按顺序迭代:从最小的键到最大的键 +foreach (key, value in fizz) { + dump(key); // 第一次迭代将转存 7,第二次迭代将转存 42 +} +``` + +了解更多相关信息:[`foreach{:tact}` loop in Book→Statements](/book/statements#foreach-loop). + +请注意,也可以将映射作为简单数组使用,只要定义一个 `map{:tact}`,键为 [`Int{:tact}`][int] 类型,值为任何允许的 `V{:tact}` 类型,并跟踪单独变量中的项数即可: + +```tact +contract Iteration { + // 持久状态变量 + counter:int as uint32; // 地图条目的计数器,序列化为 32 位无符号 + record: map; // Int to Address map + + // 合约的构造函数(初始化) + init() { + self.counter = 0; // 设置 self.counter 为 0 + } // 内部消息接收器,用于响应字符串消息 "Add" receive("Add") { // 获取上下文结构体 let c.counter = 0. + + // 内部消息接收器,响应字符串消息 "Add" + receive("Add") { + // 获取上下文结构 + let ctx:Context = context(); + // 设置条目:counter Int 作为键,ctx.sender Address 作为值 + self.record.set(self.counter, ctx.sender); + // 增加计数器 + self.counter += 1; + } + + + receive("Send") { + // 循环,直到 self.counter 的值(遍历所有 self.record 条目) + let i. Int = 0; // 声明通常的 i.counter = 1; // 增加计数器的值:Int = 0; // 为循环迭代声明通常的 i + while (i < self.counter) { + send(SendParameters{ + bounce: false, // do not bounce back this message + to: self.record.get(i)!!, // set the sender address, knowing that key i exists in the map + value: ton("0.0000001"), // 100 nanoToncoins (nano-tons) + mode:SendIgnoreErrors, // 忽略交易中的错误(如果有的话)发送 + body:"SENDING".asComment() // "SENDING "字符串转换为单元格作为消息正文 + }); + i += 1; // 不要忘记增加 i + } + } + + // 获取 self.record 值的获取函数 + get fun map(): map { + return self.record; + } + + // 获取 self.counter 值的获取函数 + get fun counter():Int { + return self.counter; + } +} +``` + +在此类地图上设置上限限制通常很有用,这样就不会[触及极限](#limits-and-drawbacks)。 + +:::caution + + 请注意,手动记录项目数或检查此类地图的长度非常容易出错,一般不建议使用。 相反,请尝试将您的地图封装到 [Struct](/book/structs-and-messages#structs) 中,并在其上定义 [extension functions](/book/functions#extension-function) 。 参见 Cookbook 中的示例:[如何使用包裹在 Struct 中的 map 来模拟数组](/cookbook/data-structures#array)。 + +::: + +:::note + + 本例改编自 [howardpen9/while-example-tact](https://github.com/howardpen9/while-example-tact/blob/de5807fcd20dba5f6a3748d112511477fb22bfcc/contracts/awesome.tact#L19C10-L19C10). + + 查看 Cookbook 中有关 map 使用的其他示例:\ + [如何使用包裹在 Struct 中的 map 来模拟堆栈](/cookbook/data-structures#stack)\ + [如何使用包裹在 Struct 中的 map 来模拟循环缓冲区](/cookbook/data-structures#circular-buffer) + +::: + +## 序列化 + +可以对映射键、值或两者进行[整数序列化](/zh-cn/book/integers#serialization-types),以[保留空间并降低存储成本](/book/integers#serialization): + +```tact +struct SerializedMapInside { + // 这里的键和值都将序列化为 8 位无符号整数, + // 从而节省空间并降低存储成本: + countersButCompact: map; +} +``` + +:::note + + 了解其他序列化选项:[与 FunC 兼容](/book/func#convert-serialization)。 + +::: + +## 局限性和缺点 + +虽然地图在小范围内使用起来很方便,但如果项目数量不受限制,地图的大小会大幅增加,就会产生很多问题: + +- 由于智能合约状态大小的上限约为 [`Cell{:tact}`][cell] 类型的 $65,000$ 项,因此整个合约的映射存储上限约为 $30,000$ 键值对。 + +- 地图中的条目越多,[计算费](https://docs.ton.org/develop/howto/fees-low-level#computation-fees) 就越高。 因此,处理大型地图使得计算费用难以预测和管理。 + +- 在单个合同中使用大型地图无法分散工作量。 因此,与使用较小的地图和大量交互式智能合约相比,这可能会使整体性能大打折扣。 + +要解决此类问题,可以将地图上的上限限制设置为常数,并在每次为地图设置新值时对其进行检查: + +```tact +contract Example { + // 为我们的 map 声明一个编译时常数上限 + const MaxMapSize:Int = 42; + + // 持久状态变量 + arr: map; // Int 值的 "数组 "作为 map + arrLength:Int = 0; // "数组 "的长度,默认为 0 + + // 将一个项目推到 "数组 "末尾的内部函数 + fun arrPush(item: Int) { + if (self.arrLength >= self.MaxMapSize) { + // 做一些事情,例如停止操作 + } else { + // 继续添加新项 + self.arr.set(self.arrLength, item); + self.arrLength += 1; + } + } +} +``` + +如果您仍然需要大地图或无约束(无限大)地图,最好按照[TON 区块链的异步和基于角色的模型](https://docs.ton.org/learn/overviews/ton-blockchain)来构建您的智能合约。 也就是说,使用合约分片,让整个区块链成为地图的一部分。 + +{/* + TODO:添加对分片页面的引用,如: https://github.com/tact-lang/tact-docs/issues/155 +*/} + +[p]: /book/types#primitive-types +[int]: /book/integers +[电池]: /book/cells#cells diff --git a/docs/src/content/docs/zh-cn/book/masterchain.mdx b/docs/src/content/docs/zh-cn/book/masterchain.mdx new file mode 100644 index 000000000..fc24dda14 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/masterchain.mdx @@ -0,0 +1,49 @@ +--- +title: 主链 +--- + +:::caution + + 除非将[配置文件](/book/config)中的 "masterchain "选项设置为 `true{:json}`,否则主链地址将被视为无效。 + +::: + +在 TON 区块链中,一条名为["主链"](https://docs.ton.org/learn/overviews/ton-blockchain#masterchain-blockchain-of-blockchains) 的特殊链用于同步消息路由和交易执行,因此网络中的节点可以固定多链状态中的某个特定点,并就该状态达成共识。 + +主链存储 [网络配置](/ref/core-advanced#getconfigparam) 和所有 [工作链] 的最终状态(https://docs.ton.org/learn/overviews/ton-blockchain#workchain-blockchain-with-your-own-rules)。 它承载着基本的协议信息,包括当前设置、活动验证器及其赌注列表、活动工作链以及相关的[分块链](https://docs.ton.org/develop/blockchain/shards)。 最重要的是,它为所有工作链和分块链维护最新的区块哈希值记录,从而在整个网络中达成共识。 + +## 主链如何保护合约 + +Tact 强制所有合约使用 [basechain](https://docs.ton.org/develop/blockchain/shards),即 ID 为 $0$ 的默认工作链。这样做是为了防止在合约中使用主链地址。 + +在未[启用主链支持](#support)的情况下,任何指向主链或以其他方式与之交互的尝试都会产生异常,并显示[退出代码 137](/book/exit-codes#137): 此合约未启用 “主链支持”。 + +也就是说,意外部署到主链、从主链账户接收消息、向此类账户发送消息以及使用主链地址或其链 ID ($-1$) 都是默认禁止的。 + +## 在编译选项中启用主链支持 {#support} + +:::caution + +大多数合约不需要部署在主链上,也不需要在主链上进行任何交互。这是因为主链主要用于投票或存储库。如果你不需要参与这些事情,就不需要启用主链支持。 + +::: + +如果您确实需要主链支持,最简单也是最推荐的方法是修改项目根目录下的 [`tact.config.json`](/book/config) 文件(如果还不存在,则创建该文件),并 [将 `masterchain` 属性设置为 `true{:json}`](/book/config#options-masterchain)。 + +如果您正在开发基于 [Blueprint][bp] 的项目,可以在合约的编译配置中启用主链支持,这些配置位于名为 `wrappers/` 的目录中: + +```typescript title="wrappers/YourContractName.compile.ts" {7} +import { CompilerConfig } from '@ton/blueprint'; + +export const compile:CompilerConfig = { + lang: 'tact', + target: 'contracts/your_contract_name.tact', + options:{ + masterchain: true, // ← that's the stuff! + } +}; +``` + +不过,[蓝图][bp] 项目中仍可使用 [`tact.config.json`](/book/config)。 在这种情况下,除非在 `wrappers/` 中修改,否则 [`tact.config.json`](/book/config)中指定的值将作为默认值。 + +[bp]: https://github.com/ton-org/blueprint diff --git a/docs/src/content/docs/zh-cn/book/message-mode.mdx b/docs/src/content/docs/zh-cn/book/message-mode.mdx new file mode 100644 index 000000000..fc466018f --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/message-mode.mdx @@ -0,0 +1,59 @@ +--- +title: Message `mode` +--- + +如前所述,信息是通过结构体 `SendParameters{:tact}` 的 `mode` 参数发送的。这是一个[`Int{:tact}`][int]值,由基本模式和可选标志(也是[`Int{:tact}`][int]值)组合而成。 + +您可以使用原始的 [`Int{:tact}`][int]值,并手动为 `mode` 提供这些值,但为了方便起见,您可以使用一组常量来轻松构建复合 `mode`。有关基本模式和可选标记的更多信息,请参阅下表。 + +## 基本模式 {#base-modes} + +| 模式值 | 恒定名称 | 说明 | +| ---------: | :------------------- | ---------------------------- | +| $0$ | - | 普通信息(默认)。 | +| $64$ | SendRemainingValue | 除了新报文中最初显示的值外,还携带入站报文的所有剩余值。 | +| $128$ | SendRemainingBalance | 携带当前智能合约的所有余额,而不是信息中最初显示的值。 | + +## 可选标记 {#optional-flags} + +| 标志值 | 恒定名称 | 说明 | +| ---------: | :--------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| $+1$ | SendPayGasSeparately | 将转发费用与信息价值分开支付。 | +| $+2$ | SendIgnoreErrors | 忽略行动阶段处理该信息时出现的任何错误。 | +| $+16$ | SendBounceIfActionFail | 如果在操作阶段出现任何错误,则退回交易。 Has no effect if flag $+2$, SendIgnoreErrors is used. | +| $+32$ | SendDestroyIfZero | Current account must be destroyed if its resulting balance is zero (often used with mode $128$, SendRemainingBalance). | + +## 将模式与标志相结合 + +要为 `SendParameters{:tact}` 的 `mode` 字段创建 [`Int{:tact}`][int] 值,只需通过 [bitwise OR](/book/operators#binary-bitwise-or) 运算将基本模式与可选标记结合起来。 + +例如,如果您想分别发送普通信息和支付转账费用,请使用模式 $0$(默认)和标志 $+1$,以获得 `mode` $= 1$,这等同于使用 `SendPayGasSeparately{:tact}` 常量。 + +或者,如果要发送全部合约余额并立即销毁,使用模式 $128$ 和标志 $+32$,得到 `mode` $= 160$,相当于 `SendRemainingBalance | SendDestroyIfZero{:tact}` 常量。 + +下面是后一个示例的代码: + +```tact +let to: Address = ...; +let value: Int = ton("1"); +send(SendParameters{ + to: to, + value: value, + mode: SendRemainingBalance | SendDestroyIfZero, + body: "Hello, World!".asComment(), +}); +``` + +:::caution + + 请注意,虽然可以将([`+{:tact}`](/book/operators#binary-add))基本模式与可选标志一起添加,但由于可能会出现多余的值,因此不鼓励这样做。 请使用位wise OR ([`|{:tact}`](/book/operators#binary-bitwise-or)),因为它是为处理此类标志和对 `mode` 的位操作而设计的。 + +::: + +:::note + + 还要注意的是,[基本模式](#base-modes) 只能有一种,但[可选标记](#optional-flags) 的数量可以不同:可以全部使用,也可以不使用或只使用其中一些。 + +::: + +[int]: /book/integers diff --git a/docs/src/content/docs/zh-cn/book/operators.mdx b/docs/src/content/docs/zh-cn/book/operators.mdx new file mode 100644 index 000000000..30d41cbfa --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/operators.mdx @@ -0,0 +1,642 @@ +--- +title: 操作员 +--- + +几乎所有合约都对数据进行操作:将某些值转换成另一个值。 范围可能各不相同,但运营商是此类修改的核心。 + +本页按照[优先级](#precedence)的递减顺序列出了 Tact 中的所有运算符,并附有使用示例。 + +:::note + + 需要注意的是,Tact 中没有隐式类型转换,因此运算符不能用来添加不同类型的值,或者在不明确转换为相同类型的情况下比较它们的相等性。 这是通过标准库中的某些函数实现的。 请参阅 [`Int.toString(){:tact}`](/ref/core-strings#inttostring),了解此类函数的示例。 + +::: + +## 操作员表 {#table} + +下表列出了按 [优先级](#precedence):从高到低递减的运算符。 + +| 简要说明 | 操作员 | +| :------------- | :------------------------------------------------------------------------------------------ | +| 括号 | [`(){:tact}`][父母] | +| 一元后缀 | [`!!{:tact}`][nna] | +| 一元前缀 | [`+{:tact}`][正]   [`-{:tact}`][负]   [`!{:tact}`][负]   [`~{:tact}`][b-not]。 | +| 乘法 | [`*{:tact}`][mul]   [`/{:tact}`][div]   [`%{:tact}`][mod]。 | +| 添加剂 | [`+{:tact}`][加]   [`-{:tact}`][分] | +| 轮班 | [`>>{:tact}`][shr]   [`<<{:tact}`][shl]。 | +| 关系 | [`>{:tact}`][gt]   [`>={:tact}`][ge]   [`<{:tact}`][lt]   [`<={:tact}`][le]。 | +| 平等 | [`=={:tact}`][eq]   [`!={:tact}`][eq]。 | +| 位与 | [`&{:tact}`][b-and]。 | +| 比特 XOR | [`^{:tact}`][b-xor]。 | +| 位操作 OR | [`\|{:tact}`][b-or]。 | +| 逻辑与 | [`&&{:tact}`][l-and]。 | +| 逻辑或 | [`\|\|{:tact}`][l-or] | +| 三元 | [`?:{:tact}`][三元] | +| 任务 | [`={:tact}`][赋值] 和 [所有增强赋值运算符](#augmented-assignment) | + +[父母]: #parentheses +[nna]: #unary-non-null-assert +[加号]: #unary-plus +[否]: #unary-negate +[请]: #unary-inverse +[不]: #unary-bitwise-not +[桑葚]: #binary-multiply +[分裂]: #binary-divide +[模式]: #binary-modulo +[增加]: #binary-add +[字幕]: #binary-subtract +[缩]: #binary-bitwise-shift-right +[shl]: #binary-bitwise-shift-left +[gt]: #binary-greater +[ge]: #binary-greater-equal +[lt]: #binary-less +[勒]: #binary-less-equal +[等式]: #二元平等 +[b 和]: #binary-bitwise-and +[b-xor]: #binary-bitwise-xor +[或]: #binary-bitwise-or +[和]: #binary-logical-and +[或]: #binary-logical-or +[三元]: #ternary +[分配]: <# 分配> + +## 优先级 {#precedence} + +本页所有运算符的优先级从高到低依次递减。 优先级用于选择在特定情况下考虑哪个运算符。 每当出现模棱两可的情况时,Tact 会优先选择优先级较高的运算符,而不是优先级较低的运算符。 + +例如,减号 (`-{:tact}`) 可被视为减法运算符或否定运算符,它将表达式的正负符号颠倒过来,反之亦然。 由于在两者有歧义的情况下,后者的优先级高于前者,Tact 将首先把 `-{:tact}` 视为否定操作符。 如果这对给定表达式没有意义,它才会将其视为减法运算符。 + +请看下面的代码 + +```tact +5 + -5; // 在这里,减号将被视为否定运算符 +5 -5; // 而在这里,尽管有格式限制,它仍将被视为减法运算符 +``` + +尽管这个例子可能很简单,但忽略优先级规则往往会导致运算符出现混乱的情况。 由于括号在所有表达式和运算符中具有最高优先级,因此用[括号](#parentheses)封装每个操作可以确保正确的操作顺序。 + +## 括号,`()` {#parentheses} + +括号(也可称为圆括号,`(){:tact}`)与其说是实际的运算符,不如说是一种标点符号,但其[优先级](#优先级)高于任何其他运算符的优先级。 使用括号可覆盖运算顺序: + +```tact +5 * 5 - 2; // 23 +5 * (5 - 2); // 15 +``` + +## 一元 + +这里的"一元"是指只应用于给定表达式的一个操作数。除了[非空断言](#unary-non-null-assert),所有一元运算符都具有相同的[优先级](#precedence)。 + +一元运算符可以是两种类型之一: + +- 前缀 - 放在表达式之前。 +- 后缀(或后缀) - 放在表达式之后。 + +### 非空断言,`!!` {#unary-non-null-assert} + +一元双叹号(_非空断言_)运算符 `!{:tact}`是一个后缀运算符,它强制执行非`null{:tact}`值,如果可选变量不是`null{:tact}`,则允许直接访问可选变量的值。 否则,如果编译器可以跟踪,则引发编译错误;如果不能跟踪,则抛出[退出代码 128](/book/exit-codes#128)异常:空引用异常"。 可应用于任何可选变量,无论其类型是`非空{:tact}`。 + +:::note + + 点击此处了解更多有关可选变量和字段的信息:[可选变量](/book/optionals) + +::: + +### 加号,`+` {#unary-plus} + +虽然 Tact 编译器的语法中指定了一元加号运算符 `+{:tact}`,但它只作为 [二元运算符](#binary-add)存在。 + +### 否定,`-` {#unary-negate} + +一元减号(_negation_)运算符 `-{:tact}` 是一个前缀运算符,用于反转表达式的符号。 只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let five:Int = 5; +five + -five; // 在这里,减号是一个否定运算符,而不是减法运算符 +-(-1); // 双倍应用返回原始值,即 1 +--1; // 1 +``` + +### 逆,`!` {#unary-inverse} + +一元感叹号(_inversion_)运算符 `!{:tact}` 是一个前缀运算符,用于反转表达式的布尔值——将 `true{:tact}` 变为 `false{:tact}`,反之亦然。只能应用于 [`Bool{:tact}`][bool] 类型的值: + +```tact +let iLikeTact:Bool = true; +!iLikeTact; // false +!false; // true +!(!false); // false +!!false; // false +``` + +### 双向 NOT, `~` {#unary-bitwise-not} + +单引号 tilde(_bitwise not_)运算符 `~{:tact}` 是一个前缀运算符,它将表达式二进制表示中的每一位反转或_flip_,即把 $1$ 改为 $0$,反之亦然。 只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let answer:Int = 42; +~answer; // -43 +~(~answer); // 42 +~(~0); // 0 +~~0; // 0 +``` + +## 二进制 + +二进制运算符按[优先级](#precedence)递减的顺序分成几个小节。 每个小节中的操作符与小节本身具有相同的 [优先级](#precedence)。 + +### 乘法 {#binary-multiplication} + +乘、除或求余数。 + +#### 乘法,`*` {#binary-multiply} + +二进制星号 (_multiplication_) 运算符 `*{:tact}` 用于两个值的乘法运算。 可能导致 [整数溢出](/book/integers#operations)。 + +只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two * two; // 4 +0 * 1_000_000_000; // 0 +-1 * 5; // -5 + +pow(2, 255) * pow(2, 255); // build error: integer overflow! +``` + +#### 除法,`/` {#binary-divide} + +二进制斜线 (_division_) 运算符 `/{:tact}` 用于两个值的整除,如果结果为正,则向零截断,如果结果为负,则从零截断。这也叫[向下舍入](https://en.wikipedia.org/wiki/Rounding#Rounding_down)(或向 $-\infty$ 舍入)。 + +如果尝试除以零,则会出现[退出代码 4](/book/exit-codes#4)错误:整数溢出。 + +只能应用于 [`Int{:tact}`][int] 类型的值: + +```tact +let two: Int = 2; +two / 2; // 1 +two / 1; // 2 +-1 / 5; // -1 +-1 / -5; // 0 +1 / -5; // -1 +1 / 5; // 0 +6 / 5; // 1,四舍五入 +-6 / 5; // -2,四舍五入(向-∞方向)。 +``` + +:::note + + 请注意,对于 `Int{:tact}` 类型,除法运算符和模数运算符之间的以下关系始终成立: + +```tact +a / b * b + a % b == a; // 对于 `a` 和 `b` 的任何 Int 值均为 true, + // 除了当 `b` 等于 0 且我们用 0 除以 `a` 时, + // 这是尝试除以 0,结果会出错。 +``` + +::: + +#### Modulo, `%` {#binary-modulo} + +二进制百分号 (_modulo_) 运算符 `%{:tact}` 用于获取整数除法的模数,不能与获取余数混淆。 对于符号相同的两个值,模运算和余运算是等价的,但当操作数的符号不同时,模运算的结果总是与_除数_(右边的值)的符号相同,而余运算的结果与_除数_(左边的值)的符号相同,这可能使它们相差一个单位的_除数_。 + +只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two % 2; // 0 +two % 1; // 1 + +1 % 5; // 1 +-1 % 5; // 4 +1 % -5; // -4 +-1 % -5; // -1 +``` + +避免两者混淆的最简单方法是通过 [`abs(x: Int){:tact}`](/ref/core-math#abs)优先使用正值: + +```tact +abs(-1) % abs(-5); // 1 +``` + +:::note + + 你知道吗,在 JavaScript 中,`%{:tact}` 可以作为_remainder_运算符,但不能作为_modul_运算符(比如在 Tact 中)? + [Remainder (%) - JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder#description) + [Modulo - Wikipedia](https://en.wikipedia.org/wiki/Modulo) + +::: + +### 加法 {#binary-addition} + +加法或减法。 + +#### 添加,`+` {#binary-add} + +二进制加法运算符 `+{:tact}` 用于将数字相加。 超出 [`Int{:tact}`][int]的最大值将导致[退出代码 4](/book/exit-codes#4)错误:整数溢出"。 + +只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two + 2; // 4 +-1 + 1; // 0 + +pow(2, 254) + pow(2, 254); // 2 * 2^254 +pow(2, 255) + pow(2, 255); // 编译错误:整数溢出! +pow(2, 255) - 1 + pow(2, 255); // 2^256 - 1,Tact 中任何整数的最大值! +``` + +#### 减去,`-` {#binary-subtract} + +二进制减号(_subtraction_)运算符 `-{:tact}` 用于将数字相减。 超出 [`Int{:tact}`][int]的最小值将导致[退出代码 4](/book/exit-codes#4)错误:整数溢出。 + +只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two - 2; // 0 +-1 - 1; // -2 + +pow(2, 254) - pow(2, 254); // 0 +pow(2, 255) - pow(2, 255); // 0 +pow(2, 256) - pow(2, 256); // build error: integer overflow! +``` + +### 位移 {#binary-bitwise-shifts} + +向左或向右移动位。 + +#### 右移,`>>` {#binary-bitwise-shift-right} + +二进制双大于号(_位向右移动_)运算符 `>>{:tact}` 返回一个整数,其二进制表示为*左操作数*的值向右移动了*右操作数*的位数。向右移位的多余位被丢弃,最左边位的副本从左边移入。这种操作也称为"符号向右移动"或"算术向右移动",因为结果数字的符号与左操作数的符号相同。这是一种更有效的方法,即用 $2^n$ 除以*左操作数*,其中 $n$ 等于*右操作数*。 + +只能应用于 [`Int{:tact}`][int] 类型的值: + +```tact +let two: Int = 2; +two >> 1; // 1 +4 >> 1; // 2 +5 >> 1; // 2,由于整数值的下限 + +pow(2, 254) >> 254; // 1 +``` + +:::note + + [Bit shifts - Wikipedia](https://en.wikipedia.org/wiki/Bitwise_operation#Bit_shifts) + [Bit manipulation - Wikipedia](https://en.wikipedia.org/wiki/Bit_manipulation) + +::: + +#### 左移,`<<` {#binary-bitwise-shift-left} + +二进制双小于号(_bitwise shift left_)运算符 `<<{:tact}` 返回一个整数,其二进制表示为*左操作数*的值向左移动*右操作数*的位数。向左移位的多余比特被丢弃,零比特从右边移入。这是一种更有效的方法,可以将*左操作数*乘以 $2^n$,其中 $n$ 等于*右操作数*。超出 [`Int{:tact}`][int] 的最大值将导致[退出代码 4](/book/exit-codes#4)错误:整数溢出。 + +只能应用于 [`Int{:tact}`][int] 类型的值: + +```tact +let two:Int = 2; +two << 1; // 4 +1 << 5; // 1 * 2^5, 即 32 +2 << 5; // 2 * 2^5, 即 64 + +pow(2, 254) == (1 << 254); // true +pow(2, 254) == 1 << 254;// true,由于 >> 优先于 == +pow(2, 255) == 1 << 255; // true,但我们在这里非常接近溢出,所以不需要括号! +``` + +:::note + + [Bit shifts - Wikipedia](https://en.wikipedia.org/wiki/Bitwise_operation#Bit_shifts) + [Bit manipulation - Wikipedia](https://en.wikipedia.org/wiki/Bit_manipulation) + +::: + +### 关系 {#binary-relation} + +查找更大、更小或相等的数值。 + +#### 大于,`>` {#binary-greater} + +二进制_大于_运算符 `>{:tact}` 如果左操作数大于右操作数,则返回 `true{:tact}`,否则返回 `false{:tact}`。 只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two > 2; // false +-1 > -3; // true +``` + +#### 大于或等于,`>=` {#binary-greater-equal} + +二进制_大于或等于_运算符 `>={:tact}` 如果左操作数大于或等于右操作数,则返回 `true{:tact}`,否则返回 `false{:tact}`。 只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two >= 2; // true +-1 >= -3; // true +``` + +#### 小于,`<` {#binary-less} + +二进制 _less than_ 运算符 `<{:tact}` 如果左操作数小于右操作数,则返回 `true{:tact}`,否则返回 `false{:tact}`。 只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two < 2; // false +-1 < -3; // false +``` + +#### 小于或等于,`<=` {#binary-less-equal} + +二进制_小于或等于_运算符 `<={:tact}` 如果左操作数小于或等于右操作数,则返回 `true{:tact}`,否则返回 `false{:tact}`。 只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two <= 2; // true +-1 <= -3; // false +``` + +### 平等与不平等,```===``!=``` {#binary-equality} + +二进制相等(_equal_)运算符 `=={:tact}` 检查其两个操作数是否_equal_,返回结果类型 [`Bool{:tact}`][bool]。 + +二元不等式(_not equal_)运算符 `!={:tact}` 检查其两个操作数是否_not equal_,返回一个 [`Bool{:tact}`][bool] 类型的结果。 + +除了 [`Cell{:tact}`][cell]和 [`Slice{:tact}`][slice]类型会通过哈希值进行隐式比较外,这两种操作符都要求操作数为相同类型,并且都不执行隐式类型转换。 + +这两种运算符都可以应用于下列类型和值: + +- [`Int{:tact}`][int]。 +- [`Bool{:tact}`][bool]。 +- [`地址{:tact}`][p] +- [`单元格{:tact}`][单元格],通过`.hash(){:tact}`隐式比较 +- [`片{:tact}`][片],通过`.hash(){:tact}`隐式比较 +- [`字符串{:tact}`][p] +- [`map{:tact}`](/book/maps),但前提是它们的键和值类型相同 +- [选项和 `null{:tact}` 值](/book/optionals) + +```tact +// Int: +2 == 3; // false +2 != 3; // true + +// Bool: +true == true; // true +false != true; // true + +// Address: +myAddress() == myAddress(); // true +myAddress() != myAddress(); // false + +// Cell: +emptyCell() == emptyCell(); // true +emptyCell() != emptyCell(); // false + +// Slice: +"A".asSlice() == "A".asSlice(); // true +"A".asSlice() != "A".asSlice(); // false + +// String: +"A" == "A"; // true +"A" != "A"; // false + +// map: +let map1: map = emptyMap(); +let map2: map = emptyMap(); +map1 == map2; // true +map1 != map2; // false + +// 可选项和空值本身 +let nullable:Int? = null; +nullable == null; // true +null == null; // true +nullable != null; // false +null != null; // false + +let anotherNullable:= 5; +nullable == anotherNullable; // false +nullable != anotherNullable; // true +``` + +### 比特 AND, `&` {#binary-bitwise-and} + +二进制安培(_比特 AND_)运算符 `&{:tact}` 应用[比特 AND](https://en.wikipedia.org/wiki/Bitwise_operation#AND),对操作数的每一对相应比特执行[逻辑 AND](#binary-bitwise-and)运算。 当我们要清除一个数字的选定位时,这一点非常有用,因为每个位都代表一个单独的标志或布尔状态,这使得每个整数可以 "存储 "多达 $257$ 个布尔值,因为 Tact 中的所有整数都是 $257$- 位有符号的。 + +只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two & 1; // 0 +4 & 1; // 0 +3 & 1; // 1 +1 & 1; // 1 + +255 & 0b00001111; // 15 +0b11111111 & 0b00001111; // 15 +``` + +:::note + + [Bitwise AND - Wikipedia](https://en.wikipedia.org/wiki/Bitwise_operation#AND) + [Bit manipulation - Wikipedia](https://en.wikipedia.org/wiki/Bit_manipulation) + +::: + +### 比特 XOR, `^` {#binary-bitwise-xor} + +二进制符串(_位向 XOR_)运算符 `^{:tact}` 应用 [位向 XOR](https://en.wikipedia.org/wiki/Bitwise_operation#XOR),对操作数的每一对相应位执行 [逻辑排他 OR](https://en.wikipedia.org/wiki/Exclusive_or)运算。 如果只有一个位是 $1$,则每个位置的结果都是 $1$ ,但如果两个位都是 $0$ 或两个位都是 $1$,则结果都是 $0$。 在这种情况下,它会对两个比特进行比较,如果两个比特不同,则给出 $1$ ;如果两个比特相同,则给出 $0$。 + +它适用于将操作数的选定位反转(也称为切换或翻转),因为任何位都可以通过与 $1$进行 "XOR "来切换。 + +只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two ^ 3; // 1 +4 ^ 1; // 0 +3 ^ 1; // 3 +1 ^ 1; // 0 + +255 ^ 0b00001111; // 240 +0b111111 11 ^ 0b00001111; // 240 +``` + +:::note + + [Bitwise XOR - Wikipedia](https://en.wikipedia.org/wiki/Bitwise_operation#XOR)/ + [Bit manipulation - Wikipedia](https://en.wikipedia.org/wiki/Bit_manipulation) + +::: + +### 位wise OR, `|` {#binary-bitwise-or} + +二进制条形 (_bitwise OR_) 运算符 `|{:tact}` 应用 [bitwise OR](https://en.wikipedia.org/wiki/Bitwise_operation#OR),对操作数的每一对相应位执行 [logical OR](#binary-logical-or) 运算。 当我们要应用特定的 [bitmask](https://en.wikipedia.org/wiki/Mask_(computing)) 时,这很有用。 + +例如,_bitwise OR_ 通常用于 Tact 中的[将基本模式与可选标记结合](/book/message-mode#combining-modes-with-flags),方法是将特定位屏蔽到 $1$ ,以构建目标[信息`模式`](/book/message-mode)。 + +只能应用于 [`Int{:tact}`][int]类型的值: + +```tact +let two: Int = 2; +two | 1; // 3 +4 | 1; // 5 +3 | 1; // 3 +1 | 1; // 1 + +255 | 0b00001111; // 255 +0b111111 | 0b00001111; // 255 +``` + +:::note + + [Bitwise OR - Wikipedia](https://en.wikipedia.org/wiki/Bitwise_operation#OR) + [Bit manipulation - Wikipedia](https://en.wikipedia.org/wiki/Bit_manipulation) + +::: + +### 逻辑 AND, `&&` {#binary-logical-and} + +二进制逻辑 AND([逻辑连接](https://en.wikipedia.org/wiki/Logical_conjunction))运算符 `&&{:tact}` 如果两个操作数都是 `true{:tact}`,则返回 `true{:tact}`,否则返回 `false{:tact}`。 它是短路的,也就是说,如果左操作数是 `false{:tact}`,它会立即将整个表达式求值为 `false{:tact}`,而不求值右操作数。 + +只能应用于 [`Bool{:tact}`][bool]类型的值: + +```tact +let iLikeTact:Bool = true; +iLikeTact && true; // true, evaluated both operands +iLikeTact && false; // false, evaluated both operands +false && iLikeTact; // false, didn't evaluate iLikeTact +``` + +### 逻辑 OR, `||` {#binary-logical-or} + +二元逻辑 OR([逻辑析取](https://en.wikipedia.org/wiki/Logical_disjunction))运算符 `||{:tact}` 只有当两个操作数都是 `false{:tact}` 时,才返回 `false{:tact}`,否则返回 `true{:tact}`。 它是短路的,也就是说,如果左操作数是 `true{:tact}`,它会立即将整个表达式评估为 `true{:tact}`,而不评估右操作数。 + +只能应用于 [`Bool{:tact}`][bool]类型的值: + +```tact +let iLikeSnails:Bool = false; +iLikeSnails || true; // true, evaluated both operands +iLikeSnails || false; // false, evaluated both operands +true || iLikeSnails; // true, didn't evaluate iLikeSnails +``` + +## 三元,`?:` {#ternary} + +条件(_ternary_)运算符是唯一一个包含三个操作数的 Tact 运算符:一个条件,后面跟一个问号(`?{:tact}`),然后是如果条件被评估为`true{:tact}`时要执行的表达式,后面跟一个冒号(`:{:tact}`),最后是如果条件被评估为`false{:tact}`时要执行的表达式。 该运算符常用于替代 [`if...else{:tact}`](/book/statements#if-else) 语句。 + +条件必须解析为 [`布尔{:tact}`][布尔] 类型: + +```tact +// condition +// ↓ +true ?"incredibly so" :"absolutely not"; // "incredibly so" +// --------------- ---------------- +// ↑ ↑ +// | alternative, when condition is false +// | +// consequence, when condition is true + +2 + 2 == 4 ? true : false; // true +``` + +三元运算符是除[赋值相关运算符](#赋值)外唯一具有右关联性的运算符。 这意味着,在模棱两可的情况下,Tact 会优先选择最长的匹配序列。 简而言之,这使得三元运算符的无括号嵌套成为可能,但仅限于替代情况(冒号 `:{:tact}` 后面的部分): + +```tact +// 在其他情况下不需要额外的括号 +false ?1 : (false ? 2 : 3); // 3 +false ?1 : false ?2 : 3; // 也是 3 +false ?1 : true ?2 : 3; // 2 + +// 后果情况需要额外的括号(介于 ? 和 : 之间的部分) +false ?(false ? 1 : 2) : 3; // 3 +false ?1 : 2 : 3; // SYNTAX ERROR! +true ?(false ? 1 : 2) : 3; // 2 +``` + +## 赋值,`=` {#assignment} + +赋值操作符 `={:tact}` 用于为变量或 [Message](/book/structs-and-messages#messages) 或 [Struct](/book/structs-and-messages#structs) 的属性赋值。 赋值是一个语句,不返回值。 + +```tact +let someVar:Int = 5; // 这里使用了赋值运算符 =... +someVar = 4; // ... 这里 +someVar = (someVar = 5); // SYNTAX ERROR! +``` + +### 增强赋值运算符 {#augmented-assignment} + +增强(或复合)赋值运算符,如 `+={:tact}`,将操作与 [赋值](#assignment)结合起来。 增强赋值是一个语句,不返回值。 + +扩充赋值在语义上等同于常规赋值,只是多了一个操作: + +```tact +let value:Int = 5; + +// this: +value += 5; +// 等同于 this: +value = value + 5; +``` + +增强赋值运算符列表: + +- `+={:tact}`,使用 [加法运算符 `+{:tact}`](#binary-addition)。 只能应用于 [`Int{:tact}`][int]类型的值。 +- `-={:tact}`,使用 [减法运算符 `-{:tact}`](#binary-subtract)。 只能应用于 [`Int{:tact}`][int]类型的值。 +- `*={:tact}`,使用 [乘法运算符 `*{:tact}`](#binary-multiply)。 只能应用于 [`Int{:tact}`][int]类型的值。 +- `/={:tact}`,使用 [除法运算符 `/{:tact}`](#binary-divide)。 只能应用于 [`Int{:tact}`][int]类型的值。 +- `%={:tact}`,使用 [modulo 运算符 `%{:tact}`](#binary-modulo)。 只能应用于 [`Int{:tact}`][int]类型的值。 +- `&={:tact}`,使用 [bitwise AND 运算符 `&{:tact}`](#binary-bitwise-and)。 只能应用于 [`Int{:tact}`][int]类型的值。 +- `^={:tact}`,它使用 [bitwise XOR 运算符 `^{:tact}`](#binary-bitwise-xor)。 只能应用于 [`Int{:tact}`][int]类型的值。 +- `|={:tact}`,它使用 [bitwise OR 运算符 `|{:tact}`](#binary-bitwise-or)。 只能应用于 [`Int{:tact}`][int]类型的值。 + +```tact +let value:Int = 5; + +// += +value + 5; // 加 5 +value = value + 5; // 加 5 并返回结果 +value += 5; // 也加 5 并返回结果 + +// -= +value - 5; // 减 5 +value = value - 5; // 减 5 并返回结果 +value -= 5; // 也减 5 并返回结果 + +// *= +value * 5; // 乘以 5 +value = value * 5; // 乘以 5 并返回结果 +value *= 5; // 也乘以 5 并返回结果 + +// /= +value / 5; // 除以 5 +value = value / 5; // 除以 5 并返回结果 +value /= 5; // 也除以 5 并返回结果 + +// %= +value % 5; // 得到 5 的模数 +value = value % 5; // 得到 5 的模数并返回结果 +value %= 5; // 也得到 5 的模数并返回结果 + +// &= +value & 5; // 位和 5 +value = value & 5; // 位和 5 并返回结果 +value &= 5; // 也位和 5 并返回结果 + +// ^= +value ^ 5; // 位似 XOR 5 +value = value ^ 5; // 位似 XOR 5 并返回结果 +value ^= 5; // 也是位似 XOR 5 并返回结果 + +// |= +value | 5; // 位似 OR 5 +value = value | 5; // 位似 OR 5 并返回结果 +value |= 5; // 也是位似 OR 5 并返回结果 +``` + +[p]: /book/types#primitive-types +[bool]: /book/types#booleans +[int]: /book/integers +[电池]: /book/cells#cells +[一片]: /book/cells#slices diff --git a/docs/src/content/docs/zh-cn/book/optionals.mdx b/docs/src/content/docs/zh-cn/book/optionals.mdx new file mode 100644 index 000000000..48e13b234 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/optionals.mdx @@ -0,0 +1,42 @@ +--- +title: 可选项 +--- + +[类型系统概述](/book/types#optionals)中提到,所有[原始类型](/book/types#primitive-types)、[结构体](/book/structs-and-messages#structs)和[消息](/book/structs-and-messages#messages)都可以为空。也就是说,除了 `null{:tact}`(一种特殊值,表示故意没有任何其他值)之外,它们不一定持有任何值。 + +[变量](/book/statements#let)或[结构](/book/structs-and-messages#structs)和[消息](/book/structs-and-messages#messages)中可容纳 `null{:tact}` 的字段被称为"可选项"。当变量不一定被使用时,它们对减少状态大小很有用。 + +在变量或字段的类型声明后添加问号 (`?{:tact}`),就可以将其设为可选变量或字段。唯一的例外是 [`map{:tact}`](/book/maps)和 [`bounced{:tact}`](/book/bounced),你不能让它们、内部键/值类型(如果是 map)或内部[消息](/book/structs-and-messages#messages)(如果是 bounced)成为可选项。 + +未定义的可选变量或可选字段默认为 `null{:tact}` 值。如果不先检查 `null{:tact}`,就无法访问它们。但如果你确定它们在某一时刻不是 `null{:tact}`,可以使用[非空断言操作符 `!!{:tact}`](/book/operators#unary-non-null-assert)访问它们的值。 + +在未使用 [`!!{:tact}`](/book/operators#unary-non-null-assert)或未事先检查 `null{:tact}` 的情况下尝试访问可选变量或可选字段的值,如果编译器可以跟踪,则会导致编译错误;如果不能跟踪,则会导致[退出代码 128](/book/exit-codes#128)异常:空引用异常。 + +可选项举例: + +```tact +struct StOpt { + opt:Int?; // Int 或 null +} + +message MsOpt { + opt:StOpt?; // 注意,在此定义中如何使用 struct StOpt +} + +contract Optionals { + opt:Int?; + address:Address?; + + init(opt: Int?) { // optionals as parameters + self.opt = opt; + self.address = null; // explicit null value + } + + receive(msg: MsOpt) { + let opt:= 12; // defining a new variable + if (self.opt != null) { // explicit check + self.opt = opt!!; // using !! as we know that opt value isn't null + } + } +} +``` diff --git a/docs/src/content/docs/zh-cn/book/programmatic.mdx b/docs/src/content/docs/zh-cn/book/programmatic.mdx new file mode 100644 index 000000000..3edd0536f --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/programmatic.mdx @@ -0,0 +1,47 @@ +--- +title: 程序化 API +--- + +您可以在节点和浏览器环境中从代码中调用 Tact 编译器。 + +:::caution + + 该应用程序接口尚未发布。 它将在 1.0.0 版本中发布。 + +::: + +## 在浏览器中运行编译器 + +```ts +import { run } from "@tact-lang/compiler"; + +// Virtual FS +const fs = { + ["main.tact"]:Buffer.from("...").toString("base64"), +}; + +const config = { + projects:[ + { + name: "Sample", + path:"main.tact", + output:"./output", + }, + ], +}; + +// Run compiler +let successful = await run({ config, fs }); + +// NOTE: Output from is written to the same fs object. +``` + +## 合同核查 + +您可以使用 `verify` 函数验证编译后的软件包。 + +```ts +import { verify } from "@tact-lang/compiler"; +const pkg: string = '...'; +const res = await verify(pkg); +``` diff --git a/docs/src/content/docs/zh-cn/book/receive.mdx b/docs/src/content/docs/zh-cn/book/receive.mdx new file mode 100644 index 000000000..b69d60270 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/receive.mdx @@ -0,0 +1,54 @@ +--- +title: 接收信息 +--- + +TON 是一个分布式区块链,这意味着合约之间的通信是通过发送和接收信息完成的。 最常见的信息类型是内部信息--从一个合约(或钱包)发送到另一个合约(或钱包)的信息。 + +## 接收内部信息 + +要接收所需类型的信息,您需要声明一个接收函数,例如`receive(“increment”){:tact}`。这个符号表示声明一个接收函数,当向合约发送值为 `“increment”{:tact}`的文本时,该函数将被调用。函数体可以修改合约的状态,并向其他合约发送信息。不可能直接调用接收器。如果需要重用某些逻辑,可以声明一个函数,然后从接收器中调用。 + +有多个接收器函数。所有接收器函数的处理顺序如下: + +* `receive(){:tact}` - 向合约发送空信息时调用。 +* `receive("message"){:tact}` - 向合约发送带有特定注释的文本信息时调用。 +* `receive(str: String){:tact}` - 向合约发送任意文本信息时调用。 +* `receive(msg: MyMessage){:tact}`-当向合约发送 “MyMessage ”类型的二进制信息时调用。 +* `receive(msg: Slice){:tact}` - 向合约发送未知类型的二进制信息时调用。 + +```tact + +message MyMessage { + value: Int; +} + +contract MyContract { + receive() { + // ... + } + receive("message") { + // ... + } + receive(str: String) { + // ... + } + receive(msg: MyMessage) { + // ... + } + receive(msg: Slice) { + // ... + } +} +``` + +用下划线`_{:tact}`命名接收函数的参数时,其值将被视为未使用的值并被丢弃。 当您不需要检查接收到的信息,而只想让它传达特定的操作码时,这就很有用了: + +```tact +message(42) UniverseCalls {} + +contract Example { + receive(_: UniverseCalls) { + // Got a Message with opcode 42 + } +} +``` diff --git a/docs/src/content/docs/zh-cn/book/send.mdx b/docs/src/content/docs/zh-cn/book/send.mdx new file mode 100644 index 000000000..922270973 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/send.mdx @@ -0,0 +1,159 @@ +--- +title: 发送信息 +--- + +TON 区块链是基于消息的--要与其他合约通信和部署新合约,您需要发送消息。 + +Tact 中的消息通常使用内置[Struct](/book/structs-and-messages#structs) `SendParameters{:tact}`组成,它由以下部分组成: + +| 现场 | 类型 | 说明 | +| :---- | :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 弹跳 | [`Bool{:tact}`][p] | `bounce` - if set to `true` (default) then the message will be bounced back to the sender if the receiver contract doesn't exist or wasn't able to process the message. | +| 到 | [`地址{:tact}`][p] | TON 区块链中的内部接收器 [`地址{:tact}`][p]。 | +| 值 | [`Int{:tact}`][int]。 | `value` in TON - the amount of TON you want to send with the message. This value is used to cover gas fees on the receiver side. | +| 模式 | [`Int{:tact}`][int]。 | 一个 8 位值,用于配置发送信息的方式,默认值为 $0$。 参见:[消息`模式`](/book/message-mode)。 | +| 身体 | [`Cell?{:tact}`][单元格] | [可选][选项]信息正文作为[`单元格{:tact}`][单元格] | +| 代码 | [`Cell?{:tact}`][单元格] | [可选][opt] 合同的初始代码(编译后的字节码) | +| 数据 | [`Cell?{:tact}`][单元格] | [可选][选项]合同的初始数据(合同的[`init(){:tact}`函数](/book/contracts#init-function)的参数) | + +字段 `code` 和 `data` 被称为 [初始包](/book/expressions#initof),用于部署新合约。 + +## 发送简单回复 {#send-simple-reply} + +最简单的信息是对接收信息的回复,返回信息的所有多余值: + +```tact +receive() { + self.reply("Hello, World!".asComment()); // asComment converts a String to a Cell with a comment +} +``` + +## 发送信息 + +如果需要更高级的逻辑,可以直接使用 `send(){:tact}` 函数和 `SendParameters{:tact}` [Struct](/book/structs-and-messages#structs) 。 + +事实上,前面使用 [`.reply(){:tact}`](#send-simple-reply)的示例可以通过调用下面的`send(){:tact}`函数来实现: + +```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 + }); +} +``` + +另一个示例是向指定的 [`Address{:tact}`][p]发送一条信息,信息的`值`为 $1$ TON,`body`为带有 [`String{:tact}`][p] `"Hello, World!"{:tact}`的注释: + +```tact +let recipient: Address = ...; +let value: Int = ton("1"); +send(SendParameters{ + // bounce is set to true by default + to: recipient, + value: value, + mode: SendIgnoreErrors, // will send the message despite any errors + body: "Hello, World!".asComment(), +}); +``` + +[可选标记](/book/message-mode#optional-flags) `SendIgnoreErrors{:tact}`表示即使在发送信息过程中发生错误,也会继续发送下一条信息。**发送阶段的任何错误都不会导致事务逆转**。 + +## 发送打字信息 + +要发送二进制键入信息,可以使用以下代码: + +```tact +let recipient: Address = ...; +let value: Int = ton("1"); +send(SendParameters{ + // bounce is set to true by default + to: recipient, + value: value, + mode: SendIgnoreErrors, // don't stop in case of errors + body: SomeMessage{arg1: 123, arg2: 1234}.toCell(), +}); +``` + +## 部署合约 + +要部署一个合约,你需要用 [`initOf{:tact}`](/book/expressions#initof)计算它的地址和初始状态,然后在初始化信息中发送它们: + +```tact +let init: StateInit = initOf SecondContract(arg1, arg2); +let address: Address = contractAddress(init); +let value: Int = ton("1"); +send(SendParameters{ + // bounce is set to true by default + to: address, + value: value, + mode: SendIgnoreErrors, // don't stop in case of errors + code: init.code, + data: init.data, + body: "Hello, World!".asComment(), // not necessary, can be omitted +}); +``` + +## 外发信息处理 + +TON 区块链上的每笔交易都由 [多个阶段][阶段] 组成。 出站报文在[计算阶段][计算]中进行评估,但\*\*\*不在该阶段发送。 在[计算阶段][compute]中列出的所有操作,如外向消息或[储备请求](/ref/core-advanced#nativereserve),都会在[操作阶段][phases]中执行。 + +由于所有值都是在[计算阶段][compute]中计算的,所有费用都是在计算结束前计算的,而且在[操作阶段][phases]中出现异常时不会恢复交易,因此向外发送信息可能会因[操作费](https://docs.ton.org/develop/howto/fees-low-level#action-fee) 或[转发费][fwdfee]不足而失败,不会出现跳转。 + +请看下面的例子: + +```tact +// This contract initially has 0 nanoToncoins on the balance +contract FailureIsNothingButAnotherStep { + // And all the funds it gets are obtained from inbound internal messages + receive() { + // 1st outbound message evaluated and queued (but not sent yet) + send(SendParameters{ + to: sender(), + value: ton("0.042"), // plus forward fee due to SendPayGasSeparately + mode: SendIgnoreErrors | SendPayGasSeparately, + }); + + // 2nd outbound message evaluated and queued (but not sent yet, and never will be!) + send(SendParameters{ + to: sender(), + value: 0, + mode: SendRemainingValue | SendIgnoreErrors, + }); + } +} +``` + +在那里,第二条信息实际上不会被发送: + +- [计算阶段][计算]结束后,计算合约的剩余价值 $\mathrm{R}$。 + +- 在出站信息处理过程中,假设入站信息中提供了足够的价值,第一条信息会在余额上留下 $\mathrm{R} - (0.042 + \mathrm{forward\_fees})$ [nanoToncoins](/book/integers#nanotoncoin) 。 + +- 处理第二条信息时,合约会尝试发送 $\mathrm{R}$ [纳米通币](/book/integers#nanotoncoin),但发送失败,因为剩余的金额已经较少。 + +:::note + + 有关所有信息发送功能的更多信息,请参阅参考资料: + +- [`send(){:tact}`](/ref/core-common#send) +- [`emit(){:tact}`](/ref/core-common#emit) +- [`self.notify(){:tact}`](/ref/core-base#self-notify) +- [`self.reply(){:tact}`](/ref/core-base#self-reply) +- [`self.forward(){:tact}`](/ref/core-base#self-forward) +- [`nativeSendMessage(){:tact}`](/ref/core-advanced#nativesendmessage) + +::: + +[p]: /book/types#primitive-types +[int]: /book/integers +[电池]: /book/cells#cells +[选择]: /book/optionals + +[阶段]: https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases +[算]: https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase +[纳米]: /book/integers#nanotoncoin +[fwdfee]: https://docs.ton.org/develop/howto/fees-low-level#forward-fees diff --git a/docs/src/content/docs/zh-cn/book/statements.mdx b/docs/src/content/docs/zh-cn/book/statements.mdx new file mode 100644 index 000000000..92e61efd7 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/statements.mdx @@ -0,0 +1,416 @@ +--- +title: 发言 +--- + +以下语句可出现在 [function](/book/functions) 主体的任何位置。 + +## `let`语句 {#let} + +`let{:tact}` 语句允许声明局部变量和 [block](#block)-scoped 变量。 + +在 Tact 中,声明一个局部变量总是需要一个初始值。但是,也可以省略类型标注,Tact 会尝试从初始值推断类型: + +```tact +let value:Int = 123; // 包含类型和值的完整声明 +let vInferred = 123; // 推断类型 Int + +let vExplicitCtx:Context = context(); // 显式类型 Context,内置结构 +let vCtx = context(); // 推断类型 Context +``` + +请注意,`null{:tact}`的初始值既可以指具有任意 `K{:tact}`和 `V{:tact}`类型的空[`map{:tact}`](/book/maps),也可以指故意不为[optional](/book/optionals)类型设置任何其他值。 这就是为什么在声明 [optional](/book/optionals) 或 [`map{:tact}`](/book/maps)时,需要明确指定类型,因为无法推断: + +```tact +let vOptional: Int? = null; // explicit type Int or null +let vOptInt = 42; // implicit type Int +vOptInt = null; // CompILATION ERROR! + +let vMap: map = emptyMap(); // explicit type map +let vMapWithSerialization: map = emptyMap(); +``` + +用下划线 `_{:tact}` 命名局部变量时,其值将被视为未使用并丢弃。 当你不需要某个函数的返回值(有副作用),并想明确地将变量标记为未使用时,这种方法就很有用。 注意,不能访问通配符变量名 `_{:tact}`: + +```tact +let _ = someFunctionWithSideEffects(); // with type inference +let _: map = emptyMap(); // with explicit type + +dump(_); // COMPILATION ERROR!无法访问 _ +``` + +## `return` 语句 {#return} + +`return{:tact}` 语句结束 [function](/book/functions) 的执行,并指定要返回给 [function](/book/functions) 调用者的值。 + +```tact +// Simple wrapper over stdlib function now() +fun getTimeFromNow(offset: Int): Int { + return now() + offset; +} +``` + +## block + +块语句用于组合零个或多个语句。 块由一对大括号("大括号"、`{}{:tact}`)分隔,包含一个由零个或多个语句和声明组成的列表。 + +某些语句,如 [`let{:tact}`](#let) 或 [`return{:tact}`](#return),必须以结束分号 `;{:tact}` 结束。 不过,语块中最后一条语句的分号是可选的,可以省略。 + +```tact +{ // <- 块的开始 + // 任意语句: + let value:Int = 2 + 2; + dump(value); +}// <- 程序块的结束 + +{ dump(2 + 2) }// 一个只有一条语句的代码块, + // 省略了最后也是唯一的分号 + +{ + let nah = 3 * 3 * 3; // 一个有两条语句的代码块, + let yay = nah + 42 // 但没有最后的分号 +} +``` + +## 表达 + +表达式语句是一种表达式,用于预期需要语句的地方。 表达式被求值后,其结果将被丢弃--因此,它只适用于有副作用的表达式,如执行函数或更新变量。 + +```tact +dump(2 + 2); // stdlib 函数 +``` + +## 任务 + +赋值语句使用 [赋值运算符](/book/operators#assignment) (`={:tact}`)或 [增强赋值运算符](/book/operators#augmented-assignment) (赋值与运算相结合): + +```tact +let value:Int; // 声明 +value = 5; // 赋值 +value += 5; // 增强赋值(众多赋值之一,见下文) +``` + +:::note + + 有关赋值和增强赋值的更多信息,请参阅其专门章节:[赋值运算符](/book/operators#assignment)。 + +::: + +## 分支机构 + +控制代码流 + +### `if...else` {#if-else} + +:::caution + + 需要使用大括号(代码块)! + +::: + +在执行 `if...else{:tact}` 语句时,首先会对指定条件进行评估。 如果结果值为 `true{:tact}`,则执行下面的语句块。 否则,如果条件评估结果为 `false{:tact}`,将执行可选的 `else{:tact}` 块。 如果缺少 `else{:tact}` 块,则什么也不会发生,执行仍将继续。 + +常规 `if{:tact}` 语句: + +```tact +// condition +// ↓ +if (true) { // consequence, when condition is true + dump(2 + 2); +} +``` + +`else{:tact}` 块: + +```tact +// condition +// ↓ +if (2 + 2 == 4) { + // consequence, when condition is true + dump(true); +} else { + // alternative, when condition is false + dump(false); +} +``` + +使用嵌套的 `if...else{:tact}`: + +```tact +// condition +// ↓ +if (2 + 2 == 3) { + // consequence, when condition is true + dump("3?"); +// condition2 +// ↓ +} else if (2 + 2 == 4) { + // another consequence, when condition2 is true + dump(true); +} else { + // alternative, when both condition and condition2 are false + dump(false); +} +``` + +:::note + + Tact 也有一个三元表达式 `?:{:tact}`,在本书前面已有介绍:[三元表达式](/book/operators#ternary)。 + +::: + +### `try...catch` {#try-catch} + +`try...catch{:tact}`语句由一个 `try{:tact}`块和一个可选的 `catch{:tact}`块组成,它接收一个 [`Int{:tact}`][int][退出代码](/book/exit-codes)作为唯一参数。 首先执行 `try{:tact}`块中的代码,如果失败,则执行`catch{:tact}`块中的代码,并尽可能回滚`try{:tact}` 块中的更改。 + +:::note + + 请注意,某些 TVM 状态参数(如编码页和 gas 计数器)不会回滚。 也就是说,`try{:tact}`程序块中的所有 gas 使用量都将被考虑在内,而改变 gas 限值的操作码的效果也将被保留。 + +::: + +常规 `try{:tact}` 语句: + +```tact +fun braveAndTrue() { + // 让我们尝试做一些错误的事情 + try { + nativeThrow(42); // 抛出退出代码 42 + } // 下面的内容将被执行,因为上面的错误代码被封装在 try 块中 dump(42); } + + // 下面的代码将被执行,因为上面的错误代码被封装在一个 try 块中 + dump(42); +} +``` + +用 `catch (e){:tact}` 块: + +```tact +fun niceCatch() { + // 让我们尝试做一些错误的事情 + try { + nativeThrow(42); // 抛出退出代码 42 + } catch (err) { + dump(err); // 这将转储捕获的退出代码,即 42 + } +} +``` + +使用嵌套的 `try...catch{:tact}`: + +```tact +try { + // Preparing an x equal to 0, in such a way that Tact compiler won't realize it (yet!) + let xs: Slice = beginCell().storeUint(0, 1).endCell().beginParse(); + let x: Int = xs.loadUint(1); // 0 + + try { + throw(101); // 1. throws with exit code 101 + } catch (err) { // 2. catches the error and captures its exit code (101) as err + return err / x; // 3. divides err by x, which is zero, throwing with exit code 4 + } + +} catch (err) { // 4. catches the new error and captures its exit code (4) as err + // ^^^ this works without name collisions because the previous err + // has a different scope and is only visible inside the previous catch block + + dump(err); // 5. dumps the last caught exit code (4) +} +``` + +请注意,与 [`let{:tact}` 语句](#let)类似,在 `catch (){:tact}` 子句中捕获的[退出代码](/book/exit-codes)可以通过指定下划线 `_{:tact}` 来丢弃: + +```tact +try { + throw(42); +} catch (_) { + dump("I don't know the exit code anymore"); +} +``` + +:::note + + 在专用页面上阅读更多有关退出代码的信息:[图书中的出口代码](/book/exit-codes)。 + +::: + +## 循环 + +有条件地多次重复某些代码块。 + +### `repeat` {#repeat-loop} + +`repeat复{:tact}`循环执行指定次数的代码块。重复次数应该是一个正的 $32$ 位 [`Int{:tact}`][int],范围从 $1$ 到 $2^{31} - 1$。如果数值大于这个范围,将出现 [exit code 5](/book/exit-codes#5),“Integer out of the expected range ”的错误。 + +如果指定的重复次数等于 $0$ 或包含范围 $-2^{256}$ 至 $-1$ 中的任何负数,则忽略该值,不执行代码块。 + +```tact +let twoPow: Int = 1; + +// Repeat exactly 10 times +repeat (10) { + twoPow *= 2; +} + +// Skipped +repeat (-1) { + twoPow *= 3333; +} + +twoPow; // 1024 +``` + +### `while` {#while-loop} + +只要给定条件为 `true{:tact}`,`while{:tact}` 循环就会继续执行代码块。 + +在下面的示例中,每次迭代时,`x` 的值都会递减 $1$,因此循环将运行 $10$ 次: + +```tact +let x: Int = 10; +while(x > 0) { + x = x - 1; +} +``` + +### `do...until` {#do-until-loop} + +`do...until{:tact}`循环是一个后测试循环,它至少执行一次代码块,然后继续执行,直到给定条件变为 `true{:tact}`。 + +在下面的示例中,每次迭代时,`x` 的值都会递减 $1$,因此循环将运行 $10$ 次: + +```tact +let x: Int = 10; +do { + x = x - 1; # do something no matter at least one time +} until (x <= 0); +``` + +### `foreach` {#foreach-loop} + +`foreach{:tact}` 循环按顺序对 [`map{:tact}`](/book/maps) 类型的键值对(条目)进行操作:从 map 的最小键到最大键。 + +该循环为给定映射中的每个条目执行一个代码块,每次迭代都会捕获键和值。 当您事先不知道地图中有多少个条目,或不想明确地使用地图的 [`.get(){:tact}`](/book/maps#get) [method](/book/functions#extension-function) 查找每个条目时,这将非常方便。 + +请注意,每次迭代时捕获的键和值对的名称是任意的,可以是任何有效的 Tact 标识符,只要它们是当前作用域的新标识符即可。 最常见的选项是k "和 "v",或 "key "和 "value"。 + +在下面的示例中,地图 `cells` 有 $4$ 个条目,因此循环将运行 $4$ 次: + +```tact +// 空地图 +let cells: map = emptyMap(); + +// 设置四个条目 +cells.set(1, beginCell().storeUint(100, 16).endCell()); +cells.set(2, beginCell().storeUint(200, 16).endCell()); +cells.set(3, beginCell().storeUint(300, 16).endCell()); +cells.set(4, beginCell().storeUint(400, 16).endCell()); + +// 用于求数值总和的变量 +let sum:Int = 0; + +// 对于单元格映射中的每个键和值对,执行: +foreach (key, value in cells) { // or just k, v + let s:Slice = value.beginParse(); // 将单元格转换为 Slice + sum += s.loadUint(16); // Slice 值的总和 +} +dump(sum); // 1000 +``` + +还可以遍历合约存储中的映射,以及作为 [Struct](/book/structs-and-messages#structs) 或 [Message](/book/structs-and-messages#messages) 类型实例成员的映射: + +```tact +import "@stdlib/deploy"; + +struct Fizz { oh_my: map } +message Buzz { oh_my: map } + +contract Iterated { + oh_my: map; + + receive("call to iterate!") { + let oh_my: map = emptyMap(); + oh_my.set(0, 42); + oh_my.set(1, 27); + + self.oh_my = oh_my; // 将本地 map 赋值给存储空间 + let fizz = Fizz{ oh_my }; // 字段双关语 + let buzz = Buzz{ oh_my }; // 字段双关语 + + // 遍历合同存储空间中的 map + foreach (key, value in self.oh_my) { + // ... + } + + // 遍历 Struct Fizz 实例的 map 成员 + foreach (key, value in fizz.oh_my) { + // ... + } + + // 遍历 Message Buzz 实例的 map 成员 + foreach (key, value in buzz.oh_my) { + // ... + } + } +} +``` + +请注意,与 [`let{:tact}` 语句](#let)类似,可以通过指定下划线 `_{:tact}` 来丢弃捕获的键或值(或两者): + +```tact +// 空地图 +let quartiles: map = emptyMap(); + +// 设置一些条目 +quartiles.set(1, 25); +quartiles.set(2, 50); +quartiles.set(3, 75); + +// 丢弃捕获的键 +// 不在地图本身中修改它们 +foreach (_, value in quartiles) {} + +// 丢弃捕获的值 +// 不在映射本身中修改它们 +foreach (key, _ in quartiles) {} + +// 丢弃键和值 +// 不在 map 本身中修改它们 +foreach (_, _ in quartiles) { + // 不能通过 _ 访问,但可以进行所需的操作 + // n 次,其中 n 是当前 map 的长度 +} +``` + +:::caution + + 请注意,目前 `foreach{:tact}` 只适用于明确提供的映射标识符和嵌套标识符结构,如 `foo.bar.targetMap{:tact}` 或 `self.baz.targetMap{:tact}`。 也就是说,从函数中返回一个映射并试图遍历其条目是行不通的: + +```tact +foreach (k, v in emptyMap()) { +// ^ 这将给出以下错误信息: +// foreach 只允许在路径表达式( +// 即标识符)或直接合同/结构/消息访问序列( +// 如 "self.foo "或 "self.structure.field")的映射上运行 +} +``` + + 试图遍历函数返回的 [Struct](/book/structs-and-messages#structs)的 map 成员也是行不通的,因为函数调用是一种表达式,而不是标识符或嵌套标识符访问: + +```tact +foreach (k, v in genCoolStruct().map) { +// ^ 这将给出以下错误信息: +// foreach 只允许在路径表达式( +// 即标识符)或直接合同/结构/消息访问序列( +// 如 "self.foo "或 "self.structure.field")的映射上使用 +} +``` + +::: + +:::note + + 有关其他循环示例,请参阅[循环示例](https://tact-by-example.org/04-loops)。 + +::: + +[initOf]: /book/integers diff --git a/docs/src/content/docs/zh-cn/book/structs-and-messages.mdx b/docs/src/content/docs/zh-cn/book/structs-and-messages.mdx new file mode 100644 index 000000000..8c0a026b3 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/structs-and-messages.mdx @@ -0,0 +1,301 @@ +--- +title: 结构和信息 +--- + +Tact 支持许多专为智能合约使用而定制的 [原始数据类型](/book/types#primitive-types)。 不过,使用单独的存储方式往往会变得繁琐,因此有 [Structs](#structs) 和 [Messages](#messages)可以将类型组合在一起。 + +:::caution + + **警告**:目前无法**循环**类型。 这意味着结构/消息 **A** 的字段不能与结构/消息 **B** 的字段相同。 + + 因此,以下代码**无法**编译: + + ```tact + struct A { + circularFieldA: B; + } + + struct B { + impossibleFieldB:A; + } + ``` + +::: + +## 结构 {#structs} + +结构体可以定义包含多个不同类型字段的复杂数据类型。 它们还可以嵌套。 + +```tact +struct Point { + x:Int as int64; + y:Int as int64; +} + +struct Line { + start:point; + end:point; +} +``` + +结构体还可以包含默认字段和定义[可选类型]字段(/book/optionals)。 如果您有很多字段,但又不想一直在 [new instances](#instantiate)中为它们指定通用值,那么这将非常有用。 + +```tact +struct Params { + name: String = "Satoshi"; // default value + + age: Int?; // field with an optional type Int? + // 默认值为空 + + point:Point; // 嵌套结构 +} +``` + +结构体还可用作获取器或其他内部函数的返回值。 它们有效地允许单个获取器返回多个返回值。 + +```tact +contract StructsShowcase { + params:Params; // Struct 作为合约的持久状态变量 + + init() { + self.params = Params{ + point:Point{ + x: 4, + y: 2, + }, + }; + } + + get fun params():Params { + return self.params; + } +} +``` + +请注意,结构声明中的最后一个分号 `;`是可选项,可以省略: + +```tact +struct Mad { ness: Bool } + +struct MoviesToWatch { + wolverine:String; + redFunnyGuy: String +} +``` + +字段的顺序很重要,因为它与[TL-B 模式](https://docs.ton.org/develop/data-formats/tl-b-language) 中的内存布局一致。 不过,与某些采用手动内存管理的语言不同,Tact 在字段之间没有任何填充。 + +## 信息 {#messages} + +消息中可以包含 [结构体](#structs): + +```tact +struct Point { + x:Int; + y:Int; +} + +message Add { + point:Point; // holds a struct Point +} +``` + +消息与 [结构体](#structs)几乎相同,唯一不同的是,消息在序列化时有一个 32 位整数头,包含唯一的数字 id,通常称为 _opcode_(操作码)。 这使得信息可以与 [接收者](/book/receive)一起使用,因为合约可以根据这个 id 区分不同类型的信息。 + +Tact 会为每个接收到的信息自动生成这些唯一 ID(操作码),但也可以手动覆盖: + +```tact +// 此消息用 0x7362d09c 覆盖其唯一 ID +message(0x7362d09c) TokenNotification { + forwardPayload:Slice as remaining; +} +``` + +这对于要处理特定智能合约的某些操作码(如 [Jetton standard](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md))的情况非常有用。 该合约能够处理的操作码简表为[此处以 FunC 表示](https://github.com/ton-blockchain/token-contract/blob/main/ft/op-codes.fc)。 它们是智能合约的接口。 + +:::note + + 更深入的信息请参见: + [Convert received messages to `op` operations](/book/func#convert-received-messages-to-op-operations)\ + [Internal message body layout in TON Docs](https://docs.ton.org/develop/smart-contracts/guidelines/internal-messages#internal-message-body)\ + [Messages of the Jetton implementation in Tact](https://github.com/howardpen9/jetton-implementation-in-tact/blob/9eee917877a92af218002874a9f2bd3f9c619229/sources/messages.tact)\ + [Jetton Standard in Tact on Tact-by-Example](https://tact-by-example.org/07-jetton-standard) + +::: + +## 业务 + +### 实例化 + +创建 [Struct](#structs) 和 [Message](#messages) 实例类似于 [function calls](/book/expressions#static-function-call),但需要用大括号 `{}{:tact}`(大括号)代替小括号 `(){:tact}`指定参数: + +```tact +struct StA { + field1:Int; + field2: Int; +} + +message MsgB { + field1:String; + field2: String; +} + +fun example() { + // Instance of a Struct StA + StA{ + field1: 42, + field2: 68 + 1, // trailing comma is allowed + }; + + // Instance of a Message MsgB + MsgB{ + field1:"May the 4th", + field2: "be with you!", // trailing comma is allowed + }; +} +``` + +当分配给字段的变量或常量的名称与该字段的名称重合时,Tact 提供了一种方便的语法快捷方式,有时称为字段双关。 有了它,你就不必输入多余的内容: + +```tact +struct PopQuiz { + vogonsCount:Int; + nicestNumber: Int; +} + +fun example() { + // 让我们引入几个变量 + let vogonsCount:Int = 42; + let nicestNumber: Int = 68 + 1; + + // 你可以像往常一样实例化 Struct 并将变量赋值给字段, + // 但这样做有时会有点重复和乏味 + PopQuiz{ vogonsCount: vogonsCount, nicestNumber: nicestNumber }; + + // 让我们使用字段双关语并减少键入, + // 因为我们的变量名恰好与字段名相同 + PopQuiz{ + vogonsCount, + nicestNumber, // 这里也允许使用尾部逗号! + }; +} +``` + +:::note + + 因为实例化是 Tact 中的一个表达式,所以在相关页面中也有描述:[实例化表达式](/book/expressions#instantiation)。 + +::: + +### 转换为 `Cell`, `.toCell()` {#tocell} + +通过使用 `.toCell(){:tact}` [扩展函数](/book/functions#extension-function),可以将任意 [Struct](#structs) 或 [Message](#messages) 转换为 [`单元格{:tact}`][单元格] 类型: + +```tact +struct Big { + f1: Int; + f2: Int; + f3: Int; + f4:Int; + f5: Int; + f6: Int; +} + +fun conversionFun() { + dump(Big{ + f1: 10000000000, f2: 10000000000, f3: 10000000000, + f4: 10000000000, f5: 10000000000, f6: 10000000000, + }.toCell()); // x{...cell with references...} +} +``` + +:::note + + 参见参考资料中的扩展函数: + [`Struct.toCell(){:tact}`](/ref/core-cells#structtocell)/ + [`Message.toCell(){:tact}`](/ref/core-cells#messagetocell)。 + +::: + +### 从 `Cell` 或 `Slice` 获取,`.fromCell()` 和 `.fromSlice()` {#fromcellslice} + +无需通过一系列相关的 `.loadSomething(){:tact}` 函数调用来手动解析 [`Cell{:tact}`][cell] 或 [`Slice{:tact}`][slice],而是可以使用 `.fromCell(){:tact}` 和 `.fromSlice(){:tact}` [扩展函数](/book/functions#extension-function)。这些扩展函数将所提供的 [`Cell{:tact}`][cell] 或 [`Slice{:tact}`][slice] 转换为所需的 [Struct](#structs) 或 [Message](#messages)。 + +这些扩展函数仅尝试根据 [Struct](#structs) 或 [Message](#messages) 的结构解析 [`Cell{:tact}`][cell] 或 [`Slice{:tact}`][slice]。如果布局不匹配,可能会抛出各种异常——确保用 [`try...catch{:tact}`](/book/statements#try-catch) 块封装代码,以防止意外结果。 + +```tact +struct Fizz { foo: Int } +message(100) Buzz { bar: Int } + +fun constructThenParse() { + let fizzCell = Fizz{foo: 42}.toCell(); + let buzzCell = Buzz{bar: 27}.toCell(); + + let parsedFizz:Fizz = Fizz.fromCell(fizzCell); + let parsedBuzz: Buzz = Buzz.fromCell(buzzCell); +} +``` + +:::note + + 参见参考资料中的扩展函数: + [`Struct.fromCell(){:tact}`][st-fc]/ + [`Struct.fromSlice(){:tact}`][st-fs]/ + [`Message.fromCell(){:tact}`][msg-fc]/ + [`Message.fromSlice(){:tact}`][msg-fs]/ 。 + +::: + +### 转换法 + +只要通过 `.toCell(){:tact}` 和 `.fromCell(){:tact}` 函数在 [`单元格{:tact}`][单元格]/[`切片{:tact}`][切片] 和 [结构体](#structs)/[消息](#messages) 之间进行转换,以下规律就会成立: + +- 对于任何 [Struct](#structs)/[Message](#messages)类型的实例,调用`.toCell(){:tact}`,然后对结果应用`Struct.fromCell(){:tact}`(或`Message.fromCell(){:tact}`),就会得到原始实例的副本: + +```tact {8-9,13-14} +struct ArbitraryStruct {} +message(0x2A) ArbitraryMessage {} + +fun lawOne() { + let structInst = ArbitraryStruct{}; + let messageInst = ArbitraryMessage{}; + + ArbitraryStruct.fromCell(structInst.toCell()); // = structInst + ArbitraryMessage.fromCell(messageInst.toCell()); // = messageInst.toCell()); // = messageInst + + // 切片也一样,有 .toCell().asSlice() 和 .fromSlice() + + ArbitraryStruct.fromSlice(structInst.toCell().asSlice()); // = structInst + ArbitraryMessage.fromSlice(messageInst.toCell().asSlice()); // = messageInst +} +``` + +- 对于任何与给定 [Struct](#structs)/[Message](#messages) 具有相同 [TL-B](https://docs.ton.org/develop/data-formats/tl-b-language) 布局的 [`单元格{:tact}`][单元格],调用 `Struct.fromCell(){:tact}`(或 `Message.fromCell(){:tact}`),然后通过 `.toCell(){:tact}` 将结果转换为 [`Cell{:tact}`][单元格],就会得到原始 [`单元格{:tact}`][单元格] 的副本: + +```tact {9-10,15-16} +struct ArbitraryStruct { val: Int as uint32 } +message(0x2A) ArbitraryMessage {} + +fun lawTwo() { + // 使用 32 位来存储 42,这样这个 cellInst 就可以 + // 在处理 ArbitraryStruct 和 ArbitraryMessage 时重复使用 + let cellInst = beginCell().storeUint(42, 32).endCell(); + + ArbitraryStruct.fromCell(cellInst).toCell(); // = cellInst + ArbitraryMessage.fromCell(cellInst).toCell(); // = cellInst + + // Slices 也是如此,使用 .fromSlice() 和 .toCell().asSlice() + let sliceInst = cellInst.asSlice(); + + ArbitraryStruct.fromSlice(sliceInst).toCell().asSlice(); // = sliceInst + ArbitraryMessage.fromSlice(sliceInst).toCell().asSlice(); // = sliceInst +} +``` + +[st-fc]: /ref/core-cells#structfromcell +[st-fs]: /ref/core-cells#structfromslice +[msg-fc]: /ref/core-cells#messagefromcell +[msg-fs]: /ref/core-cells#messagefromslice +[p]: /book/types#primitive-types +[电池]: /book/cells#cells +[一片]: /book/cells#slices diff --git a/docs/src/content/docs/zh-cn/book/types.mdx b/docs/src/content/docs/zh-cn/book/types.mdx new file mode 100644 index 000000000..78b1532b4 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/types.mdx @@ -0,0 +1,176 @@ +--- +title: 类型系统概述 +--- + +Tact 程序中的每个变量、项目和值都有一个类型。 它们可以是 + +- [原始类型](#primitive-types) 之一 +- 或 [复合类型](#复合类型) + +Additionally, many of those types [can be made nullable](#optionals). + +## 原始类型 {#primitive-types} + +Tact 支持许多专为智能合约定制的原始数据类型: + +- `Int{:tact}` — Tact 中的所有数字都是 257 位有符号整数,但可以使用[较小的表示方法](/book/integers#serialization)来减少存储成本。 +- `Bool{:tact}` — 经典布尔类型,具有 `true{:tact}` 和 `false{:tact}` 值。 +- `Address{:tact}` — TON 区块链中的标准[智能合约地址](https://docs.ton.org/learn/overviews/addresses#address-of-smart-contract)。 +- [`Cell{:tact}`](/book/cells#cells)、[`Builder{:tact}`](/book/cells#builders)、[`Slice{:tact}`](/book/cells#slices) — [TVM][tvm] 的底层基元。 +- `String{:tact}` — 不可变的文本字符串。 +- `StringBuilder{:tact}` — 辅助类型,允许以节省 gas 的方式连接字符串。 + +[tvm]: https://docs.ton.org/learn/tvm-instructions/tvm-overview + +### 布尔 {#booleans} + +原始类型 `Bool{:tact}` 是经典的布尔类型,只能容纳两个值:`true{:tact}` 和 `false{:tact}`。它便于布尔和逻辑运算,也便于存储标志。 + +Tact 中没有隐式类型转换,因此两个布尔值的加法([`+{:tact}`](/book/operators#binary-add))是不可能的。这里有许多比较[运算符](/book/operators),例如: + +- `&&{:tact}` 为 [logical AND](/book/operators#binary-logical-and) +- `||{:tact}` 为 [logical OR](/book/operators#binary-logical-or) +- `!{:tact}` 为 [logical inversion](/book/operators#unary-inverse) +- `=={:tact}` 和 `!={:tact}` 用于检查[相等](/book/operators#binary-equality) +- `!{:tact}` 表示[非空断言](/book/optionals) + +将 bools 持久化为状态非常节省空间,因为它们只占用 1 位。 每年在州[花费](https://ton.org/docs/develop/smart-contracts/fees#how-to-calculate-fees)存储 1000 个布尔约需 $0.00072$ ton 。 + +## 复合类型 + +使用单独的存储手段往往会变得繁琐,因此有办法将多个[原始类型](#primitive-types)组合在一起,创建复合类型: + +- [Maps](#maps) - 键与值的关联。 +- 复合类型,如[结构体和消息](#structs-and-messages) +- [可选项](#optionals) - [结构体和消息](#structs-and-messages)变量或字段的`null{:tact}`值。 + +除上述复合类型外,Tact 还提供了一种特殊的类型构造函数[`bounced{:tact}`](/book/bounced),它只能在[弹回消息接收器](/book/bounced)中指定。 + +请注意,虽然[合约](#contracts)和[特质](#traits)也被视为Tact类型系统的一部分,但我们不能像[结构体和消息](#structs-and-messages)那样传递它们。相反,我们可以使用[`initOf{:tact}`](/book/expressions#initof)表达式来获取给定合约的初始状态。 + +### 映射 {#maps} + +类型`map{:tact}`用于将数据与相应的键关联起来。 + +```tact +let mapExample: map = emptyMap(); // 带有 Int 键和值的空地图 +``` + +在专门页面了解更多信息:[地图][地图]。 + +[[Maps](#maps)]: /book/maps + +### 结构和信息 {#structs-and-messages} + +Possible key types: + +[Structs][structs] and [Messages][messages] are two main ways of combining multiple [primitive types](#primitive-types) into a composite one. + +Example of a [Struct][structs]: + +```tact +struct Point { + x:Int; + y:Int; +} +``` + +Example of a [Message][messages]: + +```tact +// Custom numeric id of the Message +message(0x11111111) SetValue { + key: Int; + value: Int?; // Optional + coins: Int as coins; // Serialization into TL-B types +} +``` + +有关它们的更多信息,请访问专门页面:[结构和信息][s-n-m]。 + +[s-n-m]: /book/structs-and-messages +[structs]: /book/structs-and-messages#structs +[messages]: /book/structs-and-messages#messages + +### 可选项 {#optionals} + +所有[原始类型](#primitive-types)以及[结构体和消息](#structs-and-messages)都可以为空,并持有一个特殊的 `null{:tact}`值。 + +[可选项][可选项]示例: + +```tact +let opt: Int? = null; // Int or null, with explicitly assigned null +``` + +Learn more about them on a dedicated page: [Optionals][optionals]. + +[选修课]: /book/optionals + +### 合同 {#contracts} + +Contracts are the main entry of a smart contract on the TON blockchain. It holds all functions, getters, and receivers of a TON contract. + +[合同示例](/book/contracts): + +```tact +contract HelloWorld { + // Persistent state variable + counter: Int; + + // Constructor function init(), where all the variables are initialized + init() { + self.counter = 0; + } + + // Internal message receiver, which responds to a string message "increment" + receive("increment") { + self.counter += 1; + } + + // Getter function with return type Int + get fun counter(): Int { + return self.counter; + } +} +``` + +or [Contracts](#contracts) and [Traits](#traits) + +### 特质 {#traits} + +Tact 不支持经典的类继承,而是引入了**特质**(traits)的概念。它们的结构与[合约](#contracts)相同,但不能[初始化持久状态变量](/book/contracts#init-function)。 + +特质还可以让继承它的契约重写其[函数](/book/functions#virtual-and-abstract-functions)的行为和[常量](/book/constants#virtual-and-abstract-constants)的值。 + +来自 [`@stdlib/ownable`](/ref/stdlib-ownable) 的 trait [`Ownable`](/ref/stdlib-ownable#ownable) 示例: + +```tact +trait Ownable { + // Persistent state variable, which cannot be initialized in the trait + owner: Address; + + // Internal function + fun requireOwner() { + nativeThrowUnless(132, context().sender == self.owner); + } + + // Getter function with return type Address + get fun owner(): Address { + return self.owner; + } +} +``` + +而 [contract](#contracts) 使用了 trait [`Ownable{:tact}`](/ref/stdlib-ownable#ownable): + +```tact +contract Treasure with Ownable { + // Persistent state variable, which MUST be defined in the contract + owner: Address; + + // Constructor function init(), where all the variables are initialized + init(owner: Address) { + self.owner = owner; + } +} +``` diff --git a/docs/src/content/docs/zh-cn/book/upgrades.mdx b/docs/src/content/docs/zh-cn/book/upgrades.mdx new file mode 100644 index 000000000..d9bd96266 --- /dev/null +++ b/docs/src/content/docs/zh-cn/book/upgrades.mdx @@ -0,0 +1,5 @@ +--- +title: 合同升级 +--- + +Tact 目前不允许合同升级,因为 Tact 合同比 `func` 中的合同更复杂。理论上是可行的,但所需的工具并不在这里。 diff --git a/docs/src/content/docs/zh-cn/cookbook/access.mdx b/docs/src/content/docs/zh-cn/cookbook/access.mdx new file mode 100644 index 000000000..013ad9fb6 --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/access.mdx @@ -0,0 +1,52 @@ +--- +title: 门禁控制 +--- + +本页列出了使用权限、所有权和访问控制的常见示例。 + +## 如何使用 Ownable 特质检查发件人权限 + +```tact +// 为了方便起见,必须从 stdlib 导入 Ownable 以及 Deployable: +import "@stdlib/ownable"; +import "@stdlib/deploy"; + +message FooBarMsg { + newVal: Int as uint32; +} + +// Ownable 特性可以限制某些操作只能由所有者执行 +Deployable, Ownable 的 SenderChecker 合约 { + // 持久状态变量 + owner: Address; // Ownable 特性要求你添加这个确切的状态变量 + val: Int as uint32; // 一些值 + + init() { + // 我们可以将所有者初始化为我们想要的任何值,在本例中就是部署者: + self.owner = sender(); + self.val = 0; + } + + receive("inc") { + self.requireOwner(); // throws exit code 132 if the sender isn't an owner + self.val += 1; + } + + receive(msg: FooBarMsg) { + self.requireOwner(); // throws exit code 132 if the sender isn't an owner + self.val = msg.newVal; + } +} +``` + +:::note[Useful links:] + + [核心库中的 `trait Ownable{:tact}`](/ref/stdlib-ownable#ownable) + +::: + +:::tip[Hey there!] + + 没有找到您最喜欢的访问控制范例? 您有很酷的实施方案吗? [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/algo.mdx b/docs/src/content/docs/zh-cn/cookbook/algo.mdx new file mode 100644 index 000000000..9862b9b88 --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/algo.mdx @@ -0,0 +1,11 @@ +--- +title: 算法 +--- + +算法是严格指令的有限序列,通常用于解决一类特定问题或进行计算。 + +:::danger[Not implemented] + + 本页为残页。 [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/data-structures.mdx b/docs/src/content/docs/zh-cn/cookbook/data-structures.mdx new file mode 100644 index 000000000..ab76104e0 --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/data-structures.mdx @@ -0,0 +1,377 @@ +--- +title: 数据结构 +--- + +数据结构是数据组织、管理和存储格式,通常是为了高效访问数据而选择的。 更确切地说,数据结构是数据值、数据间关系以及可用于数据的函数或操作的集合。 + +本页列出了一系列在 Tact 中实现的数据结构,方便您满足日常及其他需求。 + +这里列出的所有数据结构都是使用内置的 [`map{:tact}`][map]类型制作的。 有关地图的描述和基本用法,请参阅[本书专页][地图]。 + +## 数组 + +[数组](https://en.wikipedia.org/wiki/Array_(data_structure)) 是一种数据结构,由连续的内存块组成,代表相同内存大小的元素集合,每个元素至少由一个数组键或_index_标识。 + +下面的示例使用[结构](/book/structs-and-messages#structs)包装的[`map{:tact}`][map]模拟数组,其中`V{:tact}`可以是 map 的任何[允许值类型](/book/maps#allowed-types): + +```tact +import "@stdlib/deploy"; // for Deployable trait + +struct Array { + m: map; // 数组的 Int 值作为 Ints 到 Ints 的映射, + // 将其键序列化为 uint16 以节省空间 + length:Int = 0; // 数组的长度,默认为 0 +} // 数组的编译时常数上界。 + +// +const MaxArraySize:Int = 5_000; // 5,000 entries max, to stay reasonable far from limits + +// Extension mutation function for adding new entries to the end of the array +extends mutates fun append(self: Array, item: Int) { + require(self.length + 1 <= MaxArraySize, "数组中没有空间留给新条目了!"); + + self.map.set(self.length, item); // 设置条目(键值对) + self.length += 1; // 增加长度字段 +} + +// 扩展突变函数,用于在给定索引处插入新条目 +extends mutates fun insert(self: Array, item: Int, idx: Int) { + require(self.length + 1 <= MaxArraySize, "数组中没有空间留给新条目!"); + require(idx >= 0, "Index of the item cannot be negative!"); + require(idx < self.length, "Index is out of array bounds!"); + + // Move all items from idx to the right + let i. Int = self.length; // not not:Int = self.length; // 不是错别字,因为我们需要从不曾存在的地方开始 + while (i > idx) { + // 注意,我们使用了 !! 操作符,因为我们知道值肯定会在那里 + self.map.set(i, self.map.get(i - 1)!!); + i -= 1; + } + + // 并将新条目放入 + self.map.set(idx, item); // 设置条目(键值对) + self.length += 1; // 增加长度字段 +} + +// Extension function for getting the value at the given index +extends fun getIdx(self: Array, idx: Int):Int { + require(self.length > 0, "No items in the array!"); + require(idx >= 0, "Index of the item cannot be negative!"); + require(idx < self.length, "Index is out of array bounds!"); + + // 注意,我们使用 !! 操作符,因为我们知道值肯定会在那里 + return self.map.get(idx)!!; +} // 返回最后一个值的扩展函数。 + +// 返回最后一个值的扩展函数 +extends fun getLast(self: Array):Int { + require(self.length > 0, "No items in the array!"); + + // 注意,我们使用 !! 运算符是因为我们确定值会在那里 + return self.map.get(self.length - 1)!!; +} + +// 扩展突变函数,用于删除给定索引中的条目并返回其值 +extends mutates fun deleteIdx(self: Array, idx: Int):Int { + require(self.length > 0, "No items in the array to delete!"); + require(idx >= 0, "Index of the item cannot be negative!"); + 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)!!; + + // 将所有从 idx 开始(包括 idx)的项目移到左边 + let i:Int = idx; + while (i + 1 < self.length) { + // 注意,我们使用了!!操作符,因为我们知道该值肯定会存在 + self.map.set(i, self.map.get(i + 1)!!); + i += 1; + } + + self.map.set(self.length - 1, null); // 删除最后一个条目 + self.length -= 1; // 减少长度字段 + + return memorized; +} + +// 用于删除最后一个条目并返回其值的扩展突变函数 +extends fun deleteLast(self: Array):Int { + require(self.length > 0, "No items in the array!"); + + // 注意,我们使用了!!操作符,因为我们知道值肯定会存在 + let lastItem: Int = self.map.get(self.length - 1)!!; + self.map.set(self.length - 1, null); // 删除条目 + self.length -= 1; // 减少长度字段 + + return lastItem; +} // 删除数组中最后一个条目的扩展函数。 + +// 用于删除数组中所有项的扩展函数 +extends mutates fun deleteAll(self: Array) { + self.map = emptyMap(); + self.length = 0; +} + +// 用于创建空数组的全局静态函数 +fun emptyArray():Array { + return Array{m: emptyMap(), length: 0}; // length 默认为 0 +} + +// 契约,使用 map 仿真数组 +contract MapAsArray with Deployable { + // 持久状态变量 + array:Array; + + // 合约的构造函数(初始化)函数 + init() { + self.array = emptyArray(); + } + + // 内部消息接收器,响应字符串消息 "append" + receive("append") { + // 添加一个新项目 + self.array.append(42); + } + + // 内部消息接收器,响应字符串消息 "delete_5h" + receive("delete_5th") { + // 删除第 5 个项目(如果存在),并回复其值,否则引发错误 + self.reply(self.array.deleteIdx(4).toCoinsString().asComment()); // 索引偏移 0 + 4 给出第 5 个项目 + } + + // 内部消息接收器,对字符串消息 "del_last "做出响应 + receive("del_last") { + // 删除最后一项,并回复其值,否则引发错误 + self.reply(self.array.deleteLast().toCoinsString().asComment()); + } + + // 内部消息接收器,用于响应字符串消息 "get_last" + receive("get_last") { + // 如果数组中存在最新项目,则回复该项目,否则引发错误 + self.reply(self.array.getLast().toCoinsString().asComment()); + } + + // 内部消息接收器,用于响应字符串消息 "delete_all" + receive("delete_all") { + self.array.deleteAll(); + } +} +``` + +## 堆栈 + +[堆栈](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) 是一种由元素集合组成的数据结构,主要有两种操作: + +- 推送,将一个元素添加到集合的末尾 +- 弹出,移除最近添加的元素 + +下面的示例使用[结构](/book/structs-and-messages#structs)包装的[`map{:tact}`][map]模拟堆栈,其中 `V{:tact}`可以是 map 的任何[允许值类型](/book/maps#allowed-types): + +```tact +import "@stdlib/deploy"; // for Deployable trait + +struct Stack { + m: map; // 堆栈中的 Int 值是 Ints 到 Ints 的映射, + // 将其键序列化为 uint16 以节省空间 + length:Int = 0; // 堆栈的长度,默认为 0 +} // 编译时常数上限值。 + +// +const MaxStackSize:Int = 5_000; // 最大 5,000 个条目,以合理地远离限制 + +// 扩展突变函数,用于向堆栈末尾添加新条目 +extends mutates fun push(self: Stack, item: Int) { + require(self.length + 1 <= MaxStackSize, "No space left in the stack for new items!"); + + self.map.set(self.length, item); // set the entry (key-value pair) + self.length += 1; // increase the length field +} // Extension mutation function for deleted the item. + +// 用于删除最后一个条目并返回其值的扩展突变函数 +extends mutates fun pop(self: Stack):Int { + require(self.length > 0, "No items in the stack to delete!"); + + // 注意,我们使用了!!操作符,因为我们知道值肯定在那里 + let lastItem: Int = self.map.get(self.length - 1)!!; + self.map.set(self.length - 1, null); // 删除条目 + self.length -= 1; // 减少长度字段 + + return lastItem; +} + +// 返回最后一个值的扩展函数 +extends fun peek(self: Stack):Int { + require(self.length > 0, "No items in the stack!"); + + // 注意,我们使用 !! 运算符是因为我们知道值肯定会在那里 + return self.map.get(self.length - 1)!!; +} + +// 用于删除堆栈中所有项目的扩展函数 +extends mutates fun deleteAll(self: Stack) { + self.map = emptyMap(); + self.length = 0; +} + +// 用于创建空 Stack 的全局静态函数 +fun emptyStack():Stack { + return Stack{m: emptyMap(), length: 0}; // length 默认为 0 +} + +contract MapAsStack with Deployable { + // Persistent state variables + stack:Stack; // 我们的栈,它使用 map + + // 合约的构造函数(初始化)函数 + init() { + self.stack = emptyStack(); + } + + // 内部消息接收器,响应字符串消息 "push" + receive("push") { + // 添加一个新项目 + self.stack.push(42); + } + + // 内部信息接收器,响应字符串信息 "pop" + receive("pop") { + // 删除最后一个项目并回复 + self.reply(self.stack.pop().toCoinsString().asComment()); + } + + // 内部消息接收器,对字符串消息 "peek "做出响应 + receive("peek") { + // 如果地图中存在最新条目,则回复该条目,否则引发错误 + self.reply(self.stack.peek().toCoinsString().asComment()); + } + + // 内部消息接收器,响应字符串消息 "delete_all" + receive("delete_all") { + self.stack.deleteAll(); + } + + // 获取堆栈的获取函数 + get fun map(): map { + return self.stack.map; + } + + // 获取堆栈当前长度的 Getter 函数 + get fun length():Int { + return self.stack.length; + } +} +``` + +## 圆形缓冲区 + +[循环缓冲区](https://en.wikipedia.org/wiki/Circular_buffer)(循环队列、循环缓冲区或环形缓冲区)是一种数据结构,它使用单个固定大小的[缓冲区](https://en.wikipedia.org/wiki/Data_buffer),就像端对端连接一样。 + +下面的示例使用包裹在 [Struct](/book/structs-and-messages#structs) 中的 [`map{:tact}`][map]模拟循环缓冲区,其中 `V{:tact}` 可以是 map 的任何 [允许值类型](/book/maps#allowed-types): + +```tact +import "@stdlib/deploy"; // for Deployable trait + +struct CircularBuffer { + m: map; // Int 值的循环缓冲区作为 Ints 到 Ints 的映射, + // 将其键序列化为 uint8 以节省空间 + length:Int = 0; // 循环缓冲区的长度,默认为 0 + start:Int = 0; // 循环缓冲区的当前索引,默认为 0 +} + +// +const MaxCircularBufferSize:Int = 5; + +// 扩展突变函数,用于将新项目放入循环缓冲区 +extends mutates fun put(self: CircularBuffer, item: Int) { + if (self.length < MaxCircularBufferSize) { + self.map.set(self.length, item); // 存储项目 + self.map.set(self.length, item); // 将新项目放入循环缓冲区。length += 1; // 增加长度字段 + } else { + self.map.set(self.start, item); // 存储项目,覆盖前一个条目 + self.start = (self.start + 1) % MaxCircularBufferSize; // 更新起始位置 + } +} + +// 从循环缓冲区获取项目的扩展突变函数 +extends mutates fun getIdx(self: CircularBuffer, idx: Int):Int { + require(self.length > 0, "No items in the circular buffer!"); + require(idx >= 0, "Index of the item cannot be negative!"); + + if (self.length < MaxCircularBufferSize) { + // 注意,我们使用了!!运算符,因为我们知道值肯定会在那里 + return self.map.get(idx % self.length)!!; + } // 返回围绕圆形缓冲区旋转的值。 + + // 返回围绕圆形缓冲区旋转的值,也保证在那里 + return self.map.get((self.start + idx) % MaxCircularBufferSize)!!; +} + +// 用于遍历循环缓冲区中的所有项目并将其转储到控制台的扩展函数 +extends fun printAll(self: CircularBuffer) { + let i. Int = self.start; repeat i. Int = self.start; repeat i. Int = self.start; repeat i. Int = self.start:Int = self.start; + repeat (self.length) { + dump(self.map.get(i)!!); // !! tells the compiler this can't be null + i = (i + 1) % MaxCircularBufferSize; + } +} + +// 用于删除 CircularBuffer 中所有项的扩展函数 +extends mutates fun deleteAll(self: CircularBuffer) { + self.map = emptyMap(); + self.length = 0; + self.start = 0; +} + +// 用于创建空 CircularBuffer 的全局静态函数 +fun emptyCircularBuffer():CircularBuffer { + return CircularBuffer{m: emptyMap(), length: 0, start:0}; // length 和 start 默认为 0 +} + +// 此合约记录了接收到 "timer "消息的最后 5 个时间戳 +合约 MapAsCircularBuffer with Deployable { + // 持久状态变量 + cBuf: CircularBuffer; // 我们的循环缓冲区,使用 map + + // 合约的构造函数(初始化) + init() { + self.cBuf = emptyCircularBuffer(); + } // 内部消息接收器。 + + // 内部消息接收器,用于响应字符串消息 "timer" + // 并记录接收到该消息时的时间戳 + receive("timer") { + let timestamp:Int = now(); + self.cBuf.put(timestamp); + } + + // 内部消息接收器,响应字符串消息 "get_first" + // 并以循环缓冲区的第一个项目作为回复 + receive("get_first") { + self.reply(self.cBuf.getIdx(0).toCoinsString().asComment()); + } + + // 内部消息接收器,对字符串消息 "print_all "做出响应 + receive("print_all") { + self.cBuf.printAll(); + } + + // 响应字符串消息 "delete_all "的内部消息接收器 + receive("delete_all") { + self.cBuf.deleteAll(); + } +} +``` + +:::note + + 本例改编自[Tact-By-Example 中的 Arrays 页面](https://tact-by-example.org/04-arrays)。 + +::: + +:::tip[Hey there!] + + 没有找到您最喜欢的使用数据结构的示例? 您有很酷的实施方案吗? [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: + +[地图]: /book/maps diff --git a/docs/src/content/docs/zh-cn/cookbook/dexes/dedust.mdx b/docs/src/content/docs/zh-cn/cookbook/dexes/dedust.mdx new file mode 100644 index 000000000..97017547d --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/dexes/dedust.mdx @@ -0,0 +1,13 @@ +--- +title: DeDust.io +sidebar: + order: 1 +--- + +[DeDust](https://dedust.io)是基于[TON Blockchain](https://ton.org)和[DeDust Protocol 2.0](https://docs.dedust.io/reference/tlb-schemes)的去中心化交易所(DEX)和自动做市商(AMM)。 DeDust 的设计非常注重用户体验(UX)、 gas 效率和可扩展性。 + +:::danger[Not implemented] + + 在 [#146](https://github.com/tact-lang/tact-docs/issues/146) 获得新内容之前,本页只是一个存根。 + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/dexes/stonfi.mdx b/docs/src/content/docs/zh-cn/cookbook/dexes/stonfi.mdx new file mode 100644 index 000000000..f75735645 --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/dexes/stonfi.mdx @@ -0,0 +1,13 @@ +--- +title: STON.fi +sidebar: + order: 2 +--- + +[STON.fi](https://ston.fi)是一个建立在[TON 区块链](https://ton.org)上的去中心化自动做市商(AMM),提供几乎零费用、低滑点、极其简单的界面以及与 TON 钱包的直接集成。 + +:::danger[Not implemented] + + 在 [#149](https://github.com/tact-lang/tact-docs/issues/149) 获得新内容之前,本页只是一个存根。 + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/index.mdx b/docs/src/content/docs/zh-cn/cookbook/index.mdx new file mode 100644 index 000000000..203c0cb02 --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/index.mdx @@ -0,0 +1,84 @@ +--- +title: 食谱概览 +--- + +import { LinkCard, Card, CardGrid, Steps } from '@astrojs/starlight/components'; + +制作 Tact Cookbook 的主要目的是将 Tact 开发人员的所有经验汇集到一个地方,以便未来的开发人员可以使用。 这部分文档更侧重于每个 Tact 开发人员在开发智能合约过程中需要解决的日常任务。 + +将其作为在 TON 区块链上制作令人愉悦的智能合约的食谱,而无需在此过程中重新发明轮子。 + + + +1. #### 单一合同 {#single-contract} + + 以下页面以单个合同实例为重点,涵盖了广泛的主题: + + + + + + + + + + + + +2. #### 多重合同 {#multiple-contracts} + + 下文将重点介绍多合约示例,探讨 TON 区块链的可扩展性: + + + + + + + + 此外,还有与流行的 TON DEX(去中心化交易所)合作的例子,这些交易所通常需要许多合约和复杂的逻辑: + + + + + + + diff --git a/docs/src/content/docs/zh-cn/cookbook/jettons.mdx b/docs/src/content/docs/zh-cn/cookbook/jettons.mdx new file mode 100644 index 000000000..1bf076fda --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/jettons.mdx @@ -0,0 +1,160 @@ +--- +title: 可变型代币(Jettons) +--- + +本页列出了使用 [jettons](https://docs.ton.org/develop/dapps/asset-processing/jettons)的常见示例。 + +## 接受 jetton 转移 + +转账通知信息的结构如下 + +```tact +message(0x7362d09c) JettonTransferNotification { + queryId: Int as uint64; + amount: Int as coins; + sender: Address; + forwardPayload: Slice as remaining; +} +``` + +使用 [receiver](/book/receive) 功能接受令牌通知信息。 + +:::caution + + 必须验证转账通知的发送方! + +::: + +可使用 jetton 钱包状态初始值和计算 jetton 地址进行验证。 +请注意,通知来自您的合约的 jetton 钱包,因此所有者地址栏应使用 [`myAddress()`](/ref/core-common#myaddress)。 +钱包的初始数据布局如下所示,但有时会有所不同。 +请注意,"myJettonWalletAddress "也可以存储在合约存储中,以便在每次交易中减少 gas 使用量。 + +```tact +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()}); +} + +contract Sample { + jettonWalletCode: Cell; + jettonMasterAddress: Address; + + init(jettonWalletCode: Cell, jettonMasterAddress: Address) { + self.jettonWalletCode = jettonWalletCode; + self.jettonMasterAddress = jettonMasterAddress; + } + + receive(msg: JettonTransferNotification) { + let myJettonWalletAddress = calculateJettonWalletAddress(myAddress(), self.jettonMasterAddress, self.jettonWalletCode); + require(sender() == myJettonWalletAddress, "Notification not from your jetton wallet!"); + + // your logic of processing token notification + } +} +``` + +## 发送 jetton 转送 + +要发送 jetton 传输,请使用 [`send(){:tact}`](/book/send)函数。 +请注意,"myJettonWalletAddress" 也可以存储在合约存储中,以便在每次交易中减少 gas 使用量。 + +```tact +message(0xf8a7ea5) JettonTransfer { + queryId: Int as uint64; + amount: Int as coins; + destination: Address; + responseDestination: Address?; + customPayload: Cell? = null; + forwardTonAmount: Int as coins; + 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(), + }); +} +``` + +## 销毁 jetton + +```tact +message(0x595f07bc) JettonBurn { + queryId: Int as uint64; + amount: Int as coins; + responseDestination: Address?; + 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(), + }); +} +``` + +## USDT jetton 业务 + +除了 `JettonWalletData` 将采用以下结构外,USDT(在 TON 上)的操作保持不变: + +```tact +struct JettonWalletData { + status: Ins as uint4; + balance: Int as coins; + ownerAddress: Address; + jettonMasterAddress: Address; +} +``` + +计算钱包地址的函数如下所示: + +```tact +fun calculateJettonWalletAddress(ownerAddress: Address, jettonMasterAddress: Address, jettonWalletCode: Cell): Address { + let initData = JettonWalletData{ + status: 0, + balance: 0, + ownerAddress, + jettonMasterAddress, + }; + + return contractAddress(StateInit{code: jettonWalletCode, data: initData.toCell()}); +} +``` + +:::tip[Hey there!] + + 没有找到您最喜欢的 jetton 使用范例? 您有很酷的实施方案吗? [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/misc.mdx b/docs/src/content/docs/zh-cn/cookbook/misc.mdx new file mode 100644 index 000000000..9f615a92e --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/misc.mdx @@ -0,0 +1,37 @@ +--- +title: 杂项 +--- + +各种利基范例,这些范例还没有专门的页面,但非常有用和有趣。 + +## 如何抛出错误 + +当我们不知道要多久执行一次特定操作时,合约中的 `throw(){:tact}` 函数就非常有用。 + +它允许有意的异常或错误处理,从而导致当前事务的终止,并恢复该事务中的任何状态更改。 + +```tact +let number: Int = 198; + +// 无论如何都会触发错误 +throw(36); + +// 只有当数字大于 50 时才会触发错误 +nativeThrowIf(35, number > 50); + +// 只有当数字不等于 198 时才会触发错误 +nativeThrowUnless(39, number == 198); +``` + +:::note[Useful links:] + + [核心库中的 `throw(){:tact}`](/ref/core-debug#throw)/ + [Tact-By-Example中的错误](https://tact-by-example.org/03-errors) + +::: + +:::tip[Hey there!] + + 没有找到您最喜欢的利基工作范例? 您有很酷的实施方案吗? [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/multi-communication.mdx b/docs/src/content/docs/zh-cn/cookbook/multi-communication.mdx new file mode 100644 index 000000000..8c8f56d08 --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/multi-communication.mdx @@ -0,0 +1,9 @@ +--- +title: 多合同通信 +--- + +:::danger[Not 已实施] + + 本页为残页。 [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/nfts.mdx b/docs/src/content/docs/zh-cn/cookbook/nfts.mdx new file mode 100644 index 000000000..1e0cf8e68 --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/nfts.mdx @@ -0,0 +1,9 @@ +--- +title: 不可封代币(NFT) +--- + +:::danger[Not 已实施] + + 本页为残页。 [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/random.mdx b/docs/src/content/docs/zh-cn/cookbook/random.mdx new file mode 100644 index 000000000..98e557e1b --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/random.mdx @@ -0,0 +1,31 @@ +--- +title: 随机性 +--- + +本页列出了使用随机数、不确定性和随机性的示例。 + +## 如何生成随机数 + +```tact +// 声明一个变量来存储随机数 +let number: Int; + +// 生成一个新的随机数,它是一个无符号的 256 位整数 +number = randomInt(); + +// 生成一个介于 1 和 12 之间的随机数 +number = random(1, 12); +``` + +:::note[Useful links:] + + [`randomInt(){:tact}` in Core library](/ref/core-random#randomint)\ + [`random(){:tact}` in Core library](/ref/core-random#random) + +::: + +:::tip[Hey there!] + + 没有找到你最喜欢的使用随机性的例子? 您有很酷的实施方案吗? [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/single-communication.mdx b/docs/src/content/docs/zh-cn/cookbook/single-communication.mdx new file mode 100644 index 000000000..c367a54fd --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/single-communication.mdx @@ -0,0 +1,101 @@ +--- +title: 单项合同通信 +--- + +本页列出了单个已部署合约与区块链上其他合约进行通信的示例。 + +有关多个已部署合同之间的通信示例,请参阅:[多合约通信](/cookbook/multi-communication)。 + +## 如何进行基本回复 {#how-to-make-a-basic-reply} + +```tact +receive() { + self.reply("Hello, World!".asComment()); // asComment 将字符串转换为带注释的单元格 +} +``` + +## 如何发送简单信息 + +```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) +}); +``` + +## 如何发送包含全部余额的信息 + +如果我们需要发送智能合约的全部余额,则应使用 `SendRemainingBalance{:tact}` 发送模式。 或者,我们也可以使用 `mode:128{:tact}`,其含义相同。 + +```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) +}); +``` + +## 如何发送带有余值的信息 + +如果我们要向同一发件人发送回复,可以使用 `SendRemainingValue{:tact}`模式(即 `mode: 64{:tact}`),除了新信息中最初显示的值外,它还会携带入站信息的所有剩余值。 + +```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) +}); +``` + +通常还需要添加 `SendIgnoreErrors{:tact}`标记,以便忽略在操作阶段处理该消息时出现的任何错误L + +```tact +send(SendParameters{ + // bounce = true by default + to: sender(), // send the message back to the original sender + value: 0, + mode:SendRemainingValue | SendIgnoreErrors, // 更喜欢使用 | 而不是 + 来表示模式 + body:"Hello from Tact!".asComment(), // comment (optional) +}); +``` + +后一个示例与使用 [`.reply(){:tact}` 函数](#how-to-make-a-basic-reply)相同。 + +## 如何发送带有长文本注释的信息 + +如果我们需要发送一条带有冗长文本注释的信息,我们应该创建一个 [`String{:tact}`](/book/types#primitive-types),由超过 $127$ 个字符组成。 为此,我们可以利用 [`StringBuilder{:tact}`](/book/types#primitive-types)原始类型及其名为 `beginComment(){:tact}` 和 `append(){:tact}` 的方法。 在发送之前,我们应该使用 `toCell(){:tact}` 方法将字符串转换为单元格。 + +```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(), +}); +``` + +:::note[Useful links:] + + ["Sending messages" in the Book](/book/send#send-message)\ + ["Message `mode`" in the Book](/book/message-mode) + [`StringBuilder{:tact}` in the Book](/book/types#primitive-types) + [`Cell{:tact}` in Core library](/ref/core-cells) + +::: + +:::tip[Hey there!] + + 没有找到您最喜欢的单一合同通信案例? 您有很酷的实施方案吗? [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/time.mdx b/docs/src/content/docs/zh-cn/cookbook/time.mdx new file mode 100644 index 000000000..9a3a40eea --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/time.mdx @@ -0,0 +1,30 @@ +--- +title: 时间和日期 +--- + +## 如何获取当前时间 + +使用 `now(){:tact}` 方法获取当前标准[Unix 时间](https://en.wikipedia.org/wiki/Unix_time)。 + +如果需要在状态中存储时间或在信息中编码时间,请使用下面的 [serialization](/book/integers#serialization):`Int as uint32{:tact}`。 + +```tact +let currentTime: Int = now(); + +if (currentTime > 1672080143) { + // do something +} +``` + +:::note[Useful links:] + + [核心库中的 `now(){:tact}`](/ref/core-common#now)/ + [Tact-By-Example中的 "当前时间"](https://tact-by-example.org/04-current-time) + +::: + +:::tip[Hey there!] + + 没有找到您最喜欢的使用时间和日期的例子? 您有很酷的实施方案吗? [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: diff --git a/docs/src/content/docs/zh-cn/cookbook/type-conversion.mdx b/docs/src/content/docs/zh-cn/cookbook/type-conversion.mdx new file mode 100644 index 000000000..f28880e0a --- /dev/null +++ b/docs/src/content/docs/zh-cn/cookbook/type-conversion.mdx @@ -0,0 +1,150 @@ +--- +title: 类型转换 +--- + +本页展示了在[原始类型][p]之间进行转换以及从[复合类型](/book/types#composite-types)中获取这些类型的示例。 + +## `Int` ↔ `String` {#int-string} + +### 如何将 "字符串 "转换为 "整数 + +```tact +// 为 String 类型定义一个新的扩展函数,返回 Int 类型的值 +// 注意:当 String 包含非数字字符时,会产生意想不到的结果! +extends fun toInt(self: String):Int { + // 将 String 转换为 Slice 用于解析 + let string:Slice = self.asSlice(); + + // 一个用于存储累积数字的变量 + let acc: Int = 0; + + // 循环直到 String 为空 + while (!string.empty()) { + let char:Int = string.loadUint(8); // 从 Slice 中加载 8 位(1 个字节) + acc = (acc * 10) + (char - 48); // 使用 ASCII 表获取数值 + // 注意,当起始字符串包含非数字字符时,这种方法会产生意想不到的结果 + // 如果起始字符串包含非数字字符时,这种方法会产生意想不到的结果! + } + + // 产生结果数字 + return acc; +} + +fun runMe() { + let string:String = "26052021"; + dump(string.toInt()); +} +``` + +### 如何将 `Int` 转换为 `String` 字符 + +```tact +let number: Int = 261119911; + +// 将 [number] 转换为字符串 +let numberString:String = number.toString(); + +// 将 [number] 转换为浮点字符串, +// 其中传递的参数 3 是结果浮点字符串的指数 10^(-3), +// 它可以是 0 到 76 之间的任何整数,包括两端 +let floatString:String = number.toFloatString(3); + +// 将作为硬币的[数字]转换为人类可读的字符串 +let coinsString:String = number.toCoinsString(); + +dump(numberString); // "261119911" +dump(floatString); // "261119.911" +dump(coinsString); // "0.261119911" +``` + +:::note[Useful links:] + + [`Int.toString(){:tact}` 在核心库中](/ref/core-strings#inttostring)/ + [`Int.toFloatString(){:tact}` 在核心库中](/ref/core-strings#inttofloatstring)/ + [`Int.toCoinsString(){:tact}` 在核心库中](/ref/core-strings#inttocoinsstring) + +::: + +## `Struct` 或 `Message` ↔ `Cell` 或 `Slice` {#structmessage-cellslice} + +### 如何将任意 "结构 "或 "消息 "转换为 "单元 "或 "片"? + +```tact {19-20, 22-23} +struct Profit { + big:String?; + dict: map; + energy: Int; +} + +message(0x45) Nice { + maybeStr:String?; +} + +fun convert() { + let st = Profit{ + big: null, + dict: null, + energy: 42, + }; + let msg = Nice{ maybeStr:"今天的消息!"}; + + st.toCell(); + msg.toCell(); + + st.toCell().asSlice(); + msg.toCell().asSlice(); +} +``` + +:::note[Useful links:] + + [`Struct.toCell(){:tact}` 在核心库中](/ref/core-cells#structtocell)/ + [`Message.toCell(){:tact}` 在核心库中](/ref/core-cells#messagetocell) + +::: + +### 如何将 "单元格 "或 "切片 "转换为任意的 "结构 "或 "消息"? + +```tact {19-20, 22-23} +struct Profit { + big:String?; + dict: map; + energy: Int; +} + +message(0x45) Nice { + maybeStr:String?; +} + +fun convert() { + let stCell = Profit{ + big: null, + dict: null, + energy: 42, + }.toCell(); + let msgCell = Nice{ maybeStr:"今日消息!"}.toCell(); + + Profit.fromCell(stCell); + Nice.fromCell(msgCell); + + Profit.fromSlice(stCell.asSlice()); + Nice.fromSlice(msgCell.asSlice()); +} +``` + +:::note[Useful links:] + + [`Struct.fromCell(){:tact}` in Core library](/ref/core-cells#structfromcell)\ + [`Struct.fromSlice(){:tact}` in Core library](/ref/core-cells#structfromslice)\ + [`Message.fromCell(){:tact}` 在核心库中](/ref/core-cells#messagefromcell)/ + [`Message.fromSlice(){:tact}` 在核心库中](/ref/core-cells#messagefromslice) + +::: + +:::tip[Hey there!] + + 没有找到您最喜欢的类型转换示例? 您有很酷的实施方案吗? [欢迎投稿!](https://github.com/tact-lang/tact/issues) + +::: + +[p]: /book/types#primitive-types diff --git a/docs/src/content/docs/zh-cn/ecosystem/index.mdx b/docs/src/content/docs/zh-cn/ecosystem/index.mdx new file mode 100644 index 000000000..02e5260c1 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ecosystem/index.mdx @@ -0,0 +1,36 @@ +--- +title: 生态系统概述 +--- + +import { CardGrid, LinkCard, Steps } from '@astrojs/starlight/components'; + +欢迎来到**生态系统**版块--鸟瞰 Tact 生态系统、工具以及您可以开始为之做出贡献的方法! + +以下是其主要内容: + + + +1. #### 工具 + + 工具是官方和社区专门为 Tact 制作的工具列表,或与 Tact 语言和其他工具配合使用的工具列表。 每个工具都有简短的使用细节和附加信息,这些信息有时在相关文档中缺失,或者只是战术文档中的方便摘要。 + + + + + + + + + diff --git a/docs/src/content/docs/zh-cn/ecosystem/jetbrains.mdx b/docs/src/content/docs/zh-cn/ecosystem/jetbrains.mdx new file mode 100644 index 000000000..1967d9682 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ecosystem/jetbrains.mdx @@ -0,0 +1,25 @@ +--- +title: TON Development Plugin for JetBrains IDEs +--- + +支持在 **2023.** 及更高版本的 JetBrains 集成开发环境中突出显示 Tact 的语法。请注意,除支持 Tact 外,该插件还支持 TON 区块链的 FunC 和 Fift 语言以及 TL-B 架构。 + +JetBrains Marketplace 上的插件: [TON 开发插件](https://plugins.jetbrains.com/plugin/23382-ton) + +## 安装手册 + +1. 打开 JetBrains IDE(IntelliJ IDEA、PyCharm、WebStorm 等)。 +2. 选择 "文件">"设置/首选项">"插件",导航至 "**插件市场**"。 +3. 在插件市场的搜索栏中输入 "TON Development"。 You will see a dropdown with the extension provided by `ton-blockchain`. +4. 点击插件名称旁边的**安装**按钮。 等待安装完成。 +5. 插件安装完成后,系统会提示您重新启动 JetBrains IDE。 单击 ** 重新启动** 按钮应用更改。 +6. 重新启动后,TON 开发插件应已成功安装到 JetBrains IDE 中。 + +## 故障排除 + +如果在安装过程中遇到问题,请查阅 [插件的 GitHub 代码库](https://github.com/ton-blockchain/intellij-ton) 以获取解决方案和更多信息。 + +## 参考资料和资源 + +- [TON Development on GitHub](https://github.com/ton-blockchain/intellij-ton) +- [TON Development Plugin on JetBrains Marketplace](https://plugins.jetbrains.com/plugin/18541-ton) diff --git a/docs/src/content/docs/zh-cn/ecosystem/misti.mdx b/docs/src/content/docs/zh-cn/ecosystem/misti.mdx new file mode 100644 index 000000000..c39090687 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ecosystem/misti.mdx @@ -0,0 +1,20 @@ +--- +title: Misti 静态分析仪 +--- + +[Misti](https://nowarp.github.io/tools/misti/)是一款支持 Tact 的静态程序分析工具。 + +## 米斯蒂是什么? + +- **静态程序分析**:Misti 在不执行代码的情况下对代码进行分析,通过检查结构和语法来扫描[漏洞和安全缺陷](https://nowarp.github.io/tools/misti/docs/detectors)。 这种方法可以及早发现问题,防止问题影响生产。 + +- **自定义探测器**:创建 [自定义探测器](https://nowarp.github.io/tools/misti/docs/hacking/custom-detector),根据您的特定需求定制 Misti。 这有助于识别通用工具可能会遗漏的漏洞,确保对代码进行彻底审查。 + +- **CI/CD 集成**:[集成](https://nowarp.github.io/tools/misti/docs/tutorial/ci-cd) Misti 到您的 CI/CD 管道中,以确保持续的代码质量检查,在问题进入生产之前将其捕获。 + +## 资源 + +- [Github](https://github.com/nowarp/misti) +- [电报社区](https://t.me/misti_dev) +- [Misti文档](https://nowarp.github.io/docs/misti/) +- [Misti应用程序接口参考](https://nowarp.github.io/docs/misti/api) diff --git a/docs/src/content/docs/zh-cn/ecosystem/typescript.mdx b/docs/src/content/docs/zh-cn/ecosystem/typescript.mdx new file mode 100644 index 000000000..489ef8db3 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ecosystem/typescript.mdx @@ -0,0 +1,9 @@ +--- +title: Typescript 库 +--- + +Tact 语言内置了对 `ton` 和 `ton-core` 类型脚本库的支持。编译器会自动为这些库生成代码,您可以使用 [@tact-lang/emulator](https://github.com/tact-lang/tact-emulator), [@ton-community/sandbox](https://github.com/ton-community/sandbox) 等库。 + +## Typescript 中的契约 + +编译器会为项目中的每个合约生成名为 `{project}_{contract}.ts` 的文件,其中包含随时可用的强类型封装器,以便在任何环境下使用合约:测试、部署等。 \ No newline at end of file diff --git a/docs/src/content/docs/zh-cn/ecosystem/vscode.mdx b/docs/src/content/docs/zh-cn/ecosystem/vscode.mdx new file mode 100644 index 000000000..109a576ef --- /dev/null +++ b/docs/src/content/docs/zh-cn/ecosystem/vscode.mdx @@ -0,0 +1,63 @@ +--- +title: VS Code Extension +--- + +在 Visual Studio 代码中为 Tact 语言提供广泛支持: + +- 语法高亮 +- 高亮显示错误 +- 片段 +- 悬停信息 +- 所有变量、函数、全局参数和 Tact 独特类型的代码自动完成 +- 对当前文件中的所有合约/库以及所有引用的导入进行代码补全 +- 格式化 + +VS Code Marketplace 上的扩展: [支持 TON 区块链的 Tact 语言](https://marketplace.visualstudio.com/items?itemName=KonVik.tact-lang-vscode) + +## 安装手册 + +1. Open Visual Studio Code. + +2. 单击窗口侧面活动栏中的 "扩展 "图标,导航至 "扩展 "视图。 它看起来就像一个正方形中的正方形。 + +3. In the Extensions view input box, type "TACT Language". You should see a dropdown with the extension "TACT Language" provided by KonVik. Probably, you would see the same extension provided by TON Community but this one is deprecated and we should use KonVik’s instead + +4. 点击扩展名称旁边的安装按钮。 等待安装完成。 + +5. 安装扩展后,您可能需要重新加载 VS Code。 如有必要,扩展名旁边会有一个重新加载按钮。 如果出现该按钮,请单击该按钮。 + +6. 现在,Tact 语言扩展应该已安装到您的 VS 代码中。 + +## 启用 "保存时格式化" {#format-on-save} + +本指南将说明如何在 VS 代码中使用 “命令调板 ”和编辑 JSON 设置文件为 Tact 语言扩展启用 “保存时格式 ”功能。 + +1. 在命令调板中输入 "首选项:在命令调板中输入 "首选项:打开设置 (JSON)"。 这将打开你的 `settings.json` 文件。 + +2. 编辑 JSON 设置 + + - 您将看到一个 JSON 对象。 We're going to add some properties to this object to enable format on save for the TACT Language extension. + + - 在 JSON 对象中添加以下几行: + + ```json + { + "[tact]":{ + "editor.formatOnSave": true, + "editor.defaultFormatter":"KonVik.tact-lang-vscode" + } + } + ``` + + - 这将启用保存时的格式化(`“editor.formatOnSave”: true`),并将 Tact 文件的默认格式化(`“[tact]”: {“editor.defaultFormatter”: “KonVik.tact-lang-vscode”}` )设置为 Tact 语言扩展名。 + +3. 保存和关闭设置 + - 添加完这些行后,保存您的 `settings.json` 文件(可按 `Ctrl+S` 保存)。 + - 关闭 `settings.json` 选项卡或按 `Ctrl+W`。 + +现在,当您保存文件时,您的 Tact Language VS Code 扩展程序会自动格式化您的文件。如果您没有看到这些更改立即生效,您可能需要重新加载 VS Code。 + +## 参考资料和资源 + +- Enabling Format on Save for the TACT Language Extension +- [TACT Language VS Code Extension](https://marketplace.visualstudio.com/items?itemName=KonVik.tact-lang-vscode) diff --git a/docs/src/content/docs/zh-cn/index.mdx b/docs/src/content/docs/zh-cn/index.mdx new file mode 100644 index 000000000..ae36a44f4 --- /dev/null +++ b/docs/src/content/docs/zh-cn/index.mdx @@ -0,0 +1,197 @@ +--- +title: 学习⚡ Tact 中的所有编程知识 +description: Tact 是 TON 区块链的一种新编程语言,注重效率和简便性。 它设计得易于学习和使用,并且非常适合智能合约。 +template: splash +hero: + tagline: Tact 是 TON 区块链的一种新编程语言,注重效率和简便性。 它设计得易于学习和使用,并且非常适合智能合约。 Tact 是一种静态类型语言,具有简单的语法和强大的类型系统。 + image: + dark: /public/logomark-dark.svg + light: /public/logomark-light.svg + alt: Tact 徽标 + actions: + - text: 书籍 + link: /zh-cn/book + variant: minimal + icon: right-arrow + + - text: 🍲烹饪书 + link: /zh-cn/cookbook + variant: minimal + icon: right-arrow + + - text: 🔬 参考资料 + link: /zh-cn/ref + variant: minimal + icon: right-arrow + + - text: 🗺️ 生态系统 + link: /zh-cn/ecosystem + variant: minimal + icon: right-arrow +--- + +import { LinkCard, CardGrid, Tabs, TabItem, Steps } from '@astrojs/starlight/components'; + + {/* icon: right-arrow */} + + +## 🚀 我们开始吧! {#start} + +

+ + + +1. #### 确保已安装受支持的 Node.js 版本并可用 {#start-1} + + 要检查它,请运行 `node --version{:shell}` --它应该会显示 22.0.0 或更高版本。 + +2. #### 运行以下命令 {#start-2} + + 它将创建一个带有简单计数器合同的新项目: + + + + ```shell + # 推荐 + yarn create ton simple-counter --type tact-counter --contractName SimpleCounter + ``` + + + ```shell + npm create ton@latest -- simple-counter --type tact-counter --contractName SimpleCounter + ``` + + + ```shell + pnpm create ton@latest simple-counter --type tact-counter --contractName SimpleCounter + ``` + + + ```shell + bun create ton@latest simple-counter --type tact-counter --contractName SimpleCounter + ``` + + + +3. #### 就是这样! {#start-3} + + 您的第一个合同项目已经编写完成! + + 进入相关目录查看 - `cd simple-counter/contracts{:shell}`。 这就是它的样子: + + ```tact + import "@stdlib/deploy"; + + message Add { + queryId:Int as uint64; + amount:Int as uint32; + } + + contract SimpleCounter with Deployable { + id: Int as uint32; + counter: + + init(id: Int) { + self.id = id; + self.counter = 0; + } + + receive(msg: Add) { + self.counter += msg.amount; + + // 通知调用者接收器已执行,并将余值转回 + self.notify("Cashback".asComment()); + } + + get fun counter():Int { + return self.counter; + } + + get fun id():Int { + return self.id; + } + } + ``` + + 要重新编译或部署,请参考新创建项目根目录下 `package.json` 中脚本部分的命令,以及 [Blueprint](https://github.com/ton-org/blueprint) 的文档 - 这是我们用来在 Tact 中创建和编译第一个简单计数器合约的工具。 事实上,Blueprint 的功能远不止这些:包括测试、定制等。 + + + +## 🤔 下一站去哪里? {#next} + +

+ + + +1. #### 已经掌握了一些区块链知识? {#next-1} + + 请参阅 [Tact Cookbook](/zh-cn/cookbook),它是每个 Tact 开发人员在智能合约开发过程中面临的日常任务(和解决方案)的便捷集合。 使用它可以避免重复发明轮子。 + + 此外,还可以查看以下小抄,快速入门: + + + + + + +2. #### 想了解更多吗? {#next-2} + + 有关编译、测试和部署的进一步指导,请参阅相关页面: + + - [测试和调试](/zh-cn/book/debug) 页面将为您介绍有关 Tact 合同调试的所有内容。 + - [部署](/zh-cn/book/deploy)页面展示了部署的样子,并帮助你利用[蓝图](https://github.com/ton-org/blueprint) 的力量进行部署。 + + 有关您最喜欢的编辑器和其他工具的自定义插件,请参阅 [生态系统](/zh-cn/ecosystem) 部分。 + + 或者,也可以查看以下更广泛的章节: + + - [书籍](/zh-cn/book) 帮助您逐步学习语言 + - [Cookbook](/zh-cn/cookbook)为您提供现成的 Tact 代码食谱 + - [参考资料](/zh-cn/ref) 提供了标准库、语法和演变过程的完整词汇表 + - 最后,[生态系统](/zh-cn/ecosystem)描述了 Tact 和 TON 生态系统中的 "外面有什么"。 + + + + + + + + +3. #### 感觉有点不舒服? {#next-3} + + 如果遇到困难,请尝试搜索--搜索框就在文档顶部。 还有一个方便的Ctrl+K快捷键,可以在输入时快速对焦并开始搜索。 + + 如果您在文档中找不到答案,或者您尝试进行了一些本地测试,但仍然无济于事,请不要犹豫,联系 Tact 活跃的社区: + + + + + + + 祝你在⚡ Tact 的编码冒险中好运! + + diff --git a/docs/src/content/docs/zh-cn/ref/core-advanced.mdx b/docs/src/content/docs/zh-cn/ref/core-advanced.mdx new file mode 100644 index 000000000..3284b6c53 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/core-advanced.mdx @@ -0,0 +1,256 @@ +--- +title: 高级 +--- + +各种小众、危险或不稳定的功能,可能会产生意想不到的结果,仅供更有经验的用户使用。 + +:::caution + + 谨慎行事。 + +::: + +## Context.readForwardFee + +```tact +extends fun readForwardFee(self: Context):Int +``` + +[`Context{:tact}`](/ref/core-common#context) 的扩展函数。 + +读取 [forward fee](https://docs.ton.org/develop/smart-contracts/guidelines/processing),并以 [`Int{:tact}`][int] 的形式返回 [nanoToncoins](/book/integers#nanotoncoin) 的金额。 + +使用示例 + +```tact +let fwdFee: Int = context().readForwardFee(); +``` + +## 获取配置参数 + +```tact +fun getConfigParam(id: Int):Cell? +``` + +通过 "id "号加载 TON 区块链的[配置参数](https://docs.ton.org/develop/howto/blockchain-configs)。 + +使用示例 + +```tact +// 参数 0,存储区块链配置的特殊智能合约的地址 +let configAddrAsCell: Cell = getConfigParam(0)!!; + +// 参数 18,用于确定数据存储价格的配置 +let dataStorageFeeConfig: Cell = getConfigParam(18)!!; +``` + +:::note + + 标准库 [`@stdlib/config`](/ref/stdlib-config) 提供了两个相关的辅助函数:\ + [`getConfigAddress(){:tact}`](/ref/stdlib-config#getconfigaddress),用于获取配置 [`Address{:tact}`][p]/ + [`getElectorAddress(){:tact}`](/ref/stdlib-config#getconfigaddress),用于获取选区 [`地址{:tact}`][p]。 + + 了解有关其他参数的更多信息:[TON 文档中的配置参数](https://docs.ton.org/develop/howto/blockchain-configs)。 + +::: + +## 接受消息 + +```tact +fun acceptMessage(); +``` + +同意购买一些汽油来完成当前交易。 处理外部信息时需要这一操作,因为外部信息本身没有价值(因此没有 gas )。 + +使用示例 + +```tact {10} +contract Timeout { + timeout:Int; + + init() { + self.timeout = now() + 5 * 60; // 5 minutes from now + } + + external("timeout") { + if (now() > self.timeout) { + acceptMessage(); // 超时后开始接受外部信息 + } + } +} +``` + +:::note + + 更多详情,请参阅[TON 文档中的接受信息效果](https://docs.ton.org/develop/smart-contracts/guidelines/accept)。 + +::: + +## 承诺 + +```tact +fun commit(); +``` + +提交 [寄存器](https://docs.ton.org/learn/tvm-instructions/tvm-overview#control-registers)`c4`("持久化数据")和`c5`("操作")的当前状态,这样,即使随后在计算阶段出现异常,当前执行也会因保存的值而被视为 "成功"。 + +使用示例 + +```tact {1} +commit(); // 现在,事务被视为 "成功" +throw(42); // 这样就不会失败了 +``` + +## nativePrepareRandom {#nativePrepareRandom} + +```tact +fun nativePrepareRandom(); +``` + +使用 [`nativeRandomizeLt(){:tact}`](#本机随机化)准备随机数生成器。 由 [`randomInt(){:tact}`](/ref/core-random#randomint) 和 [`random(){:tact}`](/ref/core-random#random) 函数自动调用。 + +使用示例 + +```tact +nativePrepareRandom(); // prepare the RNG +// ... do your random things ... +``` + +## 本机随机化 + +```tact +fun nativeRandomize(x: Int); +``` + +使用指定的种子 `x` 随机化伪随机数生成器。 + +使用示例 + +```tact +nativeRandomize(); // 现在,随机数的可预测性降低了 +let idk:Int = randomInt(); // ????, it's random! +``` + +## 本机随机化 + +```tact +fun nativeRandomizeLt(); +``` + +使用当前 [逻辑时间](https://docs.ton.org/develop/smart-contracts/guidelines/message-delivery-guarantees#what-is-a-logical-time) 随机化随机数发生器。 + +使用示例 + +```tact +nativeRandomizeLt(); // 现在,随机数对用户来说是不可预测的, + // 但仍可能受到验证器或校对器的影响 + // 因为它们决定了当前代码块的种子。 +let idk:Int = randomInt(); // ???,它是随机的! +``` + +## 本机随机 + +```tact +fun nativeRandom():Int; +``` + +生成并返回 $256$-bit 随机数,就像 [`randomInt(){:tact}`](/ref/core-random#randomint),但不会事先用 [`nativePrepareRandom(){:tact}`](#nativePrepareRandom)初始化随机生成器。 + +:::note + + 不要直接使用该函数,而应使用 [`randomInt(){:tact}`](/ref/core-random#randomint)。 + +::: + +## 本机随机时间间隔 + +```tact +fun nativeRandomInterval(max: Int):Int; +``` + +生成并返回 $256$-bit 的随机数,范围从 $0$ 到 `max`,类似于 [`random(){:tact}`](/ref/core-random#random),但不会事先用 [`nativePrepareRandom(){:tact}`](#nativePrepareRandom)初始化随机生成器。 + +:::note + + 不要直接使用该函数,而应使用 [`random(){:tact}`](/ref/core-random#random)。 + +::: + +## 本机发送消息 + +```tact +fun nativeSendMessage(cell: Cell, mode: Int); +``` + +通过指定完整的 "单元格 "和[信息 "模式"](/book/message-mode),[排队发送信息](/book/send#outbound-message-processing)。 + +:::note + + 除非您有无法以其他方式表达的复杂逻辑,否则请优先使用更常见、更方便用户使用的 [`send(){:tact}`](/ref/core-common#send)函数。 + +::: + +## 本地储备金 + +```tact +fun nativeReserve(amount: Int, mode: Int); +``` + +以指定的金额和模式调用本地 `raw_reserve` 函数。 raw_reserve "是一个函数,用于创建输出操作,从账户余额中储备特定数量的[nanoToncoins](/book/integers#nanotoncoin)。 + +它在 FunC 中的签名如下 + +```func +raw_reserve(int amount, int mode) impure asm "RAWRESERVE"; +``` + +该函数有两个参数: + +- 金额:要储备的 [nanoToncoins](/book/integers#nanotoncoin)的数量。 +- 模式决定预订行为。 + +函数 "raw_reserve "大致等同于创建一个向外发送的消息,将指定的 "金额"[nanoToncoins](/book/integers#nanotoncoin)(或 "b " $-$ "金额"[nanoToncoins](/book/integers#nanotoncoin),其中 "b "为余额)发送给自己。 这就确保了后续产出行动所花费的资金不会超过剩余资金。 + +您可以使用原始的 [`Int{:tact}`][int]值,并手动为 `mode` 提供这些值,但为了方便起见,您可以使用一组常量来轻松构建复合 `mode`。 有关基本模式和可选标记的更多信息,请参阅下表。 + +:::caution + + 目前,"金额 "必须是非负整数,"模式 "的范围必须是 $0..31$(含)。 + +::: + +### 基本模式 {#nativereserve-base-modes} + +由此产生的 `mode` 值可以有以下基本模式: + +| 模式值 | 恒定名称 | 说明 | +| ---------: | :---------------------------- | ------------------------------------------------------------------------------------ | +| $0$ | `ReserveExact{:tact}` | 精确保留指定数量的 [纳顿币](/book/integers#nanotoncoin)。 | +| $1$ | `ReserveAllExcept{:tact}` | 保留所有 [纳米通币](/book/integers#nanotoncoin),但保留指定数量的 [纳米通币](/book/integers#nanotoncoin)。 | +| $2$ | `ReserveAtMost{:tact}` | 最多保留指定 "数量 "的 [纳顿币](/book/integers#nanotoncoin)。 | + +### 可选标记 {#nativereserve-optional-flags} + +此外,生成的 `mode` 还可以添加以下可选标记: + +| 标志值 | 恒定名称 | 说明 | +| ---------: | :--------------------------------- | ----------------------------------- | +| $+4$ | `ReserveAddOriginalBalance{:tact}` | 用经常账户的原始余额(计算阶段之前)增加 "金额",包括所有额外货币。 | +| $+8$ | `ReserveInvertSign{:tact}` | 在执行保留之前否定 `amount` 值。 | +| $+16$ | `ReserveBounceIfActionFail{:tact}` | 如果预订失败,则退回交易。 | + +### 使用标志组合模式 {#nativereserve-combining-modes-with-flags} + +要为 `mode` 参数创建 [`Int{:tact}`][int]值,只需通过 [bitwise OR](/book/operators#binary-bitwise-or)操作将基本模式与可选标志结合起来: + +```tact +nativeReserve(ton("0.1"), ReserveExact | ReserveBounceIfActionFail); +// ---------- ---------------------------------------- +// ↑ ↑ +// | 模式,如果精确预订失败,将反弹交易 +// 要预订的纳吨币数量 +``` + +[p]: /book/types#primitive-types +[bool]: /book/types#booleans +[int]: /book/integers diff --git a/docs/src/content/docs/zh-cn/ref/core-base.mdx b/docs/src/content/docs/zh-cn/ref/core-base.mdx new file mode 100644 index 000000000..b96ad6705 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/core-base.mdx @@ -0,0 +1,127 @@ +--- +title: 基本特质 +--- + +Tact 中的每个 [contract](/book/contracts) 和 [trait](/book/types#traits) 都隐式 [继承](/book/contracts#traits) `BaseTrait{:tact}` trait,该 trait 包含大量对任何类型的 contract 最有用的 [internal functions](/book/contracts#internal-functions) 以及一个针对 Tact 高级用户的常量 `self.storageReserve{:tact}`。 + +## 常数 + +### self.storageReserve {#self-storagereserve} + +```tact +virtual const storageReserve:Int = 0; +``` + +使用示例 + +```tact +contract AllYourStorageBelongsToUs { + // 这将改变 self.forward() 函数的行为, + // 使其在 + // 使用 SendRemainingBalance 模式转发消息之前,尝试保留此数量的纳吨币 + override const storageReserve:Int = ton("0.1"); +} +``` + +## 功能 + +### self.reply {#self-reply} + +```tact +virtual fun reply(body: Cell?); +``` + +使用以下参数调用 [`self.forward(){:tact}`](#self-forward)函数的别名: + +```tact +self.forward(sender(), body, true, null); +// ↑ ↑ ↑ ↑ +// | | | init: StateInit? +// | | bounce: Bool +// | body: Cell? +// to: Address +``` + +使用示例 + +```tact +// 这条信息会反弹给我们! +self.reply("Beware, this is my reply to you!".asComment()); +``` + +### self.notify {#self-notify} + +```tact +virtual fun notify(body: Cell?); +``` + +使用以下参数调用 [`self.forward(){:tact}`](#self-forward)函数的别名: + +```tact +self.forward(sender(), body, false, null); +// ↑ ↑ ↑ ↑ +// | | | init: StateInit? +// | | bounce: Bool +// | body: Cell? +// to: Address +``` + +使用示例 + +```tact +// 此消息不会跳转! +self.notify("Beware, this is my reply to you!".asComment()); +``` + +### self.forward {#self-forward} + +```tact +virtual fun forward(to: Address, body: Cell?, bounce: Bool, init: StateInit?); +``` + +[将信息](/book/send#outbound-message-processing)(可跳转或不可跳转)发送到指定地址 `to`。 您可以选择提供信息的 "body "和[`init`包](/book/expressions#initof)。 + +当[`self.storageReserve{:tact}`](#self-storagereserve)常量被覆盖为$> 0$时,在发送信息之前,它也会尝试从剩余余额中预留`self.storageReserve{:tact}`的[nanoToncoins][nano]金额,然后再以[`SendRemainingBalance{:tact}`](https://docs.tact-lang.org/book/message-mode#base-modes)($128$) 模式发送信息。 + +如果预订尝试失败,或在默认情况下没有尝试,则会以[`SendRemainingValue{:tact}`](https://docs.tact-lang.org/book/message-mode#base-modes)($64$)模式发送信息。 + +:::note + + 请注意,`self.forward(){:tact}` 不会在余额上可用的[nanoToncoins][nano]之外发送额外的[nanoToncoins][nano]。 + 要想用一条信息发送更多的[nanoToncoins][nano],请使用[`send(){:tact}`](/ref/core-common#send)函数。 + +::: + +使用示例 + +```tact +import "@stdlib/ownable"; + +message PayoutOk { + address:Address; + value:Int as coins; +} + +用 Ownable 签订 Payout 合同 { + completed:Bool; + owner: Address; + + init(owner: Address) { + self.owner = owner; + self.completed = false; + } + + // ......一些操作 ... + + // 被退回的接收函数,当指定的发送信息被退回时被调用 + bounced(msg: bounced) { + // 如果我们的信息被退回,则重置完成标志 + self.completed = false; + + // 使用处理此发送的剩余资金发送支付失败的通知 + self.forward(self.owner, "Payout failed".asComment(), false, null); + } // 如果我们的信息被退回,则重置完成标志。 +} +``` + +[nano]: /book/integers#nanotoncoin diff --git a/docs/src/content/docs/zh-cn/ref/core-cells.mdx b/docs/src/content/docs/zh-cn/ref/core-cells.mdx new file mode 100644 index 000000000..bf2f5438a --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/core-cells.mdx @@ -0,0 +1,963 @@ +--- +title: 细胞、建造者和切片 +--- + +[`Cell{:tact}`][cell]是 TON 区块链中表示数据的低级[基元][p]。 单元由 $1023$ 位数据组成,最多可 $4$ 引用另一个单元。 它们是只读的、不可变的,并且不能循环引用。 + +[`Builder{:tact}`][builder]是一个不可变的[基元][p],用于构建单元格;[`Slice{:tact}`][slice]是一个可变的[基元][p],用于解析单元格。 + +:::note + + 在手动构建和解析单元格时要非常小心,并始终确保记录其所需的布局:用于序列化和反序列化的值和类型的严格顺序。 + + 为此,建议高级用户使用[类型语言 - 二进制(TL-B)模式][tlb]。 + + 建议每位用户使用 [Structs][struct] 及其 [方法](/book/functions#extension-function),如 [`Struct.toCell(){:tact}`](#structtocell) 和 [`Struct.fromCell(){:tact}`](#structfromcell),而不是手动构建和解析单元格,因为 [Structs][struct] 和 [Messages][message] 最接近于[您的合约的活 TL-B 架构](/book/cells#cnp-structs)。 + +::: + +## beginCell + +```tact +fun beginCell():生成器 +``` + +创建一个新的空[`Builder{:tact}`][builder]。 + +使用示例 + +```tact +让 fizz:Builder = beginCell(); +``` + +## 空单元格 + +```tact +fun emptyCell():单元格; +``` + +创建并返回空 [`单元格{:tact}`][单元格](不含数据和引用)。 别名为 `beginCell().endCell(){:tact}`。 + +使用示例 + +```tact +让 fizz:Cell = emptyCell(); +let buzz:Cell = beginCell().endCell(); + +fizz == buzz; // true +``` + +## 空切片 + +```tact +fun emptySlice():Slice; +``` + +创建并返回空 [`片{:tact}`][片](不含数据和引用)。 与 `emptyCell().asSlice(){:tact}` 同名。 + +使用示例 + +```tact +让 fizz:Slice = emptySlice(); +let buzz:Slice = emptyCell().asSlice(); + +fizz == buzz; // true +``` + +## Cell.beginParse + +```tact +extends fun beginParse(self: Cell):Slice; +``` + +[`单元格{:tact}`][单元格]的扩展函数。 + +打开[`单元格{:tact}`][单元格]进行解析,并以[`切片{:tact}`][切片]的形式返回。 + +使用示例 + +```tact +让 c:Cell = emptyCell(); +let fizz:Slice = c.beginParse(); +``` + +## 单元格哈希值 + +```tact +extends fun hash(self: Cell):Int; +``` + +[`单元格{:tact}`][单元格]的扩展函数。 + +计算并返回给定[`单元格{:tact}`][单元格]的[标准`单元格{:tact}`表示][st-repr]的[SHA-256][sha-2]哈希值的[`Int{:tact}`][int]值。 + +使用示例 + +```tact +让 c:Cell = emptyCell(); +let fizz:Int = c.hash(); +``` + +## Cell.asSlice + +```tact +extends fun asSlice(self: Cell):Slice; +``` + +[`单元格{:tact}`][单元格]的扩展函数。 + +将单元格转换为[`Slice{:tact}`][slice]并返回。 `self.beginParse(){:tact}`的别名。 + +使用示例 + +```tact +让 c:Cell = emptyCell(); +let fizz:Slice = c.asSlice(); +``` + +## Builder.endCell + +```tact +extends fun endCell(self: Builder):Cell; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +将 [`构造器{:tact}`][构造器] 转换为普通的 [`单元格{:tact}`][单元格]。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:单元格 = b.endCell(); +``` + +## Builder.storeUint + +```tact +extends fun storeUint(self: Builder, value: Int, bits: Int):Builder; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +将 $0 \leq \text{bits} \leq 256$ 的无符号 `bits` 位 `value` 存储到 [`Builder{:tact}`][builder] 的副本中。返回该副本。 + +试图存储负 `value` 或提供不足或超界 `bits` 数时,会出现 [退出代码 5](/book/exit-codes#5) 异常:"整数超出预期范围"。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:Builder = b.storeUint(42, 6); +``` + +## Builder.storeInt + +```tact +extends fun storeInt(self: Builder, value: Int, bits: Int):Builder; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +将$0 ≤$ `bits` $≤ 257$的有符号`bits`-位`值`存储到[`builder{:tact}`][builder]的副本中。 返回该副本。 + +试图提供一个不足或超出范围的`比特`数时,会出现[退出代码5](/book/exit-codes#5)的异常:整数超出预期范围"。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:Builder = b.storeUint(42, 7); +``` + +## Builder.storeBool + +```tact +extends fun storeBool(self: Builder, value: Bool):Builder; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +将[`Bool{:tact}`][bool]`value`存储到[`Builder{:tact}`][builder]的副本中。 如果 `value` 是 `true{:tact}`,则写入 $1$ 作为单个位,否则写入 $0$。 返回 [`生成器{:tact}`][生成器] 的副本。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:Builder = b.storeBool(true); // writes 1 +let buzz:Builder = b.storeBool(false); // writes 0 +``` + +## Builder.storeSlice + +```tact +extends fun storeSlice(self: Builder, cell: Slice):Builder; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +将[`slice{:tact}`][slice]`cell`存储到[`builder{:tact}`][builder]的副本中。 返回该副本。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let s:Slice = emptyCell().asSlice(); +let fizz:Builder = b.storeSlice(s); +``` + +## Builder.storeCoins + +```tact +extends fun storeCoins(self: Builder, value: Int):Builder; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +存储(序列化)一个范围为 $0 \ldots 2^{120} - 1$ 的无符号 [`Int{:tact}`][int] `value` 到 [`Builder{:tact}`][builder] 的副本中。`value` 的序列化由 $4$ 位无符号大端整数 $l$ 组成,它是最小的整数 $l \geq 0$,使得 $\text{value} < 2^{8l}$,然后是 `value` 的 $8l$ 位无符号大端表示。返回 [`Builder{:tact}`][builder] 的副本。 + +试图存储一个超出范围的`值`时,会出现[退出代码5](/book/exit-codes#5)的异常:超出预期范围的`整数`。 + +这是最常见的[纳米通币]存储方式(/book/integers#nanotoncoin)。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:Builder = b.storeCoins(42); +``` + +:::note[Useful links:] + + [特殊的 "硬币 "序列化类型](/book/integers#serialization-coins) + +::: + +## Builder.storeAddress + +```tact +extends fun storeAddress(self: Builder, address: Address):Builder; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +将地址存储在 [`生成器{:tact}`][生成器] 的副本中。 返回该副本。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:Builder = b.storeAddress(myAddress()); +``` + +## Builder.storeRef + +```tact +extends fun storeRef(self: Builder, cell: Cell):Builder; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +将引用 `cell` 存储到[`Builder{:tact}`][builder]的副本中。 返回该副本。 + +由于单个 [`单元格{:tact}`][单元格] 最多可存储 $4$ 引用,如果尝试存储更多引用,则会出现[退出代码 8](/book/exit-codes#8)异常:单元格溢出"。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:Builder = b.storeRef(emptyCell()); +``` + +## 建造者参考资料 + +```tact +extends fun refs(self: Builder):Int; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +以 [`Int{:tact}`][int]形式返回已存储在 [`构造器{:tact}`][构造器] 中的单元格引用的数目。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:Int = b.refs(); // 0 +``` + +## 构建器位 + +```tact +extends fun bits(self: Builder):Int; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +以[`Int{:tact}`][int]形式返回已存储在[`builder{:tact}`][builder]中的数据位数。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:Int = b.bits(); // 0 +``` + +## Builder.asSlice + +```tact +extends fun asSlice(self: Builder):Slice; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +将[`builder{:tact}`][builder]转换为[`slice{:tact}`][slice]并返回。 别名为 `self.endCell().beginParse(){:tact}`。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:Slice = b.asSlice(); +``` + +## Builder.asCell + +```tact +extends fun asCell(self: Builder):Cell; +``` + +[`生成器{:tact}`][生成器]的扩展函数。 + +将[`Builder{:tact}`][builder]转换为[`Cell{:tact}`][cell]并返回。 别名为 `self.endCell(){:tact}`。 + +使用示例 + +```tact +let b: Builder = beginCell(); +let fizz:Cell = b.asCell(); +``` + +## Slice.loadUint + +```tact +extends mutates fun loadUint(self: Slice, l: Int):Int; +``` + +[`切片{:tact}`][切片]的扩展突变函数。 + +从 [`Slice{:tact}`][slice]中加载并返回一个无符号的 `l` 位 [`Int{:tact}`][int],条件是 $0 ≤$ `l` $≤ 256$。 + +试图指定一个超出范围的 `l` 值时,会出现 [exit code 5](/book/exit-codes#5)异常:超出预期范围的整数。 + +尝试加载的数据超过 [`片{:tact}`][片] 包含的数据时,会出现 [exit code 9](/book/exit-codes#9) 异常:单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeUint(42, 7).asSlice(); +let fizz:Int = s.loadUint(7); +``` + +## Slice.preloadUint + +```tact +extends fun preloadUint(self: Slice, l: Int):Int; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +为 $0 ≤$ `l` $≤ 256$ 从 [`Slice{:tact}`][slice]中预载并返回一个无符号的 `l` 位 [`Int{:tact}`][int]。 不会修改 [`切片{:tact}`][切片]。 + +试图指定一个超出范围的 `l` 值时,会出现 [exit code 5](/book/exit-codes#5)异常:超出预期范围的整数。 + +尝试预载的数据超过 [`片{:tact}`][片] 所包含的数据时,会出现 [exit code 9](/book/exit-codes#9)异常:单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeUint(42, 7).asSlice(); +let fizz:Int = s.preloadUint(7); +``` + +## Slice.loadInt + +```tact +extends mutates fun loadInt(self: Slice, l: Int):Int; +``` + +[`切片{:tact}`][切片]的扩展突变函数。 + +从 [`Slice{:tact}`][slice]中加载并返回一个有符号的 `l` 位 [`Int{:tact}`][int],值为 $0 ≤$ `l` $≤ 257$。 + +试图指定一个超出范围的 `l` 值时,会出现 [exit code 5](/book/exit-codes#5)异常:超出预期范围的整数。 + +尝试加载的数据超过 [`片{:tact}`][片] 所包含的数据时,会出现 [exit code 9](/book/exit-codes#9) 异常:单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeInt(42, 7).asSlice(); +let fizz:Int = s.loadInt(7); +``` + +## Slice.preloadInt + +```tact +extends fun preloadInt(self: Slice, l: Int):Int; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +为 $0 ≤$ `l` $≤ 257$ 从 [`Slice{:tact}`][slice]中预载并返回一个有符号的 `l` 位 [`Int{:tact}`][int]。 不会修改 [`切片{:tact}`][切片]。 + +试图指定一个超出范围的 `l` 值时,会出现 [exit code 5](/book/exit-codes#5)异常:超出预期范围的整数。 + +尝试预载的数据超过 [`片{:tact}`][片] 所包含的数据时,会出现 [exit code 9](/book/exit-codes#9)异常:单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeInt(42, 7).asSlice(); +let fizz:Int = s.preloadInt(7); +``` + +## Slice.loadBits + +```tact +extends mutates fun loadBits(self: Slice, l: Int):Slice; +``` + +[`切片{:tact}`][切片]的扩展突变函数。 + +从 [`片{:tact}`][片] 中加载 $0 ≤$ `l` $≤ 1023$ 位,并作为单独的 [`片{:tact}`][片] 返回。 + +试图指定一个超出范围的 `l` 值时,会出现 [exit code 5](/book/exit-codes#5)异常:超出预期范围的整数。 + +尝试加载的数据超过 [`片{:tact}`][片] 所包含的数据时,会出现 [exit code 9](/book/exit-codes#9) 异常:单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeInt(42, 7).asSlice(); +let fizz:Slice = s.loadBits(7); +``` + +## Slice.preloadBits + +```tact +extends fun preloadBits(self: Slice, l: Int):Slice; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +从 [`片{:tact}`][片] 中预载 $0 ≤$ `l` $≤ 1023$ 位,并将其作为单独的 [`片{:tact}`][片] 返回。 不修改原始 [`切片{:tact}`][切片]。 + +试图指定一个超出范围的 `l` 值时,会出现 [exit code 5](/book/exit-codes#5)异常:超出预期范围的整数。 + +尝试预载的数据超过 [`片{:tact}`][片] 所包含的数据时,会出现 [exit code 9](/book/exit-codes#9)异常:单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeInt(42, 7).asSlice(); +let fizz:Slice = s.preloadBits(7); +``` + +## Slice.skipBits + +```tact +extends mutates fun skipBits(self: Slice, l: Int); +``` + +[`片{:tact}`][片]的扩展突变函数。 + +从 [`片{:tact}`][片] 中加载除第一个 0 ≤$ `l` $≤ 1023$ 位以外的所有位。 + +试图指定一个超出范围的 `l` 值时,会出现 [exit code 5](/book/exit-codes#5)异常:超出预期范围的整数。 + +尝试加载的数据超过 [`片{:tact}`][片] 所包含的数据时,会出现 [exit code 9](/book/exit-codes#9) 异常:单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeInt(42, 7).asSlice(); +s.skipBits(5); // 除了前 5 位之外的所有位 +let fizz:Slice = s.loadBits(1); // 只加载 1 位 +``` + +## Slice.loadBool + +```tact +extends mutates fun loadBool(self: Slice):Bool; +``` + +[`片{:tact}`][片]的扩展突变函数。 + +从[`Slice{:tact}`][slice]加载单个位并返回[`Bool{:tact}`][bool]值。 如果加载的位等于 $1$,则读取 `true{:tact}`,否则读取 `false{:tact}`。 + +当 [`Bool{:tact}`][bool][`slice{:tact}`][slice]不包含它时,尝试加载此类 [`Bool `][bool]会产生异常,[退出代码 8](/book/exit-codes#8):单元格溢出"。 + +尝试加载的数据超过 [`片{:tact}`][片] 所包含的数据时,会出现 [exit code 9](/book/exit-codes#9) 异常:单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeBool(true).asSlice(); +let fizz:Bool = s.loadBool(); // true +``` + +## Slice.loadCoins + +```tact +extends mutates fun loadCoins(self: Slice):Int; +``` + +[`切片{:tact}`][切片]的扩展突变函数。 + +加载并返回[序列化](#builderstorecoins)的一个范围为 $0 ... 2^{120} - 1$ 的无符号 [`Int{:tact}`][int]值,来自 [`片{:tact}`][片]。该值通常代表以[纳吨币](/book/integers#nanotoncoin)为单位的金额。 + +当 [`Slice{:tact}`][slice]中不包含[`Int{:tact}`][int]时,尝试加载此类[`Int`][int]会产生异常,[退出代码为 8](/book/exit-codes#8):"单元格溢出"。 + +尝试加载的数据超过 [`片{:tact}`][片] 所包含的数据时,会出现 [exit code 9](/book/exit-codes#9) 异常:"单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeCoins(42).asSlice(); +let fizz:Int = s.loadCoins(); +``` + +:::note[Useful links:] + + [特殊的 "硬币 "序列化类型](/book/integers#serialization-coins) + +::: + +## Slice.loadAddress + +```tact +extends mutates fun loadAddress(self: Slice):地址; +``` + +[`Slice{:tact}`][slice]的扩展突变函数。 + +从[`Slice{:tact}`][slice]加载并返回一个[`Address{:tact}`][address]。 + +当[`Slice{:tact}`][slice]不包含该地址时,尝试加载该[`Address{:tact}`][address]会产生异常,[退出代码 8](/book/exit-codes#8):"单元格溢出"。 + +尝试加载的数据超过 [`Slice{:tact}`][slice] 所包含的数据时,会出现 [退出代码 9](/book/exit-codes#9) 异常:"单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeAddress(myAddress()).asSlice(); +let fizz:地址 = s.loadAddress(); +``` + +## Slice.loadRef + +```tact +extends mutates fun loadRef(self: Slice):Cell; +``` + +[`切片{:tact}`][切片]的扩展突变函数。 + +从 [`切片{:tact}`][切片] 中加载下一个引用作为 [`单元格{:tact}`][单元格]。 + +当 [`切片{:tact}`][切片]不包含该引用时,尝试加载该引用会产生异常,[退出代码 8](/book/exit-codes#8):"单元格溢出"。 + +尝试加载的数据超过 [`片{:tact}`][片] 所包含的数据时,会出现 [exit code 9](/book/exit-codes#9) 异常:单元格下溢"。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeRef(emptyCell()).asSlice(); +let fizz:Cell = s.loadRef(); + +let s:Slice = beginCell() + .storeRef(emptyCell()) + .storeRef(emptyCell()) + .asSlice(); +let ref1: Cell = s.loadRef(); +let ref2: Cell = s.loadRef(); +``` + +## Slice.refs + +```tact +extends fun refs(self: Slice):Int; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +以 [`Int{:tact}`][int]形式返回 [`片{:tact}`][片] 中引用的个数。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeRef(emptyCell()).asSlice(); +let fizz:Int = s.refs().asSlice(); let fizz: Int = s.refs().asSlice() +``` + +## Slice.bits + +```tact +extends fun bits(self: Slice):Int; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +以 [`Int{:tact}`][int]形式返回 [`片{:tact}`][片] 中的数据位数。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeRef(emptyCell()).asSlice(); +let fizz:Int = s.bits(); +``` + +## Slice.empty + +```tact +extends fun empty(self: Slice):Bool; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +检查 [`片{:tact}`][片] 是否为空(即不包含数据位和单元格引用)。 如果为空,则返回 `true{:tact}`,否则返回 `false{:tact}`。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeRef(emptyCell()).asSlice(); +let fizz:Bool = s.empty(); // false +let buzz:Bool = beginCell().asSlice().empty(); // true +``` + +:::note + + 与 [`Slice.endParse(){:tact}`](#sliceendparse)不同,即使 [`Slice{:tact}`][slice]为空,该函数也不会抛出任何异常。 + +::: + +## Slice.dataEmpty + +```tact +extends fun dataEmpty(slice: Slice):Bool; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +检查 [`片{:tact}`][片] 是否没有数据位。 如果没有数据,则返回 `true{:tact}`,否则返回 `false{:tact}`。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeRef(emptyCell()).asSlice(); +let fizz:Bool = s.dataEmpty(); // true + +let s2:Slice = beginCell().storeInt(42, 7).asSlice(); +let buzz:Bool = s2.dataEmpty(); // false +``` + +## Slice.refsEmpty + +```tact +extends fun refsEmpty(slice: Slice):Bool; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +检查 [`Slice{:tact}`][slice]是否没有引用。 如果没有引用,则返回 `true{:tact}`,否则返回 `false{:tact}`。 + +使用示例 + +```tact +让 s:Slice = beginCell().storeRef(emptyCell()).asSlice(); +let fizz:Bool = s.refsEmpty(); // false +let buzz:Bool = beginCell().asSlice().refsEmpty(); // true +``` + +## Slice.endParse + +```tact +extends fun endParse(self: Slice); +``` + +[`切片{:tact}`][切片]的扩展函数。 + +检查 [`片{:tact}`][片] 是否为空(即不包含数据位和单元格引用)。 如果不是,则抛出异常[退出代码 9](/book/exit-codes#9):单元格下溢。 + +使用示例 + +```tact {2,6} +let emptyOne: Slice = emptySlice(); +emptyOne.endParse(); // nothing, as it's empty + +let paul: Slice = "Fear is the mind-killer".asSlice(); +try { + paul.endParse(); // throws exit code 9 +} +``` + +## Slice.hash + +```tact +extends fun hash(self: Slice):Int; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +计算并返回给定[`Slice{:tact}`][slice]的[标准`Cell{:tact}`表示][st-repr]的[SHA-256][sha-2]哈希值的[`Int{:tact}`][int]值。 + +使用示例 + +```tact +让 s:Slice = beginCell().asSlice(); +let fizz:Int = s.hash(); +``` + +## Slice.asCell + +```tact +extends fun asCell(self: Slice):Cell; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +将 [`Slice{:tact}`][slice]转换为 [`Cell{:tact}`][cell]并返回。 别名为 `beginCell().storeSlice(self).endCell(){:tact}`。 + +使用示例 + +```tact +让 s:Slice = beginCell().asSlice(); +let fizz:Cell = s.asCell(); +let buzz:Cell = beginCell().storeSlice(s).endCell(); + +fizz == buzz; // true +``` + +## 地址.asSlice + +```tact +extends fun asSlice(self: Address):Slice; +``` + +[`地址{:tact}`][p]的扩展函数。 + +将[`地址{:tact}`][p]转换为[`片{:tact}`][片]并返回。 别名为 `beginCell().storeAddress(self).asSlice(){:tact}`。 + +使用示例 + +```tact +让 a:Address = myAddress(); +let fizz:Slice = a.asSlice(); +let buzz:Slice = beginCell().storeAddress(a).asSlice(); + +fizz == buzz; // true +``` + +## Struct.toCell + +```tact +extends fun toCell(self: Struct):Cell; +``` + +任何结构类型 [Struct][struct]的扩展函数。 + +将 [Struct][struct] 转换为 [`单元格{:tact}`][单元格]并返回。 + +使用示例 + +```tact +struct GuessCoin { + probably: + nothing: Int as coins; +} + +fun coinCell():Cell { + let s:GuessCoin = GuessCoin{ probably: 42, nothing: 27 }; + let fizz:Cell = s.toCell(); + + return fizz; // "x{12A11B}" +} +``` + +## Struct.fromCell + +```tact +extends fun fromCell(self: Struct, cell: Cell):Struct; +``` + +任何结构类型 [Struct][struct]的扩展函数。 + +将 [`单元格{:tact}`][单元格] 转换为指定的 [结构体][struct],并返回该 [结构体][struct]。 + +试图传递布局与指定 [Struct][struct] 不同的 [`单元格{:tact}`][单元格],或加载的数据超过 [`单元格{:tact}`][单元格] 所包含的数据时,会出现 [exit code 9](/book/exit-codes#9)异常:单元格下溢"。 + +使用示例 + +```tact +struct GuessCoin { + probably: + nothing: Int as coins; +} + +fun directParse(payload: Cell):GuessCoin { + return GuessCoin.fromCell(payload); +} + +fun cautiousParse(payload: Cell):GuessCoin?{ + let coin: GuessCoin?= null; + try { + coin = GuessCoin.fromCell(payload); + } catch (e) { + dump("Cell payload doesn't match GuessCoin Struct!"); + } + return coin; +} +``` + +## Struct.fromSlice + +```tact +extends fun fromSlice(self: Struct, cell: Slice):Struct; +``` + +任何结构类型 [Struct][struct]的扩展函数。 + +将 [`片{:tact}`][slice]转换为指定的 [Struct][struct],并返回该 [Struct][struct]。 + +尝试传递布局与指定 [结构][struct]不同的 [`片{:tact}`][片],或加载比 [`片{:tact}`][片] 包含的数据更多的数据时,会出现[退出代码 9](/book/exit-codes#9)异常:单元下溢"。 + +使用示例 + +```tact +struct GuessCoin { + probably: + nothing: Int as coins; +} + +fun directParse(payload: Slice):GuessCoin { + return GuessCoin.fromSlice(payload); +} + +fun cautiousParse(payload: Slice):GuessCoin?{ + let coin: GuessCoin?= null; + try { + coin = GuessCoin.fromSlice(payload); + } catch (e) { + dump("Slice payload doesn't match GuessCoin Struct!"); + } + return coin; +} +``` + +## Message.toCell + +```tact +extends fun toCell(self: Message):Cell; +``` + +任何报文类型 [报文][信息] 的扩展函数。 + +将[信息][message]转换为[`单元格{:tact}`][单元格]并返回。 + +使用示例 + +```tact +message GuessCoin { + probably: + nothing: Int as coins; +} + +fun coinCell():Cell { + let s:GuessCoin = GuessCoin{ probably: 42, nothing: 27 }; + let fizz:Cell = s.toCell(); + + return fizz; // "x{AB37107712A11B}" +} +``` + +## Message.fromCell + +```tact +extends fun fromCell(self: Message, cell: Cell):消息; +``` + +任何报文类型 [报文][信息] 的扩展函数。 + +将 [`单元格{:tact}`][单元格] 转换为指定的 [信息][消息],并返回该 [信息][消息]。 + +尝试传递布局与指定[信息][message]不同的[`单元格{:tact}`][单元格],或加载的数据超过[`单元格{:tact}`][单元格]所包含的数据时,会出现[退出代码 9](/book/exit-codes#9)的异常:单元格下溢"。 + +使用示例 + +```tact +message(0x777) TripleAxe { + prize:Int as uint32; +} + +fun directParse(payload: Cell):TripleAxe { + return TripleAxe.fromCell(payload); +} + +fun cautiousParse(payload: Cell):TripleAxe?{ + let coin: TripleAxe? = null; + try { + coin = TripleAxe.fromCell(payload); + } catch (e) { + dump("Cell payload doesn't match TripleAxe Message!"); + } + return coin; +} +``` + +## Message.fromSlice + +```tact +extends fun fromSlice(self: Message, cell: Slice):消息; +``` + +任何报文类型 [报文][信息] 的扩展函数。 + +将 [`片{:tact}`][片] 转换为指定的 [信息][消息],并返回该 [信息][消息]。 + +试图传递布局不同于指定[信息][消息]的[`片{:tact}`][片],或加载的数据多于[`片{:tact}`][片]所包含的数据时,会出现[退出代码 9](/book/exit-codes#9)的异常:单元下溢"。 + +使用示例 + +```tact +message(0x777) TripleAxe { + prize:Int as uint32; +} + +fun directParse(payload: Slice):TripleAxe { + return TripleAxe.fromSlice(payload); +} + +fun cautiousParse(payload: Slice):TripleAxe?{ + let coin: TripleAxe? = null; + try { + coin = TripleAxe.fromSlice(payload); + } catch (e) { + dump("Slice payload doesn't match TripleAxe Message!"); + } + return coin; +} +``` + +[p]: /book/types#primitive-types +[bool]: /book/types#booleans +[int]: /book/integers +[cell]: /book/cells#cells +[builder]: /book/cells#builders +[slice]: /book/cells#slices +[map]: /book/maps +[struct]: /book/structs-and-messages#structs +[message]: /book/structs-and-messages#messages + +[std-repr]: /book/cells#cells-representation + +[tlb]: https://docs.ton.org/develop/data-formats/tl-b-language +[sha-2]: https://en.wikipedia.org/wiki/SHA-2#Hash_standard diff --git a/docs/src/content/docs/zh-cn/ref/core-common.mdx b/docs/src/content/docs/zh-cn/ref/core-common.mdx new file mode 100644 index 000000000..d752c719b --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/core-common.mdx @@ -0,0 +1,245 @@ +--- +title: 常见问题 +--- + +最常用的内置 [全局静态函数] 列表(/book/functions#global-static-functions)。 + +## 语境 + +### 现在 + +```tact +fun now():Int +``` + +返回当前 [Unix 时间](https://en.wikipedia.org/wiki/Unix_time)。 + +使用示例 + +```tact +let timeOffset:Int = now() + 1000; // 距离 now() 千秒 +``` + +### 我的余额 + +```tact +fun myBalance():Int; +``` + +返回当前交易的 [计算阶段](https://docs.ton.org/learn/tvm-instructions/tvm-overview#compute-phase) 开始时智能合约的 [纳米通币](/book/integers#nanotoncoin) 余额。 + +使用示例 + +```tact +让 iNeedADolla:Int = myBalance(); +``` + +:::caution + + 请注意,Tact 的 [所有信息发送函数](/book/send) 可以更改_实际_合约的余额,但不会_更新此函数返回的值。 + +::: + +### 我的地址 + +```tact +fun myAddress():地址; +``` + +以[`地址{:tact}`][p]的形式返回当前智能合约的地址。 + +使用示例 + +```tact +let meMyselfAndI: Address = myAddress(); +``` + +### sender + +```tact +fun sender():地址; +``` + +返回当前信息发件人的 [`地址{:tact}`][p]。 + +使用示例 + +```tact +receive() { + let whoSentMeMessage:Address = sender(); +} +``` + +:::caution + + 对于 [getter 函数](/book/contracts#getter-functions),行为是未定义的,因为它们不能有发送者,也不能发送信息。 + +::: + +:::note + + 为了减少 gas 用量,当您只需要知道消息的发件人时,请使用此函数而不是调用 [`context().sender{:tact}`](#context)。 + +::: + +### context + +```tact +fun context():Context; +``` + +Returns `Context{:tact}` [Struct](/book/structs-and-messages#structs), that consists of: + +| 现场 | 类型 | 说明 | +| :----- | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 宣布 | [`Bool{:tact}`][bool]。 | [退信](https://ton.org/docs/learn/overviews/addresses#bounceable-vs-non-bounceable-addresses) 标志。 | +| 发件人 | [`地址{:tact}`][p] | 发送方在 TON 区块链上的内部地址。 | +| 值 | [`Int{:tact}`][int]。 | 信息中 [nanoToncoins](/book/integers#nanotoncoin) 的数量。 | +| 生 | [`切片{:tact}`][切片] | 信息的其余部分作为 [`片{:tact}`][片]。 它遵循 TON 的[内部报文布局](https://docs.ton.org/develop/smart-contracts/messages#message-layout),从目的地[`地址{:tact}`][p]([TL-B 符号](https://docs.ton.org/develop/data-formats/tl-b-language) 中的`dest:MsgAddressInt`)开始。 | + +使用示例 + +```tact +让 ctx:Context = context(); +require(ctx.value != 68 + 1, "Invalid amount of nanoToncoins, bye!"); +``` + +:::note + + 请注意,如果您只需要知道谁发送了信息,请使用 [`sender(){:tact}`](#sender) 函数,因为它耗油量较少。 + +::: + +## 地址 + +### 新地址 + +```tact +fun newAddress(chain: Int, hash: Int):地址; +``` + +根据[`链`id](https://ton-blockchain.github.io/docs/#/overviews/TON_blockchain_overview)和[SHA-256](/ref/core-math#sha256)编码的[`哈希`值](https://docs.ton.org/learn/overviews/addresses#account-id)创建一个新的[`地址{:tact}`][p]。 + +该函数尽可能在[编译时](/ref/core-comptime)解析常量值。 + +使用示例 + +```tact +let oldTonFoundationAddr: Address = + newAddress(0, 0x83dfd552e63729b472fcbcc8c45ebcc6691702558b68ec7527e1ba403a0f31a8); + // ↑ ------------------------------------------------------------------ + | // ↑ + // | 合约初始包(StateInit)的 sha-256 哈希值 + // 链 id:0 代表工作链,-1 代表主链。 +``` + +:::caution + + 如果 `chain` 无效,该方法会抛出错误[退出代码 136](/book/exit-codes#136);如果 `chain` 指向主链 ($-1$) 且未启用[主链支持](/book/masterchain),该方法会抛出错误[退出代码 137](/book/exit-codes#137)。 + +::: + +:::note[Useful links:] + + [TON文档中的`chain`(工作链ID)](https://docs.ton.org/learn/overviews/addresses#workchain-id)\ + [TON文档中的`hash`(账户ID)](https://docs.ton.org/learn/overviews/addresses#account-id)\ + [合约的初始包(`StateInit{:tact}`)](/book/expressions#initof) + +::: + +### 合同地址 + +```tact +fun contractAddress(s: StateInit):地址; +``` + +根据智能合约的 [`StateInit{:tact}`](/book/expressions#initof),计算智能合约在工作链 $0$ 中的 [`地址{:tact}`][p]。 + +使用示例 + +```tact +让 foundMeSome:Address = contractAddress(initOf SomeContract()); +``` + +### 合同地址扩展 + +```tact +fun contractAddressExt(chain: Int, code: Cell, data: Cell):地址; +``` + +根据 "链 "id、合约 "代码 "和合约初始状态 "数据 "计算智能合约的[`地址{:tact}`][p]。 使用 [`initOf{:tact}`](/book/expressions#initof)表达式获取给定合约的初始 `code` 和初始 `data` 。 + +使用示例 + +```tact +让 initPkg:StateInit = initOf SomeContract(); +let hereBeDragons:Address = contractAddressExt(0, initPkg.code, initPkg.data); +``` + +:::caution + + 如果 `chain` 无效,该方法会抛出错误[退出代码 136](/book/exit-codes#136);如果 `chain` 指向主链 ($-1$) 且未启用[主链支持](/book/masterchain),该方法会抛出错误[退出代码 137](/book/exit-codes#137)。 + +::: + +:::note + + 要使用该功能,必须在 [configuration file](/book/config). + 中为当前项目将编译器选项 `debug` 设置为 `true{:tact}`:[调试](/book/debug)。 + +::: + +## 交流 + +### 发送 + +```tact +fun send(params: SendParameters); +``` + +使用 [`发送参数{:tact}`](/book/send)[结构](/book/structs-and-messages#structs)发送[队列信息](/book/send#outbound-message-processing)。 + +使用示例 + +```tact +send(SendParameters{ + to: sender(), // back to the sender, + value: ton("1"), // with 1 Toncoin (1_000_000_000 nanoToncoin), + // and no message body +}); +``` + +:::note[Useful links:] + + [Sending messages in the Book](/book/send)/ + [Message`mode` in the Book](/book/message-mode)/ + [Single-contract communication in the Cookbook](/cookbook/single-communication) + +::: + +### 发出 + +```tact +fun emit(body: Cell); +``` + +将[信息排队](/book/send#outbound-message-processing) `body` 发送到外部世界,以便在链外进行记录和分析。 该信息没有收件人,与使用 Tact 的其他信息发送功能相比更省油。 + +使用示例 + +```tact +emit("Catch me if you can, Mr. Holmes".asComment()); // asComment() 将字符串转换为单元格 +``` + +:::note + + 要分析 `emit(){:tact}` 调用,必须查看合约产生的 [外部信息](/book/external)。 + + 了解更多信息:[通过 `emit(){:tact}`记录日志](/book/debug#logging)。 + +::: + +[p]: /book/types#primitive-types +[bool]: /book/types#booleans +[int]: /book/integers +[一片]: /book/cells#slices diff --git a/docs/src/content/docs/zh-cn/ref/core-comptime.mdx b/docs/src/content/docs/zh-cn/ref/core-comptime.mdx new file mode 100644 index 000000000..ef491aa09 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/core-comptime.mdx @@ -0,0 +1,73 @@ +--- +title: 编译时 +--- + +本页列出了所有内置 [全局静态函数](/book/functions#global-static-functions),这些函数在构建 Tact 项目时进行评估,无法处理非恒定的运行时数据。 这些函数通常被称为 "编译时函数"。 + +## 地址 + +```tact +fun address(s: String):地址; +``` + +编译时函数,用于将带有地址的 [`字符串{:tact}`][p]转换为 [`地址{:tact}`][p]类型。 + +使用示例 + +```tact +contract Example { + // Persistent state variables + addr: Address = + address("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N"); // works at compile-time! +} +``` + +## 电池 + +```tact +fun cell(bocBase64: String):单元格; +``` + +编译时函数,将 base64 编码的 [BoC](https://docs.ton.org/develop/data-formats/cell-boc#bag-of-cells)`bocBase64`作为 [`单元格{:tact}`][单元格] 嵌入到合约中。 + +使用示例 + +```tact +contract Example { + // 持久状态变量 + storedCell:Cell = + // 钱包 V3R1 的初始包为 base64-编码的 BoC + cell("te6cckEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVD++buA=");// 编译时有效! +} +``` + +:::note[Useful links:] + + [TON文档中的细胞袋](https://docs.ton.org/develop/data-formats/cell-boc#bag-of-cells) + +::: + +## ton + +```tact +fun ton(value: String):Int; +``` + +编译时函数,将给定的通币`值`从人类可读格式[`String{:tact}`][p]转换为[nanoToncoin](/book/integers#nanotoncoin)[`Int{:tact}`][int]格式。 + +使用示例 + +```tact +contract Example { + // 持久状态变量 + one:Int = ton("1"); // 10^9 枚纳米通币,等于 1 枚通币 + pointOne: Int = ton("0.1"); // 10^8 枚纳米通币,等于 0.1 枚通币 + nano: Int = ton("0.000000001"); // 1 枚纳米通币,等于 10^9 枚通币 + // 在编译时有效! +} +``` + +[p]: /book/types#primitive-types +[bool]: /book/types#booleans +[int]: /book/integers +[电池]: /book/cells#cells diff --git a/docs/src/content/docs/zh-cn/ref/core-debug.mdx b/docs/src/content/docs/zh-cn/ref/core-debug.mdx new file mode 100644 index 000000000..111c409c7 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/core-debug.mdx @@ -0,0 +1,217 @@ +--- +title: 调试 +--- + +Tact 中调试智能合约常用的函数列表。 + +:::note + + 有关调试的更多信息,请访问专用页面:[调试](/book/debug)。 + +::: + +## 要求 + +```tact +fun require(condition: Bool, error: String); +``` + +检查 `condition` 并抛出错误,如果 `condition` 为 `false{:tact}`,则从 `error` 消息中生成 [exit code](/book/exit-codes)。 除此之外,别无其他作用。 + +生成退出代码的算法如下: + +- 首先,获取`错误`报文[`字符串{:tact}`][p]的[SHA-256](https://en.wikipedia.org/wiki/SHA-2#Hash_standard) 哈希值。 +- 然后,按此顺序将其值读作一个 32 位 [big-endian](https://en.wikipedia.org/wiki/Endianness) 数字 modulo $63000$ 加 $1000$。 +- 最后,它将被放入 `.md` 编译报告文件,该文件与其他编译工件一起存放在项目的 `outputs/` 或 `build/` 目录中。 + +保证生成的退出代码不在为 TVM 和 Tact 合同错误保留的常用 $0 - 255$ 范围内,这样就可以将退出代码与 `require(){:tact}` 和任何其他 [标准退出代码](/book/exit-codes) 区分开来。 + +使用示例 + +```tact +// now() 必须返回一个大于 1000 的值,否则将抛出错误信息 +require(now() > 1000, "We're in the first 1000 seconds of January 1970!"); + +try { + // 下面的内容永远不会为真,所以这个 require 将总是抛出 + require(now() < -1, "Time is an illusion.午餐时间更是如此。"); +} catch (e) { + // e 将超出 0-255 范围 + dump(e); +} +``` + +## 转储 + +```tact +fun dump(arg); +``` + +将参数 `arg` 打印到合约的调试控制台。 仅当[配置文件](/book/config) 中的 "debug "选项设置为 `true{:json}`时才进行评估,否则不执行任何操作。 + +可应用于下列类型和值: + +- [`Int{:tact}`][int]。 +- [`Bool{:tact}`][bool]。 +- [`地址{:tact}`][p] +- [`单元格{:tact}`][单元格]、[`构建器{:tact}`][构建器]或[`切片{:tact}`][切片] +- [`String{:tact}`][p] 或 [`StringBuilder{:tact}`][p] +- [`map{:tact}`](/book/maps) +- [选项和 `null{:tact}` 值](/book/optionals) +- void",当函数没有定义返回值时隐式返回 + +使用示例 + +```tact +// Int +dump(42); + +// Bool +dump(true); +dump(false); + +// Address +dump(myAddress()); + +// Cell, Builder or Slice +dump(emptyCell()); // Cell +dump(beginCell()); // Builder +dump(emptySlice()); // Slice + +// String or StringBuilder +dump("Hello, my name is..."); // String +dump(beginTailString()); // StringBuilder + +// Maps +let m: map = emptyMap(); +m.set(2 + 2, 4); +dump(m); + +// 特殊值 +dump(null); +dump(emit("msg".asComment())); // 由于 emit() 函数不返回值,dump() 将打印 #DEBUG#: void. +``` + +:::note[Useful links:] + + [使用 `dump(){:tact}` 调试](/book/debug#tests-dump) + +::: + +## dumpStack + +```tact +fun dumpStack(); +``` + +将 [持久状态变量](/book/contracts#variables)的所有值打印到合约的调试控制台。 仅当[配置文件](/book/config) 中的 "debug "选项设置为 `true{:json}`时才进行评估,否则不执行任何操作。 + +使用示例 + +```tact {6} +contract DumpsterFire { + var1: Int = 0; + var2: Int = 5; + + receive() { + dumpStack(); // would print 0 5 + } +} +``` + +:::note[Useful links:] + + [使用 `dump(){:tact}` 调试](/book/debug#tests-dump) + +::: + +## 丢 + +```tact +fun throw(code: Int); +``` + +是 [`nativeThrow(){:tact}`](#nativethrow)的别名。 + +## nativethrow + +```tact +fun nativeThrow(code: Int); +``` + +抛出错误代码等于 `code` 的异常。 当前上下文的执行将停止("nativeThrow "后的语句将不会执行),控制权将传递给调用栈中的第一个[`try...catch{:tact}`块](/book/statements#try-catch)。 如果调用者函数中不存在 `try{:tact}`或 `try...catch{:tact}`块,[TVM](https://docs.ton.org/learn/tvm-instructions/tvm-overview)将终止事务。 + +试图在 $0 - 65535$ 范围之外指定 `code` 时,会出现 [exit code 5](/book/exit-codes#5)异常:整数超出预期范围"。 + +使用示例 + +```tact {2,7} +fun thisWillTerminate() { + nativeThrow(42); // 抛出退出代码 42 +} + +fun butThisDoesNot() { + try { + nativeThrow(42); // 抛出退出代码 42 + } + + // ... 后续逻辑 ... +} +``` + +## 原生抛出条件 + +```tact +fun nativeThrowIf(code: Int, condition: Bool); +``` + +类似于 [`nativeThrow(){:tact}`](#nativethrow),但会在 `condition` 等于 `true{:tact}` 时有条件地抛出异常。 否则不会扔。 + +试图在 $0 - 65535$ 范围之外指定 `code` 时,会出现 [exit code 5](/book/exit-codes#5) 异常:整数超出预期范围"。 + +使用示例 + +```tact {2,7} +fun thisWillTerminate() { + nativeThrowIf(42, true); // 抛出退出代码 42 +} + +fun butThisDoesNot() { + try { + nativeThrowIf(42, true); // 抛出退出代码 42 + } + // ... 后续逻辑 ... +} +``` + +## 本机无投掷 + +```tact +fun nativeThrowUnless(code: Int, condition: Bool); +``` + +类似于 [`nativeThrow(){:tact}`](#nativethrow),但会在 `condition` 等于 `false{:tact}` 时有条件地抛出异常。 否则不会扔。 + +试图在 $0 - 65535$ 范围之外指定 `code` 时,会出现 [exit code 5](/book/exit-codes#5) 异常:整数超出预期范围"。 + +使用示例 + +```tact {2,7} +fun thisWillTerminate() { + nativeThrowUnless(42, false); // 抛出退出代码 42 +} + +fun butThisDoesNot() { + try { + nativeThrowUnless(42, false); // 抛出退出代码 42 + } + // ... 后续逻辑 ... +} +``` + +[p]: /book/types#primitive-types +[bool]: /book/types#booleans +[int]: /book/integers +[电池]: /book/cells#cells +[建筑工人]: /book/cells#builders +[一片]: /book/cells#slices diff --git a/docs/src/content/docs/zh-cn/ref/core-math.mdx b/docs/src/content/docs/zh-cn/ref/core-math.mdx new file mode 100644 index 000000000..4cb2aeffe --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/core-math.mdx @@ -0,0 +1,279 @@ +--- +title: 数学 +--- + +各种数学辅助功能 + +## 分钟 + +```tact +fun min(x: Int, y: Int):Int; +``` + +计算并返回两个 [`Int{:tact}`][int]值 `x` 和 `y` 的 [最小值](https://en.wikipedia.org/wiki/Maximum_and_minimum)。 + +使用示例 + +```tact +min(1, 2); // 1 +min(2, 2); // 2 +min(007, 3); // 3 +min(0x45, 3_0_0); // 69, nice +// ↑ ↑ +// 69 300 +``` + +## 最大 + +```tact +fun max(x: Int, y: Int):Int; +``` + +计算并返回两个 [`Int{:tact}`][int]值 `x` 和 `y` 的 [最大值](https://en.wikipedia.org/wiki/Maximum_and_minimum)。 + +使用示例 + +```tact +max(1, 2); // 2 +max(2, 2); // 2 +max(007, 3); // 7 +max(0x45, 3_0_0); // 300 +// ↑ ↑ +// 69 300 +``` + +## 腹肌 + +```tact +fun abs(x: Int):Int +``` + +计算并返回[`Int{:tact}`][int]值 `x` 的[绝对值](https://en.wikipedia.org/wiki/Absolute_value)。 + +使用示例 + +```tact +abs(42); // 42 +abs(-42); // 42 +abs(-(-(-42))); // 42 +``` + +## log + +```tact +fun log(num: Int, base: Int):Int; +``` + +计算并返回数字 `num` $> 0$ 以 `base` $≥1$ 为底的 [logarithm](https://en.wikipedia.org/wiki/Logarithm) 值。 结果[四舍五入](https://en.wikipedia.org/wiki/Rounding#Rounding_down)。 传入一个非正数`num`值或`base`小于 $1$ 会产生错误[退出代码 5](/book/exit-codes#5):整数超出预期范围"。 + +使用示例 + +```tact +log(1000, 10); // 3, 因为 10^3 是 1000 +// ↑ ↑ ↑ +// num base base num + +log(1001, 10); // 3 +log(999, 10); // 2 +try { + log(-1000, 10);// 抛出退出代码 5,因为数值不是正数 +} +log(1024, 2); // 10 +try { + log(1024, -2); // 抛出退出代码 5,因为基数小于 1 +} +``` + +:::note + + 请注意,如果只需要获取以 $2$为底的对数,请使用 [`log2(){:tact}`](#log2)函数,因为它更省油。 + +::: + +## log2 + +```tact +fun log2(num: Int):Int; +``` + +类似于 [`log(){:tact}`](#log),但将 `base` 设为 $2$。 + +使用示例 + +```tact +log2(1024); // 10,因为 2^10 是 1024 +// ↑ ↑ +// num base₂ num +``` + +:::note + + 为了减少 gas 用量,当您只需要获取以 $2$为底的对数时,最好使用该函数,而不是调用 [`log(){:tact}`](#log)。 + +::: + +## pow + +```tact +fun pow(base: Int, exp: Int):Int; +``` + +计算并返回涉及两个数的 [幂级数](https://en.wikipedia.org/wiki/Exponentiation):"基数 "和指数(或_幂_)"exp"。 指数 `exp` 必须是非负数,否则将产生[退出代码 5](/book/exit-codes#5)错误:`整数超出预期范围`。 + +请注意,该函数在运行时和[编译时](/ref/core-comptime)均有效。 + +使用示例 + +```tact +contract Example { + // 持久状态变量 + p23: Int = pow(2, 3); // 将 2 提升到三次幂,即 8 + one:Int = pow(5, 0); // 将 5 提升到 0 的幂次,总是产生 1 + // 在编译时工作! + + // 内部消息接收器,接受消息 ExtMsg + receive() { + pow(self.p23, self.one + 1); // 64,运行时也有效! + pow(0, -1); // ERROR!退出代码 5:整数超出预期范围 + } +} +``` + +:::note + + 注意,如果只需要获取 $2$的幂,请使用 [`pow2(){:tact}`](#pow2)函数,因为它更省油。 + +::: + +:::note + + 仅在编译时有效的函数列表:[API 编译时间](/ref/core-comptime)。 + +::: + +## pow2 + +```tact +fun pow2(exp: Int):Int; +``` + +与 [`pow(){:tact}`](#pow) 类似,但将 `base` 设为 $2$。 在运行时和[编译时](/ref/core-comptime)均可使用。 + +使用示例 + +```tact +contract Example { + // 持久状态变量 + p23: Int = pow2(3); // 将 2 提升到三次幂,即 8 + one:Int = pow2(0); // 将 2 提升到 0 的幂,总是产生 1 + // 在编译时工作! + + // 内部消息接收器,接受消息 ExtMsg + receive() { + pow2(self.one + 1); // 4,运行时也有效! + pow2(-1); // ERROR!退出代码 5:整数超出预期范围 + } +} +``` + +:::note + + 为了减少 gas 用量,当您只需要获取 $2$的幂时,最好使用该函数,而不是调用 [`pow(){:tact}`](#pow) 。 + +::: + +:::note + + 仅在编译时有效的函数列表:[API 编译时间](/ref/core-comptime)。 + +::: + +## checksignature + +```tact +fun checkSignature(hash: Int, signature: Slice, public_key: Int):Bool; +``` + +使用由 $256$-bit unsigned [`Int{:tact}`][int]表示的 "公钥 "检查 $256$-bit unsigned [`Int{:tact}`][int]"哈希 "的[Ed25519][ed]"签名"。 签名必须包含至少 $512$ 位数据,但只使用前 $512$ 位。 + +如果签名有效,则返回 `true{:tact}`,否则返回 `false{:tact}`。 + +使用示例 + +```tact {19-24} +message ExtMsg { + signature:Slice; + data:Cell; +} + +contract Showcase { + // Persistent state variables + pub:Int as uint256; // 公钥为 256 位无符号 Int + + // 构造函数 init(),初始化所有变量 + init(pub: Int) { + self.pub = pub; // 契约初始化时存储公钥 + } // 外部消息接收器接受消息 ExtMsg + + // 外部消息接收器,接受消息 ExtMsg + external(msg: ExtMsg) { + let hash:Int = beginCell().storeRef(msg.data).endCell().hash(); + let check:Bool = checkSignature(hash, msg.signature, self.pub); + // ---- ------------- -------- + // ↑ ↑ + // | | public_key,存储在我们的合约中 + // | signature,从接收到的消息中获取 + // hash,使用接收到的消息中的数据计算 + // ... 后续逻辑... + } +} +``` + +## 检查数据签名 + +```tact +fun checkDataSignature(data: Slice, signature: Slice, public_key: Int):Bool; +``` + +使用 "公钥 "检查 "数据 "的[Ed25519][ed]"签名",类似于[`checkSignature(){:tact}`](#checksignature)。 如果 `data` 的位长不能被 $8$整除,该函数将产生错误[退出代码 9](/book/exit-codes#9):单元格下溢。 验证本身是间接进行的:根据 "数据 "的[SHA-256][sha-2] 哈希值进行验证。 + +如果签名有效,则返回 `true{:tact}`,否则返回 `false{:tact}`。 + +使用示例 + +```tact +让 data:Slice = ...; +let signature:Slice = ...; +let publicKey:Int = ...; + +let check:Bool = checkSignature(data, signature, publicKey); +``` + +## sha256 + +```tact +fun sha256(data: Slice): +fun sha256(data: String):Int; +``` + +从传递的 [`片{:tact}`][片] 或 [`字符串{:tact}`][p]`数据`计算[SHA-256][sha-2] 哈希值,并以 $256$-bit 无符号 [`Int{:tact}`][int]的形式返回。 + +如果 `data` 是一个 [`字符串{:tact}`][p],它的位数应能被 $8$除,如果它是一个 [`片{:tact}`][片],它也必须\*\*\*没有引用(即总共最多只有 1023 位数据)。 + +该函数尽可能在 [编译时](/ref/core-comptime) 解析常量字符串值。 + +使用示例 + +```tact +sha256(beginCell().asSlice()); +sha256("Hello, world!"); // 将在编译时解析 +sha256(someVariableElsewhere); // 将尝试在编译时解析, + // 并回退到运行时评估 +``` + +[p]: /book/types#primitive-types +[bool]: /book/types#booleans +[int]: /book/integers +[一片]: /book/cells#slices +[编辑]: https://en.wikipedia.org/wiki/EdDSA#Ed25519 +[屏蔽-2]: https://en.wikipedia.org/wiki/SHA-2#Hash_standard diff --git a/docs/src/content/docs/zh-cn/ref/core-random.mdx b/docs/src/content/docs/zh-cn/ref/core-random.mdx new file mode 100644 index 000000000..d92359473 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/core-random.mdx @@ -0,0 +1,44 @@ +--- +title: 随机数生成 +--- + +为 Tact 智能合约生成随机数。 + +## 随机的 + +```tact +fun random(min: Int, max: Int):Int; +``` + +在提供的半封闭区间内生成并返回一个新的伪随机无符号 [`Int{:tact}`][int]值 `x`: `min` $≤$ `x` $<$ `max` or `min` $≥$ `x` $>$ `max`, 如果 `min` 和 `max` 都是负值。 注意,"最大 "值从不包含在区间内。 + +使用示例 + +```tact +random(42, 43); // 42, always +random(0, 42); // 0-41, but never a 42 +``` + +## 随机常数 + +```tact +fun randomInt():Int; +``` + +生成并返回一个新的伪随机无符号 $256$-bit [`Int{:tact}`][int]值 `x`。 + +该算法的工作原理如下:如果 `r` 是被视为 $32$-字节数组(通过构建无符号 $256$-位 [`Int{:tact}`][int]的 big-endian 表示)的随机种子的旧值,则计算其 `sha512(r){:tact}`。 哈希值的前 $32$ 字节作为随机种子的新值 `r'` 存储,其余 $32$ 字节作为下一个随机值 `x` 返回。 + +使用示例 + +```tact +let allYourRandomBelongsToUs:Int = randomInt(); // ????, it's random :) +``` + +:::caution + + 用于处理随机数的高级函数列在一个专门页面上:[高级应用程序接口](/ref/core-advanced)。 + +::: + +[int]: /book/integers diff --git a/docs/src/content/docs/zh-cn/ref/core-strings.mdx b/docs/src/content/docs/zh-cn/ref/core-strings.mdx new file mode 100644 index 000000000..15baa333a --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/core-strings.mdx @@ -0,0 +1,354 @@ +--- +title: 字符串和字符串构造器 +--- + +字符串是不可更改的字符序列,这意味着 [`字符串{:tact}`][p]一旦创建,就不能更改。 字符串可用于存储文本,因此可将其转换为[`单元格{:tact}`][单元格]类型,用作信息体。 + +要以省 gas 的方式连接字符串,请使用 [`字符串生成器{:tact}`][p]。 + +要直接使用 [`字符串{:tact}`][p]字面量,请参阅:[字符串字面量](/book/expressions#string-literals)。 + +## beginString + +```tact +fun beginString():StringBuilder; +``` + +创建并返回空 [`StringBuilder{:tact}`][p]。 + +使用示例 + +```tact +让 fizz:StringBuilder = beginString(); +``` + +## 开始评论 + +```tact +fun beginComment():StringBuilder; +``` + +创建并返回一个空的 [`字符串生成器{:tact}`][p],用于生成注释字符串,并在生成的 [`字符串{:tact}`][p]前加上四个空字节。 这种格式用于传递文本注释作为报文正文。 + +使用示例 + +```tact +让 fizz:StringBuilder = beginComment(); +``` + +## beginTailString + +```tact +fun beginTailString():StringBuilder; +``` + +创建并返回一个空的 [`字符串生成器{:tact}`][p],用于生成尾字符串,并在生成的 [`字符串{:tact}`][p]前加上一个空字节。 这种格式在 NFT 或 Jetton 等各种标准中都有使用。 + +使用示例 + +```tact +让 fizz:StringBuilder = beginTailString(); +``` + +## beginStringFromBuilder + +```tact +fun beginStringFromBuilder(b: StringBuilder):StringBuilder; +``` + +从现有的 [`字符串生成器{:tact}`][p] `b` 创建并返回一个新的 [`字符串生成器{:tact}`][p]。 当您需要将现有的 [`字符串{:tact}`][p]序列化为 [`单元格{:tact}`][单元格],并加上一些其他数据时非常有用。 + +使用示例 + +```tact +让 fizz:StringBuilder = beginStringFromBuilder(beginString()); +``` + +## StringBuilder.append + +```tact +extends mutates fun append(self: StringBuilder, s: String); +``` + +用于 [`StringBuilder{:tact}`][p]的扩展突变函数。 + +将 [`String{:tact}`][p] `s` 追加到 [`StringBuilder{:tact}`][p]。 + +使用示例 + +```tact +让 fizz:StringBuilder = beginString(); +fizz.append("oh"); +fizz.append("my"); +fizz.append("Tact!"); +``` + +## StringBuilder.concat + +```tact +extends fun concat(self: StringBuilder, s: String):StringBuilder; +``` + +[`字符串生成器{:tact}`][p]的扩展函数。 + +与 [`字符串{:tact}`][p] `s` 连接后,返回一个新的 [`字符串构造器{:tact}`][p]。 与 [`StringBuilder.append(){:tact}`](#stringbuilderappend)不同,可以进行链式操作。 + +使用示例 + +```tact +让 fizz:StringBuilder = beginString() + .concat("oh") + .concat("my") + .concat("Tact!"); +``` + +## StringBuilder.toString + +```tact +extends fun toString(self: StringBuilder):String; +``` + +[`字符串生成器{:tact}`][p]的扩展函数。 + +返回从 [`字符串生成器{:tact}`][p]构建的 [`字符串{:tact}`][p]。 + +使用示例 + +```tact +让 fizz:StringBuilder = beginString(); +let buzz:String = fizz.toString(); +``` + +## StringBuilder.toCell + +```tact +extends fun toCell(self: StringBuilder):Cell; +``` + +[`字符串生成器{:tact}`][p]的扩展函数。 + +返回由 [`字符串生成器{:tact}`][p]组装的 [`单元格{:tact}`][单元格]。 + +使用示例 + +```tact +让 fizz:StringBuilder = beginString(); +let buzz:Cell = fizz.toCell(); +``` + +## StringBuilder.toSlice + +```tact +extends fun toSlice(self: StringBuilder):Slice; +``` + +[`字符串生成器{:tact}`][p]的扩展函数。 + +从 [`StringBuilder{:tact}`][p]返回一个组装好的 [`单元格{:tact}`][单元格] 作为 [`片{:tact}`][片]。 别名为 [`self.toCell().asSlice(){:tact}`](/ref/core-cells#cellasslice)。 + +使用示例 + +```tact +让 s:StringBuilder = beginString(); +let fizz:Slice = s.toSlice(); +let buzz:Slice = s.toCell().asSlice(); + +fizz == buzz; // true +``` + +## String.asSlice + +```tact +extends fun asSlice(self: String):Slice; +``` + +[`字符串{:tact}`][p]的扩展函数。 + +从[`字符串{:tact}`][p]返回一个[`片{:tact}`][slice],方法是尝试将其所有位打包到一个连续的[单元格][p]列表中,每个单元格引用下一个单元格,并为将来的解析打开所有单元格。 + +请注意,[`片{:tact}`][片]中没有说明特定字符可能占用多少字节,也没有说明引用列表的深度,因此只有在知道自己在做什么的情况下才能使用该函数。 + +使用示例 + +```tact +让 s:String = "It's alive!It's alive!!!"; +let fizz:Slice = s.asSlice(); +let buzz:Slice = s.asSlice().asString().asSlice(); + +fizz == buzz; // true, 但要小心,因为并非总是如此。 +``` + +:::note + + 查看 `String.asSlice{:tact}` 函数如何实际使用:[如何将 `String` 转换为 `Int`](/cookbook/type-conversion#how-to-convert-a-string-to-an-int). + +::: + +## String.asComment + +```tact +extends fun asComment(self: String):Cell; +``` + +[`字符串{:tact}`][p]的扩展函数。 + +从[`字符串{:tact}`][p]返回一个[`单元格{:tact}`][单元格],在后者的前缀加上四个空字节。 这种格式用于传递文本注释作为报文正文。 + +使用示例 + +```tact +让 s:String = "When life gives you lemons, call them 'yellow oranges' and sell them for double the price."; +let fizz:Cell = s.asComment(); + +let b: StringBuilder = beginComment(); +b.append(s); +let buzz:Cell = b.toCell(); + +fizz == buzz; // true +``` + +## String.fromBase64 + +```tact +extends fun fromBase64(self: String):Slice; +``` + +[`字符串{:tact}`][p]的扩展函数。 + +从解码后的 [Base64](https://en.wikipedia.org/wiki/Base64) [`String{:tact}`][p] 中返回 [`片{:tact}`][片]。别名为 `self.asSlice().fromBase64(){:tact}`。 + +请注意,该函数是有限制的,只能从给定的 [`字符串{:tact}`][p] 中获取前 $1023$ 位数据,当 [`字符串{:tact}`][p] 较大(即包含超过 $1023$ 位数据)时,不会抛出异常。 + +如果给定的 [`String{:tact}`][p] 包含不属于 Base64 集的字符,则会出现[退出代码 134](/book/exit-codes#134)异常:"无效参数"。 + +使用示例 + +```tact +让 s:String = "SGVyZSdzIEpvaG5ueSE="; +let fizz:Slice = s.fromBase64(); +let buzz:Slice = s.asSlice().fromBase64(); + +fizz == buzz; // true +``` + +## Slice.asString + +```tact +extends fun asString(self: Slice):String; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +从 [`字符串{:tact}`][p]返回一个[`字符串{:tact}`][片],方法是尝试加载它的所有位,而不查找它的引用(如果有的话)。 + +请注意,该函数根本不查看引用,而且会将输出截断到 $1023$ 位,因此只有在知道自己在做什么的情况下才使用它。 + +使用示例 + +```tact +让 s:String = "Keep your Slices close, but your Strings closer."; +let fizz:String = s; +let buzz:String = s.asSlice().asString(); + +fizz == buzz; // true,但要小心,因为并非总是如此。 +``` + +## Slice.fromBase64 + +```tact +extends fun fromBase64(self: Slice):Slice; +``` + +[`切片{:tact}`][切片]的扩展函数。 + +从解码后的 [Base64](https://en.wikipedia.org/wiki/Base64) [`Slice{:tact}`][slice]返回一个新的 [`片{:tact}`][片]。 + +请注意,该函数是有限制的,只能从给定的 [`片{:tact}`][片] 中获取前 $1023$ 位数据,如果 [`片{:tact}`][片] 有更多数据(即有任何引用),则不会抛出异常。 + +如果给定的 [`Slice{:tact}`][p]包含不属于 Base64 集的字符,将产生[退出代码 134](/book/exit-codes#134)异常:"无效参数"。 + +使用示例 + +```tact +让 s:Slice = "SSBhbSBHcm9vdC4=".asSlice(); +let fizz:Slice = s.fromBase64(); +``` + +## Int.toString + +```tact +extends fun toString(self: Int):String; +``` + +[`Int{:tact}`][int]的扩展函数。 + +从 [`Int{:tact}`][int]值返回 [`String{:tact}`][p]。 + +使用示例 + +```tact +让 fizz:String = (84 - 42).toString(); +``` + +## Int.toFloatString + +```tact +extends fun toFloatString(self: Int, digits: Int):String; +``` + +[`Int{:tact}`][int]的扩展函数。 + +使用小数的[定点表示](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)从[`Int{:tact}`][int]值返回[`String{:tact}`][p],其中`self`是数字的有效部分,`digits`是小数部分的位数。 + +更确切地说,"digits "是 $10^{-\mathrm{digits}}$ 表达式的指数化参数,它给出了实际 [`Int{:tact}`][int]值乘以后所代表的小数。 参数 `digits` 必须在封闭区间内:$0 <$ `digits` $< 78$,否则会出现 [exit code 134](/book/exit-codes#134)异常:"无效参数"。 + +使用示例 + +```tact +let fizz:String = (42).toFloatString(9); // "0.000000042" +``` + +## Int.toCoinsString + +```tact +extends fun toCoinsString(self: Int):String; +``` + +[`Int{:tact}`][int]的扩展函数。 + +使用小数的[定点表示法](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)从[`Int{:tact}`][int]值返回[`String{:tact}`][p]。 别名为 `self.toFloatString(9){:tact}`。 + +用于用字符串表示 [纳米通币](/book/integers#nanotoncoin) [`Int{:tact}`][int]值。 + +使用示例 + +```tact +let nanotons:Int = 42; +let fizz:String = nanotons.toCoinsString(); +let buzz:String = nanotons.toFloatString(9); + +fizz == buzz; // true, 两者都存储 "0.000000042"。 +``` + +## 地址 + +```tact +extends fun toString(self: Address):String; +``` + +[`地址{:tact}`][p]的扩展函数。 + +从 [`地址{:tact}`][p]返回 [`字符串{:tact}`][p]。 + +使用示例 + +```tact +让 community:Address = address("UQDpXLZKrkHsOuE_C1aS69C697wE568vTnqSeRfBXZfvmVOo"); +let fizz:String = community.toString(); +``` + +[p]: /book/types#primitive-types +[bool]: /book/types#booleans +[int]: /book/integers +[电池]: /book/cells#cells +[一片]: /book/cells#slices diff --git a/docs/src/content/docs/zh-cn/ref/evolution/otp-001.mdx b/docs/src/content/docs/zh-cn/ref/evolution/otp-001.mdx new file mode 100644 index 000000000..d5fded966 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/evolution/otp-001.mdx @@ -0,0 +1,57 @@ +--- +title: OTP-001:支持的接口 +sidebar: + order: 1 +--- + +本建议书推荐了一种自省智能合约并找出它们支持哪些接口的方法。 + +## 动机 + +现在,我们无法猜测用户想用合同做什么,也无法弄清交易的内容,因为没有明确的方法找到合同的内容。 在大多数情况下,人类需要记住或猜测这是怎么回事。 + +## 指南 + +当人类试图签署一项交易时,他们需要清楚地了解自己在做什么:铸币、代币转移、盯盘、DAO 投票。 虽然以太坊钱包支持签署任意结构,但仍不清楚您签署的是什么以及这样做的影响。 同样,探险家也无法以漂亮的形式展示正在发生的事情。 + +与具体合同打交道的开始就是进行反省--弄清合同对自身的声明是什么。 当应用程序知道这份合同的内容时,它就可以建立一个良好的用户界面,显示交易历史,并验证人类试图签署的内容。 + +该提案描述了一种报告合同支持哪些接口的方法。 + +接口是以自由格式的规范定义的。 与其他大多数方法不同的是,本提案不仅将接口定义为合约的技术接口(获取方法、内部信息等),还将其定义为合约行为的描述。 附加合同技术接口的散列表示可能会导致不同标准之间的冲突,因此该提案对接口的定义比较松散。 此外,它还能使接口更加流畅,例如,无法传输的令牌可以只是一个合约,它必须获得返回 "false "的方法 "can_transfer",这将意味着该令牌根本不支持传输,而无需实现该方法。 + +接口 ID 是反向域名的哈希值(就像 Java 中的软件包),这样可以避免不同团队之间的名称冲突,如果他们只想为自己构建一些东西的话。 + +## 规格 + +为了支持自省,合约必须实现 supports_interface GET 方法: + +`(int...) supported_interfaces()` +返回支持的接口代码列表。 第一个值必须是 `hash("org.ton.introspection.v0")` = `123515602279859691144772641439386770278`。 +如果第一个值不正确,应用程序必须停止尝试反省合同。 +示例 + +```func +_ supported_interfaces() method_id { + return (123515602279859691144772641439386770278); +} +``` + +接口的哈希值定义为截断为 128 位的 SHA256。 + +## 缺点 + +这项建议并不能保证合约对接口的正确执行,也不能保证避免不同接口之间的名称冲突。 这不是本提案的目标。 + +这项建议与特定的技术接口无关。 这可能导致多个接口做同样的事情,但 ID 不同。 这不是本提案的目标,因为集中式注册表对现有接口非常有用,而自定义注册表则主要在内部使用。 + +## 理由和替代方案 + +- 为什么是 128 位? 我们需要在不发生冲突的情况下保留一个全局命名空间,我们不能使用小得多的命名空间,因为发生冲突的可能性会高得多。 我们正在研究类似 UUID 的熵,它正好是 128 位,并且经过时间验证。 超过 128 太浪费了。 +- 为什么是自由形式? 如前所述,定义一些 ID 更容易尽早开始工作,然后最终建立一个标准。 此外,接口(如 ERC20)通常不仅仅是一个技术接口,还包括一些如何使用它的规则。 +- 为什么不通过反编译找出合同支持什么? 在开放世界场景中,明示总比暗示好。 我们不能依靠自己的 "拆解 "能力来进行反省,即使是很小的错误也可能是致命的。 +- 为什么不是散列代表? 目前还没有编译器支持这一点,而且这项建议是面向未来的。 如果有人想构建更自动化的东西,他们可以很容易地按照自己的规则构建自己的哈希值,对外部观察者而言,一切保持不变。 + +## 现有技术 + +[以太坊接口检测](https://eips.ethereum.org/EIPS/eip-165) diff --git a/docs/src/content/docs/zh-cn/ref/evolution/otp-002.mdx b/docs/src/content/docs/zh-cn/ref/evolution/otp-002.mdx new file mode 100644 index 000000000..0b62d6973 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/evolution/otp-002.mdx @@ -0,0 +1,78 @@ +--- +title: OTP-002:合同 ABI +sidebar: + order: 2 +--- + +ABI 定义了如何与智能合约通信。 它包含有关合同接收器、数据结构等的信息。 + +## 动机 + +ABI 是一种重要工具,允许开发人员生成方便的绑定和用户界面等。 最好的消费用途之一就是使用 DAO,并能在签署交易之前确认它到底要做什么。 + +## 指南 + +本 OTP 基于 TLB+ 中定义的类型,建议在阅读本 OTP 之前先了解这些类型。 + +## 规格 + +ABI 是一个 JSON 文件: + +```json +{ + "name": "MyContract", + "types": [ + { + "name": "MyRequest", + "header": 12315123, + "fields": [ + { + "name": "queryId", + "type": { + "kind": "simple", + "type": "int", + "optional": false, + "format": "uint256" + } + } + ] + } + ], + "receivers": [ + { "type": "binary", "kind": "internal", "name": "MyRequest" }, + { "type": "binary", "kind": "internal" }, + { "type": "comment", "kind": "internal", "comment": "Vote!" }, + { "type": "comment", "kind": "internal" }, + { "type": "empty", "kind": "internal" } + ], + "getters": [ + { "name": "getOwner", "type": "address", "args": [] }, + { + "name": "getBalance", + "type": "coins", + "args": [ + { + "name": "invested", + "type": { + "kind": "simple", + "type": "uint", + "format": "coins" + } + } + ] + } + ], + "errors": { + "123": "Error description", + "124": "Division by zero" + } +} +``` + +## 缺点 + +- ABI 的二进制和紧凑表示法可能会更好,但目前并不重要。 + +## 现有技术 + +- OTP-001,是对本 OTP 的补充。 diff --git a/docs/src/content/docs/zh-cn/ref/evolution/otp-003.mdx b/docs/src/content/docs/zh-cn/ref/evolution/otp-003.mdx new file mode 100644 index 000000000..91bbe4127 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/evolution/otp-003.mdx @@ -0,0 +1,19 @@ +--- +title: OTP-003:自我评估报告 +sidebar: + order: 3 +--- + +本建议定义了如何使用 IPFS 链接报告合同的 ABI。 + +## 动机 + +通常,ABI 是通过第三方服务或 GitHub 上的某个存储库单独提供的。 本提案建议增加一项新的合同 ABI 自我报告,使用 IPFS 链接。 这将使我们避免任何第三方依赖,并允许任何人构建依赖于 ABI 的工具,如探索器、钱包等。 + +## 规格 + +为支持这一提议,合同应实施 OTP-001,并报告一个接口 "org.ton.abi.ipfs.v0"。 然后实现一个获取方法 `get_abi_ipfs`,该方法会返回一个字符串,其中包含指向 ABI 文件的 IPFS 链接。 链接格式应为 `ipfs://`。 + +## 缺点 + +- 不更新合同就无法升级 ABI。 这个缺点只存在于硬编码链接中。 diff --git a/docs/src/content/docs/zh-cn/ref/evolution/otp-004.mdx b/docs/src/content/docs/zh-cn/ref/evolution/otp-004.mdx new file mode 100644 index 000000000..b82cb85cb --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/evolution/otp-004.mdx @@ -0,0 +1,36 @@ +--- +title: OTP-004:自动编码器 +sidebar: + order: 4 +--- + +该提案定义了一种为给定结构自动构建序列化布局的方法。 + +## 动机 + +在 TLB 中设计序列化布局是一项非常冒险的任务。 开发人员必须注意单元格的大小限制,并记住每个字段使用的位数。 这是一项非常容易出错的工作,而且很容易出错。 本建议旨在解决这一问题,它提供了一种为给定结构自动构建序列化布局的方法。 + +## 规格 + +我们将自动编码器定义为一种为给定结构构建序列化布局的急迫算法。 算法定义如下 + +```text +将当前单元格 + 中的可用引用和位分别定义为 `available_references` 和 `available_bits`。 + 注意:必须至少为序列化尾部保留一个引用,为可选标记保留一个 + 位。根据上下文,可能会保留更多的引用或位。 + +对于 A 中的每个字段: + (size_bits, size_ref) = get_field_max_size(field); + if (available_bits >= size_bits && available_references >= size_ref) { + Push field to a current cell + } else { + available_references = (1023 - 1); + available_bits = (4 - 1); + 分配一个新的尾部并从当前字段继续 +} +``` + +## 缺点 + +- 这是一种隐式算法。 目前还不清楚是否需要检查该分配器的结果,以便进行兼容的序列化。 diff --git a/docs/src/content/docs/zh-cn/ref/evolution/otp-005.mdx b/docs/src/content/docs/zh-cn/ref/evolution/otp-005.mdx new file mode 100644 index 000000000..0a92976aa --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/evolution/otp-005.mdx @@ -0,0 +1,35 @@ +--- +title: OTP-005:参数可寻址合同 +sidebar: + order: 5 +--- + +该提案定义了一种通过参数而非初始数据来处理合同的方法。 + +## 动机 + +初始数据可能与论据大相径庭。 这样,我们就能避免在当前合约的上下文中执行来自另一个合约的不受信任的代码,或在链外执行 TVM 代码进行部署,而这在某些情况下可能会有风险。 + +## 规格 + +本规范定义了一种将参数写入初始化数据单元的方法,以便在部署过程中由合约代码读取。 + +### 前缀 + +前缀由智能合约自行定义,但默认情况下假定为 "单个零位"。 合同代码使用前缀来区分已部署和未部署状态。 + +### 参数 编码 + +参数使用 [Auto Encoder](/ref/evolution/otp-004)编码。 + +### 合同要求 + +- 合约必须暴露 `lazy_deployment_completed` 获取方法,如果合约已部署,则返回 `true`,否则返回 `false`。 +- 合同必须暴露 `org.ton.deploy.lazy.v0` 接口。 + +## 缺点 + +- 合同可能处于半部署状态 +- 有多种写参数的方法,最终会产生不同的初始数据和不同的地址 +- 您可以部署一个预初始化的合约,它将有一个不同的地址,但功能完整 +- 部署时无法预测的天然气用量。 部署费用通常很高,但这项建议使费用更加高昂。 diff --git a/docs/src/content/docs/zh-cn/ref/evolution/otp-006.mdx b/docs/src/content/docs/zh-cn/ref/evolution/otp-006.mdx new file mode 100644 index 000000000..9dbb44d5a --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/evolution/otp-006.mdx @@ -0,0 +1,54 @@ +--- +title: OTP-006:合同包 +sidebar: + order: 6 +--- + +该提案定义了一种将编译合同、其依赖关系和所有相关元数据打包成一个文件的方法。 + +## 动机 + +需要一种统一的软件包格式,以简化使用各种工具部署和升级合同的过程,而无需对其进行配置。 + +## 规格 + +软件包文件的扩展名为".pkg",是一个 JSON 文件: + +```json +{ + "name": "My Contract", + "code": "... boc of code ...", + "abi": "ABI string to be uploaded as is to IPFS or Ton Storage", + "init": { + "kind": "direct", // Means that this contract can be deployed as is + "args": { + // ... Arguments in ABI format + }, + "prefix": { + // Optional prefix for contract init state + "bits": 0, // Number of bits in prefix + "value": 0 // Value of prefix + }, + "deployment": { + "kind": "system-cell", // Means that this contract can be deployed as is + "system": "... boc of system cell ..." + } + }, + "sources": { + "file.ton": "... base64 encoded source file ..." + }, + "compiler": { + "name": "func", + "version": "0.4.1", + "parameters": "..." // Optional string parameters + } +} +``` + +## 缺点 + +无 + +## 参考资料 + +- 细胞袋 (BoC): https://docs.ton.org/develop/data-formats/cell-boc#packing-a-bag-of-cells diff --git a/docs/src/content/docs/zh-cn/ref/evolution/overview.mdx b/docs/src/content/docs/zh-cn/ref/evolution/overview.mdx new file mode 100644 index 000000000..8b06521f1 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/evolution/overview.mdx @@ -0,0 +1,50 @@ +--- +title: 演变概述 +sidebar: + label: 概述 + order: 0 +--- + +import { LinkCard, CardGrid } from '@astrojs/starlight/components'; + +本小节包含 Tact 基金会定义的所有标准,这些标准用于 Tact 和 TON 生态系统的演进过程。 +此外,它还提供 TEP(TON 增强建议)和 Tact 更新的最新变更日志。 + +## 公开招标(OTP) + + + + + + + + + + +## 加强统一部落协议(TEP) + +TON Enhancement Proposals 的主要目标是提供一种方便、正式的方式来提议对 TON 区块链进行修改,并规范生态系统不同部分之间的交互方式。提案管理通过 GitHub 拉取请求完成,流程在 [TEP-1](https://github.com/ton-blockchain/TEPs/blob/master/text/0001-tep-lifecycle.md) 中有正式描述。 + +[合并的技术专家小组名单](https://github.com/ton-blockchain/TEPs#merged-teps)。 + +## 更新日志 + +主 Tact 代码库的所有显著变更都记录在 [CHANGELOG.md](https://github.com/tact-lang/tact/blob/main/CHANGELOG.md) 中。 diff --git a/docs/src/content/docs/zh-cn/ref/index.mdx b/docs/src/content/docs/zh-cn/ref/index.mdx new file mode 100644 index 000000000..d3379ca18 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/index.mdx @@ -0,0 +1,57 @@ +--- +title: 参考资料概览 +--- + +import { LinkCard, CardGrid, Steps } from '@astrojs/starlight/components'; + +欢迎来到 Tact 文档的**参考资料**部分,这里是了解 Tact 标准库、语法规范和演变过程的地方。 + +以下是其主要内容: + + + +1. #### 核心图书馆 + + [核心库](/ref/core-base)提供了自动包含的函数、特质和其他结构体的完整列表及其使用示例。 + + + + + +2. #### 标准图书馆 + + [标准库](/ref/standard-libraries) 子章节解释了如何使用捆绑的库,列出了所有库的内容及其使用示例。 + + + + + +3. #### 规格 + + [规范](/ref/spec) 页面提供了用欧姆语言编写的完整 Tact 语法,该语法用于 Tact 的编译器。 面向更有经验的程序员,但通常仍能非常方便地快速掌握该语言的所有可能语法。 + + + + + +4. #### 演变 + + 最后,[Evolution](/ref/evolution/overview) 子章节介绍了有关语言语义的重要决定、Tact 的未来以及 Tact 更新日志的链接。 + + + + + + diff --git a/docs/src/content/docs/zh-cn/ref/spec.mdx b/docs/src/content/docs/zh-cn/ref/spec.mdx new file mode 100644 index 000000000..1f8220a03 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/spec.mdx @@ -0,0 +1,306 @@ +--- +title: Tact 规格 +pagefind: false +--- + +:::danger[Not 已实施] + 在 [#76](https://github.com/tact-lang/tact-docs/issues/76) 实现之前,本页主要是一个存根。 +::: + +其编译器中使用的战术语法是用[欧姆语言](https://ohmjs.org)编写的,该语言基于[解析表达式语法](http://en.wikipedia.org/wiki/Parsing_expression_grammar) (PEG),是一种描述语法的形式化方法,类似于正则表达式和无上下文语法。 + +{/* + 这是一条注释,在页面上是隐藏的。 + + Shiki 通过 TextMate 语法对 Ohm 的高亮处理效果不佳,尽管在 VSCode 中表现良好。我认为原因可能是 Shiki 逐行高亮或使用了不同的正则表达式引擎。我不确定具体问题在哪里,所以在修复之前,最好使用 tree-sitter-ohm 并通过其 `highlight` 命令生成 ANSI 序列。然后由 Shiki 完成剩余的工作: +*/} + +```ansi +Tact { + + // Starting point of the program + Program = ProgramItem* + ProgramItem = Struct + | Contract + | Primitive + | StaticFunction + | NativeFunction + | ProgramImport + | Trait + | Constant + ProgramImport = import stringLiteral ";" + + // Built-in declarations + Primitive = "primitive" Type ";" + + // Static function + StaticFunction = Function + NativeFunction = nameAttribute "(" funcId ")" FunctionAttribute* native id "(" ListOf<FunctionArg,","> ")" ";" --withVoid + | nameAttribute "(" funcId ")" FunctionAttribute* native id "(" ListOf<FunctionArg,","> ")" ":" Type ";" --withType + + // Field declarations + Type = typeLiteral "?" --optional + | typeLiteral --required + | "map" "<" typeLiteral (as id)? "," typeLiteral (as id)? ">" --map + | "bounced" "<" typeLiteral ">" --bounced + Field = id ":" Type ";" --default + | id ":" Type "=" Expression ";" --defaultWithInit + | id ":" Type as id ";" --withSerialization + | id ":" Type as id "=" Expression ";" --withSerializationAndInit + + // Constant + ConstantAttribute = virtual --virtual + | override --override + | abstract --abstract + Constant = ConstantAttribute* ~fun const id ":" Type "=" Expression ";" --withValue + | ConstantAttribute* ~fun const id ":" Type ";" --withEmpty + + // Struct + Struct = "struct" typeLiteral "{" StructBody* "}" --originary + | "message" typeLiteral "{" StructBody* "}" --message + | "message" "(" integerLiteral ")" typeLiteral "{" StructBody* "}" --messageWithId + StructBody = Field + + // Contract + Contract = ContractAttribute* contract id "{" ContractBody* "}" --simple + | ContractAttribute* contract id with ListOf<id,","> "{" ContractBody* "}" --withTraits + ContractInit = "init" "(" ListOf<FunctionArg,","> ")" "{" Statement* "}" + ContractBody = Field + | ContractInit + | ReceiveFunction + | Function + | Constant + + // Trait + Trait = ContractAttribute* trait id "{" TraitBody* "}" --originary + | ContractAttribute* trait id with ListOf<id,","> "{" TraitBody* "}" --withTraits + TraitBody = Field + | ReceiveFunction + | Function + | Constant + + // Contract attributes + ContractAttribute = "@interface" "(" stringLiteral ")" --interface + + // Function + FunctionAttribute = "get" --getter + | mutates --mutates + | extends --extends + | virtual --virtual + | override --override + | inline --inline + | abstract --abstract + Function = FunctionAttribute* fun id "(" ListOf<FunctionArg,","> ")" "{" Statement* "}" --withVoid + | FunctionAttribute* fun id "(" ListOf<FunctionArg,","> ")" ":" Type "{" Statement* "}" --withType + | FunctionAttribute* fun id "(" ListOf<FunctionArg,","> ")" ";" --abstractVoid + | FunctionAttribute* fun id "(" ListOf<FunctionArg,","> ")" ":" Type ";" --abstractType + FunctionArg = id ":" Type + + ReceiveFunction = "receive" "(" FunctionArg ")" "{" Statement* "}" --simple + | "receive" "(" ")" "{" Statement* "}" --empty + | "receive" "(" stringLiteral ")" "{" Statement* "}" --comment + | "bounced" "(" FunctionArg ")" "{" Statement* "}" --bounced + | "external" "(" FunctionArg ")" "{" Statement* "}" --externalSimple + | "external" "(" stringLiteral ")" "{" Statement* "}" --externalComment + | "external" "(" ")" "{" Statement* "}" --externalEmpty + + // Statements + Statement = StatementLet + | StatementBlock + | StatementReturn + | StatementExpression + | StatementAssign + | StatementAugmentedAssign + | StatementCondition + | StatementWhile + | StatementRepeat + | StatementUntil + StatementBlock = "{" Statement* "}" + StatementLet = let id ":" Type "=" Expression ";" + StatementReturn = return Expression ";" --withExpression + | return ";" --withoutExpression  + StatementExpression = Expression ";" + StatementAssign = LValue "=" Expression ";" + StatementAugmentedAssign = StatementAugmentedAssignAdd + | StatementAugmentedAssignSub + | StatementAugmentedAssignMul + | StatementAugmentedAssignDiv + | StatementAugmentedAssignRem + StatementAugmentedAssignAdd = LValue "+=" Expression ";" + StatementAugmentedAssignSub = LValue "-=" Expression ";" + StatementAugmentedAssignMul = LValue "*=" Expression ";" + StatementAugmentedAssignDiv = LValue "/=" Expression ";" + StatementAugmentedAssignRem = LValue "%=" Expression ";" + StatementCondition = if Expression "{" Statement* "}" ~else --simple + | if Expression "{" Statement* "}" else "{" Statement* "}" --withElse + | if Expression "{" Statement* "}" else StatementCondition --withElseIf + StatementWhile = while "(" Expression ")" "{" Statement* "}" + StatementRepeat = repeat "(" Expression ")" "{" Statement* "}" + StatementUntil = do "{" Statement* "}" until "(" Expression ")" ";" + + // L-value + LValue = id "." LValue --more + | id --single + + // Expressions + Expression = ExpressionConditional + ExpressionConditional = ExpressionOr "?" ExpressionOr ":" ExpressionConditional --ternary + | ExpressionOr + ExpressionOr = ExpressionOr "||" ExpressionAnd --or + | ExpressionAnd + ExpressionAnd = ExpressionAnd "&&" ExpressionCompare --and + | ExpressionCompare + ExpressionCompare = ExpressionCompare "!=" ExpressionBinary --not + | ExpressionCompare "==" ExpressionBinary --eq + | ExpressionCompare ">" ExpressionBinary --gt + | ExpressionCompare ">=" ExpressionBinary --gte + | ExpressionCompare "<" ExpressionBinary --lt + | ExpressionCompare "<=" ExpressionBinary --lte + | ExpressionBinary + ExpressionBinary = ExpressionBinary ">>" ExpressionAdd --shr + | ExpressionBinary "<<" ExpressionAdd --shl + | ExpressionBinary "&" ExpressionAdd --bin_and + | ExpressionBinary "|" ExpressionAdd --bin_or + | ExpressionAdd + ExpressionAdd = ExpressionAdd "+" ~"+" ExpressionMul --add + | ExpressionAdd "-" ~"-" ExpressionMul --sub + | ExpressionMul + ExpressionMul = ExpressionMul "*" ExpressionUnary --mul + | ExpressionMul "/" ExpressionUnary --div + | ExpressionMul "%" ExpressionUnary --rem + | ExpressionUnary + ExpressionUnary = "-" ExpressionUnarySuffix --neg + | "+" ExpressionUnarySuffix --add + | "!" ExpressionUnarySuffix --not + | ExpressionUnarySuffix + ExpressionUnarySuffix = ExpressionValue "!!" --notNull + | ExpressionValue + ExpressionBracket = "(" Expression ")" + + // Order is important + ExpressionValue = ExpressionCall + | ExpressionField + | ExpressionStaticCall + | ExpressionBracket + | ExpressionNew + | integerLiteral + | boolLiteral + | id + | null + | ExpressionInitOf + | ExpressionString + ExpressionString = stringLiteral + ExpressionField = ExpressionValue "." id ~"(" + ExpressionCall = ExpressionValue "." id "(" ListOf<Expression, ","> ")" + ExpressionNew = id "{" ListOf<NewParameter, ","> "}" + NewParameter = id ":" Expression + ExpressionStaticCall = id "(" ListOf<Expression, ","> ")" + ExpressionInitOf = initOf id "(" ListOf<Expression, ","> ")" + + // Type Literal + typeLiteral = letterAsciiUC typeLiteralPart* + typeLiteralPart = letterAscii | digit | "_" + + // Integer Literal + // hexDigit defined in Ohm's built-in rules (otherwise: hexDigit = "0".."9" | "a".."f" | "A".."F") + // digit defined in Ohm's built-in rules (otherwise: digit = "0".."9") + integerLiteral = integerLiteralHex | integerLiteralBin | integerLiteralOct | integerLiteralDec // Order is important + integerLiteralDec = nonZeroDigit ("_"? digit)* --nonZeroIntegerLiteralDec + | "0" digit* --integerLiteralWithLeadingZero + integerLiteralHex = ("0x" | "0X") hexDigit ("_"? hexDigit)* + integerLiteralBin = ("0b" | "0B") binDigit ("_"? binDigit)* + integerLiteralOct = ("0o" | "0O") octDigit ("_"? octDigit)* + binDigit = "0" | "1" + octDigit = "0".."7" + nonZeroDigit = "1".."9" + + // Letters + letterAsciiLC = "a".."z" + letterAsciiUC = "A".."Z" + letterAscii = letterAsciiLC | letterAsciiUC + letterComment = letterAsciiLC | letterAsciiUC | digit | "_" + + // ID Literal + idStart = letterAscii | "_" + idPart = letterAscii | digit | "_" + id = ~reservedWord #idStart #(idPart*) + + // FunC id + funcLetter = letterAscii | "_" | "'" | "?" | "!" | "::" | "&" + funcId = funcLetter #(funcLetter | digit)* + + // Bool Literal + boolLiteral = ("true" | "false") ~idPart + + // String literal + stringLiteralCharacter = ~("\"" | "\\" | lineTerminator) any + stringLiteral = "\"" stringLiteralCharacter* "\"" + + // Keywords + // NOTE Order is important + keyword = fun + | let + | return + | extend + | native + | public + | null + | if + | else + | while + | repeat + | do + | until + | as + | mutates + | extends + | import + | with + | trait + | initOf + | override + | abstract + | virtual + | inline + | const + contract = "contract" ~idPart + let = "let" ~idPart + fun = "fun" ~idPart + return = "return" ~idPart + extend = "extend" ~idPart + native = "native" ~idPart + public = "public" ~idPart + null = "null" ~idPart + if = "if" ~idPart + else = "else" ~idPart + while = "while" ~idPart + repeat = "repeat" ~idPart + do = "do" ~idPart + until = "until" ~idPart + as = "as" ~idPart + mutates = "mutates" ~idPart + extends = "extends" ~idPart + import = "import" ~idPart + with = "with" ~idPart + trait = "trait" ~idPart + initOf = "initOf" ~idPart + virtual = "virtual" ~idPart + override = "override" ~idPart + inline = "inline" ~idPart + const = "const" ~idPart + abstract = "abstract" ~idPart + + // Attributes + nameAttribute = "@name" + + // Reserved + reservedWord = keyword + + // Comments + space += comment | lineTerminator + comment = multiLineComment | singleLineComment + lineTerminator = "\n" | "\r" | "\u2028" | "\u2029" + multiLineComment = "/*" (~"*/" any)* "*/" + singleLineComment = "//" (~lineTerminator any)* +} +``` diff --git a/docs/src/content/docs/zh-cn/ref/standard-libraries.mdx b/docs/src/content/docs/zh-cn/ref/standard-libraries.mdx new file mode 100644 index 000000000..a28493df3 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/standard-libraries.mdx @@ -0,0 +1,44 @@ +--- +title: 标准图书馆概述 +--- + +有些库(也称为标准库或 stdlibs)与 Tact 编译器捆绑在一起,但不会自动包含到项目中,除非明确指定。 + +要导入任何标准库,请使用[`import{:tact}`关键字](/book/import),然后在[字符串][p]中输入该库的名称,像这样: + +```tact +// 这将把 @stdlib/deploy 中的所有内容纳入代码库: +import "@stdlib/deploy"; +``` + +## 标准图书馆列表: {#list} + +| 图书馆 | 说明 | 常用的应用程序接口 | +| :----------------------- | :------------------------------------------------------ | :---------------------------------------------------------------------- | +| [`@stdlib/config`][1] | 配置和选民地址检索。 | [`getConfigAddress(){:tact}`][gca],[`getElectorAddress(){:tact}`][gea]。 | +| [`@stdlib/content`][2] | 将链外链接[字符串][p]编码为[`单元格{:tact}`][单元格]。 | [`createOffchainContent(){:tact}`][coff]。 | +| [`@stdlib/deploy`][3] | 统一的部署机制。 | [`可部署{:tact}`][dep],[`可部署工厂{:tact}`][fcd]。 | +| [`@stdlib/dns`][4] | 解析 [DNS][dns] 名称。 | [`DNSResolver{:tact}`][dnsr],[`dnsInternalVerify(){:tact}`][dnsi]。 | +| [`@stdlib/ownable`][5] | 所有权管理的特质。 | [`可拥有{:tact}`][拥有],[`可拥有可转让{:tact}`][拥有] | +| [`@stdlib/stoppable`][6] | 允许合同停止的特征。 需要 [@stdlib/ownable][5]。 | [`Stoppable{:tact}`][stp],[`Resumable{:tact}`][res]。 | + +[1]: /ref/stdlib-config +[gca]: /ref/stdlib-config#getconfigaddress +[gea]: /ref/stdlib-config#getelectoraddress +[2]: /ref/stdlib-content +[库克]: /ref/stdlib-content#createoffchaincontent +[3]: /ref/stdlib-deploy +[描写]: /ref/stdlib-deploy#deployable +[fcd]: /ref/stdlib-deploy#factorydeployable +[4]: /ref/stdlib-dns +[dnsr]: /ref/stdlib-dns#dnsresolver +[dnsi]: /ref/stdlib-dns#dnsinternalverify +[5]: /ref/stdlib-ownable +[拥有]: /ref/stdlib-ownable#ownable +[拥有]: /ref/stdlib-ownable#ownabletransferable +[6]: /ref/stdlib-stoppable +[stp]: /ref/stdlib-stoppable#stoppable +[重新]: /ref/stdlib-stoppable#resumable +[p]: /book/types#primitive-types +[电池]: /book/cells#cells +[dns]: https://docs.ton.org/participate/web3/dns diff --git a/docs/src/content/docs/zh-cn/ref/stdlib-config.mdx b/docs/src/content/docs/zh-cn/ref/stdlib-config.mdx new file mode 100644 index 000000000..66d2ee2ff --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/stdlib-config.mdx @@ -0,0 +1,55 @@ +--- +title: "@stdlib/config" +--- + +提供配置和选民地址检索功能。 + +要使用该库,请导入 `@stdlib/config`: + +```tact +import "@stdlib/config"; +``` + +## 功能 + +### 获取配置地址 + +```tact +fun getConfigAddress(): Address; +``` + +读取配置参数 $0$ 作为 [`地址{:tact}`][p]。 + +源代码 + +```tact +fun getConfigAddress():Address { + let cell:Cell = getConfigParam(0)!!; + let sc:Slice = cell.beginParse(); + return newAddress(-1, sc.loadUint(256)); +} +``` + +### getElectorAddress + +```tact +fun getElectorAddress(): Address; +``` + +读取配置参数 $1$ 作为 [`地址{:tact}`][p]。 + +源代码 + +```tact +fun getElectorAddress(): Address { + let cell: Cell = getConfigParam(1)!!; + let sc: Slice = cell.beginParse(); + return newAddress(-1, sc.loadUint(256)); +} +``` + +## 资料来源 + +- [config.tact](https://github.com/tact-lang/tact/blob/61541b7783098e1af669faccd7d2334c10981c72/stdlib/libs/config.tact) + +[p]: /book/types#primitive-types diff --git a/docs/src/content/docs/zh-cn/ref/stdlib-content.mdx b/docs/src/content/docs/zh-cn/ref/stdlib-content.mdx new file mode 100644 index 000000000..2b4a4b622 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/stdlib-content.mdx @@ -0,0 +1,38 @@ +--- +title: "@stdlib/content" +--- + +提供一个函数,用于编码从 [`字符串{:tact}`][p]到 [`单元格{:tact}`][单元格] 的链外链接。 + +要使用该库,请导入 `@stdlib/content`: + +```tact +import "@stdlib/content"; +``` + +## 功能 + +### 创建区块链内容 + +```tact +fun createOffchainContent(link: String):Cell; +``` + +编码从[`字符串{:tact}`][p]到[`单元{:tact}`][单元格]的链外`链接`。 + +源代码 + +```tact +fun createOffchainContent(link: String):Cell { + let builder:StringBuilder = beginStringFromBuilder(beginCell().storeUint(0x01, 8)); + builder.append(link); + return builder.toCell(); +} +``` + +## 资料来源 + +- [content.tact](https://github.com/tact-lang/tact/blob/61541b7783098e1af669faccd7d2334c10981c72/stdlib/libs/content.tact) + +[p]: /book/types#primitive-types +[电池]: /book/cells#cells diff --git a/docs/src/content/docs/zh-cn/ref/stdlib-deploy.mdx b/docs/src/content/docs/zh-cn/ref/stdlib-deploy.mdx new file mode 100644 index 000000000..5c445c0fe --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/stdlib-deploy.mdx @@ -0,0 +1,96 @@ +--- +title: "@stdlib/deploy" +--- + +提供统一的部署机制。 + +要使用该库,请导入 `@stdlib/deploy`: + +```tact +import "@stdlib/deploy"; +``` + +## 信息 + +### Deploy + +```tact +message Deploy { + queryId:Int as uint64; +} +``` + +### DeployOk + +```tact +message DeployOk { + queryId:Int as uint64; +} +``` + +### 工厂部署 + +```tact +message FactoryDeploy { + queryId:Int as uint64; + cashback:Address; +} +``` + +## 特质 + +### 可部署 + +最简单的特质 `可部署{:tact}`,通过为[Deploy](#deploy)消息实现一个简单的接收器,为部署提供了一个方便的统一机制。 + +所有合同都是通过发送信息来部署的。 虽然任何信息都可用于此目的,但最佳做法是使用特殊的 [部署](#deploy) 信息。 + +该信息只有一个字段 `queryId` ,由部署者提供(通常设置为零)。 如果部署成功,合约将回复一条 [DeployOk](#deployok) 消息,并在回复中呼应相同的 `queryId` 。 + +源代码 + +```tact +trait Deployable { + receive(deploy: Deploy) { + self.notify(DeployOk{queryId: deploy.queryId}.toCell()); + } +} +``` + +使用示例 + +```tact /Deployable/ +import "@stdlib/deploy"; + +contract ExampleContract with Deployable { + // Now, this contract has a receiver for Deploy message +} +``` + +### 工厂可部署 + +Trait `FactoryDeployable{:tact}` 为连锁部署提供了方便的统一机制。 + +源代码 + +```tact +trait FactoryDeployable { + receive(deploy: FactoryDeploy) { + self.forward(deploy.cashback, DeployOk{queryId: deploy.queryId}.toCell(), false, null); + } +} +``` + +使用示例 + +```tact /FactoryDeployable/ +import "@stdlib/deploy"; + +contract ExampleContract with FactoryDeployable { + // Now, this contract has a receiver for FactoryDeploy message +} +``` + +## 资料来源 + +- [deploy.tact](https://github.com/tact-lang/tact/blob/61541b7783098e1af669faccd7d2334c10981c72/stdlib/libs/deploy.tact) diff --git a/docs/src/content/docs/zh-cn/ref/stdlib-dns.mdx b/docs/src/content/docs/zh-cn/ref/stdlib-dns.mdx new file mode 100644 index 000000000..69018ad39 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/stdlib-dns.mdx @@ -0,0 +1,194 @@ +--- +title: "@stdlib/dns" +--- + +提供[DNS](https://docs.ton.org/participate/web3/dns)名称的解析手段。 + +要使用该库,请导入 `@stdlib/dns`: + +```tact +import "@stdlib/dns"; +``` + +## 结构 + +### DNSResolveResult + +```tact +struct DNSResolveResult { + prefix:Int; + record:Cell?; +} +``` + +## 功能 + +### dnsStringToInternal + +```tact +@name(dns_string_to_internal) +native dnsStringToInternal(str: String):Slice? +``` + +将 DNS 字符串转换为[`slice{:tact}`][slice]或[`null{:tact}`](/book/optionals),如果不可能的话。 + +源代码 (FunC):[dns.fc#L1](https://github.com/tact-lang/tact/blob/e69c7fc99dc9be3fa5ff984456c03ffe8fed3677/stdlib/libs/dns.fc#L1) + +### dnsInternalNormalize + +```tact +@name(dns_internal_normalize) +native dnsInternalNormalize(src: Slice):Slice; +``` + +将 [`Slice{:tact}`][片] 的内部 DNS 表示规范化。 传递的 [`Slice{:tact}`][slice]必须没有任何引用,否则将产生[退出代码 134](/book/exit-codes#134)异常:"无效参数"。 + +源代码 (FunC):[dns.fc#L125](https://github.com/tact-lang/tact/blob/e69c7fc99dc9be3fa5ff984456c03ffe8fed3677/stdlib/libs/dns.fc#L125) + +### dnsInternalVerify + +```tact +@name(dns_internal_verify) +native dnsInternalVerify(subdomain: Slice):Bool; +``` + +验证子域 [`Slice{:tact}`][片] 的内部 DNS 表示。 + +源代码 (FunC):[dns.fc#L81](https://github.com/tact-lang/tact/blob/e69c7fc99dc9be3fa5ff984456c03ffe8fed3677/stdlib/libs/dns.fc#L81) + +### dnsExtractTopDomainLength + +```tact +fun dnsExtractTopDomainLength(subdomain: Slice):Int; +``` + +计算顶域在 "子域"[`片{:tact}`][片]中的长度。 + +源代码 + +```tact +fun dnsExtractTopDomainLength(subdomain: Slice):Int { + let i:Int = 0; + let needBreak:Bool = false; + do { + let char:Int = subdomain.loadUint(8); // 我们不检查 domain.length,因为它必须包含 0 个字符 + needBreak = char == 0; + if (!needBreak) { + i += 8; + } + } until (needBreak); + require(i != 0, "Invalid DNS name"); + return i; +} +``` + +### dnsExtractTopDomain + +```tact +fun dnsExtractTopDomain(subdomain: Slice):Slice; +``` + +从 "子域 "中提取顶域 [`片{:tact}`][片]。 + +源代码 + +```tact +fun dnsExtractTopDomain(subdomain: Slice):Slice { + let len: Int = dnsExtractTopDomainLength(subdomain); + return subdomain.loadBits(len); +} +``` + +### dnsResolveNext + +```tact +fun dnsResolveNext(address: Address):Cell; +``` + +将`地址`[`地址{:tact}`][p]解析为[`单元格{:tact}`][单元格]。 + +源代码 + +```tact +fun dnsResolveNext(address: Address):Cell { + return beginCell() + .storeUint(0xba93, 16) + .storeAddress(address) + .endCell(); +} +``` + +### dnsResolveWallet + +```tact +fun dnsResolveWallet(address: Address):Cell; +``` + +将钱包 `address`[`Address{:tact}`][p]解析为[`Cell{:tact}`][cell]。 + +源代码 + +```tact +fun dnsResolveWallet(address: Address):Cell { + return beginCell() + .storeUint(0x9fd3, 16) + .storeAddress(address) + .storeUint(0, 8) + .endCell(); +} +``` + +## 特质 + +### DNSResolver + +Trait `DNSResolver` 为 DNS 解析提供了两个辅助函数: + +1. [getter函数](/book/functions#getter-functions) `dnsresolve(){:tact}`,对应于其[FunC变体](https://docs.ton.org/develop/howto/subresolvers#dnsresolve-code)。 +2. 虚拟函数 `doResolveDNS(){:tact}`,由子域 [`片{:tact}`][片] 位创建结构 [DNSResolveResult](#dnsresolveresult)。 + +源代码 + +```tact +trait DNSResolver { + get fun dnsresolve(subdomain: Slice, category: Int):DNSResolveResult { + // Normalize + let delta: Int = 0; + if (subdomain.preloadUint(8) == 0) { + subdomain.loadUint(8); // Skip first byte + delta += 8; + } // Check correctity + + // 检查正确性 + require(dnsInternalVerify(subdomain), "Invalid DNS name"); + + // 解析 + let res:DNSResolveResult = self.doResolveDNS(subdomain, category); + return DNSResolveResult{prefix: res.prefix + delta, record: res.record}; + } + virtual fun doResolveDNS(subdomain: Slice, category: Int):DNSResolveResult { + return DNSResolveResult{prefix: subdomain.bits(), record: null}; + } +} +``` + +使用示例 + +```tact +import "@stdlib/dns"; + +contract ExampleContract with DNSResolver { + // Now, this contract has a: + // 1. dnsresolve getter function + // 2. doResolveDNS virtual function +} +``` + +## 资料来源 + +- [dns.tact](https://github.com/tact-lang/tact/blob/61541b7783098e1af669faccd7d2334c10981c72/stdlib/libs/dns.tact) +- [dns.fc](https://github.com/tact-lang/tact/blob/e69c7fc99dc9be3fa5ff984456c03ffe8fed3677/stdlib/libs/dns.fc) + +[p]: /book/types#primitive-types +[电池]: /book/cells#cells +[一片]: /book/cells#slices diff --git a/docs/src/content/docs/zh-cn/ref/stdlib-ownable.mdx b/docs/src/content/docs/zh-cn/ref/stdlib-ownable.mdx new file mode 100644 index 000000000..48e38d731 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/stdlib-ownable.mdx @@ -0,0 +1,114 @@ +--- +title: "@stdlib/ownable" +--- + +为可拥有的合约提供 [traits](/book/types#composite-types)。 这是最常用的性状,大多数其他性状都需要它。 + +要使用该库,请导入 `@stdlib/ownable`: + +```tact +import "@stdlib/ownable"; +``` + +## 信息 + +### ChangeOwner + +```tact +message ChangeOwner { + queryId:Int as uint64; + newOwner: Address; +} +``` + +### ChangeOwnerOk + +```tact +message ChangeOwnerOk { + queryId:Int as uint64; + newOwner: Address; +} +``` + +## 特质 + +### ownable + +[Trait](/book/types#composite-types) `Ownable{:tact}` 声明了 [contract](/book/contracts) 的所有者(不可编辑),并提供了一个辅助函数 `requireOwner(){:tact}` 来检查消息是否由所有者发送。 + +此 [trait](/book/types#composite-types) 要求声明一个字段 `owner: Address{:tact}`,并公开一个 [getter 函数](/book/functions#getter-functions) `owner(){:tact}`,该函数从 [contract](/book/contracts) 中读取。 + +源代码 + +```tact +@interface("org.ton.ownable") +trait Ownable { + owner: Address; + + fun requireOwner() { + nativeThrowUnless(132, sender() == self.owner); + } + + get fun owner():Address { + return self.owner; + } +} +``` + +使用示例 + +```tact /Ownable/ +import "@stdlib/ownable"; + +contract ExampleContract with Ownable { + owner: Address; + + init(owner: Address) { + self.owner = owner; + } +} +``` + +### 可拥有可转让 + +`OwnableTransferable{:tact}` 是 [`Ownable{:tact}`](#ownable) 的扩展,允许将合同的所有权转移到另一个地址。它提供了一个安全的句柄 [Message](/book/structs-and-messages#messages) [`ChangeOwner{:tact}`](#changeowner),可供所有者调用以转移所有权。 + +如果所有者转移请求成功,合约将回复一条 [`ChangeOwnerOk{:tact}`](#changeownerok) [信息](/book/structs-and-messages#messages)。 + +源代码 + +```tact +@interface("org.ton.ownable.transferable.v2") +trait OwnableTransferable with Ownable { + owner: Address; + + receive(msg: ChangeOwner) { + // Check if the sender is the owner + self.requireOwner(); + + // 更新所有者 + self.owner = msg.newOwner; + + // 回复结果 + self.reply(ChangeOwnerOk{ queryId: msg.queryId, newOwner: msg.newOwner }.toCell()); + } +} +``` + +使用示例 + +```tact /OwnableTransferable/ +import "@stdlib/ownable"; + +contract ExampleContract with OwnableTransferable { + owner: Address; + + init(owner: Address) { + self.owner = owner; + } +} +``` + +## 资料来源 + +- [ownable.tact](https://github.com/tact-lang/tact/blob/61541b7783098e1af669faccd7d2334c10981c72/stdlib/libs/ownable.tact) diff --git a/docs/src/content/docs/zh-cn/ref/stdlib-stoppable.mdx b/docs/src/content/docs/zh-cn/ref/stdlib-stoppable.mdx new file mode 100644 index 000000000..021f88536 --- /dev/null +++ b/docs/src/content/docs/zh-cn/ref/stdlib-stoppable.mdx @@ -0,0 +1,105 @@ +--- +title: "@stdlib/stoppable" +--- + +提供[traits](/book/types#composite-types),允许停止[contract](/book/contracts)。 适用于应急或维护模式。 需要来自 [`@stdlib/ownable`](/ref/stdlib-ownable) 的 [`Ownable{:tact}`](/ref/stdlib-ownable#ownable)特质。 该特质只管理合约中的一个标记 "stopped",处理停止状态必须在合约本身中完成。 + +要使用该库,请导入 `@stdlib/stoppable`: + +```tact +import "@stdlib/stoppable"; // 这也将自动导入 @stdlib/ownable! +``` + +## 特质 + +### Stoppable + +[Trait](/book/types#composite-types) `Stoppable{:tact}` 实现可由所有者发送的 [Message](/book/structs-and-messages#messages) [string](/book/types#primitive-types) "Stop" 的接收器、实现了 `stopped(){:tact}` [getter 函数](/book/functions#getter-functions),如果合约已停止,则返回 `true{:tact}`(否则返回 `false{:tact}`),并提供了私有(非getter)函数 `requireNotStopped(){:tact}` 和 `requireStopped(){:tact}`。 + +源代码 + +```tact +@interface("org.ton.stoppable") +trait Stoppable with Ownable { + stopped:Bool; + owner: Address; + + fun requireNotStopped() { + require(!self.stopped, "Contract stopped"); + } + + fun requireStopped() { + require(self. stopped, "Contract not stopped"); } receive("Stop") { self.stopped, "Contract not stopped"); + } + + receive("Stop") { + self.requireOwner(); + self.requireNotStopped(); + self.stopped = true; + self.reply("Stopped".asComment()); + } + + get fun stopped():Bool { + return self.stopped; + } +} +``` + +使用示例 + +```tact /Stoppable/ +import "@stdlib/ownable"; +import "@stdlib/stoppable"; + +contract MyContract with Stoppable { + owner: Address; + stopped: Bool; + + init(owner: Address) { + self.owner = owner; + self.stopped = false; + } +} +``` + +### 可延期 + +`Resumable{:tact}` [trait](/book/types#composite-types) 扩展了 [`Stoppable{:tact}`](#stoppable) trait,允许恢复 [contract](/book/contracts) 的执行。 + +源代码 + +```tact +@interface("org.ton.resumable") +trait Resumable with Stoppable { + stopped:Bool; + owner: Address; + + receive("Resume") { + self.requireOwner(); + self.requireStopped(); + self.stopped = false; + self.reply("Resumed".asComment()); + } +} +``` + +使用示例 + +```tact /Resumable/ +import "@stdlib/ownable"; +import "@stdlib/stoppable"; + +contract MyContract with Resumable { + owner: Address; + stopped: Bool; + + init(owner: Address) { + self.owner = owner; + self.stopped = false; + } +} +``` + +## 资料来源 + +- [stoppable.tact](https://github.com/tact-lang/tact/blob/61541b7783098e1af669faccd7d2334c10981c72/stdlib/libs/stoppable.tact)