5

I added a check to make sure when I send ether that if it fails, I attempt to abort the transaction using a throw. Per example here: Solidity Exceptions

When make a call to a function that does this throw from another contract and the throw fails, all gas is used up on that source contract's gas budget. (gasUsed == gasSent in the transaction receipt). This happens no matter how much gas I send.

The throw is inside a complicated loop (business logic), but it's just like the example cited above:

// inside complicated business logic I wish to roll back if this fails
if (a.send(remittance) == false) {
    throw;
}

The code that is making the call from another contract looks like this. I'll note there's only one callback currently installed:

for(uint i = 0;i < callbacks.length; i++) {
    callbacks[i].oracleCallback(_value, _time, _info);
}

I'm using a fairly recent build of the master branch of geth and a fairly recent build (early January) of solidity.

Here's the transaction receipt (private test server):

{"transactionHash":"0x61474d8ecd82d5c95d7c9c548f5a4cd5213b16775809580189860bd69773c07a","transactionIndex":0,"blockNumber":440377,"blockHash":"0xb88779e44dabd14fcc55f7a0cdd31aaeab6417b990ed626748d8726463173bad","cumulativeGasUsed":800000,"gasUsed":800000,"contractAddress":null,"logs":[]}

1 Answer 1

4

Yes, all EVM exceptions cause all state changes to be rolled back, and all gas is forfeit to the miner. Thus, while throwing errors is convenient, it sometimes makes sense to just return some error value. You can also use Events to signal an exception occurred.

When calling other contracts, all exceptions bubble up, except when you use the send or call functions. These are low level functions that send Ether to, or call a method of another contract, but they only return true or false, if there was an exception.

For example, if you want to call another contract, but protect yourself from exceptions, and limit gas usage, use this:

uint gasPerCall = msg.gas / callbacks.length - 200000;
bytes4 abi = bytes4(sha3("oracleCallback(int256,uint256,bytes32)"));

for(uint i = 0; i < callbacks.length; i++) {
    if (callbacks[i].call.gas(gasPerCall)(abi, _value, _time, _info) == false) {
        FailedCallbackEvent(abi, gasPerCall, msg.gas);
    }
}

Replace the types in the ABI with the correct types. If the amount of gas sent is not important, you can just use callbacks[i].call(abi,_value,_time,_info);

If you want to send Ether along with the call, you can also use callbacks[i].call.value(1 ether)(abi,_value,_time,_info);

This will return true if the call succeeded, and false otherwise.

See the docs

18
  • Wow. That's going to be tough, because sending ether is at the very long end of a chain of business logic involving loops. (e.g. distributing ether to a list of addresses in some proportional or other algorithmic manner). I'll have to break the loop in two iterations: Make a list of of all the ether to be sent, then add it all up and make sure there's enough ether. This makes business logic potentially very complex and thus bug prone. Please consider fixing (e.g. adding a catch).
    – Paul S
    Commented Feb 5, 2016 at 3:25
  • please consider updating the solidity documentation to reflect this. For contract-contract calls it's very problematic. Please provide a link to the rationale
    – Paul S
    Commented Feb 5, 2016 at 3:27
  • btw I had this throw occur when I called the contract from web3 and I didn't get an exception thrown in my javascript code (I throw when gasUsed == gasSent). So is there a behavior difference between contract-contract and outside world-contract?
    – Paul S
    Commented Feb 5, 2016 at 3:33
  • Okay, sorry to scare you. I hadn't noticed that you were talking about a contract-contract call issue. I updated with a fix. Commented Feb 5, 2016 at 3:43
  • 1
    Just to be clear, you CANNOT use the aliased types in the ABI hash, you must be explicit. i.e. this won't work, you'll get the wrong value sha3("oracleCallback(int,uint,bytes32)"). I'll also note that you should add a debug event to a contract's default function that logs at least the ABI so you can debug when you make an error... e.g. DefaultFuncEvent(msg.sender, msg.sig, msg.gas, msg.value, msg.data);
    – Paul S
    Commented Feb 5, 2016 at 6:14

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.