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 的修改
不存在 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_size
或 code_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