Reverts

Reverts occur when a transaction or call fails for any reason. In the case of EVM networks, reverts result in funds being returned to the sender (besides network-fees) and contract state changes unwinding. Typically, in smart-contracts, user-defined reverts occur from assert statements in Vyper and require statements in Solidity.

Here is a Vyper example of an assert statement:

assert msg.sender == self.owner, "!authorized"

The string "!authorized" after the assertion is the revert-message that gets forwarded to the user.

In solidity, a require statement looks like:

require(msg.sender == owner, "!authorized");

In Ape, reverts automatically become Python exceptions. When interacting with a contract and encountering a revert, your program will crash and you will see a stacktrace showing you where the revert occurred. For example, assume you have contract instance variable contract with a Vyper method called setNumber(), and it reverts when the user is not the owner of the contract. Calling it may look like:

receipt = contract.setNumber(123, sender=not_owner)

And when it fails, Ape shows a stacktrace like this:

  File "$HOME/ApeProjects/ape-project/scripts/fail.py", line 8, in main
    receipt = contract.setNumber(5, sender=not_owner)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "$HOME/ApeProjects/ape-project/contracts/VyperContract.vy", line 98, in 
setNumber
    assert msg.sender == self.owner, "!authorized"
    ^^^^^^^^^^^^^^^^^^^^^^^

ERROR: (ContractLogicError) !authorized

One way to handle exceptions is to simply use try: / except: blocks:

from ape.exceptions import ContractLogicError

try:
    receipt = contract.setNumber(123, sender=not_owner)
except ContractLogicError as err:
    receipt = None
    print(f"The transaction failed: {err}")
# continue on!

If you wish to allow reverts without having Ape raise exceptions, use the raise_on_revert=False flag:

>>> receipt = contract.setNumber(123, sender=not_owner, raise_on_revert=False)
>>> receipt.failed
True
>>> receipt.error
ContractLogicError('!authorized')

Dev Messages

Dev messages allow smart-contract authors to save gas by avoiding revert-messages. If you are using a provider that supports tracing features and a compiler that can detect dev messages, and you encounter a revert without a revert-message but it has a dev-message, Ape will show the dev-message:

assert msg.sender == self.owner  # dev: !authorized"

And you will see a similar stacktrace as if you had used a revert-message.

In Solidity, it might look like this:

require(msg.sender == owner);  // @dev !authorized

Custom Errors

As of Solidity 0.8.4, custom errors have been introduced to the ABI. In Ape, custom errors are available on contract-instances. For example, if you have a contract like:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;

error Unauthorized(address unauth_address);

contract MyContract {
    address payable owner = payable(msg.sender);
    function withdraw() public {
        if (msg.sender != owner)
            revert Unauthorized(msg.sender);
        owner.transfer(address(this).balance);
    }
}

And if you have an instance of this contract assigned to variable contract, you can reference the custom exception by doing:

contract.Unauthorized

When invoking withdraw() with an unauthorized account using Ape, you will get an exception similar to those from require() statements, a subclass of ContractLogicError:

contract.withdraw(sender=hacker)  # assuming 'hacker' refers to the account without authorization.

Built-in Errors

Besides user-defined ContractLogicErrors, there are also builtin-errors from compilers, such as bounds-checking of arrays or paying a non-payable method, etc. These are also ContractLogicError sub-classes. Sometimes, compiler plugins such as ape-vyper or ape-solidity export these error classes for you to use.

from ape import accounts, Contract
from ape_vyper.exceptions import FallbackNotDefinedError

my_contract = Contract("0x...")
account = accounts.load("test-account")

try:
    my_contract(sender=account)
except FallbackNotDefinedError:
    print("fallback not defined")

Next, learn how to test your contracts’ errors using the ape.reverts context-manager in the testing guide.