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.
Table of Contents
Compiler and file layout
| Command | What it does |
|---|---|
solc --version | Show the compiler version |
solc --abi --bin -o build/ Vault.sol | Write 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.sol | Function selectors, e.g. 2e1a7d4d: withdraw(uint256) |
solc --standard-json < input.json | JSON 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
| Type | Notes | Example |
|---|---|---|
uint256 / int256 | Default integer types; uint8 to uint256 in steps of 8 | uint256 total; |
bool | true / false | bool public paused; |
address / address payable | 20-byte account; only payable can receive ether via call | payable(msg.sender) |
bytes32 | Fixed-size bytes; cheaper than string for hashes and IDs | bytes32 h = keccak256(data); |
string / bytes | Dynamic data | string.concat(a, " ", b) |
mapping(address => uint256) | Key-value store; every key exists with a zero default; not iterable | balances[user] += 1; |
uint256[] / uint256[3] | Dynamic / fixed array; push(), pop(), length | depositors.push(msg.sender); |
struct | Group fields; pack small types together to save storage slots | struct Account { uint128 balance; uint64 lastDeposit; } |
enum | Named states | enum Status { Open, Paused } |
constant | Fixed at compile time; no storage used | uint256 public constant MAX = 10 ether; |
immutable | Set once in the constructor; stored in the bytecode | address public immutable owner; |
transient | Transient 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
| Keyword | Meaning |
|---|---|
external | Callable only from outside (or via this.f()); arguments can use calldata |
public | Callable from anywhere; public state variables get a getter |
internal | This contract and contracts that inherit from it |
private | This contract only. Still readable on-chain by anyone |
view / pure | Reads state / reads nothing; free when called off-chain |
payable | Function can receive ether |
virtual / override | Allow / perform overriding in a child contract |
receive() external payable | Runs on plain ether transfers with empty calldata |
fallback() external | Runs 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
| Location | Lifetime and cost |
|---|---|
storage | Persistent contract state; the most expensive to write |
memory | Temporary copy for the duration of the call |
calldata | Read-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
| Syntax | Use 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 arithmetic | Overflow 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);
}
| Rule | Why |
|---|---|
| Checks, effects, interactions | Update balances before calling out, so a re-entering call sees the new state |
Use call{value: x}("") and check the result | transfer and send are deprecated since 0.8.31 (solc warns) and forward only 2300 gas |
Never authorise with tx.origin | A malicious contract in the middle can pass the check; use msg.sender |
| Prefer pull over push payments | Let users withdraw instead of looping over recipients |
Do not rely on block.timestamp for randomness | Block producers can influence it |
| Test reverts and attacks | Write 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 { /* ... */ }
}
| Keyword | Meaning |
|---|---|
contract B is A, C | Inheritance; list bases from most basic to most derived |
abstract contract | Cannot be deployed on its own |
interface | Function declarations only; all external |
library + using L for T | Reusable 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


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.