I want to get the revert reason of a failed call
and then use that revert reason in the same on-chain transaction. Is this possible?
[Ethereum] How to get the revert reason of a call in Solidity so that I can use it in the same on-chain transaction
contract-designcontract-developmentrevert-opcodesoliditytransactions
Related Solutions
Summary
After the Byzantium fork, eth.getTransactionReceipt(...)
will return a status
field. The status field has a value of 0
when a transaction has failed with the REVERT
opcode and 1
when the transaction has succeeded.
Update Oct 10 2017
Responding to the comment by @thomas-jay-rush, when a transaction fails for a reason other than REVERT
(i.e. THROW
), the status field will have a value of 0
and gasUsed
will be equal to gas
. See tx 0xa0a5e34b as an example.
Details
I have configured the Byzantium block number in my dev blockchain using the following genesis.json
:
{
"config": {
"homesteadBlock": 1,
"byzantiumBlock": 12
},
"nonce": "0",
"difficulty": "0x400",
"mixhash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x3B4A1B44",
"alloc": {
"0xa00af22d07c87d96eeeb0ed583f8f6ac7812827e": {
"balance": "10000000000000000000000000"
},
"0xa11aae29840fbb5c86e6fd4cf809eba183aef433": {
"balance": "10000000000000000000000000"
}
}
}
I can see the Byzantium fork is active in the following geth
message:
INFO [10-09|23:30:37] Initialised chain configuration config="{ChainID: Homestead: 1 DAO: DAOSupport: false EIP150: EIP155: EIP158: Byzantium: 12 Engine: unknown}"
Failing Transactions Have A Transaction Receipt status
Of 0x0
Here is the transaction receipt information from a failing transaction with the REVERT
opcode:
> eth.getTransactionReceipt("0x6c039439191ed046f8d4a92734bb6c23b92c175df39ce2f089ac84c590b94918")
{
blockHash: "0xf25b938c85772aa0abbbc9931e6da7a6e46c577619fb5004f2d85fdbe359eb60",
blockNumber: 89,
contractAddress: null,
cumulativeGasUsed: 35983,
from: "0xa11aae29840fbb5c86e6fd4cf809eba183aef433",
gasUsed: 35983,
...
status: "0x0",
to: "0x0e946b999033257976aa5cbe0e3530618ca1582d",
transactionHash: "0x6c039439191ed046f8d4a92734bb6c23b92c175df39ce2f089ac84c590b94918",
transactionIndex: 0
}
The same status: "0x0"
is returned when executing a throw
instruction compiled with Solidity 0.4.11 .
Successful Transactions Have A Transaction Receipt status
Of 0x1
And here is the transaction receipt information from a successful transaction:
> eth.getTransactionReceipt("0xbdacfebf6299a260adfa993421aeb26353aa2b4dd0e6f290d2acfd6a9b828a62")
{
blockHash: "0xbda8b2d2850306e8a02be074f11ffe4f2243003df6e9c4adb1bbdcda1faf2e42",
blockNumber: 87,
contractAddress: null,
cumulativeGasUsed: 126952,
from: "0xa11aae29840fbb5c86e6fd4cf809eba183aef433",
gasUsed: 126952,
logs: [{
address: "0x0e946b999033257976aa5cbe0e3530618ca1582d",
blockHash: "0xbda8b2d2850306e8a02be074f11ffe4f2243003df6e9c4adb1bbdcda1faf2e42",
blockNumber: 87,
data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
logIndex: 0,
removed: false,
topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000a33a6c312d9ad0e0f2e95541beed0cc081621fd0", "0x000000000000000000000000a55a151eb00fded1634d27d1127b4be4627079ea"],
transactionHash: "0xbdacfebf6299a260adfa993421aeb26353aa2b4dd0e6f290d2acfd6a9b828a62",
transactionIndex: 0
}, {
...
}],
...
status: "0x1",
to: "0x0e946b999033257976aa5cbe0e3530618ca1582d",
transactionHash: "0xbdacfebf6299a260adfa993421aeb26353aa2b4dd0e6f290d2acfd6a9b828a62",
transactionIndex: 0
}
eth.getTransactionReceipt(...)
pre-Byzantium fork does not include the status
field.
Can The Status Be Anything Other Than Than Just 0 (Fail) And 1 (Success)?
From https://github.com/ethereum/go-ethereum/blob/master/core/types/receipt.go#L39-L42:
ReceiptStatusFailed = uint(0)
ReceiptStatusSuccessful = uint(1)
There is only be a status of failed (=0) or successful (=1).
See EIP 658: Embedding transaction return data in receipts for further information.
How Does EtherScan Currently Handle The Byzantium Fork Status On Ropsten?
Here's a transaction that is successfully executed - 0x142b8830
And here's a transaction that has failed using the REVERT
opcode - 0x67a5f644
The failed transaction cannot be distinguished from the successful transaction in EtherScan currently, except that the failed transaction consumed less gas than the successful transaction, and the failed transaction did not log an event but the successful transaction did.
I've pinged EtherScan about this potential issue.
Update Oct 15 2017 There is now a TxReceipt Status: field on the EtherScan transaction page that displays Success or Fail.
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();
}
Related Topic
- [Ethereum] Web3 transaction to a contract fails with revert. Same transaction works from truffle console
- [Ethereum] How to get transaction failed reason with transaction hash with web3
- [Ethereum] How to catch solidity contract revert message using web3j
- [Ethereum] forward revert message from low level solidity call
- [Ethereum] Revert reason for arithmetic overflows in Solidity v0.8
Best Answer
I modified shanes answer:
It seems to work and that way we dont need the extra library