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.
Best Answer
There are two aspects to consider when choosing between
assert()
andrequire()
1. Gas efficiency
assert(false)
compiles to0xfe
, which is an invalid opcode, using up all remaining gas, and reverting all changes.require(false)
compiles to0xfd
which is theREVERT
opcode, meaning it will refund the remaining gas. The opcode can also return a value (useful for debugging), but I don't believe that is supported in Solidity as of this moment. (2017-11-21)2. Bytecode analysis
From the docs (emphasis mine)
The above excerpt is a reference to the still (as of 2017-11-21) experimental and undocumented
SMTChecker
.I use a few heuristics to help me decide which to use.
Use
require()
to:ie. use
require(external.send(amount))
owned
contract situationrequire
more often,Use
assert()
to:assert
less oftenBasically,
assert
is just there to prevent anything really bad from happening, but it shouldn't be possible for the condition to evaluate to false.Historical note:
The
require()
andassert()
functions were added to Solidity prior to the Byzantium fork, inv0.4.10
. Prior to Byzantium, they behaved identically, but already compiled to different opcodes. This meant that some contracts deployed before Byzantium behaved differently after the fork, the main difference being that began refunding unused gas.