Перейти к содержимому
Все статьи
Блокчейн

Solidity с нуля: из чего состоит смарт-контракт в 2026 году

·30 октября 2026 г.· 10 мин чтения
Нужен смарт-контракт или dApp?Разработка смарт-контрактов

Контракт на 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 и защиты от неё.

  1. uint256 amount = balanceOf[msg.sender];
  2. if (amount == 0) revert NothingToWithdraw(); — проверка.
  3. balanceOf[msg.sender] = 0; — изменение состояния до перевода.
  4. (bool ok, ) = msg.sender.call{value: amount}(""); — внешнее взаимодействие.
  5. if (!ok) revert TransferFailed(); — обработка неудачи.
  6. 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 проще и строже по синтаксису, но экосистема у него меньше.

Нужна помощь с проектом?

Обсудим задачу и предложим решение — от сайта до SaaS и защиты.

Связаться