Skip to content
This repository has been archived by the owner on Jun 29, 2023. It is now read-only.

Latest commit

 

History

History
162 lines (101 loc) · 8.87 KB

L12-Gas-optimizations.md

File metadata and controls

162 lines (101 loc) · 8.87 KB

⛽ Gas Optimizations in Solidity

In this tutorial, we will learn about some of the gas optimization techniques in Solidity. This has been one of our most-requested levels, so let's get started without further ado 👀

Tips and Tricks

Variable Packing

If you remember we talked about storage slots in one of our previous levels. Now the interesting point in solidity if you remember is that each storage slot is 32 bytes.

This storage can be optimized which will further mean gas optimization when you deploy your smart contract if you pack your variables correctly.

Packing your variables means that you pack or put together variables of smaller size so that they collectively form 32 bytes. For example, you can pack 32 uint8 into one storage slot but for that to happen it is important that you declare them consecutively because the order of declaration of variables matters in solidity.

Given two code samples:

uint8 num1;
uint256 num2;
uint8 num3;
uint8 num4;
uint8 num5;
uint8 num1;
uint8 num3;
uint8 num4;
uint8 num5;
uint256 num2;

The second one is better because in the second one solidity compiler will put all the uint8's in one storage slot but in the first case it will put uint8 num1 in one slot but now the next one it will see is a uint256 which is in itself requires 32 bytes cause 256/8 bits = 32 bytes so it can't be put in the same storage slot as uint8 num1 so now it will require another storage slot. After that uint8 num3, num4, num5 will be put in another storage slot. Thus the second example requires 2 storage slots as compared to the first example which requires 3 storage slots.

It's also important to note that elements in memory and calldata cannot be packed and are not optimized by solidity's compiler.

Storage vs Memory

Changing storage variables requires more gas than variables in memory. It's better to update storage variables at the end after all the logic has already been implemented.

So given two samples of code

contract A {
    uint public counter = 0;

    function count() {
        for(uint i = 0; i < 10; i++) {
            counter++;
        }
    }

}
contract B {
    uint public counter = 0;

    function count() {
        uint copyCounter;
        for(uint i = 0; i < 10; i++) {
            copyCounter++;
        }
        counter = copyCounter;
    }

}

The second sample of code is more gas optimized because we are only writing to the storage variable counter only once as compared to the first sample where we were writing to storage in every iteration. Even though we are performing one extra write overall in the second code sample, the 10 writes to memory and 1 write to storage is still cheaper than 10 writes directly to storage.

Fixed length and Variable-length variables

We talked about how fixed length and variable length variables are stored. Fixed-length variables are stored in a stack whereas variable-length variables are stored in a heap.

Essentially why this happens is because in a stack you exactly know where to find a variable and its length whereas in a heap there is an extra cost of traversing given the variable nature of the variable

So if you can make your variables fixed size, it's always good for gas optimizations

Given two examples of code:

string public text = "Hello";
uint[] public arr;
bytes32 public text = "Hello";
uint[2] public arr;

The second example is more gas optimized because all the variables are of fixed length.

External, Internal, and Public functions

Calling functions in solidity can be very gas-intensive, its better you call one function and extract all data from it than call multiple functions

Recall the public functions are those which can be called both externally (by users and other smart contracts) and internally (from another function in the same contract).

However, when your contract is creating functions that will only be called externally it means the contract itself cant call these functions, it's better you use the external keyword instead of public because all the input variables in public functions are copied to memory which costs gas whereas for external functions input variables are stored in calldata which is a special data location used to store function arguments and it requires less gas to store in calldata than in memory

The same principle applies as to why it's cheaper to call internal functions rather than public functions. This is because when you call internal functions the arguments are passed as references of the variables and are not again copied into memory but that doesn't happen in the case of public functions.

Function modifiers

This is a fascinating one because a few weeks ago, I was debugging this error from one of our students and they were experiencing the error “Stack too deep”. This usually happens when you declare a lot of variables in your function and the available stack space for that function is no longer available. As we saw in the Ethereum Storage level, the EVM only allows up to 16 variables within a single function as that it cannot perform operations beyond 16 levels of depth in the stack.

Now even after moving a lot of the require statements in the modifier it wasn't helping because function modifiers use the same stack as the function on which they are put. To solve this issue we used an internal function inside the modifier because internal functions don't share the same restricted stack as the original function but modifier does.

Use libraries

Libraries are stateless contracts that don't store any state. Now when you call a public function of a library from your contract, the bytecode of that function doesn't get deployed with your contract, and thus you can save some gas costs. For example, if your contract has functions to sort or to do maths etc. You can put them in a library and then call these library functions to do the maths or sorting for your contract. To read more about libraries follow this link.

There is a small caveat however. If you are writing your own libraries, you will need to deploy them and pay gas - but once deployed, it can be reused by other smart contracts without deploying it themselves. Since they don't store any state, libraries only need to be deployed once to the blockchain and are assigned an address that the Solidity compiler is smart enough to figure out itself. Therefore, if you use libraries from OpenZeppelin for example, they will not add to your deployment cost.

Short Circuiting Conditionals

If you are using (||) or (&&) it's better to write your conditions in such a way so that the least functions/variable values are executed or retrieved in order to determine if the entire statement is true or false.

Since conditional checks will stop the second they find the first value which satisfies the condition, you should put the variables most likely to validate/invalidate the condition first. In OR conditions (||), try to put the variable with the highest likelihood of being true first, and in AND conditions (&&), try to put the variable with the highest likelihood of being false first. As soon as that variable is checked, the conditional can exit without needing to check the other values, thereby saving gas.

Free up Storage

Since storage space costs gas, you can actually free up storage and delete unnecessary data to get gas refunds. So if you no longer need some state values, use the delete keyword in Solidity for some gas refunds.

Short Error Strings

Make sure that the error strings in your require statements are of very short length, the more the length of the string, the more gas it will cost.

require(counter >= 100, "NOT REACHED"); // Good
require(balance >= amount, "Counter is still to reach the value greater than or equal to 100, ............................................";

The first requirement is more gas optimized than the second one.

NOTE: In newer versions of Solidity, there are now custom errors using the error keyword which behave very similar to events and can achieve similar gas optimizations.


Thank you all for staying tuned to this article 🚀 Hope you liked it :)

👋 Conclusion

Hope you learnt something from this level. If you have any questions or feel stuck or just want to say Hi, hit us up on our Discord. We look forward to seeing you there!