阅读(4921) (12)

事件

2022-05-13 10:44:01 更新

Solidity 事件在 EVM 的日志记录功能之上提供了一个抽象。应用程序可以通过以太坊客户端的 RPC 接口订阅和监听这些事件。

事件是合约的可继承成员。当你调用它们时,它们会导致参数存储在交易日志中——区块链中的一种特殊数据结构。这些日志与合约的地址相关联,并入区块链中,并在区块可访问时一直存在(从现在开始一直存在,但这可能会随着 Serenity 而改变)。无法从合约内部访问日志及其事件数据(甚至无法从创建它们的合约中访问)。

可以为日志请求 Merkle 证明,因此如果外部实体提供具有此类证明的合约,它可以检查日志是否确实存在于区块链中。你必须提供区块头,因为合约只能看到最后的 256 个区块哈希。

您可以将属性添加indexed到最多三个参数,这会将它们添加到称为“主题”的特殊数据结构中,而不是日志的数据部分。一个主题只能包含一个单词(32 个字节),因此如果您对索引参数使用引用类型,则该值的 Keccak-256 哈希值将存储为主题。

所有没有该indexed属性的参数都被ABI 编码 到日志的数据部分。

主题允许您搜索事件,例如在为某些事件过滤一系列块时。您还可以按发出事件的合约地址过滤事件。

例如,下面的代码使用 web3.jssubscribe("logs") 方法过滤与某个主题与某个地址值匹配的日志:

var options = {
    fromBlock: 0,
    address: web3.eth.defaultAccount,
    topics: ["0x0000000000000000000000000000000000000000000000000000000000000000", null, null]
};
web3.eth.subscribe('logs', options, function (error, result) {
    if (!error)
        console.log(result);
})
    .on("data", function (log) {
        console.log(log);
    })
    .on("changed", function (log) {
});

事件签名的哈希是主题之一,除非您使用说明anonymous符声明事件。这意味着无法按名称过滤特定的匿名事件,只能按合约地址过滤。匿名事件的优点是部署和调用成本更低。它还允许您声明四个索引参数,而不是三个。

笔记

由于事务日志只存储事件数据而不存储类型,因此您必须知道事件的类型,包括索引哪个参数以及事件是否是匿名的,以便正确解释数据。特别是,可以使用匿名事件“伪造”另一个事件的签名。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.21 <0.9.0;

contract ClientReceipt {
    event Deposit(
        address indexed from,
        bytes32 indexed id,
        uint value
    );

    function deposit(bytes32 id) public payable {
        // Events are emitted using `emit`, followed by
        // the name of the event and the arguments
        // (if any) in parentheses. Any such invocation
        // (even deeply nested) can be detected from
        // the JavaScript API by filtering for `Deposit`.
        emit Deposit(msg.sender, id, msg.value);
    }
}

JavaScript API 中的使用如下:

var abi = /* abi as generated by the compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */);

var depositEvent = clientReceipt.Deposit();

// watch for changes
depositEvent.watch(function(error, result){
    // result contains non-indexed arguments and topics
    // given to the `Deposit` call.
    if (!error)
        console.log(result);
});


// Or pass a callback to start watching immediately
var depositEvent = clientReceipt.Deposit(function(error, result) {
    if (!error)
        console.log(result);
});

上面的输出如下所示(修剪):

{
   "returnValues": {
       "from": "0x1111…FFFFCCCC",
       "id": "0x50…sd5adb20",
       "value": "0x420042"
   },
   "raw": {
       "data": "0x7f…91385",
       "topics": ["0xfd4…b4ead7", "0x7f…1a91385"]
   }
}

了解事件的其他资源