7

In my understanding, there are some situations, where the compiler can't calculate, how much Gas it takes to complete the function call. The most obvious example - when your function is calling some function from the another contract. The compiler doesn't have the source of the another contract so it can't tell you, how much Gas is necessary, to complete the call.

But even when the function doesn't call any external function, the compiler also can't tell you the cost. A small example is listed bellow:

pragma solidity ^0.4.16;

contract Test {
    address[] owners;

    function addOwner(address newOwner) external {
        owners.push(newOwner);
    }
}

The warning message is:

Gas requirement of function browser/ballot.sol:Test.addOwner(address) unknown or not constant. If the gas requirement of a function is higher than the block gas limit, it cannot be executed. Please avoid loops in your functions or actions that modify large areas of storage (this includes clearing or copying arrays in storage)

What is the reason of this warning? I think maybe it's because of changing the state, but I'm not sure.

Thank in advance!

8

You can get the gas estimations by adding --gas to solc:

> solc --gas Test.sol

======= Test.sol:Test =======
Gas estimation:
construction:
   118 + 70800 = 70918
external:
   addOwner(address):   infinite

However, there are many cases when the gas estimator reports infinite gas. It doesn't necessarily mean that there is an infinite loop in your code or that your code is incorrect but just the estimator is quite restrictive when making decisions about how much gas can be consumed by the code. In particular, any backward jumps or loops in the assembly code will make it report infinite gas.


Detailed answer below

The warning comes from the Remix' static code analyser. From remix sources

  if (gas === null || gas >= 3000000) {
    report.push({
      warning: yo`<span>Gas requirement of function ${contractName}.${functionName} ${gasString}.<br />
      If the gas requirement of a function is higher than the block gas limit, it cannot be executed.
      Please avoid loops in your functions or actions that modify large areas of storage
      (this includes clearing or copying arrays in storage)</span>`
    })
  }

Debugging shows that the gas is null.

Gas estimations come from the solidity compiler. In this particular case the compiler estimated the gas for the addOwner function as infinity. You can see it by running solc compiler with --gas option:

> solc --gas Test.sol

======= Test.sol:Test =======
Gas estimation:
construction:
   118 + 70800 = 70918
external:
   addOwner(address):   infinite

From solidity compiler sources you can see that functional estimation is done in the GasEstimator.cpp which in turn uses PathGasMeter.cpp to estimate the maximum possible gas consumption (I marked with arrow the place where infinite gas estimation is returned).

if (item.type() == Tag || item == AssemblyItem(Instruction::JUMPDEST))
{
    // Do not allow any backwards jump. This is quite restrictive but should work for
    // the simplest things.
    if (path->visitedJumpdests.count(index))
        return GasMeter::GasConsumption::infinite(); // <---------------
    path->visitedJumpdests.insert(index);
}

// Do not allow any backwards jump. This is quite restrictive but should work for the simplest things.

Backward jumps indicate a loop which might result in unbounded gas consumption.

The piece of assembly output from solc --asm Test.sol that has a backward jump is:

tag_14:
      dup1
      dup3
      gt
      iszero
      tag_15
      jumpi
      0x0
      dup2
      0x0
      swap1
      sstore
      pop
      0x1
      add
      jump(tag_14)

This piece of opcodes clears the storage in case the length of an array is reduced. Since the storage is expensive, every time it's cleared the gas is refunded to the transaction sender.

Why would that contract need to reduce the length of the array you might ask. The reason is

arr.push[element];

is replaced by

arr.length = arr.length + 1;
arr[arr.length] = element;

The first line where the length of the array is modified is further expanded by the function that's changing the array length which in case the length is lesser than it was before will clear the storage slots not used by the array. This function iterates through unused slots and clears them one by one.

Although our contract never needs to reduce the array size the assembler includes this piece of code nevertheless which causes the gas estimator to report the infinite max gas usage.

You can try executing the addOwner(address) function multiple times. The used gas is always the same: 48829 gas. However if you add another function to the contract:

function setOwnersLength(uint newLength) public {
    owners.length = newLength;
}

and try calling it you will see that the used gas depends on by how much you reduce the array length.

Related question: Infinite gas estimation from solc for simple function

2

owners is an array. While push seems to be constant time, and therefore could conceivably have its gas usage calculated exactly, perhaps the compiler just throws up its hands. "Oops. An array. Non-constant gas cost. I better warn the programmer."

You can see this in the warning message where it mentions loops. Code that spins through a growing array will one day stop working (depending on how many items are in the array). If there's one item in the array, looping through is cheap. If there's a million, the gas usage may grow so high that you use up more gas than is allowed in a block and your smart contract will stop working.

Upshot: Be careful when using arrays. Maps don't have this problem, but you can't spin sequentially through a map.

1

EDIT: I think my answer here is incorrect. It is memory that gets quadratically more expensive. I've left the answer below unedited.

I think this is because owners.push(newOwner) expands storage usage each time you call it. Storage gets exponentially more expensive the more you use. So this function will be cheap if there are 0 owners, and will get more and more expensive the more owners you already have in the array.

It's possible to compute what the gas cost would be for the current blockchain state, but because any given transaction isn't guaranteed to be confirmed before some other transaction that modifies the state, you can't be 100% certain what it will cost when you actually put it out there.

  • Why does the storage get exponentially more expensive? – medvedev1088 Nov 1 '17 at 20:39
  • @medvedev1088 actually, I think I may be incorrect here – Aaron Sikes Nov 7 '17 at 23:30

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.