English 简体中文 繁體中文 한국 사람 日本語 Deutsch русский بالعربية TÜRKÇE português คนไทย french

简体中文 繁體中文 English 日本語 Deutsch 한국 사람 بالعربية TÜRKÇE คนไทย Français русский

回答

收藏

Tact 语言基础 | Cells、Builders 和 Slices

开源社区 开源社区 8937 人阅读 | 0 人回复 | 2025-03-25

Cells、Builders 和 Slices 是 TON 区块链的底层 primitives。 TON 区块链的虚拟机 TVM 使用cell来表示持久存储中的所有数据结构,以及内存中的大部分数据结构。
Cells
Cell是一种 primitive 和数据结构,它通常由多达 10231023 个连续排列的比特和多达 44 个指向其他 cell 的引用(refs)组成。 循环引用在 TVM 中是被禁止的,因此无法通过 TVM 的机制创建循环引用。这意味着,单元(cells)可以被视为自身的 [四叉树][quadtree] 或 有向无环图(DAG)。 智能合约代码本身由树形结构的cell表示。
单元(Cells)和单元原语是以位(bit)为导向的,而非字节(byte)为导向的:TVM 将存储在单元中的数据视为最多 10231023 位的序列(字符串或流),而不是字节。 如有必要,合约可以自由使用 2121-bit 整数字段,并将其序列化为 TVM cell,从而使用更少的持久存储字节来表示相同的数据。
种类
虽然 TVM 类型 Cell 指的是所有cell,但存在不同的cell类型,其内存布局也各不相同。 前面 描述的通常被称为 普通 (或简单) cell—这是最简单、最常用的cell,只能包含数据。 绝大多数关于cell及其用法的描述、指南和 参考文献 都假定是普通cell。
其他类型的cell统称为 exotic cell (或特殊cell)。 它们有时会出现在 TON 区块链上的区块和其他数据结构的实际表示中。 它们的内存布局和用途与普通cell大不相同。
所有cell的种类 (或子类型) 都由 −1−1 和 255255之间的整数编码。 普通cell用 −1−1编码,特殊cell可用该范围内的任何其他整数编码。 奇异cell的子类型存储在其数据的前 88 位,这意味着有效的奇异cell总是至少有 88 个数据位。
TVM 目前支持以下exotic cell子类型:
  • Pruned branch cell,子类型编码为 [size=1.21em]11 - 它们代表删除的cell子树。
  • Library reference cell,子类型编码为 [size=1.21em]22 - 它们用于存储库,通常在 masterchain 上下文中使用。
  • Merkle proof cell,子类型编码为 [size=1.21em]33 - 它们用于验证其他cell的树数据的某些部分是否属于完整树。
  • Merkle update cell,子类型编码为 [size=1.21em]44 - 它们总是有两个引用,对这两个引用的行为类似于默克尔证明。8 p! T1 A( o" x' }% S5 l
- y& d8 F0 T& m  `; ]0 J. e3 n7 R

! w/ X& ?4 U/ M( u6 f" _/ f. M7 k, {
. {3 n3 V" c! t3 H; @+ |
Levels
作为 [四叉树][quadtree],每个单元格都有一个名为 level 的属性,它由 00 和 33之间的整数表示。 普通 cell的级别总是等于其所有引用级别的最大值。 也就是说,没有引用的普通 cell 的层级为 00。
Exotic cell有不同的规则来决定它们的层级,这些规则在TON Docs 的本页上有描述。
序列化
在通过网络传输 cell 或在磁盘上存储 cell 之前,必须对其进行序列化。 有几种常用格式,如标准 Cell 表示法和 BoC。
标准表示法
标准 Cell 表示法是 tvm.pdf 中首次描述的 cells 通用序列化格式。 它的算法以八进制(字节)序列表示cell,首先将称为描述符的第一个 22 字节序列化:
  • 引用描述符(Refs descriptor)根据以下公式计算:[size=1.21em]r+8k+32lr+8k​+32l​,其中 [size=1.21em]rr 是 cell 中包含的引用数量(介于 [size=1.21em]00[size=1.21em]44 之间),[size=1.21em]kk 是 cell 类型的标志([size=1.21em]00 表示普通,[size=1.21em]11 表示特殊),[size=1.21em]ll 是 cell 的层级(介于 [size=1.21em]00[size=1.21em]33 之间)。
  • 位描述符(Bits descriptor)根据以下公式计算:[size=1.21em]⌊b8⌋+⌈b8⌉⌊8b​⌋+⌈8b​⌉,其中 [size=1.21em]bb 是 cell 中的位数(介于 [size=1.21em]00[size=1.21em]10231023 之间)。# u; Z7 I1 s  ]
然后,cell 本身的数据位被序列化为 ⌈b8⌉⌈8b​⌉ 88-bit octets(字节)。 如果 bb 不是 8 的倍数,则在数据位上附加一个二进制 11 和最多六个二进制 00s。
接下来, 22 字节存储了引用的深度,即Cell树根(当前Cell)和最深引用(包括它)之间的cells数。 例如,如果一个cell只包含一个引用而没有其他引用,则其深度为 11,而被引用cell的深度为 00。
最后,为每个参考cell存储其标准表示的 SHA-256 哈希值,每个参考cell占用 3232 字节,并递归重复上述算法。 请注意,不允许循环引用cell,因此递归总是以定义明确的方式结束。 请注意,不允许循环引用cell,因此递归总是以定义明确的方式结束。
如果我们要计算这个cell的标准表示的哈希值,就需要将上述步骤中的所有字节连接在一起,然后使用 SHA-256 哈希值进行散列。 如果我们要计算这个cell的标准表示的哈希值,就需要将上述步骤中的所有字节连接在一起,然后使用 SHA-256 哈希值进行散列。 这是TVM的HASHCU和HASHSU指令以及 Tact 的Cell.hash()和Slice.hash()函数背后的算法。
Bag of Cells
如 boc.tlb TL-B schema 所述,Bag of Cells(简称 BoC)是一种将cell序列化和去序列化为字节数组的格式。
在 TON Docs 中阅读有关 BoC 的更多信息:Bag of Cells。
不变性 (Immutability)
cell是只读和不可变的,但 TVM 中有两组主要的 ordinary cell操作指令:
  • cell创建(或序列化)指令,用于根据先前保存的值和cell构建新cell;
  • cell解析(或反序列化)指令,用于提取或加载之前通过序列化指令存储到cell中的数据。8 |3 y4 o5 L. P& l& ]/ [
此外,还有专门针对 exotic cell的指令来创建这些cell并期望它们的值。 此外,exotic cell 有专门的指令来创建它们并预期它们的值。不过,普通(ordinary) cell解析指令仍可用于 exotic cell,在这种情况下,它们会在反序列化尝试中被自动替换为 普通(ordinary) cell。
所有cell操作指令都需要将 Cell 类型的值转换为 Builder或 Slice类型,然后才能修改或检查这些cell。
Builders
Builder 是一种用于使用cell创建指令的cell操作基元。 它们就像cell一样不可改变,可以用以前保存的值和cell构建新的cell。 与cells不同,Builder类型的值只出现在TVM堆栈中,不能存储在持久存储中。 举例来说,这意味着类型为 Builder 的持久存储字段实际上是以cell的形式存储的。
Builder 类型表示部分组成的cell,为其定义了追加整数、其他cell、引用其他cell等快速操作:
  • 核心库中的 Builder.storeUint()
  • 核心库中的 Builder.storeInt()
  • 核心库中的 Builder.storeBool()
  • 核心库中的 Builder.storeSlice()
  • 核心库中的 Builder.storeCoins()
  • 核心库中的 Builder.storeAddress()
  • 核心库中的 Builder.storeRef()
    : M7 U; N0 x, ^0 ^& w+ v
虽然您可以使用它们来手动构建 cell,但强烈建议使用[结构体][structs]:使用结构体构建cell。
Slices
Slice 是使用cell解析指令的cell操作基元。 与cell不同,它们是可变的,可以通过序列化指令提取或加载之前存储在cell中的数据。 此外,与cell不同,Slice 类型的值只出现在 TVM 堆栈中,不能存储在持久存储区中。 举例来说,这就意味着类型为 Slice 的持久存储字段实际上是以cell的形式存储的。
Slice 类型表示部分解析cell的剩余部分,或位于此类cell内并通过解析指令从中提取的值(子cell):
  • 核心库中的Slice.loadUint()
  • 核心库中的Slice.loadInt()
  • 核心库中的Slice.loadBool()
  • 核心库中的Slice.loadBits()
  • 核心库中的Slice.loadCoins()
  • 核心库中的Slice.loadAddress()
  • 核心库中的Slice.loadRef()
    0 I+ |  H9 N# S2 v- q3 ?8 U5 I- K
虽然您可以将它们用于cell的 手动解析,但强烈建议使用 [结构体][structs]:使用结构体解析cell。
序列化类型
与 Int类型的序列化选项类似,Cell、Builder 和Slice 在以下情况下也有不同的值编码方式:
  • 作为合约和特性的存储变量,
  • 以及 [Structs](/zh-cn/book/structs and-messages#structs) 和 [Messages](/zh-cn/book/structs and-messages#messages) 的字段。
    1. contract SerializationExample {9 c9 E& w# o$ p( x
    2.     someCell: Cell as remaining;
      6 O& @5 Y# H' ]; m# F( J- V; n
    3.     someSlice: Slice as bytes32;) u; f9 j" Q7 Y6 I& m

    4. 5 D3 ]* I: |8 y( S: J5 G! A
    5.     // Constructor function,. V' h; Y5 j6 W) |) r1 `3 Y
    6.     // necessary for this example contract to compile
      $ b6 ?% [. A$ G$ W) T$ U/ J
    7.     init() {& W2 H4 Q5 ?* n: f( L/ Y
    8.         self.someCell = emptyCell();
      2 X5 A% I  i$ W2 Q
    9.         self.someSlice = beginCell().storeUint(42, 256).asSlice();# a" M! D9 r, k) O
    10.     }1 O9 b) Q# ^: I/ C
    11. }
    复制代码
    remaining
    remaining 序列化选项可应用于 CellBuilderSlice类型的值。
    它通过直接存储和加载cell值而不是作为引用来影响cell值的构建和解析过程。 它通过直接存储和加载cell值而不是作为引用来影响cell值的构建和解析过程。 与 cell操作指令 相似,指定 remaining 就像使用 Builder.storeSlice()Slice.loadBits() 而不是 Builder.storeRef()Slice.loadRef(),后者是默认使用的。
    此外,Tact 产生的 TL-B 表示也会发生变化:
    . R  T) T% [6 K7 {7 k+ c

7 [0 r/ O6 x* g, v3 ^/ M
  1. contract SerializationExample {1 J7 o( T& T- }  A6 d8 ^
  2.     // By default8 Z0 A( T. T$ S6 Z6 ~
  3.     cRef: Cell;    // ^cell in TL-B
    1 |: q. ~4 p" p
  4.     bRef: Builder; // ^builder in TL-B
    + i) o; B0 b9 k% C  Z! ~
  5.     sRef: Slice;   // ^slice in TL-B
    * @+ m; H) `1 ]5 {+ ~/ B

  6. / Z$ R+ c3 Q( d% K! D4 G
  7.     // With `remaining`
    3 i/ b! ]4 X3 T# q
  8.     cRem: Cell as remaining;    // remainder<cell> in TL-B
      P2 _* ]- n( l8 d' v; g' r' e) ~
  9.     bRem: Builder as remaining; // remainder<builder> in TL-B# X; q9 e% C+ l  n! J
  10.     sRem: Slice as remaining;   // remainder<slice> in TL-B5 ]4 n( y# Z" m' m2 a
  11. " d+ u( b7 @2 x( p+ `0 `
  12.     // Constructor function,
    * z' Y: R6 v5 B* M9 C! W/ F. v
  13.     // necessary for this example contract to compile
    3 s% O* k" K1 a, o5 Z3 ]( X7 W! P
  14.     init() {+ V$ N( g* O$ h/ [& z
  15.         self.cRef = emptyCell();
    9 k0 T( T2 u  ^
  16.         self.bRef = beginCell();
    2 ^% [* g9 b3 h" v& b, A' z
  17.         self.sRef = emptySlice();# I; g9 s2 L( z4 J- r
  18.         self.cRem = emptyCell();
    5 b6 l0 d& y& d0 a4 Y8 d' Q' y
  19.         self.bRem = beginCell();
    $ u; c2 W& c8 N
  20.         self.sRem = emptySlice();2 x2 N* {8 Y+ P2 f
  21.     }
    7 ?! y$ \9 W! ]( O
  22. }
复制代码
. [3 A! X% u1 d/ }
  X; p$ |6 {7 [& ]
其中,TL-B 语法中的 ^cell、^builder 和 ^slice 分别表示对 cellbuilderslice值的引用、而 cell、builder 或 slice 的 remainder<…> 则表示给定值将直接存储为 Slice,而不是作为引用。
现在,举一个真实世界的例子,想象一下你需要注意到智能合约中的入站 jetton 传输并做出反应。 相应的 [信息][消息] 结构如下: 相应的 [信息][消息] 结构如下:
  1. message(0x7362d09c) JettonTransferNotification {
    & ~2 A. c; o  X8 u0 N  C
  2.     queryId: Int as uint64;             // arbitrary request number to prevent replay attacks
    - U, E- V6 R& s0 ^2 }
  3.     amount: Int as coins;               // amount of jettons transferred# k& z$ d; q4 `) ]% V9 g
  4.     sender: Address;                    // address of the sender of the jettons
    ; j! Y! @/ K1 C& V& i! m# C, M
  5.     forwardPayload: Slice as remaining; // optional custom payload
    , W6 L: Y7 x! W
  6. }
复制代码
+ D6 s9 K& Z* G: P  h* I  j% T
, N( `; g+ r5 u6 h
合同中的 receiver 应该是这样的:
- j& P7 C$ S& Q

3 N% j; i4 m. n; J4 K% l
  1. receive(msg: JettonTransferNotification) {
    " f2 z9 K5 f6 q1 I6 y, Z
  2.     // ... you do you ...
    + ~3 X1 r0 c2 s9 X- G* v7 h1 s
  3. }
复制代码
8 ~  o' Z2 Q. L8 h- B4 I* U6 |" h

- a* [+ ~% C* ]9 b2 K" A1 Y7 B* T
收到 jetton 传输通知消息后,其cell体会被转换为 Slice,然后解析为 JettonTransferNotification 消息。在此过程结束时,forwardPayload 将包含原始信息cell的所有剩余数据。 在此过程结束时,forwardPayload 将包含原始信息cell的所有剩余数据。
在这里,将 forwardPayload: Slice as remaining 字段放在 JettonTransferNotification 消息中的任何其他位置都不会违反 jetton 标准。 这是因为 Tact 禁止在[Structs][结构]和[Messages][消息]的最后一个字段之外的任何字段中使用 as remaining,以防止滥用合同存储空间并减少 gas 消耗。

" D1 Y2 P  q+ i4 f3 S" d% T
Using Structs
结构和[消息][messages]几乎就是活生生的TL-B 模式。 也就是说,它们本质上是用可维护、可验证和用户友好的 Tact 代码表达的TL-B 模式。 也就是说,它们本质上是用可维护、可验证和用户友好的 Tact 代码表达的TL-B 模式
强烈建议使用它们及其 方法,如 Struct.toCell()和 [Struct.fromCell()][st-fc],而不是手动构造和解析cell,因为这样可以得到更多声明性和不言自明的合约。

$ v  h6 U0 T/ w7 s+ ?. E- Q) Z1 z8 ^" [
上文的手动解析示例可以使用Structs重新编写,如果愿意,还可以使用字段的描述性名称:

( e+ z' l" `$ X8 @; _5 x" A
  1. // First Struct! ]! Z" x' Q' k4 U: x
  2. struct Showcase {4 R. u- F5 d8 v) Z  {. I
  3.     id: Int as uint8;
    2 X9 b+ G+ T' u
  4.     someImportantNumber: Int as int8;
    ; J2 c4 r, S+ i0 m, A! s" p
  5.     isThatCool: Bool;
    $ M7 l/ O$ M# C# `: Y8 T; ~0 k5 h  v
  6.     payload: Slice;
    / o' T* _& S& ~' `) v1 ~9 _
  7.     nanoToncoins: Int as coins;4 L# j( b# q8 B, a$ |/ g) ~
  8.     wackyTacky: Address;
    5 G! J" o% S* v: t2 s) p
  9.     jojoRef: Adventure; // another Struct0 F+ r& \3 M6 p9 S+ d1 G7 i$ p
  10. }
    9 x5 k! {" O! F. S" p1 A; s% r

  11. # ^: H! \. Z7 u, ~+ |
  12. // Here it is
    9 r% d# W3 n1 v0 \
  13. struct Adventure {
    7 @6 a" M& A6 b/ p! J/ b2 f
  14.     bizarre: Bool = true;
      y: Z& W6 h  l7 d( a' ?
  15.     time: Bool = false;$ s+ i3 q6 r' F! z) a) F$ U
  16. }
    % n1 f- }# G9 q& H3 `
  17. 8 t4 P8 F7 q% K0 O, L9 O
  18. fun example() {
    " v$ |" K" i- D3 b6 n0 \2 n( H
  19.     // Basics3 n) [# ~7 q& U  i. u; D+ Z* h
  20.     let s = Showcase.fromCell(3 N; a3 P* A% l+ ~# q2 a: i
  21.         Showcase{, G- N. O, y; W
  22.             id: 7,* F" `& R1 M) _
  23.             someImportantNumber: 42,
    6 Z5 O: B* f+ u  {
  24.             isThatCool: true,
    ! v; O; t+ l/ V$ t4 X$ `; P
  25.             payload: emptySlice()," C4 N( r) u! d# \4 L6 @1 C$ u  {
  26.             nanoToncoins: 1330 + 7,
    # K' n0 N! S6 h$ i: ^3 V/ a% q# c
  27.             wackyTacky: myAddress(),
    0 Y- D! j9 M5 k9 ]' s
  28.             jojoRef: Adventure{ bizarre: true, time: false },
    3 a4 e* d5 z' h! x5 x: n
  29.         }.toCell());
    5 N$ s, @* w% {) C* b) j
  30.     s.isThatCool; // true
    5 D2 ]) J* x3 B1 v
  31. }
复制代码
! }( x/ c3 ~$ Z' K  ~, ^6 X2 N& \
0 ?( W7 v- t( K7 v. b5 i! Q( L
- S) ?2 v* G: d; N3 k
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则