# Gas Optimisation — Blockchain & Smart Contracts (Solidity)

Source: https://www.geekswithgeeks.com/en/solidity/g-gas

> Reduce gas costs without sacrificing safety or clarity.

## Where gas goes

Gas costs are dominated by **storage**: writing a new non-zero slot costs about 20,000 gas, updating an existing slot about 5,000 (less when the slot was already accessed in the transaction), and the first read of a slot in a transaction is a "cold" access at about 2,100 gas, with later "warm" reads much cheaper. Practical optimisations: **pack** related small variables (such as `uint128`, `uint64`, `bool`, `address`) into one 32-byte slot by declaring them adjacently; use **`constant`** and **`immutable`** for values that never change; **cache storage reads** in local variables; use **`calldata`** for read-only external parameters; prefer **custom errors** to revert strings; **emit events** instead of storing data that only off-chain apps need; avoid unbounded loops; use `unchecked` increments in loops where overflow is impossible; and use **mappings** instead of arrays for lookups. Enable the compiler **optimizer** (and consider the IR pipeline, `via_ir`). Measure with `forge snapshot` or gas reports rather than guessing. On **layer 2s**, calldata posted to Ethereum can dominate costs, so data size matters. Never trade away **safety or readability** for small savings; audits of clever code cost more than the gas saved.

## Gas cost by operation

Storage writes dwarf arithmetic; most optimisations reduce storage access.

![A horizontal bar chart with a very long bar for storage writes, a medium bar for cold storage reads, and tiny bars for memory and arithmetic.](assets/figures/solidity/section-8-map.svg) — Figure 8.1 — Relative gas costs of common operations.

## Before and after optimisation

Packing, immutables, cached reads and custom errors.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// Before: 4 storage slots per stake, repeated storage reads, string errors
contract StakingBefore {
    struct Stake { uint256 amount; uint256 since; bool active; address owner; }
    mapping(uint256 => Stake) public stakes;
    address public admin;                         // never changes, but stored in storage
    uint256 public rewardRateBps = 500;           // never changes either

    constructor() { admin = msg.sender; }

    function reward(uint256 id) external view returns (uint256) {
        require(stakes[id].active, "Stake is not active right now");
        return stakes[id].amount * rewardRateBps * (block.timestamp - stakes[id].since) / (10_000 * 365 days);
    }
}

// After: 2 slots per stake, immutable/constant config, one storage read, custom error
contract StakingAfter {
    struct Stake {
        uint128 amount;     // slot 0: amount (16 bytes) + since (8 bytes) + active (1 byte)
        uint64 since;
        bool active;
        address owner;      // slot 1
    }
    mapping(uint256 => Stake) public stakes;
    address public immutable admin;
    uint256 public constant REWARD_RATE_BPS = 500;

    error InactiveStake(uint256 id);

    constructor() { admin = msg.sender; }

    function reward(uint256 id) external view returns (uint256) {
        Stake memory s = stakes[id];             // read once
        if (!s.active) revert InactiveStake(id);
        return uint256(s.amount) * REWARD_RATE_BPS * (block.timestamp - s.since) / (10_000 * 365 days);
    }
}
```

## Measure, then optimise

Use `forge snapshot --diff` or a gas reporter to compare versions. Many "optimisations" found online save a few gas while making code harder to audit; storage layout and avoiding unnecessary writes matter far more.

**Quiz:** Which change usually saves the most gas?

- [ ] Renaming variables
- [ ] Using shorter comments
- [x] Reducing storage writes and packing variables into fewer slots
- [ ] Adding more events

*Answer:* Reducing storage writes and packing variables into fewer slots. Storage operations are by far the most expensive.
