Gas
is a unit of computational measure in Solidity. This is the amount that we pay to interact and transact with a smart contracts. Thegas
is calculated via a simple formulae which is:Gas = Gas Price * Gas used
.
Thegas
price is not constant and it is dependent on the congestion of the network. The more users pushing transaction through the network the higher thegas
price. The amount ofgas
used is determined by the operation performed by the smart contract.
This blog post will discuss some techniques we might employ to reduce thegas
spent when sending transaction to a smart contract. We will consider the following listed techniques to reduce thegas
foot print of a smart contract.
- Use constant and immutable variables for variable that don't change
- Cache read variables in memory
- Incrementing and decrementing by 1
- calldata and memory
- Calling view function
- Integer overflow/underflow
- Use revert instead of require
- Avoid loading too many data in memory
- Other tips
Use constant and immutable variables for variable that don't change
Using theconstant
and theimmutable
keywords for variables that do not change helps to save ongas
used. The reason been thatconstant
andimmutable
variables do not occupy a storage slot when compiled. They are saved inside the contract byte code. Lets see an example.
//SPDX-License-Identifier:MITpragmasolidity^0.8.3;interfaceIERC20{functiontransfer(addressto,uint256value)externalreturns(bool);functionapprove(addressspender,uint256value)externalreturns(bool);functiontransferFrom(addressfrom,addressto,uint256value)externalreturns(bool);//rest of the interface code}//Gas used: 187302contractExpensive{IERC20publictoken;uint256publictimeStamp=566777;constructor(addresstoken_address){token=IERC20();}}//Gas used: 142890contractGasSaver{IERC20publicimmutablei_token;uint256publicconstantTIMESTAMP=566777;constructor(addresstoken_address){i_token=IERC20(token_address);}}
The first contractExpensive
has a public variable of typeIERC20
and auint256
also defined as public. When we deployed this contract, the contract deployment used up a total of 187302gas
when deployed to Remix. The second contract namedGasSaver
also have the same variable structure as theExpensive
contract but we made use ofconstant
keyword for theTIMESTAMP
because we will not be changing the value. For theinterface
we used theimmutable
keyword which means we can no longer modify the value of thei_token
variable after setting it inside theconstructor
. This action alone reduced the gas consumption from 187302 to 142890.
Cache read variables in memory
Reading from a variable in storage costgas
. When we access a variable for the first time it cost2100 gas
and subsequent access will cost100 gas
. If we have an array and want to perform some computation on the array elements. It could be more gas effective to read thelength
of the array and store it in a variable. If the array is not too large we could also read the wholearray
into memory and access it from memory instead of continuously reading from storage.
//SPDX-License-Identifier:MIT;pragmasolidity^0.8.3;contractExpensive{uint256[]publicnumbers;constructor(uint256[]memory_numbers){numbers=_numbers;}//Gas used: 40146functionsum()publicviewreturns(uint256){uint256total=0;for(uint256i=0;i<numbers.length;i++){total+=numbers[i];}returntotal;}}contractGasSaver{uint256[]publicnumbers;constructor(uint256[]memory_numbers){numbers=_numbers;}//39434functionsum()publicviewreturns(uint256){uint256total=0;uint256arrLength=numbers.length;uint256[]memory_numbersInMemory=numbers;for(uint256i=0;i<arrLength;i++){total+=_numbersInMemory[i];}returntotal;}}
The contractGasSaver
above saved the length of the array in a variablearrLength
and also loaded the array into memory. This helps to reduce thegas
spent inside the function.
Incrementing and Decrementing by 1
There are four ways to increment and decrement a variable by 1 in Solidity. Let us see an example.
//SPDX-License-Identifier:MIT;pragmasolidity^0.8.3;contractOne{uint256publicnumber;//Gas used : 43800functionincrementByOne()publicreturns(uint256){number+=1;returnnumber;}}contractTwo{uint256publicnumber;//Gas used : 43787functionincrementByOne()publicreturns(uint256){number=number+1;returnnumber;}}contractThree{uint256publicnumber;//Gas used : 43634functionincrementByOne()publicreturns(uint256){returnnumber++;}}contractFour{uint256publicnumber;//Gas used : 43628functionincrementByOne()publicreturns(uint256){return++number;}}
The example above list four contracts which shows the way we could increment a number variable by a value of 1. The contractFour
is more gas effective out of the lot when ran. To save more ongas
when incrementing a variable use the method of increment in contractFour
(++number
) which is moregas
efficient.
calldata and memory
When running a function we could pass the function parameters ascalldata
ormemory
for variables such asstrings
,structs
,arrays
etc. If we are not modifying the passed parameter we should pass it ascalldata
becausecalldata
is moregas
efficient thanmemory
. Let's see an example:
//SPDX-License-Identifier:MIT;pragmasolidity^0.8.3;contractGasSaver{//Gas used: 22471functionpassParameterAsCallData(stringcalldata_name)publicreturns(stringmemory){}//Gas used: 22913functionpassParameterAsMemory(stringmemory_name)publicreturns(stringmemory){}}
Calling these two functions in Remix and passing the same parameter to the functions, you will noticed that setting a function_name
parameter ascalldata
reduced thegas
used.
You can only used
calldata
when you are not going to modify the value of the variable passed ascalldata
inside the function.
Calling aview
function
Aview
function does not usegas
when called, but if we decide to call a view function inside of another function which is a transaction, it then usesgas
.
//SPDX-License-Identifier:MIT;pragmasolidity^0.8.3;contractGasSaver{uint256[]privatenumbers=[2,3,5,67,34];functiongetNumberAt(uint256_index)publicviewreturns(uint256){returnnumbers[_index];}//Gas used when getNumber was called: 44778//Gas used without calling getNumber() 44450functionsumAndMultiply()public{uint256[]memory_numbers=numbers;uint256arrlength=_numbers.length;for(uint256i=0;i<arrlength;++i){numbers[i]=_numbers[i]*i;}//getNumberAt(2);}}
The above is a simple contract to illustrate the point about callingview
functions inside of a transaction. The functiongetNumberAt
is aview
function that returns the value of the array at the passed index. Calling this function uses nogas
because it is a view function, but if we decide to call this function inside of thesumAndMultiply
function, we can see that thegas
usage ofsumAndMultiply
has increased because theview
function is called within it.
Gas
used whensumAndMutiply
functions is called:44778
.Gas
used whengetNumberAt
view
function is also called inside thesumAndMultiply
function:44450
.
So, we can see that calling aview
function inside a transaction increased the amount ofgas
that should have been spent, if theview
function was not included in the call.
Integer overflow/underflow
Prior to Solidity0.8.0
version, interger overflow and underflow checks were performed by using theSafeMath
library. From Solidity0.8.0
upward, the compiler does that check for us.
This extra check costgas
. If we know that the mathematical operations we will perform inside the contract will not overflow or underflow, we can tell the compiler not to check for overflow or underflow in the operation.
//SPDX-License-Identifier:MIT;pragmasolidity^0.8.3;contractOverFlow{uint256publicnumberOne;uint256publicnumberTwo;//Gas used: 43440functionsetNumberOne()public{++numberOne;}//Gas used: 43339functionsetNumberTwo()public{unchecked{++numberTwo;}}}
The above contractOverFlow
has two functions used to set two variables inside the contract. The compiler insetNumberOne
checks and prevents the number from overflow or underflow. In the second functionsetNumberTwo
we are telling the compiler to mind its business and not check for overflow or underflow. Usingunchecked
means that the code block will not be checked for overflow or underflow.
If we know that our mathematics will be safe we could safe a little gas by usingunchecked
.
Use revert instead of require
We userequire
to check if a statement istrue
. If the statement evaluates to false the transaction is reverted and the remaining gas returned to the user. Since the advert of custom error in Solidity we could use therevert
statement to throw a custom error. Usingrevert
instead ofrequire
is more gas efficient. You can run the code below to see the the amount ofgas
saved whenrevert
is used instead ofrequire
.
//SPDX-License-Identifier:MIT;pragmasolidity^0.8.3;errorNotEnough();contractGasSaver{uint256publicnumber;//Gas Used: 21898functionsetNumber(uint256_number)public{require(_number>10,"number too small please");number=_number;}//Gas Used: 21669functionsetNumberAndRevert(uint256_number)public{if(_number<10){revertNotEnough();}number=_number;}}
Avoid loading too many data in memory
Loading data in memory in a single transaction has the effect of increasinggas
used. Thegas
used increase in a quadratic factor when more than32kb
of memory is used in a single transaction. Let's see an example.
//SPDX-License-Identifier:MIT;pragmasolidity^0.8.3;contractOne{//Gas used: 29261functionsetArrayInMemory()publicpure{uint256[1000]memory_array;}}contractTwo{//Gas used: 276761functionsetArrayInMemory()publicpure{uint256[10000]memory_array;}}//Gas used: 922855contractThree{functionsetArrayInMemory()publicpure{uint256[20000]memory_array;}}contractFour{//Gas used: 3386918functionsetArrayInMemory()publicpure{uint256[40000]memory_array;}}
The above example defined four contracts and each contract has a function namedsetArrayInMemory
where memory space is reserved for an array ofuint256
.
ContractOne
loads reserves space in memory for 1000uint256
numbers and when the function is executed it uses up 29261gas
. ContractTwo
creates memory space for 10000uint256
numbers and we can see that thegas
used is 276761 which is roughly about 10 times of thegas
used by ContractOne
.
Things gets interesting in ContractThree
which reserves space for 20000uint256
numbers. From thegas
used which is 922855, we can see that once the memory used in a single transaction exceeds 32 kilobyte, the quadratic factor of the memory expansion cost kicks in thereby increasing thegas
used in a quadratic way.
Do not populate arrays with enormous amounts of items in a single transaction to prevent
gas
used increase in a quadratic way.
Other tips
Always put your
require
statement on top in the function before making any state change so that if therequire
statement fails, the remaininggas
is refunded to the user on revert.When comparing operation using
&&
or||
with arequire
statement; you should put the cheaper part of the operation first so that if it fails, the compiler will not compare the second part thereby saving ongas
used.
In conclusion We can use a combination of these techniques highlighted here to improve thegas
consumption of our smart contracts thereby saving our users from spending too much ongas
when interacting with our smart contracts.
Thanks for reading.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse