Hello Everyone..
Welcome to another day of exploring Web3 Engineering. In today's blog, let us look into the tips and patterns to follow during solidity smart contract development to save gas costs while making transactions. So, without any further ado, let's get started.
Don't use a loop.
As we all know by now, in solidity, every single operation costs some gas. When we run a
for
loop as shown below.
for(int i = 0; i< array.length(); i++) {
if(array[i] == val) {
array[i] = k;
}
}
We are initializing a variable called
count
We are also incrementing it for every iteration
These two operations will increase the gas in linear fashion depending on the length of the array.
Avoid Arrays and use Mappings
Mappings are cheaper than than the Arrays in solidity.
Array values are supposed to saved in serial fashion. Which means, index 1 element will be saved in the memory location right after the index 0 element and so on.
In the storage space, the first memory block contains the length of the array and the values are stored from the 2nd block on wards.
If there is any value appended, it needs to update both values list and the length of the array. The two operations are expensive
Mappings don't have any concept of length. The mapping are stored as key value pairs in the storage space.
Keys in mappings are hashed to a unique identifier that is used to locate the corresponding value in storage.
Which means whenever there is a need to update the mapping, there is only operation taking place.
Avoid calling global/storage variable multiple times
While writing contracts, we often use multiple global variables such as
block.timestamp
,msg.sender
,msg.value
etc. But accessing global variables or storage variables is expensive. So, we need to create a local copy of the variable which will create an in-memory copy of the variable and accessing variables from memory is relatively cheaper. We can look into an example below.
// expensive function
function func1() external {
require(admins[msg.sender], "Not eligible");
changesCounter[msg.sender] += 1;
emit Changed(msg.sender, block.timestamp);
}
// cheaper function
function func2() external {
address caller = msg.sender;
require(admins[caller], "Not eligible");
changesCounter[caller] += 1;
emit Changed(caller, block.timestamp);
}
Avoid calling Public functions inside another function
An external / public returns ABI encoded data while an internal function returns bytes data. So, using a public function inside a function is expensive than caling an internal function.
The code can be rewritten as shown in below in order to save gas.
// Expensive method function childExpensive() public returns(uint) { return 10; } function expensive() external returns(uint) { return childExpensive(); } // Cheaper method function _childInternal() internal returns(uint) { return 10; } function childCheap() external returns(uint) { return _childInternal(); } function cheap() external returns(uint) { return _childInternal(); }
- In the cheaper method, we have converted the logic into a
internal
function and created a wrapper calledchildCheap
to make it available for the external sources. And also whenever the logic is required we are calling the internal function.
- In the cheaper method, we have converted the logic into a
Comment down if you have any more tips to save gas. Thank you