5

See this transaction on etherscan:

https://etherscan.io/tx/0xd20a95a9f051d6961ca766a9b2d0f18b6afdf6d8465931a3f70f2c4b1c023688

Notice the odd note saying some part of that transaction failed but the transaction itself succeeded.

First: why did this happen?

Second: Does the new post-Byzantium transaction receipt status field still exhibit this behavior.

In other words, can a transaction's receipt have a '1' in its status field but still have had one of its internal calls fail?

2

If you call smart contract A, and it calls smart contract B, and smart contract B fails, it is up to smart contact A how to handle that. In most reasonable cases, the only sane action is for smart contact A to abort (and this is the default in Solidity), but it doesn't have to, and can continue, and complete successfully (for its definition of successfully).

The status field is always the status of the outermost contract - the one you called. If it made contract calls that failed, but it continued, the status will be 1.

  • 1
    Do me a favor? For future readers, change the last sentence to say “...the status will be 1, which means success.” It would just be more clear. Another thing that might improve the answer would be example code of the calling contract even if only a few lines that would show the call and how the error return is handled. Thanks. – Thomas Jay Rush Jun 9 '18 at 9:49
0

can a transaction's receipt have a '1' in its status field but still have had one of its internal calls fail?

Yes.

I tend to agree with James:

In most reasonable cases, the only sane action is for smart contact A to abort.

But, the alternative exists.

If the Outer contract uses .send() it continues after inner fails. The compiler raises a warning if the result isn't checked, but carrying on is possible. This sort of thing almost always adds undesirable complexity to smart contracts, so don't do it unless you really know what you're doing.

function soldierOn() public {
  bool success = target.send(amount);
  if(success) {
    // do something
  } else {
    // do something else. 
    // Outer is still executing and can succeed (by its definition of success).
  }

call() returns (bool success, bytes memory response). It's possible to carry on. This requires a departure from Solidity's more natural syntax and default behaviour.

pragma solidity 0.5.11;

contract Outer {

    Inner inner;

    constructor(Inner _inner) public {
        inner = _inner;
    }

    /**
     * Pass in false to make Inner to fail.
     */

    function explored(bool succeed) public returns(bool success, bytes memory response) {
        // instead of inner.tryIt(succeed) that would make everything fail if Inner fails ... 
        (success, response) = address(inner).call(abi.encodeWithSignature("tryIt(bool)", succeed));
    }        
}

contract Inner {

    event LogActivity(address sender, bool successful);

    function tryIt(bool succeed) public returns(bool result) {
        emit LogActivity(msg.sender, succeed);
        require(succeed, "You wanted me to fail.");
        return true;
    }      
}

Again, just to reinforce the point for others who might find this. There are other reasons for using call(), e.g. managing gas or value transfer, and the right thing to do is almost always to just abort if the Inner contract complains.

(success, response) = address(inner).call(abi.encodeWithSignature("tryIt(bool)", succeed));
require(success, "Inner failed, so we are aborting.");

Hope it helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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