
Введение
Чтобы обойти эти ограничения, придумали несколько решений второго уровня, типа Base, которые контролируют масштабируемость и снижают расходы на газ. Хотя L2 значительно снижают комиссии по сравнению с основной сетью Ethereum, разработчики смарт-контрактов не могут игнорировать оптимизацию газа при разработке.
Это руководство поможет вам улучшить пользовательский опыт и создать более конкурентоспособные децентрализованные приложения с помощью продвинутых стратегий, которые снижают расходы на газ в тестируемых смарт-контрактах. Примеры, которые они приводят, смоделированы с помощью простых контрактов и в основном учитывают цены на газ во время выполнения, так как затраты на развертывание сильно отличаются в зависимости от размера контракта.
Оптимизация газа важна для разработчиков, пользователей и успеха проекта в долгосрочной перспективе. Экономичное использование газа смарт-контрактов может сделать протоколы более рентабельными и масштабируемыми, а также менее уязвимыми для угроз безопасности, таких как атаки типа «отказ в обслуживании».
Чтобы использовать смарт-контракты, нужно тщательно и регулярно проверять каждый из них.
Почему важно оптимизировать газ в Solidity
Оптимизация газа позволяет смарт-контрактам, протоколам и проектам работать в условиях перегруженных сетей и делает их дешевыми и эффективными. Благодаря улучшению кода контракта можно обнаружить возможные уязвимости, а протоколы и пользователи чувствуют себя более уверенно.
Оптимизация газа тоже важна для разработки. Это не просто приятная вещь, а ключ к долгосрочному успеху и безопасности смарт-контрактов. То, что можно строить на L2 с относительно низкими комиссиями, не значит, что комиссии за газ не будут значительно выше, чем у конкурентов.
Все тесты используют Foundry и Solidity версии 0.8.13, локальный блокчейн-узел Anvil, команды forge test и 100 прогонов оптимизации.
Профессиональные аудиторские фирмы должны заниматься полными проверками безопасности, а это руководство — просто подспорье.
Сократите объем данных в цепочке
Незаменимые токены стали популярными в 2021 году и привлекли внимание к полностью ончейн-NFT. Ончейн-NFT, в отличие от традиционных NFT, которые используют оффчейн-данные, включая метаданные и ссылки на изображения, помещают всю информацию прямо в блокчейн.
Эти токены известны своей дороговизной, и гибридные решения являются стандартом, когда пользователи сталкиваются с проблемами комиссий. Независимо от того, создаете ли вы NFT, игры или DeFi-протоколы, всегда стоит подумать о том, какие данные действительно нужно хранить в блокчейне, и о компромиссах между обоими альтернативами.
Значительно сократите использование газа, храня информацию вне цепочки, что требует меньшего объема памяти для переменных. Один из конкретных методов заключается в использовании событий для хранения данных вне цепочки, а не в фактическом хранилище в цепочке.
Стоимость газа для транзакций увеличивается из-за дополнительных функций emit, связанных с событиями; но информация не хранится в цепочке, поэтому экономия обычно превышает затраты.
Представьте себе смарт-контракт, который позволяет пользователям голосовать «за» или «против». Первый из них хранит голоса пользователей в структурах цепочки. Демонстрация теста функции голосования с Foundry с более чем 100 итерациями показывает определенные затраты на газ.
Сравните со смарт-контрактом, который не хранит информацию в цепочке, но генерирует событие при вызове функции голосования. Тест с участием Foundry более 100 раз в новой функции голосования дает результаты.
Сокращение данных в цепочке снизило средний расход газа на 90,34%.
Чтобы получить доступ к данным вне цепочки в цепочке, функции Chainlink и решения поддержки можно интегрировать с большинством популярных сетей L2, таких как Base.
Используйте сопоставления над массивами
В Solidity есть две основные структуры данных: массивы и отображения.
- Массивы используются для хранения наборов элементов, которые привязаны к определенным индексам
- Сопоставления ключ-значение — это структуры данных, которые позволяют напрямую обращаться к данным с помощью уникальных ключей
Хотя массивы могут быть полезны для хранения векторов и других похожих данных, обычно предпочитают использовать отображения из-за их газовой эффективности. Они особенно хорошо подходят для ситуаций, когда нужны данные по запросу, например, по имени, адресу кошелька или балансу счета.
Чтобы понять, сколько газа используется при работе с массивами или отображениями, может понадобиться посмотреть, сколько газа используют связанные с ними операционные коды EVM. Операционные коды — это низкоуровневые инструкции, которые виртуальная машина Ethereum выполняет при работе смарт-контрактов, и каждый операционный код имеет свою стоимость газа.
Чтобы получить доступ к значениям массива, нужно платить за единицу газа, которую используют операционные коды EVM. Чтобы сохранить адреса пользователей и их балансы в массиве, нужно пройтись по всем элементам массива, проверить, совпадают ли userAddress и аргумент, и вернуть баланс, если они совпадают.
Переход прямо к конкретному балансу пользователя позволил бы избежать необходимости просматривать все элементы массива. После замены массивов на отображения потребление газа для доступа к данным сократилось на 89 процентов.
Сравнение использования газа: массивы против отображений
| Метод | Перед оптимизацией | После оптимизации | Экономия топлива |
|---|---|---|---|
| Доступ к данным | 30 586 | 3 081 | 89% |
| Добавление данных | - | - | 93% |
Используйте константы и неизменяемые величины, чтобы снизить расходы на газ в смарт-контрактах
Еще один совет по оптимизации — использовать константы и неизменяемые переменные. Когда переменные объявляются неизменяемыми или константами, их значения задаются только при создании контракта и больше не меняются.
Они не занимают место в EVM, в отличие от других переменных. Можно напрямую кодировать значения в байт-коде смарт-контракта, что значит, что стоимость хранения данных снижается, потому что переменные, такие как maxSupply и owner, не имеют ключевых слов constant или immutable.
Когда тесты запускаются 100 раз, средняя стоимость газа составляет 112 222 единиц. Поскольку maxSupply и owner — это известные значения, которые не планируется менять, лучше всего объявить максимальный запас и владельца, которые не используют место для хранения.
Тесты с постоянными или неизменяемыми переменными дают в среднем 35,89% экономии (с 112 222 до 71 940 единиц газа).
Оптимизируйте неиспользуемые переменные
Очевидный совет по оптимизации газа — оптимизировать переменные в смарт-контрактах. Однако в большинстве случаев ненужные переменные сохраняются при выполнении контракта, что приводит к ненужному расходу газа.
Удаление отдельных неиспользуемых переменных может помочь сэкономить на расходе газа, в среднем на 18%, если речь идет о контрактах с неиспользуемыми переменными, которые определены и обрабатываются в функциях, но никогда не вызываются в других местах.
Результаты оптимизации неиспользуемых переменных
| Этап оптимизации | Использование газа | Экономия |
|---|---|---|
| Перед оптимизацией | 32 513 | - |
| После оптимизации | 27 429 | 18% |
Возврат газа Solidity: удаление неиспользуемых переменных
Удаление неиспользуемых переменных — это попытка присвоить переменным значения по умолчанию после того, как они были полностью вычислены, без сохранения данных в памяти. Например, значением по умолчанию для переменных типа uint является 0.
Если не уничтожать переменные функции, когда функции заканчиваются, средние затраты газа составляют 100 300 единиц для присвоения переменным данных. В среднем экономия газа составляет 19% при использовании клавиши «Delete» для удаления переменных данных (чтобы установить переменные равными 0).
Используйте массивы с фиксированным размером вместо динамических, чтобы сэкономить на газах смарт-контрактов
По возможности используй сопоставления, чтобы снизить расходы на газ для смарт-контрактов. Но если нужны массивы, массивы фиксированного размера более экономичны с точки зрения газа, чем массивы динамического размера, так как массивы динамического размера могут расширяться бесконечно, что приводит к более высоким расходам на газ.
Динамические массивы можно расширять, поэтому EVM должен следить за их длиной и обновлять их, когда добавляются новые элементы.
Подумай о коде, который определяет массивы с динамическим размером и обновляет их с помощью функций updateArray. Оператор require убедится, что указанные индексы находятся в пределах массива фиксированного размера. При 100 повторениях теста среднее количество использованного газа составляет 12 541.
Изменение массивов на фиксированный размер 5 создает массивы фиксированного размера длиной 5, типа uint256. EVM знает, что fixedArray с переменной размера 5 имеет 5 слотов, и длина не хранится в памяти. Замена динамических массивов на фиксированные экономит 17,99% газа.
Оптимизируйте свои смарт-контракты уже сегодня
Начни использовать эти методы оптимизации газа, чтобы сократить расходы до 90% на Base и других цепочках L2.
Использование uint8 вместо uint256
Это менее эффективно и может быть дороже, чем uint256, из-за работы EVM. EVM имеет 256-битные слова. Операции с 256-битными целыми числами (uint256) обычно самые эффективные, потому что они подходят под размер слова EVM, а для меньших типов (таких как uint8) компилятору Solidity нужно создавать дополнительные операции, чтобы поместить меньшие типы в один 256-битный слот памяти.
В общем, хотя маленькие типы, такие как uint8, могут быть полезны для хранения (несколько маленьких типов можно упаковать в одну операцию с 256-битным слотом хранения), замечено, что меньшие типы помогают только с хранением. Экономия места может быть сведена на нет при конвертации в uint256 и обратно для вычислений.
Объединение переменных меньше 256 бит
Объединение переменных размером менее 256 бит обычно не так эффективно, как 256-битные переменные. Но иногда приходится использовать менее мощные типы меньшего размера, например, булевы переменные, которые занимают 1 байт или 8 бит для хранения.
Когда используешь менее мощные типы, можно объявить переменные состояния с учетом места для хранения, и Solidity может их упаковать и хранить, используя один и тот же слот хранения. Преимущество упаковки переменных обычно учитывается в операциях хранения, а не в операциях с памятью или стеком.
Поскольку размеры двусторонних комбинаций составляют 16 бит, или на 240 бит меньше, чем емкость одного слота хранения, Solidity можно использовать для упаковки переменных в один и тот же слот, чтобы минимизировать использование газа при развертывании, поскольку для хранения переменных состояния не требуется много слотов.
Перестановка объявлений переменных позволяет оптимизировать в среднем 13% газа.
Результаты упаковки переменных
| Этап | Использование газа | Экономия |
|---|---|---|
| Предварительная оптимизация | 1 678 | - |
| После оптимизации | 1 447 | 13% |
Выберите внешний модификатор видимости
Чтобы максимально использовать газ смарт-контракта, стоит выбрать правильную видимость функций. Внешние модификаторы видимости могут быть более эффективными в использовании газа, чем публичные, из-за того, как публичная функция управляет своими аргументами и как данные передаются в функции.
Внешние функции могут получить доступ к calldata, это временное пространство в EVM, которое можно только читать и которое содержит параметры вызова функции. Можно вызывать публичные функции:
- Внешне (когда их вызывают извне транзакцией, используя calldata)
- Внутри (когда их вызывают в контракте с помощью calldata)
Поскольку функции являются общедоступными, им нужно получать массивы в памяти, что может быть дорого в плане затрат на газ, если массивы большие. Переключение функций на внешний режим позволяет получать массивы в calldata, что более экономично в ситуациях с большими массивами, экономя в среднем 0,3% единиц газа на каждый вызов.
Хранение для повторного чтения одного и того же значения
Хороший способ сэкономить газ — не читать одно и то же значение в хранилище много раз. Чтение из хранилища стоит дороже, чем чтение из памяти. Повторное чтение одних и тех же переменных в хранилище много раз и запись в хранилище, когда это не нужно, может быть пустой тратой газа в смарт-контрактах.
Этого можно избежать, если определить переменные, которые есть в памяти в начале функций, и присвоить им значения числовых переменных.
**Это экономит 17% газа, который потребляют функции sumNumbers, но чем больше массив, тем больше будет количество итераций и используемого газа.
Кэширование переменных Результаты
| Этап | Использование газа | Экономия |
|---|---|---|
| Перед оптимизацией | 3 527 | - |
| После оптимизации | 2 905 | 17% |
Не задавайте переменным значения по умолчанию
Когда объявляешь переменные состояния без их инициализации (то есть, не присваивая им начальное значение), эти переменные автоматически инициализируются значениями по умолчанию:
- uint: 0
- bool: false
- адрес: address(0)
Последнее дешевле, чем объявлять значения по умолчанию и просто обновлять их, когда пользователь взаимодействует с системой, что неэффективно с точки зрения расхода газа. Без инициализации переменных оптимизация показывает среднюю экономию газа в 4%.
Поддержка оптимизации компилятора Solidity
Solidity имеет компиляторы с настройками, которые легко изменить, чтобы оптимизировать скомпилированный код. Оптимизированная сборка запускается несколько сотен раз, оптимизируя код и конвертируя его в более дешевые формы, которые требуют меньше газа для работы.
Компиляторы можно настроить, чтобы найти баланс между затратами на развертывание и затратами на выполнение. Определите примерное количество контрактов, которые нужно запустить, с помощью команд запуска:
- Более высокие уровни лучше с точки зрения низких цен на газ при выполнении контракта
- Меньшее количество цифр — это лучше с точки зрения снижения затрат на газ при развертывании контракта
Оптимизируйте флаги и значение run=200, чтобы компиляторы сделали код оптимальным для экономии газа при 200-кратном запуске функций incrementCount. Распределите такие настройки по категориям в соответствии с уникальными требованиями приложения.
Бонус по оптимизации газа Solidity: ассемблер
Когда пишешь смарт-контракты на Solidity, они компилируются в байт-коды, то есть последовательность операционных кодов EVM. С помощью ассемблера можно просто писать код, который работает на уровнях, более совместимых с операционными кодами, и в некоторых случаях возможность вручную оптимизировать операционные коды дает ему преимущество перед байт-кодом Solidity.
Хотя писать код на таком низком уровне не самая простая задача, это дает возможность вручную оптимизировать операционные коды, и поэтому в целом лучше, чем байт-код Solidity. Такой уровень оптимизации делает выполнение контракта более эффективным и результативным.
Даже в простых случаях, когда две функции должны сложить два числа, простые версии Solidity и Assembly будут немного отличаться, но версии Assembly будут дешевле.
Реализация проектов на L2, таких как Base, означает, что пользователям будет дешевле использовать протоколы, но разработчики должны позаботиться о том, чтобы применять методы оптимизации газа. Эти советы могут значительно снизить расходы на транзакции, добавить масштабируемость и повысить эффективность контракта в целом.
Assembly можно использовать, чтобы оптимизировать работу смарт-контрактов с помощью газа, но иногда версии Assembly могут привести к созданию небезопасного кода. Есть хорошие советы привлечь экспертов по безопасности смарт-контрактов к проверке контрактов перед их развертыванием.


