Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Jamiebones
Jamiebones

Posted on

     

Gas Saving Techniques in Solidity

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

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);}}
Enter fullscreen modeExit fullscreen mode

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;}}
Enter fullscreen modeExit fullscreen mode

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;}}
Enter fullscreen modeExit fullscreen mode

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){}}
Enter fullscreen modeExit fullscreen mode

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 usedcalldata 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);}}
Enter fullscreen modeExit fullscreen mode

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 whengetNumberAtview 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;}}}
Enter fullscreen modeExit fullscreen mode

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;}}
Enter fullscreen modeExit fullscreen mode

Avoid loading too many data in memory

Loading data in memory in a single transaction has the effect of increasinggasused. 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;}}
Enter fullscreen modeExit fullscreen mode

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 preventgas used increase in a quadratic way.

Other tips

  • Always put yourrequire 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)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Javascript developer working hard to master the craft.
  • Location
    Uyo, Nigeria
  • Education
    University of Ilorin
  • Work
    Author @ Educative.io
  • Joined

More fromJamiebones

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp