Оптимизация газа смарт-контрактов
Деплой контракта обошёлся в 0.8 ETH вместо ожидаемых 0.3. Или пользователи платят по $15 за каждый transfer() при gas price 30 gwei, хотя конкуренты — по $4. Это не вопрос «когда у сети нагрузка спадёт». Это вопрос storage layout, неоптимальных opcodes и паттернов, которые компилятор Solidity не исправляет за вас.
Откуда берётся лишний газ
Storage — главный источник потерь
SSTORE стоит 20 000 gas при записи в холодный слот, 2 900 gas при обновлении тёплого. SLOAD — 2 100 gas для холодного, 100 для тёплого (EIP-2929). Именно поэтому архитектура хранилища определяет 60-80% стоимости контракта.
Slot packing — первый инструмент. EVM хранит данные в 32-байтовых слотах. Если объявить переменные так:
uint128 a; // слот 0
uint256 b; // слот 1 — ОТДЕЛЬНЫЙ слот, хотя мог бы упаковаться
uint128 c; // слот 2
Получаем три слота. Переупорядочивание:
uint128 a; // слот 0, байты 0-15
uint128 c; // слот 0, байты 16-31 — упакованы!
uint256 b; // слот 1
Даёт два слота. На контракте с 10 000 деплоями экономия — сотни ETH суммарно по экосистеме.
На практике: один крупный DeFi-проект пришёл к нам с ERC-1155 контрактом, где структура TokenInfo занимала 3 слота вместо 1. Переупорядочивание полей и замена uint256 decimals на uint8 decimals срезали 62% газа на mint().
Mappings vs Arrays
mapping(uint256 => address) — O(1) доступ, gas-эффективен. address[] с поиском по значению — O(n) и в 99% случаев ошибка архитектуры. Если нужна итерация — индексируй через события, читай off-chain через The Graph.
Неочевидный источник: keccak256 на коротких строках
string memory name в функции, которая вызывается тысячи раз — это ABI encoding overhead. Замена строк на bytes32 константы там, где строки известны заранее, даёт 200-500 gas на вызов.
Инструменты анализа
| Инструмент | Что показывает |
|---|---|
| Hardhat Gas Reporter | Gas на каждый вызов функции в тестах |
Foundry forge test --gas-report |
То же, но быстрее и с diff между коммитами |
eth-gas-reporter |
Детализация по opcodes через --verbose |
| Tenderly Gas Profiler | Breakdown по EVM-трейсу реальной транзакции |
| Remix Gas Estimation | Быстрая проверка без setup |
Foundry — предпочтительный выбор. forge snapshot создаёт .gas-snapshot файл, который можно коммитить в репозиторий и отслеживать регрессии газа в CI:
forge snapshot
# изменяем код
forge snapshot --diff
Разница сразу видна построчно по каждой функции.
Конкретные паттерны оптимизации
Custom errors вместо require с строками
// До: 24 000 gas на деплой одной строки
require(amount > 0, "Amount must be positive");
// После: экономия ~200 gas per revert + меньше байткода
error AmountZero();
if (amount == 0) revert AmountZero();
Custom errors (EIP-838) стали стандартом с Solidity 0.8.4. Строки в require — это bytecode, который увеличивает стоимость деплоя и revert.
Unchecked arithmetic
С Solidity 0.8.0 все арифметические операции проверяют overflow по умолчанию. Проверка стоит ~100 gas per operation. Там, где overflow математически невозможен:
unchecked {
++i; // в цикле for — стандартный паттерн
total += amounts[i]; // если суммы ограничены и проверены выше
}
На цикле из 100 итераций — экономия 10 000+ gas.
Immutable и constant
constant — значение встраивается в байткод, SLOAD не нужен. immutable — значение записывается в байткод при деплое, читается как PUSH32. Оба в ~3 раза дешевле чтения из storage. Адрес токена, fee basis points, адрес owner в контракте, который не апгрейдится — всё это кандидаты на immutable.
Calldata vs memory для входных параметров
// memory — копирует данные в память
function process(uint256[] memory ids) external
// calldata — читает напрямую из calldata, не копирует
function process(uint256[] calldata ids) external
Для внешних функций (external), где данные только читаются — calldata дешевле. Разница растёт с размером массива: на массиве из 50 элементов — 3 000-5 000 gas.
Процесс оптимизации
Аудит baseline. Запускаем все тесты с forge test --gas-report, фиксируем baseline. Никаких изменений без измерений до и после.
Профилирование через Tenderly. Берём реальные транзакции из mainnet (если контракт уже задеплоен) или симулируем в Tenderly fork. Смотрим breakdown по EVM opcodes — где процентно больше всего тратится.
Итерации. Применяем изменения по одному, измеряем. Slot packing обычно даёт наибольший эффект — начинаем с него.
Regression testing. forge snapshot в CI. Любой PR, который увеличивает gas более чем на 1%, требует явного обоснования в review.
Ориентиры по срокам
Аудит газа существующего контракта + отчёт с рекомендациями: 1-2 дня. Оптимизация с имплементацией изменений и тестами: 2-3 дня в зависимости от сложности контракта. Полная переработка storage layout (если архитектура изначально неоптимальна): от 1 недели, так как требует миграционных скриптов для существующих данных.
Стоимость рассчитывается после анализа контракта и текущего gas profile.







