Шаблоны оптимизации газа (gas patterns) — это практические приёмы, которые помогают снижать стоимость выполнения смарт-контрактов под EVM. В высоконагруженных протоколах (DEX, лендинги, агрегаторы) правильная работа с газом влияет и на UX пользователей, и на экономику протокола.
Эта страница — обзор типовых паттернов. За теорией газа см. газ в Ethereum, за деталями хранения — storage layout в Solidity.
Общие принципы оптимизации газа
- Storage дороже всего
Операции SSTORE и SLOAD — самые дорогие. Любой паттерн, который:
- уменьшает количество обращений к storage;
- заменяет их вычислениями в memory/calldata;
даёт существенную экономию.
- Чем меньше байткод — тем дешевле деплой
Большие библиотеки и дублирование логики увеличивают стоимость развёртывания контракта.
- Газ против читаемости и безопасности
Агрессивная оптимизация делает код сложнее. Важно искать баланс: сначала безопасность и предсказуемость, потом — микроген.
- Профилирование важнее «магии»
Реальный выигрыш видно только по результатам профилирования и тестов, а не по «чувству». Сначала измеряем, потом оптимизируем.
Паттерны работы со storage
- Кэширование чтений из storage в память
Вместо многократного обращения:
```solidity
function f() external {
doSomething(balances[msg.sender]);
doAnotherThing(balances[msg.sender]);
}
```
лучше:
```solidity
function f() external {
uint256 balance = balances[msg.sender];
doSomething(balance);
doAnotherThing(balance);
}
```
Один SLOAD вместо двух и более.
- Обновление storage один раз в конце
Если в функции несколько раз изменяется одно и то же поле:
```solidity
user.position += size;
user.position -= fee;
user.position += bonus;
```
выгоднее считать в локальной переменной, а в storage записать итоговое значение один раз.
- Минимизация «случайных» структур
Сложные nested-struct с множеством полей часто означают лишние SLOAD/SSTORE. Иногда выгоднее нормализовать данные или разделить одну сущность на несколько, если это упрощает доступ к данным.
Calldata, memory и параметры функций
- Использовать calldata для входных параметров
Внешние функции (external) с большими массивами или строками выгоднее объявлять с параметрами в calldata:
```solidity
function batchTransfer(address[] calldata recipients, uint256 amount) external;
```
— вместо memory. Это экономит копирование данных и уменьшает газ.
- Передавать ссылки, а не копировать
Там, где возможно, лучше передавать массивы и структуры по ссылке (через storage/calldata), а не копировать их в новые memory-объекты без нужды.
- Структуры в памяти, если не нужно хранить в storage
Временные объекты удобно держать в memory: это дешевле, чем создавать «вспомогательные» поля в storage.
Циклы и обходы массивов
- Минимизировать длину и количество циклов
Каждый шаг цикла — это дополнительные инструкции и газ. Типовая оптимизация:
- избегать линейных обходов, если можно обратиться по индексу/маппингу;
- разбивать большие batch-операции на несколько транзакций.
- Убирать лишние проверки внутри цикла
Инварианты, которые не зависят от индекса, можно вынести за цикл, чтобы не проверять их на каждой итерации.
- Batch-операции вместо множества транзакций
Иногда дешевле сделать одну batch-функцию с циклом, чем десятки отдельных транзакций, особенно если часть газа платит вызывающий контракт/протокол.
События и логирование (events)
- Логировать только необходимое
События нужны для аналитики и индексации, но каждый эмит — это LOG-инструкции и газ за данные и indexed-поля. Полезные паттерны:
- не дублировать в событиях то, что легко восстановить по другим данным;
- аккуратно выбирать, что делать indexed, а что нет;
- объединять несколько близких событий, если это не ухудшает DX аналитиков.
- Избегать лишних событий в горячих путях
В функциях, которые вызываются чаще всего (например, свопы), каждое лишнее событие стоит денег пользователю.
Арифметика и типы данных
- Использовать минимально достаточные типы
В places, где значения точно укладываются, uint64/uint128:
- помогают с packing в слоты storage;
- уменьшают размер байткода (но нужно внимательно следить за переполнением).
- Переиспользовать вычисления
Если формула повторяется, лучше вычислить её один раз и сохранить в локальной переменной, а не пересчитывать каждый раз.
- Не злоупотреблять сложной математикой на ончейне
Если можно перенести часть вычислений off-chain, а на ончейне только верифицировать результат (подпись, proof), это почти всегда выгоднее.
Внешние вызовы и архитектурные решения
- Минимизировать количество внешних вызовов
Каждый call/delegatecall/staticcall — это дополнительный расход газа + риск по безопасности (см. риски DELEGATECALL и reentrancy). Нередко выгоднее:
- агрегировать несколько операций в один внешний вызов;
- хранить часть состояния локально, а не дергать внешний контракт на каждый шаг.
- Продумать архитектуру протокола
Иногда самая большая экономия достигается не микропаттернами, а изменением архитектуры:
- хранить меньше данных ончейн;
- внедрять off-chain исполнительные слои;
- использовать rollup- или zk-подходы, если это вписывается в модель протокола.
Баланс между оптимизацией и безопасностью
- Не жертвовать безопасностью ради экономии газа
Удаление проверок, усложнение логики и нестандартные конструкции ради пары процентов экономии газа часто приводят к уязвимостям, которые обходятся гораздо дороже.
- Сначала корректность, затем оптимизация
Практический порядок:
- покрыть тестами;
- провести аудит;
- после этого точечно оптимизировать горячие участки, при необходимости используя Yul.
- Документировать нестандартные паттерны
Всякий раз, когда вы отходите от очевидной реализации ради газа, фиксируйте это в комментариях и документации: зачем так сделано, какой выигрыш и какие риски.
Мини-чеклист gas patterns для ревью
Перед ревью/аудитом смарт-контракта полезно пройтись по чеклисту:
- не дублируются ли SLOAD/SSTORE в рамках одной функции;
- используются ли calldata для массивов/строк во внешних функциях;
- нет ли тяжёлых циклов по неограниченным массивам;
- не логируются ли лишние данные в событиях;
- нет ли избыточных внешних вызовов одного и того же контракта;
- не усложнён ли код ради микроскопической экономии газа.
Если контракт выдержал этот чеклист и при этом остаётся понятным и безопасным — значит, базовый уровень оптимизации газа уже достигнут.
