Solidity с нуля: из чего состоит смарт-контракт в 2026 году
Контракт на Solidity состоит из шести частей: лицензии и pragma, импортов, переменных состояния, событий и ошибок, модификаторов и функций. Актуальная версия компилятора на 29 октября 2026 года — 0.8.38, она вышла 22 октября и исправляет ошибку в оптимизаторе Yul. Ниже разбираем каждую часть на коротком примере и объясняем, что чаще всего ломают новички.
Версия компилятора и почему её фиксируют
Solidity развивается быстро: в 2026 году вышли версии от 0.8.34 до 0.8.38, и в нескольких из них исправлены ошибки, которые влияли на итоговый байт-код. Список изменений публикуется в блоге soliditylang.org и в релизах репозитория. Поэтому в боевом коде версию фиксируют точно, а не диапазоном.
| Запись | Что значит | Когда уместна |
|---|---|---|
pragma solidity 0.8.38; | Только эта версия | Боевой контракт, который пойдёт в аудит и деплой |
pragma solidity ^0.8.24; | Любая 0.8.x от 0.8.24 | Библиотеки, например OpenZeppelin, чтобы подходить разным проектам |
pragma solidity >=0.8.20 <0.9.0; | Явный диапазон | Интерфейсы и общие файлы |
Вторая настройка, о которой забывают, — evmVersion. Компилятор по умолчанию собирает код под свежую версию EVM, а сеть, куда вы публикуете контракт, может её ещё не поддерживать. Известный пример — инструкция PUSH0: с версии 0.8.20 компилятор стал её использовать, и контракты не разворачивались в сетях без этой инструкции. Выставляйте evmVersion под целевую сеть явно.
Скелет контракта по частям
Возьмём учебный пример: простое хранилище депозитов, где пользователь кладёт эфир и забирает свой остаток. Это пример для объяснения структуры, а не готовый продукт.
// SPDX-License-Identifier: MIT— лицензия, компилятор предупреждает, если её нет.pragma solidity 0.8.38;— точная версия компилятора.import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";— проверенная защита из библиотеки.contract Vault is ReentrancyGuardTransient {— объявление и наследование.mapping(address => uint256) public balanceOf;— состояние: остатки пользователей.event Deposited(address indexed user, uint256 amount);— событие для внешних систем.error NothingToWithdraw();— собственная ошибка вместо строки.function deposit() external payable { balanceOf[msg.sender] += msg.value; emit Deposited(msg.sender, msg.value); }function withdraw() external nonReentrant { ... }— вывод с защитой от повторного входа.
Переменные состояния
Всё, что объявлено на уровне контракта, хранится в storage — постоянной памяти блокчейна. Это самое дорогое место: первая запись в пустую ячейку стоит 20 000 газа, чтение «холодной» ячейки — 2 100. Модификатор constant подставляет значение при компиляции, immutable задаётся один раз в конструкторе. Оба не занимают storage и экономят газ.
События
События (event) не меняют состояние, а пишут записи в лог транзакции. Их читают бэкенды, индексаторы и интерфейсы. Поля с indexed можно фильтровать. Правило простое: каждое изменение денег, прав или настроек сопровождается событием, иначе мониторинг и бухгалтерия ничего не увидят.
Ошибки и проверки
Собственные ошибки (error NothingToWithdraw();) дешевле строковых сообщений и понятнее интерфейсу. С версии 0.8.0 арифметика проверяется автоматически: переполнение uint256 откатывает транзакцию. Блок unchecked { } отключает проверку, и его используют только там, где переполнение доказуемо невозможно.
Модификаторы и видимость
| Ключевое слово | Кто может вызвать | Примечание |
|---|---|---|
external | Только извне, другими транзакциями и контрактами | Типичный выбор для публичного API |
public | Извне и изнутри контракта | Для переменных автоматически создаётся геттер |
internal | Этот контракт и наследники | По умолчанию для функций без указания видимости в старом коде |
private | Только этот контракт | Не скрывает данные: storage читается любым узлом |
view / pure | Не меняют состояние | Вызов вне транзакции бесплатен |
payable | Функция принимает эфир | Без него перевод эфира откатится |
Функция вывода: правильный порядок действий
Самое важное правило для функций, которые отправляют деньги, — «проверки, изменения, взаимодействия» (checks-effects-interactions). Сначала проверяем условия, потом обновляем состояние, и только в конце вызываем внешний адрес. Подробно об этом в разборе reentrancy и защиты от неё.
uint256 amount = balanceOf[msg.sender];if (amount == 0) revert NothingToWithdraw();— проверка.balanceOf[msg.sender] = 0;— изменение состояния до перевода.(bool ok, ) = msg.sender.call{value: amount}("");— внешнее взаимодействие.if (!ok) revert TransferFailed();— обработка неудачи.emit Withdrawn(msg.sender, amount);— событие.
Типичные ошибки новичков
- Проверка прав через
tx.originвместоmsg.sender. Это открывает фишинговые сценарии, когда пользователя заставляют вызвать вредный контракт. - Циклы по массиву без ограничения длины. Когда массив вырастет, функция перестанет помещаться в лимит газа блока.
- Использование
transferиsendс фиксированными 2 300 газа. Получатель-контракт может не уложиться в лимит, поэтому применяютcallс проверкой результата и защитой от повторного входа. - Копирование кода токенов и прав доступа вместо импорта OpenZeppelin Contracts. Актуальная версия библиотеки — 5.7.0.
- Отсутствие тестов на граничные значения: ноль, максимум, повторный вызов, вызов чужим адресом.
- Контракт больше 24 576 байт. Это лимит размера кода из EIP-170, такой контракт не развернуть.
Наследование и конструктор
Solidity поддерживает множественное наследование. Порядок базовых контрактов в объявлении важен: компилятор выстраивает их линейно, и при переопределении функции нужно перечислить все родительские версии в override(...). Конструктор выполняется один раз при деплое, и именно в нём задают владельца, начальную эмиссию и неизменяемые параметры. У контрактов за прокси конструктор не работает, вместо него используют функцию initialize, об этом мы пишем в статье об апгрейдируемых контрактах.
Хорошая практика — держать контракт маленьким и понятным: одна ответственность, минимум административных функций, явные события. Если логика разрастается, её делят на несколько контрактов или выносят общие части в библиотеки.
Инструменты, с которыми стоит начать
| Инструмент | Для чего | Где документация |
|---|---|---|
| Remix | Попробовать код в браузере без установки | remix.ethereum.org |
| Foundry (forge, cast, anvil) | Сборка, тесты на Solidity, фаззинг, локальная сеть | getfoundry.sh, актуальная версия 1.8.7 |
| Hardhat 3 | Сборка и тесты на TypeScript и Solidity, плагины | hardhat.org, версия 3.21 |
| Slither | Статический анализ типовых ошибок | github.com/crytic/slither |
Как писать тесты, мы разобрали отдельно в статье о тестировании в Foundry и Hardhat. Документация языка с примерами лежит на docs.soliditylang.org.
Что из этого важно заказчику
Даже если вы не пишете код сами, по структуре контракта можно задать подрядчику точные вопросы. Зафиксирована ли версия компилятора и evmVersion? Какие события эмитятся при каждом изменении денег и прав? Импортированы ли токены и права из OpenZeppelin или написаны вручную? Кто имеет доступ к функциям администратора? Ответы на них хорошо показывают зрелость команды. Если нужна помощь с проектом, загляните на страницу разработки смарт-контрактов. Для токена есть отдельное решение ERC-20 и BEP-20, а перед запуском с реальными деньгами понадобится аудит смарт-контракта.
Вопросы и ответы
Какую версию Solidity использовать в 2026 году?
На 29 октября 2026 года последняя версия — 0.8.38. Для нового контракта берите актуальную стабильную версию, фиксируйте её точно и следите за списком исправленных ошибок компилятора.
Сколько времени нужно, чтобы выучить Solidity?
Синтаксис похож на JavaScript и C++, базу разработчик осваивает за несколько недель. Безопасная разработка требует больше: понимания EVM, газа, типовых уязвимостей и привычки писать тесты на граничные случаи.
Можно ли писать смарт-контракты без OpenZeppelin?
Можно, но для стандартных вещей это лишний риск. Токены, права доступа и защиту от повторного входа лучше импортировать из проверенной библиотеки и писать вручную только бизнес-логику.
Чем Solidity отличается от Vyper?
Оба языка компилируются в байт-код EVM. Solidity распространён шире, у него больше библиотек и инструментов. Vyper проще и строже по синтаксису, но экосистема у него меньше.
Читайте также
Сколько стоит разработка смарт-контракта и из чего складывается цена
БлокчейнСмарт-контракт простыми словами: что это и когда бизнесу он не нужен
БлокчейнАпгрейдируемые смарт-контракты: proxy, UUPS, плюсы и риски
БлокчейнТестирование смарт-контрактов: Foundry, Hardhat, фаззинг, инварианты
БлокчейнЧек-лист перед аудитом смарт-контракта: что подготовить команде
БлокчейнДеплой и верификация смарт-контракта: ключи, скрипты, Etherscan