Solidity异常
异常
error是solidity 0.8.4版本新加的内容,方便且高效(省gas)地向用户解释操作失败的原因,同时还可以在抛出异常的同时携带参数,帮助开发者更好地调试。人们可以在contract之外定义异常。下面,我们定义一个TransferNotOwner异常,当用户不是代币owner的时候尝试转账,会抛出错误:
error TransferNotOwner(); // 自定义error
我们也可以定义一个携带参数的异常,来提示尝试转账的账户地址
error TransferNotOwner(address sender); // 自定义的带参数的error
在执行当中,error必须搭配revert(回退)命令使用。
function transferOwner1(uint256 tokenId, address newOwner) public {
if(_owners[tokenId] != msg.sender){
revert TransferNotOwner();
// revert TransferNotOwner(msg.sender);
}
_owners[tokenId] = newOwner;
}
我们定义了一个transferOwner1()函数,它会检查代币的owner是不是发起人,如果不是,就会抛出TransferNotOwner异常;如果是的话,就会转账。
Require
require命令是solidity 0.8版本之前抛出异常的常用方法,目前很多主流合约仍然还在使用它。它很好用,唯一的缺点就是gas随着描述异常的字符串长度增加,比error命令要高。使用方法:require(检查条件,"异常的描述"),当检查条件不成立的时候,就会抛出异常。
我们用require命令重写一下上面的transferOwner函数:
function transferOwner2(uint256 tokenId, address newOwner) public {
require(_owners[tokenId] == msg.sender, "Transfer Not Owner");
_owners[tokenId] = newOwner;
}
Assert
assert命令一般用于程序员写程序debug,因为它不能解释抛出异常的原因(比require少个字符串)。它的用法很简单,assert(检查条件),当检查条件不成立的时候,就会抛出异常。
我们用assert命令重写一下上面的transferOwner函数:
function transferOwner3(uint256 tokenId, address newOwner) public {
assert(_owners[tokenId] == msg.sender);
_owners[tokenId] = newOwner;
}
try-catch
在solidity中,try-catch只能被用于external函数或创建合约时constructor(被视为external函数)的调用。基本语法如下:
try externalContract.f() {
// call成功的情况下 运行一些代码
} catch {
// call失败的情况下 运行一些代码
}
其中externalContract.f()是某个外部合约的函数调用,try模块在调用成功的情况下运行,而catch模块则在调用失败时运行。
同样可以使用this.f()来替代externalContract.f(),this.f()也被视作为外部调用,但不可在构造函数中使用,因为此时合约还未创建。
如果调用的函数有返回值,那么必须在try之后声明returns(returnType val),并且在try模块中可以使用返回的变量;如果是创建合约,那么返回值是新创建的合约变量。
try externalContract.f() returns(returnType val){
// call成功的情况下 运行一些代码
} catch {
// call失败的情况下 运行一些代码
}
另外,catch模块支持捕获特殊的异常原因:
try externalContract.f() returns(returnType){
// call成功的情况下 运行一些代码
} catch Error(string memory reason) {
// 捕获失败的 revert() 和 require()
} catch (bytes memory reason) {
// 捕获失败的 assert()
}