7.1 报错控制
Solidity中抛出异常的方法有:require、revert、assert
。本章分别介绍三种方法的使用。
7.1.1 require
require命令是solidity 0.8版本之前抛出异常的常用方法,目前很多主流合约仍然还在使用它。require使用简单方便,唯一的缺点就是gas随着描述异常的字符串长度增加,比error命令(下一节介绍)要高。使用方法:require(检查条件,"异常的描述")
,当检查条件不成立的时候,就会抛出异常。
示例代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract errorDemo {
// 定义owner变量
address public owner;
// 在合约构造方法中设置owner
constructor() {
owner = msg.sender;
}
// 变更合约owner
function transferOwner(address _oneOwner) external {
require(msg.sender == owner, "No permissions!");
owner = _oneOwner;
}
}
7.1.2 revert
7.1.2.1 默认revert
revert使用方法相似,只是判断写在revert语句之外,且判断方向和require相反。revert中只包含报错信息。
示例代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract errorDemo {
// 定义owner变量
address public owner;
// 在合约构造方法中设置owner
constructor() {
owner = msg.sender;
}
// 变更合约owner
function transferOwnerRevert(address _oneOwner) external {
if (msg.sender != owner) {
revert("No permissions!");
}
owner = _oneOwner;
}
}
7.1.2.2 revert自定义error
error是solidity 0.8.4版本新增加的内容,允许开发者自定义错误。error不仅能够携带操作失败的原因信息,还能够携带参数,方便开发者进行调试,提高合约调用的操作体验。error需要和revert配合使用。
示例代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract errorDemo {
// 定义owner变量
address public owner;
// 在合约构造方法中设置owner
constructor() {
owner = msg.sender;
}
// 自定义错误
error NoPermissions(address caller, address owner);
// 变更合约owner
function transferOwnerRevertError(address _owner) external {
if (msg.sender != owner) {
revert NoPermissions(msg.sender, owner);
}
owner = _owner;
}
}
报错信息:
NoPermissions
Parameters:
{
"caller": {
"value": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2"
},
"owner": {
"value": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
}
}
7.1.3 assert
assert命令一般用于程序员写程序debug,因为它不能解释抛出异常的原因(比require少个字符串)。它的用法很简单:assert(检查条件)
,当检查条件不成立的时候,就会抛出异常。
示例代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract errorDemo {
// 定义owner变量
address public owner;
// 在合约构造方法中设置owner
constructor() {
owner = msg.sender;
}
// 变更合约owner
function transferOwnerAssert(address _oneOwner) external {
assert(msg.sender == owner);
owner = _oneOwner;
}
TODO:try catch
7.2 事件
7.2.1 智能合约中的事件
Solidity中的事件(event)是EVM上日志的抽象,它具有两个特点:
-
响应:应用程序(
ethers.js
)可以通过RPC
接口订阅和监听这些事件,并在前端做响应。 - 经济:事件是EVM上比较经济的存储数据的方式,每个大概消耗2,000 gas;相比之下,链上存储一个新变量至少需要20,000 gas。
7.2.1.1 事件声明
事件的声明由event关键字开头,接着是事件名称,括号里面写好事件需要记录的变量类型和变量名。以ERC20代币合约的Transfer事件为例:
event Transfer(address indexed from, address indexed to, uint value);
该事件包含from、to、value三个参数,其中,from和to参数使用indexed修饰。indexed关键字有以下特点:
- indexed关键字修饰的参数会保存在以太坊虚拟机日志的topics中,可以进行检索。
- 一个事件可以有多个参数,但是使用indexed关键字修饰的参数最多只能有3个。
7.2..1.2 释放事件
上述自定义事件的释放示例函数:
function emitFunc(address _from, address _to, uint _value) external {
emit Transfer(_from, _to, _value);
}
remix中的log记录:
[
{
"from": "0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99",
"topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"event": "Transfer",
"args": {
"0": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"1": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
"2": "123",
"from": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
"to": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",
"value": "123"
}
}
]
区块链浏览器中的log记录:

- Address:合约地址
- Name:事件名称
-
Topics:事件描述数组,最大长度为4(一个事件hash,最多三个indexd参数)
- 0:事件hash值,计算方式如下:
function getTopics0() external pure returns (bytes32) { return keccak256("Transfer(address,address,uint256)"); // uint默认为uint256参与计算 // 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef }
- 1:indexed修饰的参数(from)
- 2:indexed修饰的参数(to)
- Data:其他非indexed参数(value)
网友评论