I have a contract that calls another contract that calls another. If there is a throw (or out-of-gas error) that occurs somewhere in the second contract, does the entire transaction get reverted or is it just the stuff in the second and third contract that does, while the execution from the first contract still stands?
3 Answers
In Solidity, by default, yes.
On the EVM-level, a throw (bad jump, out-of-gas, or any other exception) only reverts the call it is inside. Solidity helpfully continues the exception down the stack until everything is undone.
It is possible, using lower-level code (specifically, address.call()), to prevent this. Here is an example of this being used as an improvised try-catch construct.
-
If I make a call within my contract, to an unknown contract, I can still be sure the unknown contract does not have the ability to leave my contract in an intermediate state.– ZMittonCommented Sep 12, 2016 at 21:33
-
That's correct. Unless you deliberately use address.call(), the exception will propagate downwards automatically. That said, you may wish to investigate the situation in depth. An unknown contract can later call your contract again (the reentrancy attack), and your contract will be for that moment in an intermediate state. Commented Sep 13, 2016 at 16:40
-
I have to use address.call() because it's an 'unknown' contract, but it looks like I'm still safe from my code being left half done. If they throw, the only options are: 1 total revert. Or 2 the call from address.call() is reverted and my code continues as if nothing happened. (Ignoring reentrancy which I've protected myself against in other ways)– ZMittonCommented Sep 13, 2016 at 17:10
To complement @Matthew's answer, it depends on how the call is made in Solidity.
If C
calls D.foo()
, and foo
does a throw
, then yes the entire transaction is reverted.
If C
does a "lower-level raw call" like D.call(bytes4(sha3('foo()')))
, and foo
does a throw
, then only foo
and its subcalls are reverted. This is because a raw call does not propagate any exceptions: a raw call like D.call
only returns a boolean which indicates if the call succeeded or encountered an exception.
More details
In Solidity, a throw
causes an exception by generating bytecode that leads to an invalid jump destination. (For the other cases, see All cases when Solidity compiles to invalid jump destination.)
From the Solidity perspective, an exception that is not swallowed or is uncaught/unhandled, will cause the entire transaction to be reverted.
There is currently no way to catch an exception in Solidity, thus an exception, such as throw
, will cause the entire transaction to be reverted.
However, raw calls "swallow" exceptions: raw calls don't propagate exceptions and that's why only the subcalls will be reverted.
It is the whole transaction that is reverted.
Plus, all the gas attached to the transaction (including the remaining one) goes to the miner.
-
5Usually, the whole transaction is reverted, but you can also prevent this from happening. See the answer by Matthew.– chrisethCommented Sep 12, 2016 at 20:22