22

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 3

21

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.

3
  • 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.
    – ZMitton
    Commented 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)
    – ZMitton
    Commented Sep 13, 2016 at 17:10
16

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.

2

It is the whole transaction that is reverted.

Plus, all the gas attached to the transaction (including the remaining one) goes to the miner.

1
  • 5
    Usually, the whole transaction is reverted, but you can also prevent this from happening. See the answer by Matthew.
    – chriseth
    Commented Sep 12, 2016 at 20:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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