错误处理
Solidity
中有两种错误:
- Error:外部错误,属于可以预见或可修复的错误,错误归因是外部环境导致,非代码逻辑本身存在问题。例如用户传参错误
- Panic:内部编程错误,属于不可预见或不可修复的错误,错误归因通常是内部代码逻辑存在问题。例如未检查导致除0错误
当抛出的错误没有被处理时,整个合约交易将被取消,并重置到交易执行前的状态。
revert 与 Error
我们可以使用 revert
来抛出 Error
。使用方式如下:
Revert.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract Revert {
int public count;
function revert1() public {
count++;
// 直接抛出错误,不符带任何数据
revert();
}
function revert2() public {
count++;
// 抛出错误,带一个字符串揭示错误原因
revert("Not owner");
}
// 一个自定义错误
error NotOwner(address from);
function revert3() public {
count++;
// 抛出自定义错误
revert NotOwner(msg.sender);
}
}
部署合约后触发 revert1
函数,将抛出错误,且没有返回任何错误信息。
触发 revert2
函数,将抛出错误,并附带有解释错误原因的字符串:Reason provided by the contract: "Not owner".
触发 revert3
函数,将抛出错误,并附有 NotOwner
错误的参数:
Error provided by the contract:
NotOwner
Parameters:
{
"from": {
"value": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
}
}
以前三个函数抛出错误,合约将重置回调用前状态,使得 count
值将一直维持为 0
值。
require
为了使用简便,特别是在验证输入参数是否合法时,可以使用 require
来简化判断合法性以及在非法时抛出错误:
Require.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract Require {
address owner;
function Destroy(address payable addr) public {
// 要求 owner 等于 msg.sender,否则抛出错误 Error("Not owner")
require(owner == msg.sender, "Not owner");
selfdestruct(addr);
}
}
require
接受两个参数,第一个参数为一个布尔值(通常使用一个布尔表达式),当布尔值为真是不抛出错误;当布尔值为假时,将抛出一个 Error
,Error
可以附带一个错误原因字符串。该字符串由 require
第二个参数给出。
assert 与 Panic
对于 Panic
,除了由EVM检测并触发,开发人员也可以通过 assert
触发:
Assert.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract Assert {
function add(uint a, uint b) public pure returns (uint c) {
c = a + b;
assert(c > a && c > b);
}
}
assert
只接受一个布尔值作为参数,如果为假则 Panic
。
Panic 代码
一个 Panic 错误会携带一个 Panic 代码,用于指示 Panic 类型:
- 0x00:编译器插入的 panic
- 0x01:使用 assert 触发的 panic
- 0x11:未使用
unchecked {}
包围的数字运算出现了溢出 - 0x12:除0或求余0
- 0x21:把一个负数或过大的数,转换为枚举
- 0x22:访问一个错误编码的存储区字节数组
- 0x31:对空数组调用
pop()
函数 - 0x32:数组访问下标非法
- 0x41:分配内存过大,或创建数组过大
- 0x51:调用一个未被初始化的内部函数变量
try/catch
对于外部调用以及合约创建导致的异常(Error 或 Panic),可以通过 try/catch
语句捕获:
TryCatch.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract Callee {
function callee(string calldata name) external pure returns (string memory) {
require(
keccak256(bytes(name)) == keccak256(bytes("Mathew")),
"Only Mathew can call this"
);
return "Hi, Mathew";
}
}
contract TryCatch {
function test(string calldata name) public returns (string memory) {
Callee c = new Callee();
// try 后面必须跟一个外部调用或者,或者合约创建语句
try c.callee(name) returns (string memory result) {
return result;
} catch Error(string memory reason) {
// catch Error(string memory) 用于捕获 Error 型错误
// 直接 revert(reason) 再次抛出该错误
revert(reason);
} catch Panic(uint errorCode) {
// catch Panic 用于捕获 Panic 型错误,errorCode 为 Panic 代码
revert(unicode"调用 callee 导致 Panic");
} catch (bytes memory lowLevelData) {
// catch(bytes memory lowLevelData) 用于捕获所有异常
// Panic 代码或者 Error 参数保存在 lowLevelData 中
return string(lowLevelData);
} catch {
// 与 catch(bytes memory lowLevelData) 一样用于捕获所有异常
// 但是并不关心异常附带的数据,因此没有参数
// 与 catch(bytes memory lowLevelData) 只能二选一
return "Something went wrong.";
}
}
}