TL-B 类型
此信息属于非常底层的内容,对新手来说可能难以理解。 因此,您可以稍后再阅读。
在本节中,我们将分析复杂和非传统的类型化语言二进制(TL-B)结构。开始之前,我们建议先阅读此文档,以更熟悉该主题。
以上任一情况
left$0 {X:Type} {Y:Type} value:X = Either X Y;
right$1 {X:Type} {Y:Type} value:Y = Either X Y;
Either类型用于可能出现两种结果类型之一的情 况。在这种情况下,类型选择取决于所示的前缀位。如果前缀位是0,则序列化左类型,而如果使用1前缀位,则序列化右类型。
例如,在序列化消息时使用它,当消息体要么是主cell的一部分,要么链接到另一个cell。
Maybe
nothing$0 {X:Type} = Maybe X;
just$1 {X:Type} value:X = Maybe X;
Maybe类型与可选值一起使用。在这些情况下,如果第一位是0,则不会序列化该值(实际上被跳过),而如果值是1,则会被序列化。
Both
pair$_ {X:Type} {Y:Type} first:X second:Y = Both X Y;
Both类型变体仅与普通对一起使用,两种类型依次序列化,没有条件。
Unary
Unary函数类型通常用于动态大小的结构,例如hml_short。
Unary提供两个主要选项:
unary_zero$0 = Unary ~0;
unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1);
Unary序列化
通常,使用unary_zero
变体相当简单:如果第一位是0,则Unary反序列化的结果为0。
然而,unary_succ
变体更复杂,因为它是递归加载的,具有~(n + 1)
的值。这意味着它会顺序调用自身,直到达到unary_zero
。换句话说,所需的值将等于一连串单位的数量。
例如,让我们分析位串110
的序列化。
调用链如下:
unary_succ$1 -> unary_succ$1 -> unary_zero$0
一旦到达unary_zero
,值就会返回到序列化位串的末尾,类似于递归函数调用。
现在,为了更清楚地理解结果,让我们检索返回值路径,显示如下:
0 -> ~(0 + 1) -> ~(1 + 1) -> 2
,这意味着我们将110
序列化为Unary 2
。
Unary反序列化
假设我们有Foo
类型:
foo$_ u:(Unary 2) = Foo;
根据上述内容,Foo
将被反序列化为:


foo u:(unary_succ x:(unary_succ x:(unnary_zero)))
Hashmap
Hashmap复杂类型用于存储FunC智能合约代码中的字典(dict
)。
以下TL-B结构用于序列化具有固定键长度的Hashmap:
hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l n)
{n = (~m) + l} node:(HashmapNode m X) = Hashmap n X;
hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X;
hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X)
right:^(Hashmap n X) = HashmapNode (n + 1) X;
hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m;
hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m;
hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m;
unary_zero$0 = Unary ~0;
unary_succ$1 {n:#} x:(Unary ~n) = Unary ~(n + 1);
hme_empty$0 {n:#} {X:Type} = HashmapE n X;
hme_root$1 {n:#} {X:Type} root:^(Hashmap n X) = HashmapE n X;
这意味着根结构使用HashmapE
及其两种状态之一:包括hme_empty
或hme_root
。
Hashmap解析示例
例如,考虑以下以二进制形式给出的Cell。
1[1] -> {
2[00] -> {
7[1001000] -> {
25[1010000010000001100001001],
25[1010000010000000001101111]
},
28[1011100000000000001100001001]
}
}
此Cell使用了HashmapE
结构类型,并具有8位键大小,其值使用uint16
数字框架(HashmapE 8 uint16
)。HashmapE使用3种不同的键类型:
1 = 777
17 = 111
128 = 777
为了解析此Hashmap,我们需要预先知道使用哪种结构类型,hme_empty
还是hme_root
。这是通过识别正确前缀
来确定的。hme empty变体使用一个位0(hme_empty$0
),而hme root使用一个位1(hme_root$1
)。读取第一位后,确定它等于一(1[1]
),意味着它是hme_root
变体。
现在,让我们用已知值填充结构变量,初始结果为:
hme_root$1 {n:#} {X:Type} root:^(Hashmap 8 uint16) = HashmapE 8 uint16;
这里已经读取了一位前缀,而{}
中的条件不需要读取。条件{n:#}
表示n是任何uint32数字,而{X:Type}
表示X可以使用任何类型。
接下来需要读取的部分是root:^(Hashmap 8 uint16)
,而^
符号表示必须加载的链接。
2[00] -> {
7[1001000] -> {
25[1010000010000001100001001],
25[1010000010000000001101111]
},
28[1011100000000000001100001001]
}
初始化分支解析
根据我们的架构,这是正确的Hashmap 8 uint16
结构。接下来,我们用已知的值填充它,并得到如下结果:
hm_edge#_ {n:#} {X:Type} {l:#} {m:#} label:(HmLabel ~l 8)
{8 = (~m) + l} node:(HashmapNode m uint16) = Hashmap 8 uint16;
如上所示,现在出现了条件变量{l:#}
和{m:#}
,但我们不知道这两个变量的值。此外,在读取相应的label
之后,很明显n
涉及等式{n = (~m) + l}
,在这种情况下我们计算l
和m
,符号~
告诉我们结果值。
为了确定l
的值,我们必须加载label:(HmLabel ~l uint16)
序列。如下所示,HmLabel
有三种基本结构选项:
hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= m} s:(n * Bit) = HmLabel ~n m;
hml_long$10 {m:#} n:(#<= m) s:(n * Bit) = HmLabel ~n m;
hml_same$11 {m:#} v:Bit n:(#<= m) = HmLabel ~n m;
每个选项都由相应的前缀确定。目前,我们的根cell由两个零位组成,显示为:(2[00]
)。因此,唯一合理的选项是以0开头的前缀hml_short$0
。
用已知值填充hml_short
:
hml_short$0 {m:#} {n:#} len:(Unary ~n) {n <= 8} s:(n * Bit) = HmLabel ~n 8
在这种情况下,我们不知道n
的值,但由于它有一个~
字符,可以计算它。为此,我们加载len:(Unary ~n)
,关于Unary的更多信息。
在这种情况下,我们从2[00]
开始,但在定义了HmLabel
类型之后,两个位中只剩下一个。
因此,我们加载它并看到它的值是0,这意味着它显然使用了unary_zero$0
变体。这意味着使用HmLabel
变体的n值为零。
接下来,需要使用计算出的n值完成hml_short
变体序列:
hml_short$0 {m:#} {n:#} len:0 {n <= 8} s:(0 * Bit) = HmLabel 0 8
结果我们得到一个空的HmLabel
,表示为s = 0,因此没有什么可以下载的。
接下来,我们用计算 出的l
值补充我们的结构,如下所示:
hm_edge#_ {n:#} {X:Type} {l:0} {m:#} label:(HmLabel 0 8)
{8 = (~m) + 0} node:(HashmapNode m uint16) = Hashmap 8 uint16;
现在我们已经计算出l
的值,我们可以使用等式n = (~m) + 0
来计算m
,即m = n - 0
,m = n = 8。
确定所有未知值后,现在可以加载node:(HashmapNode 8 uint16)
。
至于HashmapNode,我们有以下选项:
hmn_leaf#_ {X:Type} value:X = HashmapNode 0 X;
hmn_fork#_ {n:#} {X:Type} left:^(Hashmap n X)
right:^(Hashmap n X) = HashmapNode (n + 1) X;
在这种情况下,我们不
hmn_fork#_ {n:#} {X:uint16} left:^(Hashmap n uint16)
right:^(Hashmap n uint16) = HashmapNode (n + 1) uint16;
输入已知值后,我们必须计算 HashmapNode (n + 1) uint16
。这意味着得出的 n 值必须等于我们的参数,即 8。
要计算 n 的本地值,我们需要使用以下公式:n = (n_local + 1)
-> n_local = (n - 1)
-> n_local = (8 - 1)
-> n_local = 7
。
hmn_fork#_ {n:#} {X:uint16} left:^(Hashmap 7 uint16)
right:^(Hashmap 7 uint16) = HashmapNode (7 + 1) uint16;
既然我们知道需要上述公式,那么获得最终结果就很简单了。 接下来,我们加载左分支和右分支,并对随后的每个分支重复该过程 。