Unable to use `try`/`catch` to catch local reverts in extra code generated for high-level external calls
- Dominant language
- C++
- Stars
- 25.7k
- Forks
- 6.2k
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 29
Description
How can we call an external code, and reliably catch errors?
Consider the following code, which tries to create "`safeBalance`" method, which calls balanceOf and never reverts.
(spoiler: `try/catch` doesn't catch a lot of cases)
- It returns a proper balance of an external token
- It properly catches revert in the external balanceOf method
But....
- it **crashes** if calling non-existent address (e.g. `address(0)`)
- it **crashes** if the target contract doesn't have that method.
- it **crashes** if the target contract returns wrong number of arguments.
So basically, we **can't** rely on try/catch ...
The only alternative is to resort to low-level call (`address.call()`) and manually parse the result - in `realSafeBalance()`
This solution is error-prone, type-unsafe and more expensive in its gas usage.
```solidity
pragma solidity ^0.8.17;
//SPDX-License-Identifier: MIT
interface IERC20 {
function balanceOf(address) external returns (uint);
}
contract ATestSafeBalance {
event Debug(uint bal);
constructor () {
IERC20 a;
// a = IERC20(address(this));
// a = IERC20(address(0));
// a = new Token();
// a = new RevertToken();
a = IERC20(address(new NoReturnValue()));
uint bal = pseudoSafeBalance(a,address(this));
emit Debug(bal);
}
function pseudoSafeBalance(IERC20 token, address addr) public returns (uint) {
try token.balanceOf(addr) returns (uint ret) {
return ret;
}
catch {
return 11111;
}
}
function realSafeBalance(IERC20 token, address addr) public returns (uint retBalance) {
(bool success, bytes memory ret) = address(token).call(abi.encodeCall(IERC20.balanceOf, addr));
if (!success || ret.length != 32) return 11111;
(retBalance) = abi.decode(ret, (uint));
}
}
contract NoReturnValue {
function balanceOf(address) external {
}
}
contract RevertToken is IERC20 {
function balanceOf(address) external override returns (uint) {
revert("just because");
}
}
contract Token is IERC20 {
function balanceOf(address) external override returns (uint) {
return 1;
}
}
```
Contributor guide
Research direction
Start by compiling and running the Solidity 0.8.17 example, comparing pseudoSafeBalance with realSafeBalance for the listed target-contract cases. Trace the compiler-generated code for the high-level external call and its try/catch handling; done means the intended failure cases are reliably caught without requiring the low-level-call workaround.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- solidity
- Domain
- blockchain, compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100