Intermediate

Gas Optimisation Basics — Kudos build — optimization pass

Write cheaper, cleaner code.

Developer Intermediate
5/6 — Gas Optimisation Basics — Kudos build — optimization pass

Objective Make the Kudos badge/token flows cheaper without losing clarity.

Blockchains solve coordination problems, not just financial ones.
— Vitalik Buterin

Flow Add batch minting for badges, cache lengths, and keep unchecked blocks documented. Snapshot gas before/after.

What to write

  1. Batch mint
    Loop with cached length and documented unchecked increment.
    function batchMint(address[] calldata tos, string[] calldata uris) external onlyRole(MINTER_ROLE) {
        uint256 len = tos.length;
        require(len == uris.length, "len mismatch");
        uint256 id = _nextId;
        unchecked {
            for (uint256 i; i < len; i++) {
                _safeMint(tos[i], id);
                _setTokenURI(id, uris[i]);
                id++;
            }
        }
        _nextId = id;
    }

    Caching length + unchecked increment trims gas while remaining safe.

  2. Pack + cache storage
    Pack small fields; cache SLOADs in memory.
    struct BadgeMeta { uint64 minted; uint64 burned; uint128 reserved; }
    mapping(uint256 => BadgeMeta) internal _meta;
    
    function stats(uint256 id) external view returns (uint64 minted, uint64 burned) {
        BadgeMeta memory m = _meta[id]; // single SLOAD
        return (m.minted, m.burned);
    }

    Packing and caching reduce SLOADs; keep types documented to avoid layout mistakes.

  3. Measure
    Record before/after gas for hot paths.

    Keep only optimizations with meaningful savings.

Why Batching reduces gas per badge; documenting unchecked math and measuring deltas keeps optimizations safe and defensible.

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

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

contract KudosBadge is ERC721URIStorage, AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    uint256 private _nextId = 1;
    string private _base;

    struct BadgeMeta { uint64 minted; uint64 burned; uint128 reserved; }
    mapping(uint256 => BadgeMeta) internal _meta;

    constructor(string memory base) ERC721("Kudos Badge", "KBDG") {
        _base = base;
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);
    }

    function batchMint(address[] calldata tos, string[] calldata uris) external onlyRole(MINTER_ROLE) {
        uint256 len = tos.length;
        require(len == uris.length, "len mismatch");
        uint256 id = _nextId;
        unchecked {
            for (uint256 i; i < len; i++) {
                _safeMint(tos[i], id);
                _setTokenURI(id, uris[i]);
                _meta[id].minted++;
                id++;
            }
        }
        _nextId = id;
    }

    function _baseURI() internal view override returns (string memory) { return _base; }
}

Next You now have a full intermediate-grade Kudos stack—ship, monitor, and iterate.

← Previous section
Next section →