Skip to content

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

Open
@drortirosh

Description

@drortirosh

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.

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;
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    high effortA lot to implement but still doable by a single person. The task is large or difficult.high impactChanges are very prominent and affect users or the project in a major way.language design :rage4:Any changes to the language, e.g. new featuresmust haveSomething we consider an essential part of Solidity 1.0.needs designThe proposal is too vague to be implemented right away

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions