事件
以太坊存在一种日志功能。当智能合约运行时,可以触发某个事件。该事件的数据会作为日志被保存到以太坊区块链中。
客户端(dapp)可以通过以太坊网络的RPC接口监听事件的发出,从而实现回调的功能。
一个事件可以带上多个参数,这些参数以及发出事件的合约地址等信息,都会做为日志数据写入以太坊区块链。
事件可以声明在文件层级,也可以声明在合约层级。
事件的声明与发出示例如下:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract Event {
// 定义一个事件 SomebodyCall,接受两个参数
event SomebodyCall(string name, uint time);
function call(string calldata name) public {
// 发送事件 SomebodyCall
emit SomebodyCall(name, block.timestamp);
}
}
我们部署该合约后,调用 call
,Remix IDE
中查看发出的日志信息如下:
[
{
// 发出的合约地址
"from": "0xf8e81D47203A594245E36C48e151709F0C19fBe8",
// 索引
"topic": "0x0e896679c2db63f7cc88a48d09b1f09e82b9575508982c01a9f64fed35552ac6",
// 事件名
"event": "SomebodyCall",
// 两个参数
"args": {
"0": "Mathew",
"1": "1710299542",
"name": "Mathew",
"time": "1710299542"
}
}
]
我们这里是直接在 Remix IDE
中查看。一个典型的交互流程是:
- 客户端监听事件
- 用户通过客户端调用合约函数
- 合约函数执行,并发出事件
- 客户端收到事件并做出处理
对于不同的客户端,如 web3
ethers
,连接和监听事件的方法不同,这里不做示例。
事件索引
为了方便监听时,对事件进行过滤和索引查找,我们可以为一个事件添加最多四个索引。事件的索引在日志中,称为 topics
(话题)。
对于我们上面命名的事件:
event SomebodyCall(string name, uint time);
我们还没有为它指定任何索引,但是我们看上面的日志输出却有一个:
"topic": "0x0e896679c2db63f7cc88a48d09b1f09e82b9575508982c01a9f64fed35552ac6"
这是因为事件的默认第一个索引,是事件签名经过 hash
之后的值,例如上面的 topic
值是通过以下方式计算的:
keccak256("SomebodyCall(string,uint256)")
由于默认的事件签名索引占用了一个索引名额,因此我们实际上只能额外多定义三个索引。我们使用 indexed
来指明希望用作索引的参数:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract Event {
// 定义一个事件 SomebodyCall,接受两个参数,都指定为索引
event SomebodyCall(string indexed name, uint256 indexed time);
function call(string calldata name) public {
// 发送事件 SomebodyCall
emit SomebodyCall(name, block.timestamp);
}
}
由于每一个 topic 大小不能超过 256 位,因此像 string 这种引用类型,会通过 keccak256
hash
到 256
位。
匿名事件
我们可以通过在事件名称后面加上 anonymous
标识符,来声明一个匿名事件。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract Event {
// 定义一个事件 SomebodyCall,接受两个参数,指定为索引
event SomebodyCall(string indexed name, uint256 indexed time) anonymous;
function call(string calldata name) public {
// 发送事件 SomebodyCall
emit SomebodyCall(name, block.timestamp);
}
}
匿名事件不会有默认的事件签名 topic,因此匿名事件支持最多声明四个索引参数,而且匿名参数发送的 gas 耗费更低。
由于没有了事件签名的 topic,因此如果可能出现事件混淆的情况,开发人员应当自行考虑如何进行区分。
事件重载
Solidity
还支持事件重载,即可以有同名的事件,只要事件接受的参数不同:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract Event {
// 事件重载
event SomebodyCall(string indexed name);
event SomebodyCall(string indexed name, uint256 indexed time) anonymous;
function call(string calldata name) public {
// 发送事件 SomebodyCall
emit SomebodyCall(name, block.timestamp);
}
}