4

There is solidity code that fails here:

error InvalidCheck(uint32 _check);

modifier someCheck(uint32 _check) {
  if (check != _check) revert InvalidCheck(check);
  _;
}

My frontend application fails on this check and doesn't provide any context about the error.

"message: 'cannot estimate gas; transaction may fail or may r…UNPREDICTABLE_GAS_LIMIT"

When my contract fails due to it being paused:

require(!paused(), "Pausable: paused");

This provides the "Pausable: paused:" message to my frontend.

Is using require the solution if I want to display error context to the frontend?

1
  • What version of ethers js are you using and the backend/node? Can you also pls post the entire error? If using geth, it includes the error data in the response and ethers js parses it under error.error, can you once check? May 25, 2022 at 12:02

4 Answers 4

1

Require and revert are both fine, but what you can do is a callStatic (previously 'staticCall') to try the transaction and then handle the error however you'd like.

contract.transfer(someAddress, someValue).then((tx) => {
    return tx.wait().then((receipt) => {
        // This is entered if the transaction receipt indicates success
        return true;
    }, (error) => {
        // This is entered if the status of the receipt is failure
        return error.callStatic().then((error) => {
            console.log("Error", error);
            return false;
        });
    }
});
1
+50

You can use the parseError method from your contract's interface. Read the docs for better understanding about Interface parsing

Using your contract's interface, you can use the parseError method and pass it the error's data. In the following example, the data is found in error.data.data but this can be different on different RPCs as stated in this comment on github: https://github.com/ethers-io/ethers.js/discussions/3027#discussioncomment-4233143. Although I have no experience with Avalanche tho, so don't take my word for it.

Here's how you can do it:

try {
  await myContract.someMethod();
} catch (error) {
  const revertData = error.data.data;
  const decodedError = myContract.interface.parseError(revertData);
  console.log(`Transaction failed: ${decodedError.name}`);
}
0

Revert undoes all state changes made during the current call, including any state changes made by called functions. It also provides a means for the calling function to determine the reason for the revert.

Require only undoes the state changes made in the current function and does not provide a way to communicate the reason for the failure.

Your usage will depend on requirements. Here is how I would handle it:

try {
  // call contract function that may throw an error
  await contract.someFunction();
} catch (error) {
  // check if the error is an InvalidCheck error
  if (error.message.includes("InvalidCheck")) {
    // display the error message to the user 
console.log(error.message);
  } else {
// handle other errors
  }
}
0

Lets test it.

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;


contract Test {
    uint32 check;
    error InvalidCheck(uint32 _check);
    error StringError(string why);

    modifier someCheck(uint32 _check) {
        if (check != _check) revert InvalidCheck(check);
        _;
    }
    function revertDirect() public {
        revert("revertDirect");
    }
    function stringError() public {
        revert StringError("it's string error");
    }
    function revertIt(uint32 _check) someCheck(_check) public {
    }
    function requireIt() public {
        revert("it's required");
    }
}

Result:
revert with InvalidCheck:reason: 'execution reverted: . Innermost error is at 0xbe45…c3d2: Vm reverted. .'

require: reason: "execution reverted: : it's required. Innermost error is at 0xbe45…c3d2: Vm reverted. it's required."

revert with StringError: reason: 'execution reverted: . Innermost error is at 0xbe45…c3d2: Vm reverted. .'

revertDirect: reason: 'execution reverted: : revertDirect. Innermost error is at 0xbe45…c3d2: Vm reverted. revertDirect.'

Conclusion: Using revert(message) or require(condition, message) could expose reason, using revert(Customer Error) could not.

Not the answer you're looking for? Browse other questions tagged or ask your own question.