## DEV Community

SK Sabiruddin

Posted on • Updated on

# Ethereum Gas Optimization Techniques

## Gas Units:

• Making transactions on Ethereum or any other Ethereum backed blockchain will cost you a gas fee. The total gas fee consists of two parts: `Gas Units x Gas Price`

• Gas units are a number that depends on the amount of computation required for a transaction. E.g. If you send some Ether (Ethereum’s native cryptocurrency) to someone, it requires `21,000 gas units`. It’s the `minimum number of units required for any transaction`.

• The number of required gas units increases as the complexity of the transaction increases. E.g. Sending an `ERC-20 token` might cost around `60,000 gas units`. `Minting an NFT might cost around 120,000 gas units`, and so on.

• It all depends on how many computations need to be made while processing the transaction.

## Gas Price:

• Gas price is determined by the demand for making transactions. The more the traffic, the higher the price.

• It’s usually expressed in `Gwei` which is a billionth part of an Ether. If the gas price is 100 Gwei, it means 1 gas unit currently costs 100 Gwei as per the demand. The gas price will vary throughout the day depending on the total traffic.

• Let’s combine these two now. If you need to send some Ether to someone at a time when gas price for 1 gas unit is 100 Gwei, you’ll have to pay a total of: `21,000 gas units x 100 Gwei = 2,100,000 Gwei`.

• Since Gwei is `1/1bn of Ether, you’ll pay 0.0021 Ether`.

• To find the amount in dollars, multiply it with the current price of ether i.e. 1200 USD. Hence `0.0021 Ether x 1200 USD = 2.52 USD` total gas fee. To summarise, the gas fee depends on:

• Complexity of transaction (gas unit)
• Demand for making transactions (gas price)
• Price of Ether (to calculate dollar value)

## Where does my gas fee go?

• So, we know that we need to pay a gas fee when making a transaction. But where does it go now? Is it burnt? Or is it given to the validator?
• Actually, it’s both, after the `EIP-1559` (an upgrade to Ethereum that happened in September 2022).
• Total gas fee is made up of:
• Base Fee (fixed for every transaction of a block) – which gets burnt entirely.
• Max Priority Fee (decided by the user) – which goes to the validator partially or entirely depending on the Max Total Fee which is again decided by the user.
• Let’s run through an example:

• Suppose a user wants to pay a Max Total Fee of `100 Gwei` per `gas unit consumed` for the transaction. It’s inclusive of the Base Fee (burnt) and Max Priority Fee (for validator).
• While making the transaction, the user doesn’t know what the exact Base Fee will be for the transaction/block.
• During peak time, a lot of people will be trying to make a transaction, thus congesting the network. This will cause your transaction to take a `long time before going through`. In order to prioritise his/her transaction, a user may want to `pay some (extra) fee to the block validator`. This fee is called the `Max Priority Fee`. Suppose the user wants it to be 30 Gwei per gas unit.
• Hence, the user enters the values of `100 Gwei` and `30 Gwei` respectively.
• When the block is confirmed, there are 2 possibilities depending on what is the final value of Base Fee:
``````Case 1: Base Fee + Max Priority Fee <= Max Total Fee

E.g.

Base Fee = 60, Max Priority Fee = 30, Max Total Fee = 100

Hence,

- 60 Gwei is burnt, (per gas unit)

- 30 Gwei goes to the validator, (per gas unit)

- The remaining 10 Gwei is refunded to the user (per gas unit)
``````
``````Case 2: Base Fee + Max Priority Fee > Max Total Fee

E.g.

Base Fee = 80, Max Priority Fee = 30, Max Total Fee = 100

Hence,

- 80 Gwei is burnt, (per gas unit)

- Only 20 Gwei goes to the validator (per gas unit), since Max Total Fee is 100 Gwei

- 0 refund for the user since the max limit is consumed.
``````
• Note: `Base Fee` can’t be >= `Max Total Fee`. Your Metamask wallet will let you know if the Max Total Fee entered by you is not sufficient as per the network conditions.

## How to save Gas Cost:

Important: This document contains useful information to optimize your Gas Cost in Ethereum Blockchain. The following document contains `90%` of the gas optimization techniques in Ethereum.

• We will see which method is useful and which method is useless. (or event harmful)

• We will see how much gas you can save by using each method.

• We will rank all the methods by using a tier list. (`A = excellent, D= useless/harmful`)

1. Deployment optimization vs Call optimization:

At first, you need to know that there are 2 ways to optimize gas on a Decentralized Application.

A). Optimizing Deployment costs:

• You can optimize the cost to deploy a smart contract.

• The `cost = 21000 gas (for creating transaction) + 32000 gas (for creating a contract) + CONSTRUCTOR EXECUTION + BYTES DEPLOYED * 78.125`

• So, for deploying a smart contract, you will spend `at least 53000 gas`.

• To minimize gas costs you need to `minimize the size of a smart contract and the cost of the constructor`.

B). Optimize every function call:

• You can also optimize the `cost of calling every function` in your smart contract.

• The cost is equal to`21000 gas` (for creating transaction) + the function execution cost).

• So, the objective will be to reduce the function execution cost.

Note that these 2 means of optimization are not the same!!

• A lot of times, you may deploy a larger solidity code (so with higher deployment gas cost) but more optimized for every function call. (So with a smaller calling gas cost)

• If you need to choose which type of optimization, you need to use it wisely.

• I strongly advise you to `optimize every function call` as the `deployment is made only one time` and the `user experience will be enhanced`. (unless your smart contract `size exceeds the limit of 24Kb`)

2. Ranking Gas optimization methods:

• We will rank all the gas optimization methods by using a tier list. (`Because there is a difference between saving 2 gas on a transaction of 21000 gas and saving 10000 gas.`)

• Tier A: If you don’t know these rules, you’re not a TRUE solidity developer.
• Tier B: Essential, should be known by all the developers.
• Tier C: may be useful sometime, should be known by developers too.
• Tier D: Useless/harmful, don’t use them (unless you know what you are doing). You may lose, either your time or the security of the smart contract.

• Tier A:

A1 : Knowing how much each `EVM opcodes` cost:

• Here is the 6 of the `most costly` gas opcodes (among the most used is smart contracts):

• CREATE/CREATE2 -> Deploys a smart contract. (`32,000 gas`)
• SSTORE -> Stores a value in storage. (`20,000 gas` if the slot hasn’t been accessed, `2900`otherwise.)
• EXTCODESIZE -> Get the size in bytes of a smart contract code. (`2600` if not accessed before, `100` gas otherwise)
• BALANCE (`address(this).balance`), same as `EXTCODESIZE`.
• SLOAD -> Access a value in storage. (`2100 gas` if not accessed before `100 gas` otherwise)
• LOG4 -> Create an event with `4 topics`. (`1875 gas`)
• (This is in fact an estimation. Some of them may vary depending on the situation.)

• The best website which describes how much each opcode is https://www.evm.codes/, it’s very well explained.

• If you know that, you’ll better understand solidity and you’ll get a “better” intuition of how much each line of code costs you.

A2: Use `view` the modifier:

``````function getBalance() external view {
return balance;
}
``````
• You can easily cut your gas costs to 0 `when you call the smart contract if you don’t write to the storage`. (moreover, you don’t need to wait `5–20 seconds the response`)
• (A3 Understand the solidity programming language & A4 Become good at computer science/algorithms)

• Tier B:

These tips and tricks can save you a good amount of gas and should be implemented as much as possible. IMPORTANT NOTE BEFORE STARTING:

• As the `not_optimized()` function is situated before `optimized()` function in the byte code, Calling `optimized()` costs `more than not_optimized()` with the same code (`22 more gas`), so I will subtract `22 gas` on each call to`optimized() function`.

B1: Batching operations (Saved gas: `21000 gas * batched transactions`):

• Submitting a transaction alone on the blockchain costs a lot of gas (`21,000` gas to be precise), so if you can find a way to `batch transactions` for your users, it can save you a good amount of gas.

B2: change `orders` of storage slot (`20,000` gas saved at deployment):

• Ethereum storage is composed of `slots of 32 bytes`, the problem is that writing is expensive. (Up to `20,000 gas` using “cold” writing)
• Let’s say you have 3 variables:
``````
// These vars use 3 different storage slots
unit128 a; // slot 0(16 bytes)
unit256 b; // slot 1(32 bytes)
unit128 c; // slot 2(16 bytes)

// These var use 2 different storage slots
unit256 b; // slot 0(32 bytes)
unit128 a; // slot 1(16 bytes)
unit128 c; // slot 1(16 bytes)
``````
• But if we align the `3 slots well`, we can economize `1 slot`. (`a` and `c` variable will be in the `same slot`)

• Therefore, there are `1` less used slot at the deployment. (So `20,000 gas` saved)

• Moreover, if you want to access the `c` variable, but the `b` variable is already accessed. It will count as a warm access (`accessing a storage slot already accessed`) so it will cost you `1000`instead of `2900 gas` which is pretty significant.

• If you don’t understand the storage, the documentation may help you: https://docs.soliditylang.org/en/v0.8.13/internals/layout_in_storage.html

B3: Use the `optimizer` (Saved gas: `10–50%` on deployment & call):

• You can use the solidity `built-in optimizer`.
• This is a very simple way to save a lot of gas without learning any new concepts. You need to check the “`enable optimization`” checkbox under Advance configuration section.
• A value near to `1` will optimize gas cost, but a value near to the `max (2³²-1)` will optimize calls to the function. In most of the cases you can use the `default value (200)`

B4: use `mapping` instead of `array` (`5000 gas` saved per value):

• Mappings are usually less expensive than arrays, but you can’t iterate over them.
``````      mapping(unit => unit) map;
unit[] map2;

function optimized() external{
map[1] = 10;
}

function not_optimized() external{
map2.push(10);
}
``````
• `not_optimised()` function gas usage: `48,409 gas`
• `optimised()` function gas usage: `43,400 gas`

B5: Use `require` instead of `assert`:

• `Assert` should NOT be used by other means than testing purposes. (because when the `assertion fails, gas is NOT refunded` contrary to require)

B6: Use `self-destruct` (save up to `24,000` gas):

• `selfdestruct()` function destroys the smart contract and refunds `24,000 gas`.
• In a more complex transaction like deploying another smart contract, you can call `selfdestruct()` on the smart contract to economize some gas.

B7: Make fewer `external` calls (amount saved: variable):

• Make a call to another contract only when you’re obligated to do so.

B8: Look for `dead code` (saves a variable amount of gas on deployment):

• Sometimes, developers forget to remove useless code like:
``````      require(a == 0)
if (a == 0) {
return true;
} else {
return false
}
``````
• This is a dump example because it shows you that some devs can forget to remove `useless parts`.

B9: use `immutable` variables (saves `15,000` gas on deployment)":

• Use immutable storage variables whenever it’s possible.
``````       contract not_optimised{
unit256 public a;
constructor(unit256 _a) public{
a = _a;
}
}
``````
``````       contract optimised{
unit256 public immutable a;
constructor(unit256 _a) public{
a = _a;
}
}
``````
• `not_optimised`contract gas usage: `11,6800` gas
• `optimised` contract gas usage: `10,1013` gas

B10: Storing data in `events` (up to `20,000` gas saved on function call):

• If you don’t need to access the data on-chain in solidity, you can store it using events.
``````                address store;
event Store(uint256 indexed key,

function optimized() external{
emit Store(1, 0xaaC532..);
}

function not_optimized() external{
store = 0xaaC532..;
}
``````
• `not_optimised()` function gas usage: `43,353` gas
• `optimised()` function gas usage: `22,747` gas

• Tier C:

• These tips can save you a fair amount of gas and cost you nothing.

C1: Use `static size` type in solidity (about `50` gas saved):

• Static size types (like `bool`, `uint256`, `bytes5`) are cheaper than dynamic size type (like `string` or `bytes`).
``````                function optimized() external{
bytes4 str = 0x61626364;
}

function not_optimized() external{
bytes4 memory str = "abcd";
}
``````
• `not_optimised()` function gas usage: `21,255` gas
• `optimised()` function gas usage: `21,227` gas

C2: `Cold access` vs `warm access` (`70` saved gas):

• Don’t access the same storage variable twice.
``````                uint256 a = 100;
function optimized() external{
uint tmp =  a;
uint b   =  tmp;
uint c   =  tmp;
}

function not_optimized() external{
uint b   =  a;
uint c   =  a;
}
``````
• `not_optimised()` function gas usage: `23,412` gas
• `optimised()` function gas usage: `23,347` gas

C3: Using `calldata` instead of `memory` (`450` saved gas per call):

``````        string str;
function optimized(string calldata _str) external{

}

function not_optimized(string memory _str) external{

}
``````
• `not_optimised()` function gas usage: `22,442` gas
• `optimised()` function gas usage: `21,994` gas

C4: Use `Indexed events` (`62` gas saved on function call per topic):

• You can mark each event as `indexed` as bellow:
• Indexed events allow events to be `searched` more easily.
``````   event Test1(address a, address b);

function optimized(string calldata _str) external{

}

function not_optimized() external {
emit Test1(0x..,0x..)
}
function optimized() external {
emit Test2(0x..,0x..)
}
``````
• Here the function `optimised()` uses `135` less gas than `not_optimised()`.

C5: Changing the `order of calls` (`20–2000` saved gas per call):

• We can call every smart contract, you supply in `msg.data` the signature of the function. (the first `4 bytes` of the function `keccak256` hash).

• Firstly, the EVM will do a “`switch`” of this signature, to see which function to execute.

``````      switch(msg.data[0:4]) { // compare to signature
case 0x01234567: go to functionA;

// cost 22 more gas..
case 0x11111111: go to functionB;

// cost 22+22 more gas...
case 0x4913aaaa: go to functionC;
...
}
``````

C6: Use `i++` instead of `i = i+1` (`62` gas saved on each call):

• This sounds like a joke, but it seems to work.:
``````               function optimized() external{
uint i = 10;
i++;
}
function not_optimized() external{
uint i = 10;
i = i + 1;
}
``````
• `not_optimised()` function gas usage: `21,401` gas
• `optimised()` function gas usage: `21,339` gas

C7: Use `uint256` instead of `uintXX` on storage (`55` gas saved per call):

• To `write/access` data on storage, the EVM uses `32 bytes (= 256 bits slots)`, by using smaller types than uint256, the EVM needs to perform additional operations to assure the conversion. So better to use `uint256` instead of `uint8` for storing data.
``````                uint256 balance;
uint8 balance2;
function not_optimized() external{
balance2 = 10;
}
function optimized() external{
balance = 10;
}
``````
• `not_optimised()` function gas usage: `43,353` gas
• `optimised()` function gas usage: `43,298` gas

C8: Create a `custom error` (`24` gas saved per call):

• You can create a custom error on solidity and revert with it like below:
``````                error err(string test);
function not_optimized() external{
revert("test");
}
function optimized() external{
revert err("test");
}
``````
• `not_optimised()` function gas usage: `21,476` gas
• `optimised()` function gas usage: `21,474` gas

C9: Exchanging 2 variables by using a `tuple (a, b) = (b, a)` (saves `5` gas on every call):

``````               function not_optimized() external{
uint i = 10 ;
uint j = 20 ;
uint temp = i ;
i = j ;
j = temp;
}
function optimized() external{
uint i = 10;
uint j = 20 ;
(i,j) = (j,i);
}
``````
• `not_optimised()` function gas usage: `21,241` gas
• `optimised()` function gas usage: `21,236` gas
• We put it on the C tier because the notation is way more readable.

C10: Use Calldata instead of Memory in function arguments in case of not changing the passed data:

• Using calldata in a summing function that loops over an input array can save about 1829 gas (or 3.5%) on average.
``````   function not_optimised(uint[] memory nums) external {
for (uint i = 0; i < nums.length; ++i){
...
}
}

function optimised(uint[] calldata nums) external {
for (uint i = 0; i < nums.length; ++i){
...
}
}

``````
• `not_optimised()` function gas usage: `50,992` gas
• `optimised()` function gas usage: `49,163` gas

• Tier D: (Useless/harmful)

These optimizing gas tips are useless, don’t lose your time for saving a low amount of gas it will be shadowed anyway by the `21,000`gas transaction.

D1: using `unchecked` (gas saved: `150`/operation):

• Since solidity 0.8.0, operations like + * / — are checked every time if there is an `overflow/underflow`.

• But it cost a little bit of gas to verify if there is an overflow/underflow.

• Unless you are doing a lot of operations in a single function call (like in for loops), you should NOT use `unchecked{}`.

``````               function not_optimized() external{
uint i = 10 ;
uint j = 20 ;
}
function optimized() external{
uint i = 10 ;
uint j = 20 ;
unchecked {c = a + b};
}
``````
• `not_optimised()` function gas usage: `21,419` gas
• `optimised()` function gas usage: `21253` gas

D2: changing and optimizing the `bytecode` yourself:

• Don’t do this unless you have a good reason to do so, some of the functions may end up with undefined behavior and security issues.
• Use the optimizer instead or implement a very strict testing policy.

D3: Delete `Errors string` messages (about `78.125` gas saved on deployment cost per characters):

• Don’t do it, the code may be harder to debug for you and for users.
• For example, someone can replace:
``````         require(account != address(0),
"ERC20: mint to the zero address");
To:-
``````

D4: using `low level assembly` (gas saved: variable):

• If you don't know what you’re doing, `DON’T do solidity assembly`. You will end up having a lot more chances to have a security hole on your contract for a marginal saved gas amount.

D5: Use `external` instead of `public`:

• Surprisingly they are not any difference between `external` and `public` functions,
``````       function not_optimized() public returns (uint){
return 777;
}
function optimized() external returns(uint){
return 777;
}
``````
• `not_optimised()` function gas usage: `21,401` gas
• `optimised()` function gas usage: `21,379` gas (22)

D6: Use `left shift` instead of `multiplication` >> (150 saved gas):

• `Shifting binary data n times to the left results in multiplying the data by 2^n`. Here is an example:
``````      00000001 = 1 dec
00000010 = 2 dec
00000100 = 4 dec
00001000 = 8 dec

function not_optimized()  external returns (uint){
uint a = 5;
uint b = a * 1024;
return b;
}

function optimized() external returns(uint){
uint a = 5;
uint b = a >> 10;
return b;
}

``````
• `not_optimised()` function gas usage: `21,615` gas
• `optimised()` function gas usage: `21,436` gas
• Gains are not bad, but the `>>` operator doesn't check for an overflow.

D7: Delete `metadata hash` (`3000–5000` gas on deployment):

• On every smart contract byte code a “`hash`” is appended in the end.
• This is the `hash of all the metadata of the smart contract`. (Including `abi`, `comments`, `code`…)
• As the metadata hash is `32 bytes` in length, it can save you a bunch of gas.
• But this is quite hard to do, because you have to do it by hand.
• So it may be dangerous if executed badly.

D8: Add `payable` to every function (`24` gas saved per call):

• Adding `payable` to a function, removes the verification on `msg.value`. Therefore, some gas is saved.
``````              function optimized() external payable{
...
}

function not_optimized() external{
...
}
``````
• `not_optimised()` function gas usage: `21,186` gas
• `optimised()` function gas usage: `21,184` gas

D9: Doing the important work `offchain`:

• Be careful about the work you’re doing off-chain (Like in JS).
• For example, you `can’t do security verification off-chain` in the website front end, this is very dangerous as anyone may be able to change the JS code in the browser and send bad input to the smart contract.
• `ALWAYS DO THE SECURITY VERIFICATION ON-CHAIN`.!!!

### Some other gas saving techniques: -

``````- Loading state variable to memory
- Replace for loop i++ with ++i
- Caching array elements
- Short circuit
``````