10

I run a geth/parity nodes for broadcasting user's transactions to Ethereum blockchain. Some of contract functions provide reasons for revert (see examble below):

contract Example {
  function test (uint i) {
    require(i == 1, "ERROR_CODE")
  }
}

As I understand it is not possible to get revert reason string with eth_getTransactionReceipt. However I still need to get revert reason for failed transactions.

How to get revert reason ("ERROR_CODE" in example above) for past failed transactions? (at least for transactions in last 20 blocks)

5

Using eth_Call to execute function:

Example.test(0x0000000000000000000000000000000000000000000000000000000000000002) 

of Example contract we will receive following reply from geth node:

0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a4552524f525f434f444500000000000000000000000000000000000000000000

Which gives following output arguments:

arg0: 0x08c379a
arg1: 0x20
arg2: 0x0a
arg3: 0x4552524f525f434f444500000000000000000000000000000000000000000000

where arg3 == 'ERROR_CODE' (utf8 + hex encoded)

So, I suppose, for fully syched node we can use eth_Call with explicit indication of failed transaction block number:

{
  method: "eth_Call",
  params: [
    {
      from: '0x...address',
      to: '0x...contract',
      value: '0x0',
      data: "0x29e99f070000000000000000000000000000000000000000000000000000000000000002"
    }, 
    FALED_TRANSACTION_BLOCK_NR
  ]
}

However this is not convenient way of getting error codes.. Is there any other way?

| improve this answer | |
  • I can't do the edit because it's too short, but notice that arg0 is actually 0x08c379a0 (notice the trailing 0), which represents the 8 first bytes of the hash of Error(string). – Franco Victorio Feb 28 at 1:36
4

In solidity 0.4.22 the require and revert reason were added. As can be seen here, they are abi-encoded as if it were a call to a function "Error(string)".

This blog post gives an example: an eth_call to a function

function myFunction(uint256 input) public view returns (uint256) {
    require(input >= 5, "myFunction only accepts arguments which are greather than or equal to 5");
    return input * input - 25;
}

with an invalid input argument (less than 5 in this example), will return

0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000476d7946756e6374696f6e206f6e6c79206163636570747320617267756d656e747320776869636820617265206772656174686572207468616e206f7220657175616c20746f203500000000000000000000000000000000000000000000000000

which is

0x08c379a0                                                       // Function selector
0000000000000000000000000000000000000000000000000000000000000020 // Offset of string return value
0000000000000000000000000000000000000000000000000000000000000047 // Length of string return value (the revert reason)
6d7946756e6374696f6e206f6e6c79206163636570747320617267756d656e74 // first 32 bytes of the revert reason
7320776869636820617265206772656174686572207468616e206f7220657175 // next 32 bytes of the revert reason
616c20746f203500000000000000000000000000000000000000000000000000 // last 7 bytes of the revert reason

So decoding the returned string will give you the revert reason.

With Web3j this could be done like:

public Optional<String> getRevertReason(EthCall ethCall) {
    String errorMethodId = "0x08c379a0"; // Numeric.toHexString(Hash.sha3("Error(string)".getBytes())).substring(0, 10)
    List<TypeReference<Type>> revertReasonTypes = Collections.singletonList(TypeReference.create((Class<Type>) AbiTypes.getType("string")));

    if (!ethCall.hasError() && ethCall.getValue() != null && ethCall.getValue().startsWith(errorMethodId)) {
        String encodedRevertReason = ethCall.getValue().substring(errorMethodId.length());
        List<Type> decoded = FunctionReturnDecoder.decode(encodedRevertReason, revertReasonTypes);
        Utf8String decodedRevertReason = (Utf8String) decoded.get(0);
        return Optional.of(decodedRevertReason.getValue());
    }
    return Optional.empty();
}
| improve this answer | |
2

After being really annoyed for a long time I wrote a bash script to fetch the revert reason from geth by a tx hash:

https://gist.github.com/gluk64/fdea559472d957f1138ed93bcbc6f78a

| improve this answer | |
  • this is really helpful, could you also help me do the same for metamask failed transactions – cryptoKTM Oct 7 '19 at 6:41
0

It is also possible with go-ethereum. Tested with a simulated blockchain backend.

import (
    "bytes"
    "context"
    "math/big"

    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/accounts"
    "github.com/ethereum/go-ethereum/accounts/abi"
    "github.com/ethereum/go-ethereum/accounts/abi/bind"
    "github.com/ethereum/go-ethereum/accounts/keystore"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/pkg/errors"
)

// other code using the following...

func errorReason(ctx context.Context, b ethereum.ContractCaller, from common.Address, tx *types.Transaction, blockNum *big.Int) (string, error) {
    msg := ethereum.CallMsg{
        From:     from,
        To:       tx.To(),
        Gas:      tx.Gas(),
        GasPrice: tx.GasPrice(),
        Value:    tx.Value(),
        Data:     tx.Data(),
    }
    res, err := b.CallContract(ctx, msg, blockNum)
    if err != nil {
        return "", errors.Wrap(err, "CallContract")
    }
    return unpackError(res)
}

var (
    errorSig            = []byte{0x08, 0xc3, 0x79, 0xa0} // Keccak256("Error(string)")[:4]
    abiString, _        = abi.NewType("string", "", nil)
)

func unpackError(result []byte) (string, error) {
    if !bytes.Equal(result[:4], errorSig) {
        return "<tx result not Error(string)>", errors.New("TX result not of type Error(string)")
    }
    vs, err := abi.Arguments{{Type: abiString}}.UnpackValues(result[4:])
    if err != nil {
        return "<invalid tx result>", errors.Wrap(err, "unpacking revert reason")
    }
    return vs[0].(string), nil
}

After a transaction failed, you usually hold a *types.Receipt from bind.WaitMined, which has the field BlockNumber, so you know with which block number to call errorReason.

| improve this answer | |
0

I created an NPM package to handle this: eth-revert-reason


It is difficult to decode the revert reason in a general manner. Many different factors, such as Geth vs. Parity, web3.js vs ethers, etc. will result in different results for all the answers posted here. Some issues are:

  1. For a Kovan transaction, you need a custom provider that exposes Parity trace methods.
  2. Transactions may result in different messages depending on the context of the block they are called from. Because of this, you may need to be running a full-archival node in order to retrieve the correct error message.

In a happy-path case, the code to retrieve the revert reason is:

const provider = customProvider || ethers.getDefaultProvider(network)
const tx = await provider.getTransaction(txHash)
const code = await provider.call(tx)

While the code to produce the revert reason in the normal case is simple, the non-standard cases are surprisingly difficult to handle, however this package attempts to appease this.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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