argotorg / argotorg/solidity

Unable to use `try`/`catch` to catch local reverts in extra code generated for high-level external calls

Open
#13,869 8 comments 1 reaction 0 assignees View on GitHub
high effort high impact language design :rage4: must have needs design
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.