TVM 概述
所有 TON 智能合约都在其自己的 TON 虚拟机(TVM)上执行。TVM 基于 栈原理 构建,这使得它高效且易于实现。
本文提供了TVM如何执行交易的概览。
提示
- TVM 源码 — TVM C++ 实现
交易和阶段
当 TON 链中的某个帐户发生某些事件时,它会引发一个交易。最常见的事件是"接收某个消息",但一般来说还可能有 tick-tock
、merge
、split
和其他事件。
每个交易包含最多 5 个阶段:
- Storage phase - 在此阶段,由于合约占用链状态中的某些空间而积累的存储费用被计算。阅读更多信息 存储费用。
- Credit phase - 在此阶段,根据(可能的)传入消息值和收集的存储费用计算合约的余额。
- Compute phase - 在此阶段,TVM 执行合约(见下文),合约执行的结果是
exit_code
、actions
(动作序列的序列化列表)、gas_details
、new_storage
等的聚合。 - Action phase - 如果 Compute phase 成功,在此阶段处理来自 Compute Phase 的
actions
。特别是,动作可能包括发送消息、更新智能合约代码、更新库等。请注意,在处理过程中某些动作可能失败(例如,如果我们尝试发送比合约拥有的 TON 更多的消息),在这种情况下整个交易可能会回滚或跳过此动作(这取决于动作的模式,换句话说,合约可能会发送send-or-revert
或try-send-if-no-ignore
类型的消息)。 - Bounce phase - 如果 Compute Phase 失败(它返回
exit_code >= 2
),在此阶段为由传入消息发起的事务形成 反弹消息。
Compute phase
在此阶段,执行 TVM。
TVM 状态
在任何给定时刻,TVM 状态由 6 个属性完全确定:
- Stack - (见下文)
- Control registers - (见下文)简单地说,这意味着最多可在执行期间直接设置和读取的 16 个变量
- Current continuation - 描述当前执行的指令序列的对象
- Current codepage - 简单地说,这表示当前正在运行的 TVM 版本
- Gas limits - 一组 4 个整数值;当前 gas 限制 gl、最大 gas 限制 gm、剩余 gas gr 和 gas 信用 gc
- Library context - 可以由 TVM 调用的库的哈希映射
TVM 是堆栈机
TVM 是一个后进先出的堆栈机。总共有 7 种类型的变量可以存储在堆栈中 — 三种非cell类型:
- Integer(整数) - 有符号的 257 位整数
- Tuple(元组) - 由最多 255 个元素组成的有序集合,具有任意值类型,可能是不同的。
- Null(空)
以及四种不同的cell类型:
- Cell(cell) - TON 区块链用于存储所有数据的基本(可能是嵌套的)不透明结构
- Slice(切片) - 一种特殊的对象,允许从cell中读取
- Builder(构建器) - 一种特殊的对象,允许您创建新的cell
- Continuation(延续对象) - 一种特殊的对象,允许您将cell TVM 指令的源
控制寄存器
c0
— 包含下一个 continuation 或返回 continuation(类似于常规设计中的子例程返回地址)。此值必须是一个 Continuation。c1
— 包含备用(返回) continuation ;此值必须是一个 Continuation。c2
— 包含异常处理程序。此值是一个 Continuation,在触发异常时调用。c3
— 支持寄存器,包含当前字典,本质上是一个包含程序中使用的所有函数的代码的哈希映射。此值必须是一个 Continuation。c4
— 包含持久数据的根,或简单地说,合约的data
部分。此值是一个 Cell。c5
— 包含输出动作。此值是一个 Cell。c7
— 包含临时数据的根。它是一个 Tuple。
TVM 的初始化
TVM 在事务执行到达 Compute Phase 时初始化,然后从 当前 continuation 执行命令(操作码),直到没有更多的命令要执行(并且没有用于返回跳转的 continuation)。
可以在这里找到初始化过程的详细说明:TVM 初始化
TVM 指令
TVM 指令列表可以在这里找到:TVM 指令。
TVM 执行的结果
除了 exit_code
和消耗的 gas 数据之外,TVM 还间接输出以下数据:
- c4 寄存器 - 将作为智能合约的新
data
存储的cell(如果执行不会在此阶段或以后的阶段回滚) - c5 寄存器 - (输出动作列表)列表中最后一个动作和对先前动作的引用的cell(递归)
所有其他寄存器值都将被忽略。
请注意,由于最大cell深度 <1024
以及特别是 c4 和 c5 深度 <=512
的限制,一次 tx 中的输出动作数量将受到限制 <=255
。如果合约需要发送的消息超过这个限制,它可以发送带有请求 continue_sending
的消息给自己,并在随后的交易中发送所有必要的消息。