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