Emergency server help: get in touch

Solidity Cheat Sheet: 0.8.x Types, Functions and Errors

A compact Solidity 0.8.x reference covering solc, types, data locations, functions, custom errors, events, inheritance and safe ether transfers.

Published 6 min read

Short answer: Start every file with an SPDX licence line and pragma solidity ^0.8.28;. Use uint256, address, mapping and struct for state, memory or calldata for function arguments, and external, public, internal or private for visibility. Since 0.8.0, arithmetic reverts on overflow. Reject bad input with custom errors (revert InsufficientBalance(bal, amount)), log changes with event/emit, and send ether with call{value: x}("") after updating state.

The examples in this Solidity cheat sheet were compiled with solc 0.8.37 and tested with Foundry (forge 1.5.1) on Linux on 9 October 2026. They are teaching examples, not audited code.

Compiler and file layout

CommandWhat it does
solc --versionShow the compiler version
solc --abi --bin -o build/ Vault.solWrite ABI and bytecode files for each contract
solc --optimize --optimize-runs 200 ...Enable the optimiser; tune runs for deploy cost vs call cost
solc --via-ir ...Compile through the Yul IR pipeline (often fixes “stack too deep”)
solc --evm-version cancun ...Target an older EVM; the default in 0.8.31+ is osaka
solc --hashes Vault.solFunction selectors, e.g. 2e1a7d4d: withdraw(uint256)
solc --standard-json < input.jsonJSON in and out, as used by build tools

For build servers and CI: download the static solc binary for the exact version from binaries.soliditylang.org (Ubuntu PPA packages were discontinued in 0.8.31) and pin that version, so every build produces the same bytecode. Most projects drive solc through Foundry or Hardhat.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;      // 0.8.28+ needed for transient state variables

import {IERC20} from "./IERC20.sol";

contract Vault {
    // state variables, events, errors, modifiers, functions
}

Types and state variables

TypeNotesExample
uint256 / int256Default integer types; uint8 to uint256 in steps of 8uint256 total;
booltrue / falsebool public paused;
address / address payable20-byte account; only payable can receive ether via callpayable(msg.sender)
bytes32Fixed-size bytes; cheaper than string for hashes and IDsbytes32 h = keccak256(data);
string / bytesDynamic datastring.concat(a, " ", b)
mapping(address => uint256)Key-value store; every key exists with a zero default; not iterablebalances[user] += 1;
uint256[] / uint256[3]Dynamic / fixed array; push(), pop(), lengthdepositors.push(msg.sender);
structGroup fields; pack small types together to save storage slotsstruct Account { uint128 balance; uint64 lastDeposit; }
enumNamed statesenum Status { Open, Paused }
constantFixed at compile time; no storage useduint256 public constant MAX = 10 ether;
immutableSet once in the constructor; stored in the bytecodeaddress public immutable owner;
transientTransient storage: cleared at the end of each transaction (0.8.28+, value types only)bool transient locked;

Units and globals: 1 ether, 1 gwei, 1 days, msg.sender, msg.value, block.timestamp, block.number, address(this).balance.

Functions, visibility and modifiers

KeywordMeaning
externalCallable only from outside (or via this.f()); arguments can use calldata
publicCallable from anywhere; public state variables get a getter
internalThis contract and contracts that inherit from it
privateThis contract only. Still readable on-chain by anyone
view / pureReads state / reads nothing; free when called off-chain
payableFunction can receive ether
virtual / overrideAllow / perform overriding in a child contract
receive() external payableRuns on plain ether transfers with empty calldata
fallback() externalRuns when no function matches
modifier onlyOwner() {
    require(msg.sender == owner, NotOwner(msg.sender));   // custom error in require (0.8.27+)
    _;                                                    // the function body runs here
}

function setStatus(Status s) external onlyOwner {
    status = s;
}

function balanceOf(address who) external view returns (uint256) {
    return accounts[who].balance;
}

Data locations: storage, memory and calldata

LocationLifetime and cost
storagePersistent contract state; the most expensive to write
memoryTemporary copy for the duration of the call
calldataRead-only function input; the cheapest choice for external arguments
Account storage acct = accounts[msg.sender];Reference: changes write to storage
Account memory acct = accounts[msg.sender];Copy: changes are lost when the function ends
function hash(string calldata s) external pure returns (bytes32) {
    return keccak256(abi.encodePacked(s));
}

function joinNames(string memory a, string memory b) external pure returns (string memory) {
    return string.concat(a, " ", b);
}

Errors, checks and arithmetic

SyntaxUse it for
error InsufficientBalance(uint256 available, uint256 requested);Declare a custom error (cheaper than revert strings)
if (x > bal) revert InsufficientBalance(bal, x);Revert with typed data callers can decode
require(cond, "message") / require(cond, MyError())Input checks
assert(cond)Internal invariants that should never fail
Checked arithmeticOverflow and underflow revert with Panic(0x11) since 0.8.0
unchecked { ++i; }Skip the check when overflow is impossible; saves gas
try other.f() returns (uint v) { } catch Error(string memory r) { } catch (bytes memory d) { }Handle a failing external call
function sumTo(uint256 n) external pure returns (uint256 total) {
    for (uint256 i = 1; i <= n; ) {
        total += i;              // checked: reverts on overflow
        unchecked { ++i; }       // safe: i cannot overflow before n
    }
}

Events

event Deposited(address indexed from, uint256 amount);

function deposit() public payable {
    if (status == Status.Paused) revert Paused();
    require(msg.value > 0 && msg.value <= MAX_DEPOSIT, "bad amount");
    Account storage acct = accounts[msg.sender];
    if (acct.balance == 0) depositors.push(msg.sender);
    acct.balance += uint128(msg.value);
    acct.lastDeposit = uint64(block.timestamp);
    emit Deposited(msg.sender, msg.value);
}

Up to three parameters can be indexed, which makes them searchable as log topics. Events are cheap storage for history that contracts never need to read back; off-chain indexers and front ends read them instead.

Sending ether and reentrancy

bool transient locked;

modifier nonReentrant() {
    require(!locked, "reentrant call");
    locked = true;
    _;
    locked = false;
}

function withdraw(uint256 amount) external nonReentrant {
    uint256 bal = accounts[msg.sender].balance;                        // checks
    if (amount > bal) revert InsufficientBalance(bal, amount);
    accounts[msg.sender].balance = uint128(bal - amount);             // effects
    (bool ok, ) = payable(msg.sender).call{value: amount}("");       // interactions
    if (!ok) revert TransferFailed();
    emit Withdrawn(msg.sender, amount);
}
RuleWhy
Checks, effects, interactionsUpdate balances before calling out, so a re-entering call sees the new state
Use call{value: x}("") and check the resulttransfer and send are deprecated since 0.8.31 (solc warns) and forward only 2300 gas
Never authorise with tx.originA malicious contract in the middle can pass the check; use msg.sender
Prefer pull over push paymentsLet users withdraw instead of looping over recipients
Do not rely on block.timestamp for randomnessBlock producers can influence it
Test reverts and attacksWrite tests that call your contract from a hostile contract

Inheritance, interfaces and libraries

interface IVault {
    function deposit() external payable;
    function withdraw(uint256 amount) external;
}

library MathLib {
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }
}

abstract contract Owned {
    address public immutable owner;
    error NotOwner(address caller);
    constructor() { owner = msg.sender; }
}

contract Vault is IVault, Owned {
    using MathLib for uint256;            // x.min(100)
    function deposit() public payable override { /* ... */ }
    function withdraw(uint256 amount) external override { /* ... */ }
}
KeywordMeaning
contract B is A, CInheritance; list bases from most basic to most derived
abstract contractCannot be deployed on its own
interfaceFunction declarations only; all external
library + using L for TReusable functions called as methods on a type
super.f()Call the next implementation in the inheritance order

Official documentation (checked 9 October 2026): Solidity 0.8.37 documentation · Solidity: Cheatsheet · Solidity: Types · Solidity: Expressions and Control Structures · Solidity: Security Considerations · Solidity: Using the Compiler · Solidity 0.8.31 release announcement · Solidity 0.8.37 release announcement

Solidity Cheat Sheet at a glance

Solidity Cheat Sheet – short answer
In short: A compact Solidity 0.8.x reference covering solc, types, data locations, functions, custom errors, events, inheritance and safe ether transfers.
Solidity Cheat Sheet – what this guide covers
Solidity Cheat Sheet: what this guide covers.

Related: JavaScript Cheat Sheet: Modern Syntax for Browser and Node · TypeScript Cheat Sheet: Types, Generics and tsconfig · Hash Generator (MD5, SHA-1, SHA-256, SHA-512).

Reading a lot of our guides? Go ad-free with srvScripts Pro for $6 a month. It also pays for new guides. Cancel any time.

Share this guide

Free website test

Is your website set up right?

Check SSL, security headers, redirects, robots.txt, sitemap, llms.txt and security.txt in one test. It takes about 30 seconds.