A tour of the Solidity language - types, functions, errors and events by example
A short practical tour of Solidity for those who can read code: contract structure, types, visibility, modifiers, errors and events. Examples from the escrow.
In this article
Solidity is the main smart contract language for Ethereum and compatible networks. It resembles JavaScript and C++, but with important differences: every operation costs gas, code cannot be fixed after deployment, and a mistake can cost money. Here we will not teach programming from scratch; the goal is to learn to read contracts like ours and understand what happens in them. It is useful to first read what a smart contract is.
Contract structure
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
contract Escrow is Ownable2Step {
uint16 public constant PLATFORM_FEE_BPS = 100; // a constant
uint256 public dealCount; // a state variable
event OfferCreated(uint256 indexed dealId); // an event
error ZeroAmount(); // an error
function createOffer(uint256 amount) external returns (uint256) { ... }
}- The licence and compiler version (
pragma) at the start. The escrow is built with version 0.8.24 (how to verify). - Imports from libraries (OpenZeppelin).
- The contract - like a class: state + functions.
- Inheritance (
is): the contract takes over the parents' code.
Data types
| Type | Description | Example |
|---|---|---|
uint256, uint96, uint16 | unsigned integers of different sizes | amount, rate |
int256 | signed integers | rare |
bool | true/false | the funded flag |
address | an address (20 bytes) | the buyer |
address payable | an address ETH can be sent to | a recipient |
bytes32 | 32 arbitrary bytes | the terms hash |
string, bytes | dynamic data | rare in money code |
enum | a set of named values | Status.Active |
struct | a composite type | the Deal |
mapping(K => V) | a table | deals[id] |
There are no fractional numbers (why). Sizes affect packing in storage.
Enums and structs
enum Status { None, Offered, Active, Disputed, Settled }
struct Deal {
address buyer;
uint96 amount;
Status status;
// ...
}
mapping(uint256 => Deal) private _deals;Statuses form a state machine: a deal passes from one state to another by strict rules (analysis).
Functions and visibility
function confirmDelivery(uint256 dealId) external nonReentrant { ... }Visibility:
external- callable only from outside;public- from outside and inside;internal- inside the contract and its heirs;private- only inside this contract (though the data is still readable).
Mutability:
view- reads state, does not change it;pure- neither reads nor changes;payable- can accept ETH.
Data locations: storage, memory, calldata
For reference types a location is specified:
- storage - the contract's persistent memory (expensive);
- memory - temporary within a call;
- calldata - the transaction's input data, read-only (cheap).
function createOffer(OfferParams calldata p) external payable { ... }calldata for function parameters saves gas because the data is not copied.
Modifiers
A modifier is a reusable "wrapper" around a function:
modifier onlyOwner() {
require(msg.sender == owner(), "not owner");
_;
}_ is where the function body runs. You will meet onlyOwner, whenNotPaused, nonReentrant (access patterns, reentrancy).
Errors
Previously require(cond, "text") was used. The modern way is custom errors:
error WrongStatus(Status expected, Status actual);
if (d.status != expected) revert WrongStatus(expected, d.status);They are cheaper in gas and carry parameters meaningful to the interface. A revert cancels all changes of the transaction.
Events
event DealSettled(uint256 indexed dealId, Outcome outcome, uint256 buyerAmount, ...);
emit DealSettled(dealId, outcome, buyerAmount, sellerAmount, arbiterFee, platformFee);Events are written to the transaction log and are cheap. Websites and indexers read them to build history (more). indexed fields allow fast filtering.
Global variables
| Variable | What it is |
|---|---|
msg.sender | who called |
msg.value | how much ETH was sent |
block.timestamp | the block's time (seconds) |
address(this) | the contract's own address |
uint64 deadline = uint64(block.timestamp) + 7 days;The time units 1 days, 7 days are just numbers of seconds.
Handling ETH and tokens
Sending the main coin:
(bool ok, ) = payable(to).call{value: amount, gas: 50_000}("");ERC-20 tokens go through the IERC20 interface and the SafeERC20 wrapper, which allows for non-standard implementations (specifics, implementation). The order "state first, external calls after" protects against attacks.
Reading a contract: a quick algorithm
- Find the data structures and statuses - that is the model.
- List the external functions - that is the interface.
- For each, see who can call and in which state.
- Find the places where money goes out (
call,transfer) and check the order. - Read the events: they tell what the contract considers important.
What to read next
If you want to write, not only read: ERC-20 from scratch and the Foundry tutorial. The internals are in the section about the EVM.
Keep reading
- 3 · Under the hood
ERC-20 from scratch - writing a token in Solidity and analysing every line
A minimal ERC-20 implementation in Solidity: balances, transfers, allowances, events, common mistakes and why proven libraries are needed in production.
5 min read - 3 · Under the hood
Writing escrow in Solidity: the state machine and a real contract walkthrough
How an escrow contract is built in Solidity: statuses, transitions, access checks, events and payouts - a minimal version and the production contract.
8 min read
Mentioned in
- What is a smart contract and why you can trust it with money
- Glossary - blockchain, wallets, smart contracts and escrow from A to Z
- The EVM under the hood: stack, memory, storage, calldata and the cost of gas
- ERC-20 from scratch - writing a token in Solidity and analysing every line
- Non-standard tokens: USDT without a return value, fee-on-transfer and blacklists
- Foundry for beginners - compiling, tests, fuzzing and a local network
- Testing smart contracts: unit, fuzz, invariants and static analysis
Ready to try it?
Create your first deal in a couple of minutes. The money is locked in a smart contract and paid out by rules you can see in advance.

