TVM

Tron 虚拟机

TVM 即 TRON Virtual Machine. 相关实现参考 EVM.

资源消耗模型

TRON 对于合约的资源消耗,引入了 User Energy Pay Ratio 的概念,开发者和用户可以共同按比例支付合约调用的 energy 消耗。

内存模型

内存模型线性。基于堆栈的虚拟机,字长 256 位。

Solidity reserves four 32-byte slots.

  • 0x00 - 0x3f (64 bytes): scratch space for hashing methods

  • 0x40 - 0x5f (32 bytes): currently allocated memory size (aka. free memory pointer)

  • 0x60 - 0x7f (32 bytes): zero slot

the free memory pointer points to 0x80 initially.

所有 value type 在寄存器中均扩展到 256 位处理。

对于其他类型而言:

  • bytes, string 使用 长度 + 具体字节的方式编码,至少占用两个字长

  • refernce type 通过偏移量 + 字段的方式处理

事件模型

事件通过 TVM 的 logX OpCode 实现。将合约运行过程中的 emit 操作,转换为对 logX 的调用, 输出到合约执行结果的“收据”或 transaction_info 中。供事件 API 索引,对外提供服务。

ref: https://sawtooth.hyperledger.org/docs/core/releases/0.8/solidity_developers_guide/seth_transaction_receipts_and_events.html

ref: https://solidity.readthedocs.io/en/v0.4.24/contracts.html#low-level-interface-to-logs

Events allow the convenient usage of the EVM logging facilities.

Events are inheritable members of contracts. When they are called, they cause the arguments to be stored in the transaction’s log - a special data structure in the blockchain.

EVM event = log0, log1, log2, log3 and log4.
log0(_id); // the event id with out any parameter

The first argument will be used for the data part of the log and the others as topics.

// Protobuf message to store in the Sawtooth Event's event_data field
message EvmLogData {
// 20 Bytes - address from which this log originated.
bytes address = 1;
// Array of 0 to 4 32-byte blobs of data.
// (In solidity: The first topic is the hash of the signature
// of the event (e.g. Deposit(address,bytes32,uint256)), except
// you declared the event with the 'anonymous' specifier.)
// See the following:
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges
repeated bytes topics = 2;
// contains one or more 32 Bytes non-indexed arguments of the log.
bytes data = 3;
}

ref: paritytech/substrate:frame/evm/src/backend.rs

pub struct Log {
/// Source address of the log.
pub address: H160,
/// Topics of the log.
pub topics: Vec<H256>,
/// Byte array data of the log.
pub data: Vec<u8>,
}

contract: TXnBP1MHkYww9R9mrAgcZ8i4t5TKvFnXWy txn id: 605d7130028c116d280682faf6f2a73ae0aaa684bb81d978e102eae0f58affef

[
{
"address": "ef3cd68915b0aaf61d4301eeb0a9222c9730bee4",
"data":
"0000000000000000000000009c7e0a1ab7a5eb3b9ba6c7d5e69abb7ba392704f
0000000000000000000000000000000000000000000000000000000004c4b400
000000000000000000000000000000000000000000000000000000000000005d
0000000000000000000000000000000000000000000000000000000000000001
000000000115151674e0819c1f30252f20a761efc29e52db1483f47e26c3bb39
0000000000000000000000000000000000000000000000000000000000000100
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000000010
0000000000000000000000000000000000000000000000000000000000000040
6630313936373731386132626566626139626666623065613566336331353666
6364636435323936313165643234343238366133626537326431663232383737",
"topics": [
"ea4f248a9a34b0ef200e6f5463b313b5568faaf8b073b8b541e16f9a2110c817"
]
}
]
event diceResult(address user, uint256 betValue, uint256 prediction,
uint256 is_under, bytes32 blockHash, string tx, bool win, uint256 lucky_number)
=> ea4f248a9a34b0ef200e6f5463b313b5568faaf8b073b8b541e16f9a2110c817

event 通过 keccak256 编码为 256 位 topic. indexed 参数直接也出现在 topic 中。

event Deposit(
address indexed _from,
bytes32 indexed _id,
uint _value
);

Indexed arguments will not be stored themselves. You can only search for the values, but it is impossible to retrieve the values themselves. 在 data 字段会被忽略,通过 topics 字段保存.

txn id: b4264ed1e516724bdbdde36cba1e0adde71b843abffe20c98efe718e23e61756
! Sender Address(base58check): TCu2ovwasJNKvd4tefqo3toKaX8cvC4Qyc
! Contract Address(base58check): TWmhXhXjgXTj87trBufmWFuXtQP8sCWZZV
! function GoodLuck(uint256 _number, uint256 _direction) payable
! GoodLuck(uint256,uint256) [a3082be9]
! Arguments:
_number: uint256 = 50
_direction: uint256 = 0
event DiceCreate(uint256 indexed _orderId, address indexed _bettor, uint256 _number,
uint256 _direction, uint256 _amount)
=> 4a0276f32d1dc1f56ff4e6491876a2f2512eb9ed7e9da8307716b6204e3c6340
[
{
"address": "e42d76d15b7ecd27a92cc9551738c2635c63b71c",
"data": "00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001312d000",
"topics": [
"4a0276f32d1dc1f56ff4e6491876a2f2512eb9ed7e9da8307716b6204e3c6340",
"00000000000000000000000000000000000000000000000000000000000089bf",
"000000000000000000000000201e34acefe5b39fcc01d261e7c3e7b9c1a54502"
]
}
]

解析实例数据

event Entrust(uint256 orderID, uint256 _freezeAmount, uint256 _payAmount,
uint256 _days, address _payAddr, address _energyAddr, uint256 _ratio)
=> 7acd6555213574e8ac7128e5478b1190fa43d16eed19ae22a68bbb0bc9ed79fa
0000000000000000000000000000000000000000000000000000000000001dff # orderID
000000000000000000000000000000000000000000000000000000003b9aca00 # freezeAmount
00000000000000000000000000000000000000000000000000000000004f1ea1 # payAmount = 5185185
0000000000000000000000000000000000000000000000000000000000000007 # days = 7
0000000000000000000000003d83e45a78d53b57b7d96035ae678b4ad88484ce # payAddr = TFaUDn4x3zgcPWrrAkweGS94TrZvteyxHi
000000000000000000000000c3e153e557806e83199f229d2f0b73d9e4ef1ede # energyAddr = TTpvhWYn28siymoW2NrvrPw4zPovofx52A
0000000000000000000000000000000000000000000000000000000000000000 # ratio = 0

相对于 EVM 的修改

  • TVM 使用 energy 概念替代 gas

  • 不存在 gasprice 概念,恒为 0. 实际上 energy 费率稳定,也可以通过冻结 TRX 获得

  • 不存在 block.difficulty 概念,恒为 0

  • 不存在 block.gaslimit 概念,恒为 0

  • 增加 TRC10 token 功能, trcToken 类型及若干指令, 用于处理 TRC10 转账

    • 若编译时未使用 tron-solidity 编译器,将导致任意非 payable 的 external 函数都可以接受带 TRC10 转账的调用,此时 payable 修饰符失效

  • 增加批量验签名和验证 multisig 的指令

    • 但验证帐号 multisig 功能实现为 precompile, 导致不一致(是唯一一个依赖外部状态的 precompile)

  • 增加匿名合约及若干 librustzcash precompile

    • 实现比较奇怪, 混用了 librustzcash 和 libsodium, 但实际上所引入的 libsodium 算法在 librustzcash 有提供

    • 相关实现的 JNI 和 Java 边界不清晰, 比较随意

  • 增加了 ISCONTRACT 指令

    • 实际上根本没有必要, 用 ext_code_sizecode_hash 判断即可

  • 不支持 SELFBALANCE, CHIANID 指令

  • 多数指令的消耗等价于 EVM, memory 操作指令消耗小于 EVM, 即部分指令的 memory cost 会覆盖掉指令的 tier cost(可能是 bug, 即需要 += 的地方错写为 =, 但由此也成为了 feature, 毕竟消耗少了)

  • 没有 storage 操作 refund 机制(可能是 bug, 因为 refund 有记录但最后执行结束前没有加回, 大概写代码忘了)

  • 没有 suicide(selfdestruct) 操作 refund 机制

  • 对 SSTORE 的 energy 消耗计算有 bug

  • 对带转账类别的子调用 energy 计算有 bug

  • CREATE2 指令创建的合约代码为空(目前是假实现), extcodesize(address) = 0

  • CREATE2 指令创建的合约地址计算时,前缀字节为 0x41, 而非 EVM 的 0xff

  • CREATE 指令创建的合约地址计算方法有修改

    • keccak256(transaction root hash + nonce), 其中 nonce 是当前 internal transaction 的 index, 0 起始

  • Internal Transaction 无 data 信息

  • 引入一种新的错误类型 TransactionException, 会中断 VM 执行,且不消耗剩余 energy(属于不一致的设计)

    • 例如合约中给自己转账,会触发该异常

    • 但搞笑的是,如果余额不足,不会触发该异常,是一次普通的 revert(重要验证逻辑次序颠倒)

  • COINBASE 指令返回 21 字节地址,与 VM 其他 OpCode 不一致

    • ORIGIN, CALLER, ADDRESS 均使用 20 字节地址

    • See-also: txn://681afe64edab762a767932d8518ccd7d81c22518a76986532c463cad38081ace