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 theminimum 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 around60,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
pergas 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 topay some (extra) fee to the block validator
. This fee is called theMax Priority Fee
. Suppose the user wants it to be 30 Gwei per gas unit. - Hence, the user enters the values of
100 Gwei
and30 Gwei
respectively. - When the block is confirmed, there are 2 possibilities depending on what is the final value of Base Fee:
- Suppose a user wants to pay a Max Total Fee of
Case 1: Base Fee + Max Priority Fee <= Max Total Fee
example:
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
example:
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 thedeployment is made only one time
and theuser experience will be enhanced
. (unless your smart contractsize 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 asEXTCODESIZE
. -
SLOAD -> Access a value in storage. (
2100 gas
if not accessed before100 gas
otherwise) -
LOG4 -> Create an event with
4 topics
. (1875 gas
)
-
CREATE/CREATE2 -> Deploys a smart contract. (
(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 wait5–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 beforeoptimized()
function in the byte code, Callingoptimized()
costsmore than not_optimized()
with the same code (22 more gas
), so I will subtract22 gas
on each call tooptimized() 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 tobatch 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 to20,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 economize1 slot
. (a
andc
variable will be in thesame slot
)Therefore, there are
1
less used slot at the deployment. (So20,000 gas
saved)Moreover, if you want to access the
c
variable, but theb
variable is already accessed. It will count as a warm access (accessing a storage slot already accessed
) so it will cost you1000
instead of2900 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 themax (2³²-1)
will optimize calls to the function. In most of the cases you can use thedefault value (200)
B4: use
mapping
instead ofarray
(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 not_optimized() external{
map2.push(10);
}
function optimized() external{
map[1] = 10;
}
-
not_optimised()
function gas usage:48,409 gas
-
optimised()
function gas usage:43,400 gas
B5: Use
require
instead ofassert
:
-
Assert
should NOT be used by other means than testing purposes. (because when theassertion fails, gas is NOT refunded
contrary to require)
B6: Use
self-destruct
(save up to24,000
gas):
-
selfdestruct()
function destroys the smart contract and refunds24,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 (saves15,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 to20,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,
address indexed data);
function not_optimized() external{
store = 0xaaC532..;
}
function optimized() external{
emit Store(1, 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 (about50
gas saved):
- Static size types (like
bool
,uint256
,bytes5
) are cheaper than dynamic size type (likestring
orbytes
).
function not_optimized() external{
bytes4 memory str = "abcd";
}
function optimized() external{
bytes4 str = 0x61626364;
}
-
not_optimised()
function gas usage:21,255
gas -
optimised()
function gas usage:21,227
gas
C2:
Cold access
vswarm access
(70
saved gas):
- Don’t access the same storage variable twice.
uint256 a = 100;
function not_optimized() external{
uint b = a;
uint c = a;
}
function optimized() external{
uint tmp = a;
uint b = tmp;
uint c = tmp;
}
-
not_optimised()
function gas usage:23,412
gas -
optimised()
function gas usage:23,347
gas
C3: Using
calldata
instead ofmemory
(450
saved gas per call):
string str;
function not_optimized(string memory _str) external{
//code ...
}
function optimized(string calldata _str) external{
// code...
}
-
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);
event Test2(address indexed a, address indexed b);
function optimized(string calldata _str) external{
// code ...
}
function not_optimized() external {
emit Test1(0x..,0x..)
}
function optimized() external {
emit Test2(0x..,0x..)
}
- Here the function
optimised()
uses135
less gas thannot_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 first4 bytes
of the functionkeccak256
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;
...
}
As
comparing to signature computation
use a bit of gas (22
gas), you need to place the most called function in the first place of theswitch()
by assuring that the signature is in the first place (by changing its name)You can learn more about this par is my reversing EVM articles: https://trustchain.medium.com/reversing-and-debugging-evm-smart-contracts-392fdadef32d
C6: Use
++i
instead ofi = i+1
(62
gas saved on each call):
- This sounds like a joke, but it seems to work.:
function not_optimized() external{
uint i = 10;
i = i + 1;
}
function optimized() external{
uint i = 10;
++i;
}
-
not_optimised()
function gas usage:21,401
gas -
optimised()
function gas usage:21,339
gas
C7: Use
uint256
instead ofuintXX
on storage (55
gas saved per call):
- To
write/access
data on storage, the EVM uses32 bytes (= 256 bits slots)
, by using smaller types than uint256, the EVM needs to perform additional operations to assure the conversion. So better to useuint256
instead ofuint8
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)
(saves5
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){
// code ...
}
}
function optimised(uint[] calldata nums) external {
for (uint i = 0; i < nums.length; ++i){
// code ...
}
}
-
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 ;
uint add;
add = a + b;
}
function optimized() external{
uint i = 10 ;
uint j = 20 ;
uint add;
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 (about78.125
gas saved on deployment cost per characters):
- Don’t do it, the code may be harder to debug for you and for users. Use the custom errpr instead.
- For example, someone can replace:
require(account != address(0),
"ERC20: mint to the zero address");
To:-
require(account != address(0))
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 ofpublic
:
- Surprisingly they are not any difference between
external
andpublic
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 ofmultiplication
>> (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
. (Includingabi
,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 onmsg.value
. Therefore, some gas is saved.
function not_optimized() external{
// code ...
}
function optimized() external payable{
// code ...
}
-
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
Useful links:
https://medium.com/@sabir.sk/solidity-gas-optimisation-techniques-664b47eabb66
https://0xmacro.com/blog/solidity-gas-optimizations-cheat-sheet/
https://mudit.blog/solidity-gas-optimization-tips/?ref=0xmacro.com
https://github.com/code-423n4/2022-08-olympus-findings/issues/140
https://cryptoguide.dev/posts/solidity-gas-optimizations-guide/
https://dev.to/juanxavier/advanced-gas-optimizations-tips-for-solidity-1j2f
Top comments (0)