目前 Solidity 0.8.x 版本有三种方式处理异常:revert require assert。
error 是 solidity 0.8.4 版本新加的内容,必须搭配 revert 命令使用。相比字符串错误信息,自定义 error 更加节省 gas,这是因为:
error CustomError();
error CustomErrorWithMessage(string message);
revert();
revert("Error message");
revert CustomError();
revert CustomErrorWithMessage("Error message");
require(num > 0);
require(num > 0, "Error message");
assert 函数创建了一个 Panic(uint256) 类型的错误。正确运行的代码不应该创建一个 Panic 异常,甚至在无效的外部输入时也不应该。因此 assert 应该只用于测试内部错误,以及检查变量值。
assert(address == 0x1111111111111111111111111111111111111111);
Error 是业务层面的错误,通常也会作为业务的一部分,在编程的过程中进行分支判断和处理。
Panic 是程序层面的错误,在编程阶段可能无法预知,通常在编译阶段发现并得以修复。
当我们与外部合约进行交互,并且希望捕获外部合约抛出的异常时,可以使用 try catch 语句。但对于 send 和低级别函数 call delegatecall staticcall,它们的错误无法被捕获,因为在发生异常时,false 作为第一个参数被返回,而不是“冒泡”。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.1;
interface DataFeed { function getData(address token) external returns (uint value); }
contract FeedConsumer {
DataFeed feed;
uint errorCount;
function rate(address token) public returns (uint value, bool success) {
// 如果有10个以上的错误,就永久停用该机制。
require(errorCount < 10);
try feed.getData(token) returns (uint v) {
return (v, true);
} catch Error(string memory /*reason*/) {
// 如果在getData中调用revert,并且提供了一个原因字符串,则执行该命令。
errorCount++;
return (0, false);
} catch Panic(uint /*errorCode*/) {
// 在发生Panic异常的情况下执行,错误代码可以用来确定错误的种类。
errorCount++;
return (0, false);
} catch (bytes memory /*lowLevelData*/) {
// 在使用revert()的情况下,会执行这个命令。
errorCount++;
return (0, false);
}
}
}
returns (uint v) {...}
可选,正确调用外部函数时,程序会进入该分支,并返回外部函数的返回值catch Error(string memory /*reason*/) {...}
如果错误由 Error 异常引起的,且带有错误信息,这个子句将被运行catch Panic(uint /*errorCode*/) {...}
如果错误由 Panic 异常引起的,这个子句将被运行catch (bytes memory /*lowLevelData*/) {...}
如果错误签名与其他子句不匹配, 或者在解码错误信息时出现了错误,或者没有与异常一起提供错误数据, 那么这个子句就会被执行。在这种情况下,声明的变量提供了对低级错误信息的访问catch {...}
如果您对错误数据不感兴趣,您可以直接使用该语句(甚至作为唯一的catch子句)来代替前面的子句为了捕捉所有的错误情况,您至少要有 catch {...}
或 catch (bytes memory lowLevelData) {...}
子句。