跳到主要内容

TVM 升级 2023.07

提示

此升级于 2023 年 12 月在主网上启动,详细信息请参考 run

c7

c7 是存储有关合约执行所需的本地 context 信息的寄存器 (如时间、lt、网络配置等)。

c7 元组从 10 扩展到 14 个元素:

  • 10: 存储智能合约本身的 cell
  • 11: [integer, maybe_dict]:传入消息的 TON 值,额外代币。
  • 12: integer,存储阶段收取的费用。
  • 13: tuple 包含有关先前区块的信息。

10 当前智能合约的代码仅以可执行继续的形式在 TVM 级别呈现,无法转换为cell。这段代码通常用于授权相同类型的 neighbor 合约,例如 Jetton 钱包授权 Jetton 钱包。目前我们需要显式地代码cell存储在存储器中,这使得存储和 init_wrapper 变得更加麻烦。 使用 10 作为代码对于 tvm 的 Everscale 更新兼容。

11 当前,传入消息的值在 TVM 初始化后以堆栈形式呈现,因此如果在执行过程中需要, 则需要将其存储到全局变量或通过本地变量传递(在 funC 级别看起来像所有函数中的额外 msg_value 参数)。通过将其放在 11 元素中,我们将重复合约余额的行为:它既出现在堆栈中,也出现在 c7 中。

12 目前计算存储费用的唯一方法是在先前的交易中存储余额,以某种方式计算 prev 交易中的 gas 用量,然后与当前余额减去消息值进行比较。与此同时,经常希望考虑存储费用。

13 目前没有办法检索先前区块的数据。TON 的一个关键特性是每个结构都是 Merkle 证明友好的cell(树),此外,TVM 也是cell和 Merkle 证明友好的。通过在 TVM context中包含区块信息,将能够实现许多不信任的情景:合约 A 可以检查合约 B 上的交易(无需 B 的合作),可以恢复中断的消息链(当恢复合约获取并检查某些事务发生但被还原的证明时),还需要了解主链区块哈希以在链上进行某些验证 fisherman 函数功能。

区块 id 的表示如下:

[ wc:Integer shard:Integer seqno:Integer root_hash:Integer file_hash:Integer ] = BlockId;
[ last_mc_blocks:[BlockId0, BlockId1, ..., BlockId15]
prev_key_block:BlockId ] : PrevBlocksInfo

包括主链的最后 16 个区块的 id(如果主链 seqno 小于 16,则为少于 16 个),以及最后的关键区块。包含有关分片区块的数据可能会导致一些数据可用性问题(由于合并/拆分事件),这并非必需(因为可以使用主链区块来证明任何事件/数据),因此我们决定不包含。

新的操作码

在选择新操作码的 gas 成本时的经验法则是它不应少于正常成本(从操作码长度计算)且不应超过每个 gas 单位 20 ns。

用于处理新 c7 值的操作码

每个操作码消耗 26 gas,除了 PREVMCBLOCKSPREVKEYBLOCK(34 gas)。

xxxxxxxxxxxxxxxxxxxxxx
Fift 语法
xxxxxxxxx
堆栈
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
描述
MYCODE- c从 c7 检索智能合约的代码
INCOMINGVALUE- t从 c7 检索传入消息的值
STORAGEFEES- i从 c7 检索存储阶段费用的值
PREVBLOCKSINFOTUPLE- t从 c7 中检索 PrevBlocksInfo: [last_mc_blocks, prev_key_block]
PREVMCBLOCKS- t仅检索 last_mc_blocks
PREVKEYBLOCK- t仅检索 prev_key_block
GLOBALID- i从网络配置的第 19 项检索 global_id

Gas

xxxxxxxxxxxxxx
Fift 语法
xxxxxxxx
堆栈
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
描述
GASCONSUMED- g_c返回到目前为止 VM 消耗的 gas(包括此指令)。
26 gas

算术

添加了 除法操作码A9mscdf)的新变体: d=0 从堆栈中获取一个额外的整数,并将其添加到除法/右移之前的中间值。这些操作返回商和余数(与 d=3 类似)。

还提供了静默变体(例如 QMULADDDIVMODQUIET MULADDDIVMOD)。

如果返回值不适应 257 位整数或除数为零,非静默操作会引发整数溢出异常。静默操作返回 NaN 而不是不适应的值(如果除数为零则返回两个 NaN)。

Gas 成本等于 10 加上操作码长度:大多数操作码为 26 gas,LSHIFT#/RSHIFT# 额外加 8,静默额外加 8。

xxxxxxxxxxxxxxxxxxxxxx
Fift 语法
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
堆栈
MULADDDIVMODx y w z - q=floor((xy+w)/z) r=(xy+w)-zq
MULADDDIVMODRx y w z - q=round((xy+w)/z) r=(xy+w)-zq
MULADDDIVMODCx y w z - q=ceil((xy+w)/z) r=(xy+w)-zq
ADDDIVMODx w z - q=floor((x+w)/z) r=(x+w)-zq
ADDDIVMODRx w z - q=round((x+w)/z) r=(x+w)-zq
ADDDIVMODCx w y - q=ceil((x+w)/z) r=(x+w)-zq
ADDRSHIFTMOD`x w z - q=floor((x+w)/2^z) r=(x+w)-q2^z`*
ADDRSHIFTMODR`x w z - q=round((x+w)/2^z) r=(x+w)-q2^z`*
ADDRSHIFTMODC`x w z - q=ceil((x+w)/2^z) r=(x+w)-q2^z`*
z ADDRSHIFT#MOD`x w - q=floor((x+w)/2^z) r=(x+w)-q2^z`*
z ADDRSHIFTR#MOD`x w - q=round((x+w)/2^z) r=(x+w)-q2^z`*
z ADDRSHIFTC#MOD`x w - q=ceil((x+w)/2^z) r=(x+w)-q2^z`*
MULADDHIFTMOD`x y w z - q=floor((xy+w)/2^z) r=(xy+w)-q2^z`*
MULADDRSHIFTRMOD`x y w z - q=round((xy+w)/2^z) r=(xy+w)-q2^z`*
MULADDHIFTCMOD`x y w z - q=ceil((xy+w)/2^z) r=(xy+w)-q2^z`*
z MULADDRSHIFT#MOD`x y w - q=floor((xy+w)/2^z) r=(xy+w)-q2^z`*
z MULADDRSHIFTR#MOD`x y w - q=round((xy+w)/2^z) r=(xy+w)-q2^z`*
z MULADDRSHIFTC#MOD`x y w - q=ceil((xy+w)/2^z) r=(xy+w)-q2^z`*
LSHIFTADDDIVMOD`x w z y - q=floor((x2^y+w)/z) r=(x2^y+w)-zq`
LSHIFTADDDIVMODR`x w z y - q=round((x2^y+w)/z) r=(x2^y+w)-zq`
LSHIFTADDDIVMODC`x w z y - q=ceil((x2^y+w)/z) r=(x2^y+w)-zq`
y LSHIFT#ADDDIVMOD`x w z - q=floor((x2^y+w)/z) r=(x2^y+w)-zq`
y LSHIFT#ADDDIVMODR`x w z - q=round((x2^y+w)/z) r=(x2^y+w)-zq`
y LSHIFT#ADDDIVMODC`x w z - q=ceil((x2^y+w)/z) r=(x2^y+w)-zq`

堆栈操作

目前,所有堆栈操作的参数都以 256 为界。 这意味着如果堆栈深度超过 256,就很难管理深堆栈元素。 在大多数情况下,这种限制并没有安全方面的原因,也就是说,限制参数并不是为了防止过于昂贵的操作。 对于某些大规模堆栈操作,如 ROLLREV(计算时间与参数值成线性关系),气体成本也与参数值成线性关系。

  • 现在,PICKROLLROLLREVBLKSWXREVXDROPXXCHGXCHKDEPTHONLYTOPXONLYX 的参数不受限制。
  • 当参数较大时,ROLL, ROLLREV, REVX, ONLYTOPX 耗气量更大:额外耗气量为 max(arg-255,0)(参数小于 256 时,耗气量不变,与当前行为一致)
  • 对于 BLKSWX,额外费用为 max(arg1+arg2-255,0)(这与当前行为不符,因为当前 arg1arg2 都限制为 255)。

哈希值

目前,TVM 只提供两种散列操作:计算单元格/片的表示散列和数据的 sha256,但最多只能计算 127 字节(一个单元格只能容纳这么多数据)。

HASHEXT[A][R]_(HASH) 系列操作被添加:

xxxxxxxxxxxxxxxxxxx
Fift 语法
xxxxxxxxxxxxxxxxxxxxxx
堆栈
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
说明
HASHEXT_(HASH)s_1 ... s_n n - h计算并返回片段(或构建器)s_1...s_n的连接哈希值。
HASHEXTR_(HASH)s_n ... s_1 n - h同理,但参数顺序相反。
HASHEXTA_(HASH)b s_1 ... s_n n - b'将生成的哈希值追加到构造函数 b 中,而不是推送到堆栈中。
HASHEXTAR_(HASH)b s_n ... s_1 n - b'参数以相反顺序给出,并将哈希值追加到生成器中。

仅使用 s_i 的根cell的位。

每个块 s_i 可能包含非整数数量的字节。但所有块的位的和应该是 8 的倍数。注意 TON 使用最高位优先顺序,因此当连接两个具有非整数字节的切片时,第一个切片的位变为最高位。

Gas 消耗取决于哈希字节数和所选算法。每个块额外消耗 1 gas 单位。

如果未启用 [A],则哈希的结果将作为无符号整数返回,如果适应 256 位,否则返回整数的元组。

可用以下算法:

  • SHA256 - openssl 实现,每字节 1/33 gas,哈希为 256 位。
  • SHA512 - openssl 实现,每字节 1/16 gas,哈希为 512 位。
  • BLAKE2B - openssl 实现,每字节 1/19 gas,哈希为 512 位。
  • KECCAK256 - 以太坊兼容实现,每字节 1/11 gas,哈希为 256 位。
  • KECCAK512 - 以太坊兼容实现,每字节 1/6 gas,哈希为 512 位。

Gas 用量四舍五入。

加密货币

目前唯一可用的加密算法是 "CCHKSIGN":检查哈希 "h "与公钥 "k "的 Ed25519 签名。

  • 为了与比特币和以太坊等上一代区块链兼容,我们还需要检查 secp256k1 签名。
  • 对于现代密码算法,最低要求是曲线的加法和乘法。
  • 为了与以太坊 2.0 PoS 和其他现代密码学的兼容性,我们需要在 bls12-381 曲线上进行 BLS 签名方案。
  • 对于某些安全硬件,需要 secp256r1 == P256 == prime256v1

secp256k1

比特币/以太坊签名。使用 libsecp256k1 实现

xxxxxxxxxxxxx
Fift 语法
xxxxxxxxxxxxxxxxx
堆栈
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
说明<0/>
ECRECOVERhash v r s - 0 or h x1 x2 -1从签名恢复公钥,与比特币/以太坊操作相同。
以 32 字节哈希作为 uint256 hash;以 65 字节签名作为 uint8 v 和 uint256 rs
失败返回 0,成功返回公钥和 -1
以 65 字节公钥返回为 uint8 h,uint256 x1x2
1526 gas

secp256r1

使用 OpenSSL 实现。界面类似于 CHKSIGNS/CHKSIGNU。与 Apple Secure Enclave 兼容。

xxxxxxxxxxxxx
Fift 语法
xxxxxxxxxxxxxxxxx
堆栈
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
说明
P256_CHKSIGNSd sig k - ?检查片段 d 的数据部分和公钥 k 的 seck256r1-signature sig。成功时返回-1,失败时返回 0。
公钥是一个 33 字节的片段(根据 SECG SEC 1 第 2.3.4 节第 2 点编码)。
签名 sig 是一个 64 字节的片段(两个 256 位无符号整数 rs)。
3526 gas
P256_CHKSIGNUh sig k - ?相同,但签名的数据是 32 字节对 256 位无符号整数 h 的编码。
3526 gas

Ristretto

扩展文档 此处。简而言之,Curve25519 在开发时考虑到了性能,但由于组元素有多种表示形式,因此它具有对称性。较简单的协议(如 Schnorr 签名或 Diffie-Hellman 协议)在协议层面采用了一些技巧来缓解某些问题,但却破坏了密钥推导和密钥保密方案。这些技巧无法扩展到更复杂的协议,如防弹协议。Ristretto 是 Curve25519 的算术抽象,每个组元素对应一个唯一的点,这是大多数加密协议的要求。Ristretto 本质上是 Curve25519 的压缩/解压缩协议,提供了所需的算术抽象。因此,加密协议很容易正确编写,同时还能受益于 Curve25519 的高性能。

Ristretto 操作允许在 Curve25519 上计算曲线操作(反之则不行),因此我们可以认为我们在一个步骤中同时添加了 Ristretto 和 Curve25519 曲线操作。

libsodium 实现已被使用。

所有 ristretto-255 点都在TVM中表示为 256 位无符号整数。非静默操作在参数无效的情况下引发 range_chk。零点表示为整数 0

xxxxxxxxxxxxx
Fift 语法
xxxxxxxxxxxxxxxxx
堆栈
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
说明
RIST255_FROMHASHh1 h2 - x从 512 位哈希(由两个 256 位整数给出)确定性生成有效点 x
626 gas
RIST255_VALIDATEx -检查整数 x 是否是某个曲线点的有效表示。出错时会抛出 range_chk
226 gas
RIST255_ADDx y - x+y在曲线上两个点的相加。
626 gas
RIST255_SUBx y - x-y在曲线上两个点的相减。
626 gas
RIST255_MUL`x n - xn`*将点 x 乘以标量 n
任何 n 都有效,包括负数。
2026 gas
RIST255_MULBASE`n - gn`*将生成器点 g 乘以标量 n
任何 n 都有效,包括负数。
776 gas
RIST255_PUSHL- l推送整数 l=2^252+27742317777372353535851937790883648493,这是群的阶。
26 gas
RIST255_QVALIDATEx - 0 或 -1RIST255_VALIDATE 的静默版本。
234 gas
RIST255_QADDx y - 0 或 x+y -1RIST255_ADD 的静默版本。
634 gas
RIST255_QSUBx y - 0 或 x-y -1RIST255_SUB 的静默版本。
634 gas
RIST255_QMUL`x n - 0 或 xn -1`*RIST255_MUL 的静默版本。
2034 gas
RIST255_QMULBASE`n - 0 或 gn -1`*RIST255_MULBASE 的静默版本。
784 gas

BLS12-381

在配对友好的 BLS12-381 曲线上进行操作。使用 BLST 实现。此外,还对基于该曲线的 BLS 签名方案进行了操作。

BLS 值在 TVM 中的表示方法如下:

  • G1点和公钥:48字节切片。
  • G2点和签名:96字节切片。
  • 字段FP的元素:48字节切片。
  • 字段FP2的元素:96字节切片。
  • 信息:切片。位数应能被 8 整除。

当输入值是一个点或一个字段元素时,片段的长度可能超过 48/96 字节。在这种情况下,只取前 48/96 字节。如果片段的字节数少于 48/96(或信息大小不能被 8 整除),则会出现单元格下溢异常。

高级操作

这些是验证 BLS 签名的高级操作。

xxxxxxxxxxxxx
Fift 语法
xxxxxxxxxxxxxxxxx
堆栈
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
说明
BLS_VERIFYpk msg sgn - bool检查 BLS 签名,成功时返回 true,否则返回 false。
61034 gas
BLS_AGREGATEsig_1 ... sig_n n - sig聚合签名。n>0.如果 n=0 或某些 sig_i 不是有效签名,则抛出异常。
`gas=n4350-2616`*
BLS_FASTAGREGATEVERIFY-pk_1 ... pk_n n msg sig - bool检查密钥 pk_1...pk_n 和信息 msg 的聚合 BLS 签名。成功时返回 true,否则返回 false。如果 n=0 则返回 false。
`gas=58034+n3000`*
BLS_AGREGATEVERIFYpk_1 msg_1 ... pk_n msg_n n sgn - bool检查密钥-消息对 pk_1 msg_1...pk_n msg_n 的聚合 BLS 签名。成功时返回 true,否则返回 false。如果 n=0 则返回 false。
`gas=38534+n22500`*

VERIFY 指令不会对无效签名和公钥抛出异常(单元格下溢异常除外),而是返回 false。

低级操作

这些是对组元素的算术操作。

xxxxxxxxxxxxx
Fift 语法
xxxxxxxxxxxxxxxxx
堆栈
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
说明
BLS_G1_ADDx y - x+yG1上的加法。
3934 gas
BLS_G1_SUBx y - x-yG1上的减法。
3934 gas
BLS_G1_NEGx - -xG1上的取反。
784 gas
BLS_G1_MUL`x s - xs`*将G1点x乘以标量s
任何s都是有效的,包括负数。
5234 gas
BLS_G1_MULTIEXP`x_1 s_1 ... x_n s_n n - x_1s_1+...+x_ns_n`计算G1点x_i和标量s_ix_1*s_1+...+x_n*s_n。如果n=0,返回零点。
任何s_i都是有效的,包括负数。
gas=11409+n*630+n/floor(max(log2(n),4))*8820
BLS_G1_ZERO- zero推送零点到G1中。
34 gas
BLS_MAP_TO_G1f - x将FP元素f转换为G1点。
2384 gas
BLS_G1_INTROUPx - bool检查切片x是否表示有效的G1元素。
2984 gas
BLS_G1_ISZEROx - bool检查G1点x是否等于零。
34 gas
BLS_G2_ADDx y - x+yG2上的加法。
6134 gas
BLS_G2_SUBx y - x-yG2上的减法。
6134 gas
BLS_G2_NEGx - -xG2上的取反。
1584 gas
BLS_G2_MUL`x s - xs`*将G2点x乘以标量s
任何s都是有效的,包括负数。
10584 gas
BLS_G2_MULTIEXP`x_1 s_1 ... x_n s_n n - x_1s_1+...+x_ns_n`计算G2点x_i和标量s_ix_1*s_1+...+x_n*s_n。如果n=0,返回零点。
任何s_i都是有效的,包括负数。
gas=30422+n*1280+n/floor(max(log2(n),4))*22840
BLS_G2_ZERO- zero推送零点到G2中。
34 gas
BLS_MAP_TO_G2f - x将FP2元素f转换为G2点。
7984 gas
BLS_G2_INTROUPx - bool检查切片x是否表示有效的G2元素。
4284 gas
BLS_G2_ISZEROx - bool检查G2点x是否等于零。
34 gas
BLS_PAIRINGx_1 y_1 ... x_n y_n n - bool给定 G1 点 x_i 和 G2 点 y_i,计算并乘以 x_i,y_i 的配对。如果结果是 FP12 中的乘法同一性,则返回 true,否则返回 false。如果 n=0 则返回 false。
`gas=20034+n11800`*
BLS_PUSHR- r推送G1和G2的阶(约为2^255)。
34 gas

INGROUPISZERO在无效的点上(除了cell下溢异常)不会引发异常,而是返回false。

其他算术操作在无效的曲线点上引发异常。请注意,它们不检查给定的曲线点是否属于G1/G2群。使用 INGROUP 指令来检查这一点。

RUNVM

目前,TVM 中的代码无法调用 "沙盒 "中的外部不信任代码。换句话说,外部代码始终可以不可逆地更新代码、合约数据或设置操作(如发送所有资金)。 RUNVM 指令允许生成一个独立的虚拟机实例,运行所需的代码并获取所需的数据(堆栈、寄存器、耗气量等),而没有污染调用者状态的风险。以安全的方式运行任意代码可能对[v4 类型插件](/participate/wallets/contracts#wallet-v4)、Tact 的init类型子合约合计算等有用。

xxxxxxxxxxxxx
Fift 语法
xxxxxxxxxxxxxxxxx
堆栈
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
说明
flags RUNVMx_1 ... x_n n code [r] [c4] [c7] [g_l] [g_m] - x'_1 ... x'_m exitcode [data'] [c4'] [c5] [g_c]以代码 code 和堆栈 x_1...x_n 运行子虚拟机。返回生成的堆栈 x'_1...x'_m 和 exitcode。
其他参数和返回值由标志启用,见下文。
RUNVMXx_1 ... x_n n code [r] [c4] [c7] [g_l] [g_m] flags - x'_1 ... x'_m exitcode [data'] [c4'] [c5] [g_c]相同,但会从堆栈中弹出标志。

标志类似于 fift 中的 runvmx

  • +1:将c3设置为代码
  • +2:在运行代码之前推送一个隐式的0
  • +4:从堆栈中取c4(持久性数据),返回其最终值
  • +8:从堆栈中取gas限制g_l,返回消耗的gas g_c
  • +16: 从堆栈中取出 c7 (智能合约上下文)
  • +32:返回c5的最终值(操作)
  • +64:从堆栈中弹出硬gas限制(由ACCEPT启用)g_m
  • +128:"孤立的 gas 消耗"。子虚拟机将有一组单独的访问 cell 和一个单独的 chksgn 计数器。
  • +256:弹出整数r,从堆栈顶部返回确切的r个值(仅当exitcode=0或1时;如果不够,则exitcode=stk_und

gas成本:

  • 66 Gas
  • 每向子虚拟机提供一个堆栈元素,就产生 1 个 gas(前 32 个免费)
  • 子虚拟机每返回一个堆栈元素,就产生 1 个 gas(前 32 个免费)

发送信息

目前在合约中难以计算发送消息的成本(导致了一些近似,比如在jettons中)并且如果 Action Phase 不正确,则无法将请求反弹回。精确减去传入消息的“合约逻辑的常量费用”和“gas费用”是不可能的。

  • SENDMSG 将 cell 和模式作为输入。创建一个输出操作,并返回创建信息的费用。模式的作用与 SENDRAWMSG 相同。此外,+1024 表示不创建操作,只估算费用。其他模式对费用计算的影响如下:+64 "替换接收信息的全部余额作为接收值(略微不准确,因为计算完成前无法估算的 gas 费用未被考虑在内),"+128 "替换计算阶段开始前合同的全部余额值(略微不准确,因为计算完成前无法估算的 gas 费用未被考虑在内)。
  • SENDRAWMSGRAWRESERVESETLIBCODECHANGELIB - 添加了+16标志位,这意味着在操作失败时反弹交易。如果使用了+2,则没有效果。