Blockchain · Guide

Smart Contracts

Code that runs on a machine nobody controls, cannot be patched, and holds real money.

— min read Blockchain

Immutable Code With Money In It

A smart contract is a program deployed to an address, executed by every node, with permanent public state. Once deployed the bytecode cannot change — there is no hotfix, and a bug is a bug forever.

Three constraints shape everything about how this code is written. Execution is deterministic, because every node must reach the same result — no randomness, no clock, no network calls. Everything is public, including variables marked private, which only restricts other contracts, not observers. And every operation costs money, so efficiency is a budget line rather than a nicety.

Solidity

Solidity is the dominant language: statically typed, curly-braced, and unforgiving. A contract looks familiar and behaves differently.

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

contract Escrow {
    address public immutable seller;   // set once, cheap to read
    mapping(address => uint256) private deposits;

    event Deposited(address indexed from, uint256 amount);

    error NotSeller();

    constructor(address _seller) { seller = _seller; }

    function deposit() external payable {
        deposits[msg.sender] += msg.value;
        emit Deposited(msg.sender, msg.value);
    }

    function release(address to) external {
        if (msg.sender != seller) revert NotSeller();
        uint256 amount = deposits[to];
        deposits[to] = 0;                       // state first
        (bool ok, ) = to.call{value: amount}(""); // transfer after
        require(ok, "transfer failed");
    }
}
DetailWhy it matters
msg.senderThe caller — the basis of every access check
payableA function may only receive value if marked so
view / pureReads or computes without writing — free when called off-chain
Custom errorsFar cheaper than revert strings
indexed eventsFilterable logs, the cheap way to publish data

Note the ordering in release: state is updated before the external call. That is not style — it is the pattern that prevents reentrancy, and the reason is in the security lesson.

The EVM

The Ethereum Virtual Machine is a stack machine that every node runs identically. Solidity compiles to its bytecode, and the machine's shape explains most of what feels strange about contract code.

LocationLifetimeCost
StackWithin one operationAlmost free
MemoryOne transactionCheap, grows quadratically
StorageForever, on every nodeExtremely expensive
CalldataRead-only transaction inputCheapest place to read from
LogsPermanent, not readable by contractsCheap

Storage is a mapping of 256-bit slots. Two uint128 fields declared next to each other share one slot and one write; the same fields separated by a uint256 cost two. Declaration order is a performance decision.

Transactions are atomic. A revert undoes every state change in the call — but the gas already burned is not refunded, and any external contract you called observed your state before the revert.

Token Standards

A token is just a contract tracking balances. Standards matter because they are what let wallets, exchanges and other contracts interact with something they have never seen before.

StandardForKey idea
ERC-20Fungible tokensBalances and allowances
ERC-721Non-fungible tokensOne owner per unique id
ERC-1155Mixed batchesMany token types in one contract
ERC-4626Yield vaultsA shared deposit and share-price interface

The ERC-20 approve-then-transferFrom flow is worth understanding because it is the source of so much risk: you grant a contract an allowance, and it can move that much of your balance whenever it likes, until you revoke it. Unlimited approvals are convenient and are how a great deal of value has been drained after an unrelated contract turned out to be compromised.

Use audited implementations — OpenZeppelin's, typically — rather than writing a token from scratch. Every subtle deviation from the standard is a place where an integration silently breaks or a balance is lost.

Upgrade Patterns

Deployed bytecode is immutable, so upgrading means separating storage from logic. A proxy contract holds the state and the address; calls arrive at the proxy and are delegated to an implementation contract that can be swapped.

// The proxy delegates: code from the implementation, storage from the proxy
fallback() external payable {
    address impl = _implementation();
    assembly {
        calldatacopy(0, 0, calldatasize())
        let ok := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
        returndatacopy(0, 0, returndatasize())
        switch ok case 0 { revert(0, returndatasize()) }
                  default { return(0, returndatasize()) }
    }
}
Storage layout must only ever be appended to. Reordering or inserting a variable in a new implementation makes it read a different slot than before — the old value is interpreted as something else entirely, and funds move accordingly.

Upgradeability is also a trust decision, not just a technique. Whoever can swap the implementation can replace the rules — so the honest question for any protocol is who holds that key, and whether a timelock and a multisig stand between them and your money.

Interview Questions

Why must contract execution be deterministic?

Every node re-executes it and must reach the same state. That rules out randomness, wall-clock time and network calls — anything two nodes could disagree about.

Is a private variable actually private?

No. Storage is public on every node; the modifier only stops other contracts reading it through the language. Anything secret must be hashed or kept off-chain.

Why does variable declaration order matter?

Storage is 256-bit slots. Fields that fit together are packed into one slot and one write if declared adjacently; separated by a full-width field they cost two.

What is the risk of an unlimited ERC-20 approval?

The approved contract can move that balance at any time in the future. If it is later compromised or was malicious, the allowance is still live until revoked.

How does a proxy upgrade work?

The proxy holds storage and delegatecalls into an implementation contract, so logic runs against the proxy's state. Swapping the implementation address changes the code without moving the data.

What is the danger with upgradeable storage?

Layout must only be appended. Reordering or inserting a variable makes the new implementation read a slot that holds something else, silently corrupting state.

Quick Quiz

1. A variable marked private in Solidity is…
2. The cheapest place to read transaction input from is…
3. ERC-721 represents…
4. In a proxy upgrade, storage lives in…
5. Custom errors are preferred over revert strings because they are…