3

Gnosis' MultiSig Wallet substract a fixed amount of gas (34710) when calling an external address.

function external_call(address destination, uint value, uint dataLength, bytes data) internal returns (bool) {
    bool result;
    assembly {
        let x := mload(0x40)   // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention)
        let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that
        result := call(
            sub(gas, 34710),   // 34710 is the value that solidity is currently emitting
                               // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) +
                               // callNewAccountGas (25000, in case the destination address does not exist and needs creating)
            destination,
            value,
            d,
            dataLength,        // Size of the input (in bytes) - this is what fixes the padding problem
            x,
            0                  // Output is ignored, therefore the output size is zero
        )
    }
    return result;
}

It seems to reserve some gas for the worst case scenario. But it causes strange behavior in some circumstances.

  • gas available less than 34710: sub() will overflow, but EVM fill cap gas to the max available, so the call may succeed.
  • gas available slightly more to 34710: sub() returns a small amount of thas that will make the inner call to fail with out of gas.

Is there any reason for doing that instead of just sending all available gas?

  • For all it matters, 34710 = allowance for callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) + callNewAccountGas (25000, in case the destination address does not exist and needs creating). I'm pretty sure that if the remaining gas is less than that, then the transaction will revert (silently, since you're doing it in assembly). – goodvibration May 12 at 17:50
  • 1
    I think your only problem here is that estimateGas would give you an underestimate on this function (more accurately, since this function is internal, on each one of the functions which call it). – goodvibration May 12 at 17:54
  • @goodvibration If destination is an EOA with non-zero balance you don't have to pay 25k, and call will succeed. There are two things I don't like here: 1) a 'constant' depending on opcode prices, 2) estimateGas is not monotone. I found one related issue that seems to state it was motivated by a bug in estimateGas github.com/ethereum/go-ethereum/issues/…, but I haven't found any confirmation yet. – Ismael May 12 at 20:26
  • 1
    Like I said - 25000, in case the destination address does not exist and needs creating. So the constant value of 34710 is the maximum required. I agree about the dependence issue, which makes this code tightly coupled with the EVM version. – goodvibration May 13 at 4:10
  • Unrelated: you might wanna take a shot at this interesting question. I've answered it and the answer was accepted, but I don't feel that it's correct (see my own comment on the answer). – goodvibration May 13 at 6:27
4
+100

This contract is quite "old" (pragma solidity ^0.4.15;). The logic is actually taken from the solidity compiler. When targeting older versions of the evm it was not possible to send along all gas. The solidity code still exists even now: https://github.com/ethereum/solidity/blob/develop/libsolidity/codegen/ExpressionCompiler.cpp#L2343

Solidity changed this with https://github.com/ethereum/solidity/releases/tag/v0.4.18 (in preparation for 0.5.0):

Code Generator: Always use all available gas for calls as experimental 0.5.0 feature (previously, some amount was retained in order to work in pre-Tangerine-Whistle EVM versions)

Why this was initially done in assembly in the MultiSig contract?

This is because in older Solidity versions the msg.data of a Solidity call was padded which caused errors with some contracts. (Guards for short address attacks, see https://vessenes.com/the-erc20-short-address-attack-explained/)

| improve this answer | |
  • 1
    So if the target EVM supports tangerine or better we can send all gas and it should be fine? – Ismael May 20 at 21:27
  • 1
    At least that is what Solidity does when you target a newer evm version (and also what we do with the Gnosis Safe contracts). Also the padding issues has been fixed for a while now and when writing a new contract I would probably try to avoid the assembly version if not really necessary. This because as far as I know more optimization and static analysis can be applied to Solidity code (but would need to double check this). – Wicket May 21 at 22:06

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.