State Variables, Constants & Immutables

Declared at contract level; stored permanently in storage.

12.1 State variables

Declared at contract level; stored permanently in storage.

everywhere; compiler generates an external getter internal (default) this contract and derived contracts this contract only

State variables cannot be external. Nothing on-chain is private — anyone can read any slot with eth_getStorageAt / cast storage.

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

contract StateVars {
    uint256 public count;                 // public → getter count()
    uint256 internal secretish;           // NOT secret: all storage is publicly readable
    address private admin;                // private only restricts other contracts' access
    mapping(address => uint256) public balances;
    uint256[] public history;             // getter history(uint256 index)
}

12.2 Constants and immutables

regular state var at declaration (compile-time expression) in constructor (or declaration) inlined in bytecode in runtime bytecode 2,100 cold / 100 warm value types and string/bytes value types only

Use constant/immutable whenever a value never changes after deployment — a major gas saving.

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

contract Config {
    uint256 public constant FEE_BPS = 30;                  // fixed at compile time; inlined
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    address public immutable OWNER;                        // set once in constructor; stored in bytecode
    uint256 public immutable DEPLOYED_AT;

    constructor() {
        OWNER = msg.sender;
        DEPLOYED_AT = block.timestamp;
    }
}

12.3 Getters for complex types

Arrays: history(uint256 i) returns one element (not the whole array).

Mappings: one parameter per key.

Structs: returns members as a tuple, omitting mappings and arrays.

🧪 Part I Exercises

Deploy HelloWorld in Remix VM, call setGreeting from a non-owner account, and read the revert reason in the console.

Using cast, compute the selector of approve(address,uint256) and the keccak256 of the string "MINTER_ROLE".

Write a contract demonstrating that uint8(300) returns 44 and that uint8 x = 255; x++; reverts; then make it wrap with unchecked.

Build a Counter with increment, decrement (reverting below zero with a custom error), and reset callable only by the deployer.

Write a Todo contract with structs and arrays: create, toggle, update text, and delete (swap-and-pop).

Implement a simple address book: mapping(address => string) plus an array of keys so all entries can be enumerated.

Demonstrate the difference between storage and memory struct variables with two functions and explain the results in comments.

Write a function that takes uint256[] calldata and returns the sum, max, and min; compare its gas against a memory version.

Create an enum-driven state machine for an order (Created → Paid → Shipped → Delivered / Cancelled) that rejects invalid transitions.

Compare gas for reading a constant, an immutable, and a regular state variable using Remix or forge test --gas-report.