目前中项目的代码中经常发现使用Try/Catch来捕获错误,防止让程序revert,而是让它能继续执行下去。在使用过程中,容易出现的问题是,开发人员认为try/catch能捕获所有的异常错误。从而可以处理所有的异常情况。实际情况是有两类错误捕获不到:
目前中项目的代码中经常发现使用Try/Catch来捕获错误,防止让程序revert,而是让它能继续执行下去。Solidity文档例子如下。
// 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) {
// Permanently disable the mechanism if there are
// more than 10 errors.
require(errorCount < 10);
try feed.getData(token) returns (uint v) {
return (v, true);
} catch Error(string memory /*reason*/) {
// This is executed in case
// revert was called inside getData
// and a reason string was provided.
errorCount++;
return (0, false);
} catch Panic(uint /*errorCode*/) {
// This is executed in case of a panic,
// i.e. a serious error like division by zero
// or overflow. The error code can be used
// to determine the kind of error.
errorCount++;
return (0, false);
} catch (bytes memory /*lowLevelData*/) {
// This is executed in case revert() was used.
errorCount++;
return (0, false);
}
}
}
在使用过程中,容易出现的问题是,开发人员认为try/catch能捕获所有的异常错误。从而可以处理所有的异常情况。实际情况是有两类错误捕获不到:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
// External contract used for try / catch examples
contract Foo {
address public owner;
constructor(address _owner) {
require(_owner != address(0), "invalid address");
assert(_owner != 0x0000000000000000000000000000000000000001);
owner = _owner;
}
function myFunc(uint256 x) public pure returns (string memory) {
require(x != 0, "require failed");
return "my func was called";
}
function divFunc(uint256 x) public pure{
uint256 y = 1/x;
}
}
contract Boo{
function myFunc(uint256 x) public pure {
require(x != 0, "require failed");
}
}
contract Bar {
event Log(string message);
event LogBytes(bytes data);
Foo public foo;
constructor() {
// This Foo contract is used for example of try catch with external call
foo = new Foo(msg.sender);
}
// Example of try / catch with external call
// tryCatchExternalCall(0) => Log("external call failed")
// tryCatchExternalCall(1) => Log("my func was called")
function tryCatchExternalCall(uint256 _i) public {
try foo.myFunc(_i) returns (string memory result) {
emit Log(result);
} catch {
emit Log("external call failed");
}
}
// Example of try / catch with contract creation
// tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address")
// tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("")
// tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created")
function tryCatchNewContract(address _owner) public {
try new Foo(_owner) returns (Foo foo) {
// you can use variable foo here
emit Log("Foo created");
} catch Error(string memory reason) {
// catch failing revert() and require()
emit Log(reason);
}catch Panic(uint errorCode){
//如果加了catch Panic(uint errorCode)则落入 failing assert, division by zero, invalid array access, arithmetic overflow and others,
emit Log("assert error");
}
catch (bytes memory reason) {
// catch failing assert()
emit LogBytes(reason);
}
}
function tryDiv(uint256 x) public {
try foo.divFunc(x) {
// you can use variable foo here
emit Log("div ok");
} catch Error(string memory reason) {
// catch failing revert() and require()
emit Log(reason);
}
catch Panic(uint errorCode){
//如果加了catch Panic(uint errorCode)则落入 failing assert, division by zero, invalid array access, arithmetic overflow and others,
emit Log("division by zero");
}
catch (bytes memory reason) {//其他的落入到这个catch里面,
// catch failing assert()
emit LogBytes(reason);
}
}
//ReturnDecodeError
function tryCatchExternalCallReturnDecodeError(address foo_address , uint256 _i) public {
try Foo(foo_address).myFunc(_i) returns (string memory result) {
emit Log(result);
} catch {
emit Log("external call failed");
}
}
//FunctionNotExit
function tryCatchExternalCallFunctionNotExit(address foo_address , uint256 _i) public {
try Foo(foo_address).divFunc(_i) {
emit Log("ok");
} catch (bytes memory reason) {//其他的落入到这个catch里面,
// catch failing assert()
emit LogBytes(reason);
}
}
}
TryCatchDemoTest.t.sol
```javascript
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
// External contract used for try / catch examples
contract Foo {
address public owner;
constructor(address _owner) {
require(_owner != address(0), "invalid address");
assert(_owner != 0x0000000000000000000000000000000000000001);
owner = _owner;
}
function myFunc(uint256 x) public pure returns (string memory) {
require(x != 0, "require failed");
return "my func was called";
}
function divFunc(uint256 x) public pure{
uint256 y = 1/x;
}
}
contract Boo{
function myFunc(uint256 x) public pure {
require(x != 0, "require failed");
}
}
contract Bar {
event Log(string message);
event LogBytes(bytes data);
Foo public foo;
constructor() {
// This Foo contract is used for example of try catch with external call
foo = new Foo(msg.sender);
}
// Example of try / catch with external call
// tryCatchExternalCall(0) => Log("external call failed")
// tryCatchExternalCall(1) => Log("my func was called")
function tryCatchExternalCall(uint256 _i) public {
try foo.myFunc(_i) returns (string memory result) {
emit Log(result);
} catch {
emit Log("external call failed");
}
}
// Example of try / catch with contract creation
// tryCatchNewContract(0x0000000000000000000000000000000000000000) => Log("invalid address")
// tryCatchNewContract(0x0000000000000000000000000000000000000001) => LogBytes("")
// tryCatchNewContract(0x0000000000000000000000000000000000000002) => Log("Foo created")
function tryCatchNewContract(address _owner) public {
try new Foo(_owner) returns (Foo foo) {
// you can use variable foo here
emit Log("Foo created");
} catch Error(string memory reason) {
// catch failing revert() and require()
emit Log(reason);
}catch Panic(uint errorCode){
//如果加了catch Panic(uint errorCode)则落入 failing assert, division by zero, invalid array access, arithmetic overflow and others,
emit Log("assert error");
}
catch (bytes memory reason) {
// catch failing assert()
emit LogBytes(reason);
}
}
function tryDiv(uint256 x) public {
try foo.divFunc(x) {
// you can use variable foo here
emit Log("div ok");
} catch Error(string memory reason) {
// catch failing revert() and require()
emit Log(reason);
}
catch Panic(uint errorCode){
//如果加了catch Panic(uint errorCode)则落入 failing assert, division by zero, invalid array access, arithmetic overflow and others,
emit Log("division by zero");
}
catch (bytes memory reason) {//其他的落入到这个catch里面,
// catch failing assert()
emit LogBytes(reason);
}
}
//ReturnDecodeError
function tryCatchExternalCallReturnDecodeError(address foo_address , uint256 _i) public {
try Foo(foo_address).myFunc(_i) returns (string memory result) {
emit Log(result);
} catch {
emit Log("external call failed");
}
}
//FunctionNotExit
function tryCatchExternalCallFunctionNotExit(address foo_address , uint256 _i) public {
try Foo(foo_address).divFunc(_i) {
emit Log("ok");
} catch (bytes memory reason) {//其他的落入到这个catch里面,
// catch failing assert()
emit LogBytes(reason);
}
}
}
类似的错误。在审计中发现具有不少的价值。例如最近审计的一个项目,该问题获得了2000u的奖励: https://github.com/sherlock-audit/2024-04-teller-finance/blob/defe55469a2576735af67483acf31d623e13592d/teller-protocol-v2-audit-2024/packages/contracts/contracts/TellerV2.sol#L953
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!