Gas Optimization for Smart Contracts

What is Gas

How tx cost is calculated when transferring ETH

Gas after EIP-1559

Txs when interacting with smart contracts

USDC Transfer

Heavy and Light Functions in Solidity

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

contract Sample {

    uint256 myVar = 20;
    bytes32 myBytes;

    function lightFunction() external returns(uint256) {
        myVar++;
        return myVar;
    }

    function heavyFunction() external returns(bytes32) {
        bytes32 x = keccak256(abi.encode(myVar));
        for(uint256 i = 0; i < 100; i++) {
            x = keccak256(abi.encode(x));
        }
        myBytes = x;
        return x;
    }
}

Understanding Block Limit

Gas Efficient Chains - Write about gas in L2s, Avalanche and Solana

Storage Slots in Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract StorageSlots {
    uint256 a = 10;
    uint256 b = 20;
    uint256 c = 30;

    function getValueAtSlot(uint256 slot) external view returns(uint256 value) {
        assembly {
            value := sload(slot)
        }
    }
}

Opcodes

Opcodes Gas Cost

https://ethereum.github.io/yellowpaper/paper.pdf

Calling an Empty Function

Non-payable Functions

What is Gas Limit?

Why 21,000 Gas?

Solidity Optimizer

Storage Overview

1. setting a storage variable `0 to non-zero` → `20,000` gas units
2. setting a storage variable `non-zero to non-zero` → `5,000` gas units
3. setting storage from `non-zero to 0` → refund
4. Pay an additional 2,100 gas units if it's the first time accessing a variable in tx
5. Pay 100 gas units if the variable has already been touched
contract StorageExample {
    uint256 private myVar;

    function setVarToOne() external { 
				// Costs 43,344 Gas = 21,000(tx cost) + 20,000(0 -> 1 cost)
				// + 2,100 (accessing the storage for the first time in a tx)
				// + 244 (cost of doing nothing)
        myVar = 1;
    }

    function setVarToTwo() external { // Costs 26,266 Gas
				// Costs 26,266 Gas = 21,000(tx cost) + 5,000(1 -> 2 cost incl cold 
				// storage access) + 266 (cost of doing nothing)
        myVar = 2;
    }

    function setVarToZero() external { // Costs 21,400
				// 
        myVar = 0;
    }

    function getAndSetVar() external {
				// Costs 43,523 Gas = cost of (cold read + write) == cost of (write)
        uint256 _myVar = myVar;
        myVar = _myVar + 1;
    }
contract StorageExample {
    uint256 private myVar = 1;
     
    // we are setting the variable to the same value it already has
    function setVarToOne() external { 
				// Costs 23,570 Gas = 21,000(tx cost) + 2,100 (cold access)
				// + 100 (warm access cost)
        myVar = 1;
    }

Gas Cost of Array Storage

contract ArrayExample {
    uint256[] private myArray;

    function setArray(uint256[] calldata _val) external {
        myArray = _val;
    }

    function getArrayLength() external returns(uint256) {
        return myArray.length;
    }

    function getSlotValue() external returns(uint256 val) {
        assembly {
            val := sload(myArray.slot)
        }
    }
}

Refunds on setting a storage variable to 0

1. Setting to zero can cost between 200 to 5,000 gas, depending on 
how much of a refund you're able to get.
2. Deleting an array or setting many values to zero can be surprisingly expensive.
3. Setting a value from non-zero to non-zero is the same as setting it from non-zero to zero if you do
4. For every o operation try spending 24,000 gas elsewhere to get a refund.
5. counting down is more efficient than counting up.

ERC20 Transfers

Sender balanceReceiver BalanceGas CostNotes
non-zero → zerozero → non-zero46,686costly because of 0 → non zero tx and slightly cheaper because of non-zero → 0 gas refund
non-zero → non-zerozero → non-zero51,474expensive because of 0 → !0 tx
non-zero → zeronon-zero → non-zero34,374cheaper cuz no 0 → !0 tx
non-zero → non-zeronon-zero → zero29,586cheapest cuz !0 → !0 tx and a gas refund

Events cost extra gas

Storage Cost for files

Structs and Strings

contract StructExample {

    struct MyStruct {
        uint256 a;
        address b;
    }

    MyStruct myStruct;

    function setStruct() external {
				// ~65,000 = 21,000(tx cost) + 2 * 22,100 (cold access + 0 -> !0)
        myStruct = MyStruct({
            a: 10,
            b: msg.sender
        });
    }

    function setStructV2() external {
       // ~29,000 = 21,000(tx cost) + 22,000(setting variable to same cost) 
       // + 5000 (!0 -> !0 cost) 
        myStruct = MyStruct({
            a: 20,
            b: msg.sender
        });
    }

}

Storing Strings

Variable Packing

contract VariablePackingExample {
    uint128 private a = 1;
    uint128 private b = 2;

    function getSlotA() external pure returns(uint256 value) {
        assembly {
            value := a.slot
        }
    }

    function getSlotB() external pure returns(uint256 value) {
        assembly {
            value := b.slot
        }
    }

    function loadSlot0() external view returns(bytes32 value) {
        assembly {
            value := sload(0)
        }
    }

    function getOffsetA() external pure returns(uint256 value) {
        assembly {
            value := a.offset
        }
    }

    function getOffsetB() external pure returns(uint256 value) {
        assembly {
            value := b.offset
        }
    }

}

Array Length

contract ArrayCache {
    uint256 myArray[] = [1,2,3,4,5,6,7,8,9,10];
    
    function getSum() external view returns(uint256 sum) {
        // 49,119 gas units
        for(uint256 i = 0; i < myArray.length; i++) {
            sum += myArray[i];
        }
    }

    function getSumOptimized() external view returns(uint256 sum) {
        // 48,124 gas units
        uint256 _length = myArray.length;
        for(uint256 i = 0; i < _length; i++) {
            sum += myArray[i];
        }
    }
}

Memory vs Calldata Costs

contract Memory {

    function doNothing(bytes memory _myBytes) external pure {
				// 22,011 Gas
    }

     function doNothing(bytes calldata _myBytes) external pure {
				// 21,865 Gas
    }
    function doNothing(bytes memory _myBytes) external pure returns(bytes memory) {
        // 22.419
				_myBytes[0] = 0xaa;
        return _myBytes;
    }

     function doNothing(bytes calldata _myBytes) external pure returns(bytes memory) {
        // 22,442
				bytes memory _myLocalBytes = _myBytes;
        _myLocalBytes[0] = 0xaa;
        return _myLocalBytes;
    }
}

Memory Explosion

Solidity tricks

Resources