When you throw
an exception, all gas is consumed. What is the rationale for this design decision?
4 Answers
All gas is consumed because the EVM essentially only has 1 exception: Out of Gas.
To see this clearer, take a look at the difference between a "pure" exception, and an error due to bad/buggy/invalid EVM code. Out of Gas is the former. Now there are errors such as stack underflow, invalid JUMP, and invalid opcode: they can be called "exceptions" but they are more like errors than an EVM exception, because the developer of the contract has not written valid EVM code according to the rules of the EVM. For example, an invalid opcode is like writing "class" instead of "function" in Javascript: native Javascript does not have "class", unlike a language like Java. With stack underflow, it's like the developer telling the EVM to add 2 numbers, but the developer does not provide the 2nd number.
Note also how "exception" in the EVM, is different from other languages. For example, there is no divide by zero exception in the EVM (though Solidity 0.4+ will generate an exception for this). Assuming valid EVM code, there are no exceptions except for Out of Gas.
Since there's only an Out of Gas exception, these other errors were made to behave as Out of Gas. This is relatively consistent with some Ethereum design principles of generalization and minimizing complexity. In other words, when implementing the behavior of stack underflow, invalid JUMP, and invalid opcode, it's simpler and easier for the protocol to reuse the implementation and behavior of Out of Gas.
Other answers have explained how Solidity compiles throw
to an invalid JUMP. (Serpent instead uses an invalid opcode, and calls it invalid
instead of throw
.)
EDIT: The Byzantium release includes EIP 140, a new opcode named REVERT
in the EVM that:
provides a way to stop execution and revert state changes, without consuming all provided gas and with the ability to return a reason.
throw
is being deprecated in favor of revert()
, require()
, assert()
-
class
is a valid keyword in javascript used to define a class.– 0xcaffCommented Jul 22, 2017 at 17:53 -
@0xcaff Thanks, yes, JavaScript is evolving too and
class
added in ECMAScript 2015 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… (still takes time for ECMA standards to become widely known and adopted).– eth ♦Commented Jul 24, 2017 at 22:00
The Solidity throw
instruction gets compiled to an invalid JUMP instruction (i.e. a jump to an invalid location).
The section 9.4.2 of the Yellow Paper explains how exceptional halting works, in chapter 8 it explains that no gas is refunded in this case.
In chapter 8 it states:
Just as with contract creation, if the execution halts in an exceptional fashion (i.e. due to an exhausted gas supply, stack underflow, invalid jump destination or invalid instruction), then no gas is refunded to the caller and the state is reverted to the point immediately prior to balance transfer (i.e. σ).
And referring back to chapter 7:
If such an exception does not occur, then the remaining gas is refunded to the originator and the now-altered state is allowed to persist.
As to why it has been decided this way I have no information about.
In part I believe this was a security decision to ensure spamming the network with bogus transactions cost the sender gas. For instance, if gas was refunded after a throw, a malicious actor could create a contract that consumed some gas and then would throw. That actor could then send one or many transactions with a high gas price and a lot of gas, making their transactions take priority under default miner terms. Their transactions could take up all the gas in each block so no other transactions could go through, and if the gas was returned to them, they could do this for free indefinitely. With gas being consumed, this would cost them a good deal of ether.
The throw
instruction is a EVM hack that reverts all changes made by current contract execution. It exploits exceptional termination of EVM which also consumes all gas.
-
4That doesn't explain the rationale for why it is designed that way. Many examples in the Solidity documentation use
throw
as if it's the best way to do things. But it clearly isn't if it costs callers the maximum fee. Commented Mar 25, 2016 at 17:06 -
1By your explanation, it should cost at most as much gas as has already been consumed in the transaction up until the point that the exception occurred (a naive implementation might "undo" each operation one at at time). Commented Mar 25, 2016 at 20:47
-
2This would require adding a system to track all state changes, including those in calls to other contracts, which is not realistically possible Commented Jan 25, 2017 at 22:03