Как мы все знаем, есть много факторов, которые определяют хороший смарт-контракт, например:
Безопасность : он имеет минимальную/нулевую уязвимость, поэтому противник не может им воспользоваться. Иммунитет к атакам.
Стоимость : сколько всего
(a) smart contract deployment costs,
(b) running/invoking each function of it costs.
Правильность : выполняется так, как было запланировано.
В этом вопросе я хотел бы сосредоточиться на стоимости смарт-контракта.
Вопрос 1: Как сделать рентабельный смарт-контракт?
Вопрос 2: Как не использовать некоторые дорогостоящие функции и какие есть альтернативы? Другими словами, есть ли известные функции, которые дороже других операций, и можем ли мы заменить их более качественными?
Вопрос 3: В целом, какова хорошая практика написания смарт-контракта с минимальными затратами?
Вкратце: Как мы можем сэкономить газ : эфир : деньги?
В Ethereum транзакции стоят газа и, следовательно, эфира. Потребление газа транзакцией зависит от кодов операций, которые должен выполнить EVM. Стоимость газа для каждого кода операции можно узнать, как описано в этом вопросе . Несколько общих кодов операций и газа,
Operation Gas Description
ADD/SUB 3 Arithmetic operation
MUL/DIV 5 Arithmetic operation
ADDMOD/MULMOD 8 Arithmetic operation
AND/OR/XOR 3 Bitwise logic operation
LT/GT/SLT/SGT/EQ 3 Comparison operation
POP 2 Stack operation
PUSH/DUP/SWAP 3 Stack operation
MLOAD/MSTORE 3 Memory operation
JUMP 8 Unconditional jump
JUMPI 10 Conditional jump
SLOAD 200 Storage operation
SSTORE 5,000/20,000 Storage operation
BALANCE 400 Get balance of an account
CREATE 32,000 Create a new account using CREATE
CALL 25,000 Create a new account using CALL
Это вызывает беспокойство, когда речь идет о смарт-контрактах, поскольку транзакции также участвуют, и важно учитывать стоимость газа при разработке контракта.
Сокращение расхода газа по контракту важно в двух ситуациях:
Стоимость развертывания контракта
Для этого большая часть оптимизации выполняется во время компиляции, как описано в документации-faqs ,
Включены ли комментарии в развернутые контракты и увеличиваются ли они при развертывании?
Нет, все, что не нужно для выполнения, удаляется при компиляции. Сюда входят, среди прочего, комментарии, имена переменных и имена типов.
А подробности об оптимизаторе можно посмотреть здесь .
Другой способ уменьшить размер — удалить бесполезный код. Например:
1 function p1 ( uint x ){
2 if ( x > 5)
3 if ( x*x < 20)
4 XXX }
В приведенном выше коде строки 3 и 4 никогда не будут выполняться, и этого типа бесполезного кода можно избежать, тщательно просматривая логику контракта, что уменьшит размер смарт-контракта.
Стоимость вызова контрактных функций
Когда вызываются функции контрактов, для выполнения функции требуется газ. Следовательно, важна оптимизация функций для использования меньшего количества газа. При рассмотрении индивидуального контракта может быть много разных способов сделать это. Вот несколько, которые могут сэкономить газ во время выполнения,
Дорогие операции — это коды операций, которые имеют больше значений газа, таких как SSTORE
. Ниже приведены некоторые методы сокращения дорогостоящих операций.
A) Использование правил короткого замыкания
Операторы || и && применяют общие правила короткого замыкания. Это означает, что в выражении f(x) || g(y), если f(x) оценивается как true, g(y) не будет оцениваться, даже если это может иметь побочные эффекты.
Таким образом, если логическая операция включает в себя дорогостоящую операцию и операцию с низкой стоимостью, организация таким образом, чтобы дорогостоящая операция могла быть замкнута накоротко, уменьшит количество газа при некоторых выполнениях.
Если f(x) недорогая, а g(y) дорогая, организация логических операций
f(x) || g(y)
f(x) && g(y)
сэкономит больше газа в случае короткого замыкания.
If f(x)
имеет значительно более высокую вероятность возврата false по сравнению с g(y)
, организация операций AND f(x) && g(y)
может привести к экономии большего количества газа при выполнении за счет короткого замыкания.
Если f(x)
имеет значительно более высокую вероятность возврата true по сравнению с g(y)
, организация операций ИЛИ f(x) || g(y)
может привести к экономии большего количества газа при выполнении за счет короткого замыкания.
Б) дорогие операции в цикле
например:
uint sum = 0;
function p3 ( uint x ){
for ( uint i = 0 ; i < x ; i++)
sum += i; }
В приведенном выше коде, поскольку sum
переменная хранения читается и записывается каждый раз внутри цикла, операции хранения, которые являются дорогостоящими, выполняются на каждой итерации. Этого можно избежать, введя следующую локальную переменную для экономии газа.
uint sum = 0;
function p3 ( uint x ){
uint temp = 0;
for ( uint i = 0 ; i < x ; i++)
temp += i; }
sum += temp;
петля, объединяющая
function p5 ( uint x ){
uint m = 0;
uint v = 0;
for ( uint i = 0 ; i < x ; i++) //loop-1
m += i;
for ( uint j = 0 ; j < x ; j++) //loop-2
v -= j; }
петлю-1 и петлю-2 можно комбинировать и экономить газ,
function p5 ( uint x ){
uint m = 0;
uint v = 0;
for ( uint i = 0 ; i < x ; i++) //loop-1
m += i;
v -= j; }
и еще несколько шаблонов петель можно найти здесь .
Из Документов ,
Можно использовать массив байтов как byte[], но это тратит много места, 31 байт на каждый элемент, если быть точным, при передаче вызовов. Лучше использовать байты.
а также
Как правило, используйте байты для необработанных байтовых данных произвольной длины и строку для строковых данных произвольной длины (UTF-8). Если вы можете ограничить длину определенным количеством байтов, всегда используйте один из байтов от 1 до байт32, потому что они намного дешевле.
наличие фиксированной длины всегда экономит газ. Обратитесь и к этому вопросу .
Удаление бесполезного кода, как объяснялось ранее при развертывании по контракту, сэкономит газ даже при выполнении функций, если это можно сделать внутри функций.
Неиспользование библиотек при реализации функциональности дешевле для простых применений.
Вызов библиотеки для простого использования может быть дорогостоящим. Если функциональность проста и возможна для реализации внутри контракта, поскольку она позволяет избежать шага вызова библиотеки. стоимость выполнения только для функциональности будет по-прежнему одинаковой для обоих.
Использование видимости external
для функций, доступ к которым осуществляется только извне, заставляет использовать их calldata
в качестве местоположения параметра, и это экономит газ при выполнении функции.
Использование memory
переменных внутри функций локально, когда это возможно, экономит газ при доступе к файлу storage
.
Это некоторые способы экономии газа, и может быть много других способов в зависимости от требований.
&&
, заключается в том, что g() с большей вероятностью вернет false, что с большей вероятностью приведет к короткому замыканию &&
, но не ||
.Ваш код Solidity будет скомпилирован в байт-код, состоящий из инструкций виртуальной машины Ethereum (EVM). Стоимость газа вызова функции представляет собой сумму стоимости газа всех выполненных инструкций EVM. Удобную для чтения таблицу точной стоимости газа для каждой из этих инструкций можно найти здесь:
Если вам нужны все подробности, вы должны взглянуть на приложение H желтой бумаги:
Как видите, инструкции вроде ADD
и MUL
стоят очень дешево, только на 3
газу 5
.
Инструкции вроде BALANCE
и CREATE
стоят намного дороже у 400
и 32000
газ.
Чтение из памяти стоит 200
за SLOAD
инструкцию, а запись в память стоит 5000
газа, но чтение из памяти и запись в память стоит только 3
газа.
Внутренние транзакции (отправка эфира) будут стоить вам 21000
каждый раз.
Исходя из этого, вот несколько советов по снижению стоимости газа:
Записывайте в хранилище только после завершения вычислений, используйте локальные переменные для запоминания вашего прогресса (если применимо).
Избегайте слишком большого количества операций записи и чтения из хранилища
Избегайте создания слишком большого количества новых контрактов
Избегайте слишком большого чтения глобального состояния (например, получения баланса адреса).
Избегайте слишком большого количества транзакций
Используйте bytes32
вместо string
для небольших строк
Избегайте циклов, которые выполняются слишком много раз, особенно если они содержат дорогостоящие инструкции.
Я считаю, что написание функций в смарт-контракте с использованием встроенных функций сборки делает его более экономичным, чем любые изменения, которые вы пытаетесь внести в свой контракт. простая функция цикла for в Solidity, принимающая x количество wei. где та же функция, написанная на встроенной сборке Solidity, требует всего 0,7 (x) количества wei.
Бадр Беллай