Типы TL-B
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
Эта информация очень низкого уровня и может быть трудной для понимания новичками. Так что не стесняйтесь прочитать об этом позже.
В этом разделе анализируются сложные и нетрадиционные структуры типизированного языка двоичных данных (TL-B). Для начала мы рекомендуем сначала прочитать эту документацию, чтобы лучше ознакомиться с темой.
Either
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, сериализуется правый.
Он используется, например, при сериализации сообщений, когда тело является либо частью основной ячейки, либо связано с другой ячейкой.
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_zero
довольно просто: если первый бит равен 0, то результатом всей унарной десериализации будет 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
.
Унарная десериализация
Предположим, у нас есть тип 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
В качестве примера рассмотрим следующую ячейку, заданную в двоичной форме.
1[1] -> {
2[00] -> {
7[1001000] -> {
25[1010000010000001100001001],
25[1010000010000000001101111]
},
28[1011100000000000001100001001]
}
}
Эта ячейка использует тип структуры 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;