开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
一、什么是事件
合约事件类似我们熟悉的应用日志,用来记录区块链上发生的特定行为变化。Solidity与我们熟悉的C语言、Javascript语言不同的地方在于,Solidity是单向通信的机制,也就是当开发者对链上的Solidity合约进行调用时,不能直接从调用的返回参数里获取链上的变更结果,合约函数返回的只是一个交易收据。但是实际上,开发者需要了解链上交易事件的发生、状态变更等信息,此时就可以用web3客户端来订阅合约事件。需要注意的是事件只能被链外agent监听和捕获,无法被合约内的方法或者另外一个合约监听到。
二、事件的用法
- 事件的定义
在Solidity合约中,用event关键字来定义事件,用indexed来标识参数是否为事件的主题。事件的主题可以理解为搜索事件的索引,使用者可以根据主题在链上的交易日志中搜索到指定的日志。同时要注意的是,由于EVM的机制,限定了event最多只支持使用者定义三个indexed的事件参数属性。
定义事件的代码示例:
event Transfer(address indexed _from,address indexed _to,uint256 _value);
- 事件的触发
在Solidity合约中,用emit关键字来触发事件(老版本不需要采用emit关键字),Solidity允许在任意函数中进行事件的触发,但是需要注意的是,日志和事件在任何合约中都是无法被访问的。
触发事件的代码示例:
emit Transfer(msg.sender,_to,_value);
- 事件的监听
定义事件的简单示意合约如下所示:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
contract EventDemo{
event Transfer(address indexed _from,address indexed _to,uint256 _value);
function transfer(address _to,uint256 _value) public {
emit Transfer(msg.sender,_to,_value);
}
}
用JavaScript API监听事件的方式:
var abi = /* abi 由编译器产生 */;
var EventDemo = web3.eth.contract(abi);
var demo=EventDemo.at(/* 合约地址 */);
var event=demo.Transfer();
//监听事件
event.watch(function(err,result){
if(!err){
console.log(result);
}
});
三、以太坊上的事件处理
- EVM对事件的处理
上文已经介绍了触发事件要用emit关键字,在EVM层面为emit操作分配的opcode是LOG0~LOG4,LOG的操作码示意可以通过查询EVM Opcodes了解其操作指令,如下图所示:
每个日志记录包含两部分内容,即主题(Topic)和数据(Data),除匿名事件外,其他事件的第一个主题是事件名和参数类型的哈希签名,比如上文示例中定义的event Transfer事件,它的第一个主题为:
keccak256("Transfer(address,address,uint256)")
由于在事件Transfer的定义中,声明了第一个参数和第二个参数都是indexed,因此该事件的第二个主题和第三个主题分别为:address _from和address _to,我们可以认为这个事件支持根据主题进行搜索,比如搜索从address A到address B的所有转账,所有从address A转出,或者所有转账到address B的转账记录。
- 事件的GAS计算
从上图中也可以看到每个LOG操作的最小gas消耗,实际上每种LOG操作的gas消耗可以根据下面这个公式计算出:
gas_cost = 375 + 375 * num_topics + 8 * data_size + mem_expansion_cost
其中,num_topics是主题的数量,LOG0对应0,LOG2对应2,data_size是要记录的log的大小,以字节为单位,mem_expansion_cost是指内存扩张要额外付出的gas。如果发生一次标准的ERC-20类型的转账,按上面示例中定义的Transfer事件,有3个topic,由于ERC-20限制了转账的最大值为32字节,因此一次转账的事件记录消耗gas费用为:375+3753+832=1756 gas。同时,我们也知道标准的以太币(非代币)转账至少要花费21000 gas,这样一做对比可以发现事件的gas消耗相对便宜,开发者可以充分利用事件来做数据的存储和对外通知。
参考资料:
- 理解以太坊上的事件日志:learnblockchain.cn/article/187…
- 搞懂 Solidity 事件Event - 如何在DApp中使用:learnblockchain.cn/2018/05/09/…
- Logging in Ethereum: coders-errand.com/logging-in-…