# Solidity Events 详解

By [xyyme.eth](https://paragraph.com/@xyyme) · 2022-06-08

---

Events 是 Solidity 中记录事件的工具，可以简单理解为日志。Events 的优点在于，一是能够利用较少的 Gas 就能将数据记录在区块链上，二是可以方便链下对链上数据进行监听。

### 代码示例

先来看一段简单的代码：

    pragma solidity 0.8.10;
    
    contract EventsDemo {
        // 定义 Events
        event Transfer(
            address indexed from, 
            address indexed to, 
            uint256 amount
        );
    
        function transfer(address to, uint256 amount) external {
            // 发送 Events
            emit Transfer(msg.sender, to, amount);
        }
    }
    

在上述代码中，我们通过 `event` 关键字定义事件，通过 `emit` 关键字发送事件。这样，在调用 `transfer` 函数的时候，就会发送 `Transfer` 事件，将其数据记录在链上。

接下来我们实际部署一下，并调用 `transfer` 看看会发生什么。

调用 `transfer` 时的参数我们分别设为：

*   `to` → 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
    
*   `amount` → 123
    

调用成功后，查看 Etherscan 的 Logs 页面：

![](https://storage.googleapis.com/papyrus_images/cf2fc7341c858ba146e6f80a42cd119790a2b0fd2ea28a7e5f033c92ba01cac0.png)

我们可以看到页面中有三部分，分别是：

*   Address
    
*   Topics
    
*   Data
    

其中 `Address` 是合约地址，这个很容易理解，那么剩下的两个字段分别代表什么呢？

### 基本数据结构

Events 打印出的日志中包含两种数据结构，分别是 `topic` 和 `data`。每个日志中最多可以包含 4 个 `topic`，`data` 则没有限制。`topic` 的第一个字段默认是事件签名，在上例中，`Topics[0]` 中的 `0xddf25…b3ef` 哈希值就是 `Transfer` 事件的签名：

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

图示即为（图片来自于[这里](https://medium.com/mycrypto/understanding-event-logs-on-the-ethereum-blockchain-f4ae7ba50378)）：

![](https://storage.googleapis.com/papyrus_images/af3ed3af032c440b52b7cc7d90bd853a923942c3f5b30c6efdadb45d09c2f7e5.png)

我们注意到，前面定义的 `Transfer` 事件中，`from` 和 `to` 字段都带有一个 `indexed` 关键字，它的作用就是将该字段列为 `topic`。由于最多限制 4 个 `topic`，且第一个 `topic` 默认是事件签名，因此在合约中定义事件的时候，最多只能有 3 个字段可以使用 `indexed`。那么 `topic` 到底有什么用呢？

将字段列为 `topic` 可以方便检索。例如，我们想要在链下监听 `Transfer` 事件，但是并不想监听每一笔事件，只想监听 `from` 是我的地址的事件，那么就必须将 `from` 设置为 `topic`，否则 `data` 类型的日志是没有办法做到的。

在上例中，我们将 `from` 和 `to` 设置为 `indexed`，因此我们可以在上图中看到，有三个 `topic`，分别是

*   事件签名
    
*   `from`，这里是调用合约的地址
    
*   `to`
    

那么剩下的 `data` 部分就是 `amount` 字段的数据了。

`data` 的内容是将数据经过 `abi-encoded` 的结果。

我们在 Etherscan 上传代码，刷新页面：

![](https://storage.googleapis.com/papyrus_images/deeffb0c96cf15c8b41986b2f37e6fb546e47ccd480992afe1a037a5369ebed6.png)

这时 Etherscan 就可以识别出事件的 `name`，并且可以将 `topic` 和 `data` 数据解码。

#### Topic 的限制

前面我们说到，一个事件日志中，最多只能有 4 个 `topic`，也就是说最多有 3 个 `indexed`。同时，`topic` 本身也有长度限制，每个 `topic` 只能容纳 32 个字节的数据。那么像 `string` 或者 `bytes` 这种非定长的数据能否设置为 `topic` 呢？

答案是可以的，但是需要对其内容进行哈希。我们来看一段代码：

    pragma solidity 0.8.10;
    
    contract EventsDemo {
        event Message(
            address indexed from,
            address indexed to,
            string message
        );
    
        function sendMessage(address to, string memory message) public {
            emit Message(msg.sender, to, message);
        }
    }
    

首先，我们先将 `Message` 事件中的 `message` 字段设置非 `indexed`，部署并调用，调用的参数分别是：

*   `to` → 0x5b38da6a701c568545dcfcb03fcb875f56beddc4
    
*   `message` → Hello World
    

结果为：

![](https://storage.googleapis.com/papyrus_images/c3b0085bae2c061dee06a418d42135807338a464e64a9134426173fe4e256907.png)

`message` 字段位于 `data` 中，可以被正常解码。

接下来，我们给 `message` 加上 `indexed`：

    event Message(
        address indexed from,
        address indexed to,
        string indexed message
    );
    

部署并调用，使用同样的参数，结果为：

![](https://storage.googleapis.com/papyrus_images/d34a00a942909c29e59972cc6dcc232a85182d9697d625779040f7de847f2d59.png)

此时，`data` 中内容为空，而 `topic[3]` 的值就是 `message` 的哈希值：

    keccak256(bytes("Hello World"))
    

### Events 的 Gas 花费

根据以太坊黄皮书的内容，日志的基础费用是 375 Gas。另外每个 `topic` 同样需要花费 375 Gas，`data` 中每个字节需要花费 8 Gas。

![](https://storage.googleapis.com/papyrus_images/df1a401a106678e860b6052d9aaa923573c2aebd0ba0caca1ee6642271842709.png)

![](https://storage.googleapis.com/papyrus_images/5a9a7fbfbfb2306eedd046918c423426152b6a8ceb87a4f23be22e871f92ad07.png)

因此，我们前面的 `Transfer` 事件花费的 Gas 为：

> 1756 = 375（基础费用）+ 375 \* 3（3 个 `topic`）+ 32 \* 8（`data` 中共 32 字节）

### Low-level log

Solidity 老版本中还存在一种底层调用，例如：

    pragma solidity >=0.4.10 <0.7.0;
    
    contract C {
        function f() public payable {
            uint256 _id = 0x420042;
            log2(
                bytes32(msg.value),
                bytes32(uint256(msg.sender)),
                bytes32(_id)
            );
        }
    }
    

包括 `log0`、`log1`、`log2`、`log3`、`log4`，不过新版文档中已经去除了这部分内容，因此我们也不再介绍，了解其存在即可，感兴趣的朋友可以查看[这里](https://docs.soliditylang.org/en/v0.4.21/contracts.html#low-level-interface-to-logs)。

### 监听事件

链下监听事件有很多种方法，几乎所有主流语言都有对应的 web3 库可以实现。我前面写的一篇 `ethers` 使用教程的[文章](https://mirror.xyz/xyyme.eth/hT78s8EI3wYp-IixUC951fQNWTON-EhNcGd2jXEtcdM)中包含了这部分内容，感兴趣的朋友可以前往查看，这里就不再赘述了。

### 总结

Solidity 中 Events 是一个很实用的日志工具，花费 Gas 少，利于链下来监听链上交易。

### 关于我

欢迎[和我交流](https://linktr.ee/xyymeeth)

### 参考

[

Events and Logging in SoliditySolidity Events and Logging \[With Examples\] | Chainlink
---------------------------------------------------------------------------------------

In this technical tutorial, learn all about events and logging in Solidity. This is everything you need to know.

https://blog.chain.link

![](https://storage.googleapis.com/papyrus_images/b218cea228e9e79992cea48e52f57b281ff46211a42030990faf87e8537db154.png)

](https://blog.chain.link/events-and-logging-in-solidity/)

[https://docs.soliditylang.org/en/latest/contracts.html#events](https://docs.soliditylang.org/en/latest/contracts.html#events)

[

Understanding event logs on the Ethereum blockchain
---------------------------------------------------

Understanding event logs on the Ethereum blockchain Most transactions have an event log, but those event logs can be hard to read. Preface: If you haven't read my article on the Ethereum Virtual ...

https://medium.com

![](https://storage.googleapis.com/papyrus_images/c12c6c4c5fe872b13178930c95b00c154d5e7ff59cb67d8eb99fd4d4236b893d.png)

](https://medium.com/mycrypto/understanding-event-logs-on-the-ethereum-blockchain-f4ae7ba50378)

---

*Originally published on [xyyme.eth](https://paragraph.com/@xyyme/solidity-events)*
