Обзор языка Solidity: типы, функции, ошибки и события на примерах
Короткий практический тур по Solidity для тех, кто умеет читать код: структура контракта, типы, видимость, модификаторы, ошибки и события. Примеры из эскроу.
В этой статье
Solidity - основной язык смарт-контрактов для Ethereum и совместимых сетей. Он похож на JavaScript и C++, но с важными отличиями: каждая операция стоит газа, код нельзя поправить после развёртывания, а ошибка может стоить денег. Здесь мы не будем учить программированию с нуля; цель - научиться читать контракты вроде нашего и понимать, что в них происходит. Предварительно полезно прочитать что такое смарт-контракт.
Структура контракта
// 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; // константа
uint256 public dealCount; // переменная состояния
event OfferCreated(uint256 indexed dealId); // событие
error ZeroAmount(); // ошибка
function createOffer(uint256 amount) external returns (uint256) { ... }
}- Лицензия и версия компилятора (
pragma) в начале. Эскроу собран версией 0.8.24 (как проверить). - Импорты из библиотек (OpenZeppelin).
- Контракт - как класс: состояние + функции.
- Наследование (
is): контракт перенимает код родителей.
Типы данных
| Тип | Описание | Пример |
|---|---|---|
uint256, uint96, uint16 | целые без знака разных размеров | сумма, ставка |
int256 | целые со знаком | редко |
bool | истина/ложь | флаг funded |
address | адрес (20 байт) | покупатель |
address payable | адрес, которому можно отправить ETH | получатель |
bytes32 | 32 произвольных байта | хеш условий |
string, bytes | динамические данные | редко в деньгах |
enum | набор именованных значений | Status.Active |
struct | составной тип | сделка Deal |
mapping(K => V) | таблица | deals[id] |
Дробных чисел нет (почему). Размеры влияют на упаковку в хранилище.
Перечисления и структуры
enum Status { None, Offered, Active, Disputed, Settled }
struct Deal {
address buyer;
uint96 amount;
Status status;
// ...
}
mapping(uint256 => Deal) private _deals;Статусы образуют конечный автомат: сделка переходит из одного состояния в другое по строгим правилам (разбор).
Функции и видимость
function confirmDelivery(uint256 dealId) external nonReentrant { ... }Видимость:
external- вызывается только извне;public- извне и изнутри;internal- внутри контракта и наследников;private- только внутри этого контракта (но данные всё равно читаемы).
Изменяемость:
view- читает состояние, не меняет;pure- не читает и не меняет;payable- может принимать ETH.
Данные: storage, memory, calldata
Для ссылочных типов указывается место:
- storage - постоянная память контракта (дорого);
- memory - временная в рамках вызова;
- calldata - входные данные транзакции, только для чтения (дёшево).
function createOffer(OfferParams calldata p) external payable { ... }calldata для параметров функции экономит газ, потому что данные не копируются.
Модификаторы
Модификатор - повторно используемая «обёртка» над функцией:
modifier onlyOwner() {
require(msg.sender == owner(), "not owner");
_;
}_ - место, где выполняется тело функции. Встречаются onlyOwner, whenNotPaused, nonReentrant (шаблоны доступа, реентерабельность).
Ошибки
Раньше использовали require(cond, "текст"). Современный способ - пользовательские ошибки:
error WrongStatus(Status expected, Status actual);
if (d.status != expected) revert WrongStatus(expected, d.status);Они компактнее по газу и несут параметры, понятные интерфейсу. Откат отменяет все изменения транзакции.
События
event DealSettled(uint256 indexed dealId, Outcome outcome, uint256 buyerAmount, ...);
emit DealSettled(dealId, outcome, buyerAmount, sellerAmount, arbiterFee, platformFee);События пишутся в журнал транзакции и дёшевы. Сайты и индексаторы читают их, чтобы построить историю (подробнее). Поля indexed позволяют быстро фильтровать.
Глобальные переменные
| Переменная | Что это |
|---|---|
msg.sender | кто вызвал |
msg.value | сколько ETH отправлено |
block.timestamp | время блока (секунды) |
address(this) | адрес самого контракта |
uint64 deadline = uint64(block.timestamp) + 7 days;Единицы времени 1 days, 7 days - просто числа секунд.
Работа с ETH и токенами
Отправка основной монеты:
(bool ok, ) = payable(to).call{value: amount, gas: 50_000}("");Токены ERC-20 - через интерфейс IERC20 и обёртку SafeERC20, учитывающую нестандартные реализации (особенности, реализация). Порядок «сначала состояние, потом внешние вызовы» защищает от атак.
Чтение контракта: быстрый алгоритм
- Найдите структуры данных и статусы - это модель.
- Составьте список внешних функций - это интерфейс.
- Для каждой посмотрите кто может вызвать и в каком состоянии.
- Найдите места, где уходят деньги (
call,transfer) и проверьте порядок. - Прочитайте события: они расскажут, что контракт считает важным.
Что почитать дальше
Если хотите писать, а не только читать: ERC-20 с нуля и руководство по Foundry. Внутреннее устройство - в разделе об EVM.
Читайте также
- 3 · Под капотом
ERC-20 с нуля: пишем токен на Solidity и разбираем каждую строку
Минимальная реализация стандарта ERC-20 на Solidity: балансы, переводы, allowance, события, типичные ошибки и почему в продакшене нужны проверенные библиотеки.
4 мин чтения - 3 · Под капотом
Пишем эскроу на Solidity: конечный автомат и разбор реального контракта
Как устроен эскроу-контракт в коде: статусы, переходы, проверки доступа, события и расчёт выплат. Минимальная версия с нуля и сравнение с боевым контрактом.
7 мин чтения
Упоминается в статьях
- Что такое смарт-контракт и почему ему можно доверять деньги
- Словарь терминов: блокчейн, кошельки, смарт-контракты и эскроу от A до Я
- EVM изнутри: стек, память, storage, calldata и стоимость газа
- ERC-20 с нуля: пишем токен на Solidity и разбираем каждую строку
- Нестандартные токены: USDT без return, fee-on-transfer, блэклисты и decimals
- Foundry для начинающих: компиляция, тесты, фаззинг и локальная сеть
- Тестирование смарт-контрактов: unit, fuzz, инварианты и статический анализ
Готовы попробовать?
Создайте первую сделку за пару минут. Деньги блокируются в смарт-контракте и выплачиваются по правилам, которые видны заранее.

