From solidity 0.8.x the compiler automatically checks for overflows and underflows. This costs additional gas.
My question is how much gas it costs and how much you can save by using the unchecked {}
.
Thanks
gassolidity
From solidity 0.8.x the compiler automatically checks for overflows and underflows. This costs additional gas.
My question is how much gas it costs and how much you can save by using the unchecked {}
.
Thanks
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
In Ethereum, transactions cost gas and hence ether. The gas consumption of a transaction depends on the opcodes that the EVM has to execute. The gas cost for each Opcode can be found as explained in this question. Few common opcodes and gas are,
Operation Gas Description
ADD/SUB 3 Arithmetic operation
MUL/DIV 5 Arithmetic operation
ADDMOD/MULMOD 8 Arithmetic operation
AND/OR/XOR 3 Bitwise logic operation
LT/GT/SLT/SGT/EQ 3 Comparison operation
POP 2 Stack operation
PUSH/DUP/SWAP 3 Stack operation
MLOAD/MSTORE 3 Memory operation
JUMP 8 Unconditional jump
JUMPI 10 Conditional jump
SLOAD 200 Storage operation
SSTORE 5,000/20,000 Storage operation
BALANCE 400 Get balance of an account
CREATE 32,000 Create a new account using CREATE
CALL 25,000 Create a new account using CALL
This is a concern when it comes to smart contracts as transactions are also involved and it's important to consider the gas cost when designing a contract.
Reducing the gas consumed by a contract is important in two situations,
Cost of deploying a contract
For this, most of the optimizations are done at compilation time as described in the documentation-faqs,
Are comments included with deployed contracts and do they increase deployment gas?
No, everything that is not needed for execution is removed during compilation. This includes, among others, comments, variable names and type names.
And the details of the optimizer can be found here.
Another way of reducing the size is by removing useless code. For example:
1 function p1 ( uint x ){
2 if ( x > 5)
3 if ( x*x < 20)
4 XXX }
In above code, line 3 and 4 will never be executed and these type of useless code can be avoided by carefully going through the contract logic and that will reduce the size of the smart contract.
Cost to call the contract functions
When contracts' functions are called, for the execution of function it costs gas. Hence optimizing functions to use less gas is important. There can be many different ways of doing it when individual contract is considered. Here are few that might save gas during execution,
Expensive operations are the opcodes that has more gas values such as SSTORE
. Below are some methods of reducing expensive operations.
A) Use of Short Circuiting rules
The operators || and && apply the common short-circuiting rules. This means that in the expression f(x) || g(y), if f(x) evaluates to true, g(y) will not be evaluated even if it may have side-effects.
So if a logical operation includes an expensive operation and a low cost operation, arranging in a way that the expensive operation can be short circuited will reduce gas at some executions.
If f(x) is low cost and g(y) is expensive, arranging logical operations
f(x) || g(y)
f(x) && g(y)
will save more gas if short circuited.
If f(x)
has a considerably higher probability of returning false compared to g(y)
, arranging AND operations as f(x) && g(y)
might cause to save more gas in execution by short circuiting.
If f(x)
has a considerably higher probability of returning true compared to g(y)
, arranging OR operations as f(x) || g(y)
might cause to save more gas in execution by short circuiting.
B) expensive operations in a loop
eg:
uint sum = 0;
function p3 ( uint x ){
for ( uint i = 0 ; i < x ; i++)
sum += i; }
In the above code, since sum
storage variable is read and written every time inside the loop, storage operations that are expensive take place at every iteration. This can be avoided by introducing a local variable as follows to save gas.
uint sum = 0;
function p3 ( uint x ){
uint temp = 0;
for ( uint i = 0 ; i < x ; i++)
temp += i; }
sum += temp;
loop combining
function p5 ( uint x ){
uint m = 0;
uint v = 0;
for ( uint i = 0 ; i < x ; i++) //loop-1
m += i;
for ( uint j = 0 ; j < x ; j++) //loop-2
v -= j; }
loop-1 and loop-2 can be combined and gas can be saved,
function p5 ( uint x ){
uint m = 0;
uint v = 0;
for ( uint i = 0 ; i < x ; i++) //loop-1
m += i;
v -= j; }
and a few more loop patterns can be found here.
From Docs,
It is possible to use an array of bytes as byte[], but it is wasting a lot of space, 31 bytes every element, to be exact, when passing in calls. It is better to use bytes.
and
As a rule of thumb, use bytes for arbitrary-length raw byte data and string for arbitrary-length string (UTF-8) data. If you can limit the length to a certain number of bytes, always use one of bytes1 to bytes32 because they are much cheaper.
having a fixed length always saves gas. Refer to this question as well.
Removing useless code as explained earlier under contract deployment will save gas even when functions are executed, if that can be done inside functions.
Not using libraries when implementing the functionality is cheaper for simple usages.
Calling library for simple usages may be costly. If the functionality is simple and feasible to implement inside the contract as it avoids the step of calling the library. execution cost for the functionality only will still be the same for both.
Using visibility external
for the functions only accessed externally forces to use calldata
as the parameter location and this saves some gas when the function executes.
Using memory
variables within functions locally when possible saves gas of accessing the storage
.
These are some ways of saving gas and there may be many other methods depending on the requirements.
Best Answer
I have tested the following smart contract with an optimization of: 200000
executing the function add in remix result in a gas cost of: 43289
executing the function add in remix result in a gas cost of: 43362
as you can see the difference is the "unchecked" and this resulted in a difference of: 73 unit of gas
here you have another example:
gas cost: 21531
gas cost: 21445
For this example the difference in terms of gas is: 86