Solidity入门学习(5)-事件

641 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

一、什么是事件

合约事件类似我们熟悉的应用日志,用来记录区块链上发生的特定行为变化。Solidity与我们熟悉的C语言、Javascript语言不同的地方在于,Solidity是单向通信的机制,也就是当开发者对链上的Solidity合约进行调用时,不能直接从调用的返回参数里获取链上的变更结果,合约函数返回的只是一个交易收据。但是实际上,开发者需要了解链上交易事件的发生、状态变更等信息,此时就可以用web3客户端来订阅合约事件。需要注意的是事件只能被链外agent监听和捕获,无法被合约内的方法或者另外一个合约监听到。

二、事件的用法

  1. 事件的定义

在Solidity合约中,用event关键字来定义事件,用indexed来标识参数是否为事件的主题。事件的主题可以理解为搜索事件的索引,使用者可以根据主题在链上的交易日志中搜索到指定的日志。同时要注意的是,由于EVM的机制,限定了event最多只支持使用者定义三个indexed的事件参数属性。

定义事件的代码示例:

event Transfer(address indexed _from,address indexed _to,uint256 _value);
  1. 事件的触发

在Solidity合约中,用emit关键字来触发事件(老版本不需要采用emit关键字),Solidity允许在任意函数中进行事件的触发,但是需要注意的是,日志和事件在任何合约中都是无法被访问的。

触发事件的代码示例:

emit Transfer(msg.sender,_to,_value);
  1. 事件的监听

定义事件的简单示意合约如下所示:

// 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);
	}
});

三、以太坊上的事件处理

  1. EVM对事件的处理

上文已经介绍了触发事件要用emit关键字,在EVM层面为emit操作分配的opcode是LOG0~LOG4,LOG的操作码示意可以通过查询EVM Opcodes了解其操作指令,如下图所示:

LOG-OP.png 每个日志记录包含两部分内容,即主题(Topic)和数据(Data),除匿名事件外,其他事件的第一个主题是事件名和参数类型的哈希签名,比如上文示例中定义的event Transfer事件,它的第一个主题为:

keccak256("Transfer(address,address,uint256)")

由于在事件Transfer的定义中,声明了第一个参数和第二个参数都是indexed,因此该事件的第二个主题和第三个主题分别为:address _from和address _to,我们可以认为这个事件支持根据主题进行搜索,比如搜索从address A到address B的所有转账,所有从address A转出,或者所有转账到address B的转账记录。

  1. 事件的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消耗相对便宜,开发者可以充分利用事件来做数据的存储和对外通知。

参考资料:

  1. 理解以太坊上的事件日志:learnblockchain.cn/article/187…
  2. 搞懂 Solidity 事件Event - 如何在DApp中使用:learnblockchain.cn/2018/05/09/…
  3. Logging in Ethereum: coders-errand.com/logging-in-…