Подсказки по безопасности - 6
Pragma и версии Solidity.
Использование последних версий Solidity позволяет избегать ошибок и багов, которые встречались в более ранних версиях.
Также нужно обращать внимание на то, чтобы все контракты, которые связаны друг с другом, работали с одной и той же версией языка.
При деплое смарт контрактов рекомендуется блокировать версию pragma (убирать значок ^), для избегания загрузки не правильным компилятором (выше или ниже версии вашего контракта).
#security #tip #st
Pragma и версии Solidity.
Использование последних версий Solidity позволяет избегать ошибок и багов, которые встречались в более ранних версиях.
Также нужно обращать внимание на то, чтобы все контракты, которые связаны друг с другом, работали с одной и той же версией языка.
При деплое смарт контрактов рекомендуется блокировать версию pragma (убирать значок ^), для избегания загрузки не правильным компилятором (выше или ниже версии вашего контракта).
#security #tip #st
👍1
Ethernaut. Задача 14. Gatekeeper 2
Отступление. Чем больше я разбираю задачи, тем яснее понимаю, что еще многого не знаю. Поэтому некоторые задачи я сейчас пропущу и вернусь к ним, как пройду остальные. Например, с gatekeeper 1 я вчера долго возился, разобрал, но не понял как объяснить ее, и значит "не понял".
Но вернемся к Gatekeeper 2.
В этой задаче нам нужно пройти через все проверки, чтобы наш адрес записался в переменную entrant.
Ссылка на задачу
В чем суть?
Первые два модификатора пройти достаточно просто. GateOne - сделан по примеру задачи Telephone, а gateTwo, с его "ужасным" assembly {x := extcodesize(caller())} - просит, чтобы байткод нашего контракта был равен 0, что бывает в том случае, если там нет никаких функций. Кстати, на extcodesize нельзя полагаться в вопросе безопасности! Не делайте так!
С обоими "воротами" мы можем справиться, вызвав constructor в нашем контракте на GatekeeperTwo.
С третьими воротами сложнее.
Требуется выполнить равенство:
uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1
Т.е. мы берем адрес вызывающего функцию, кодируем, получаем хеш через keccac256, потом берем первые 8 байтов. Все это оборачиваем в uint64 и... Приводим к равенству.
Но, что обозначает эта каретка "^". И тут, если не знаешь, то не пройдешь дальше.
А означает она - XOR, или сложение по модулю 2, или исключающее "или". Звучит замысловато, но по сути это побитовое сложение, когда отдельные разряды складываются независимо от других. Например, у нас есть такой порядок бит:
010101
И нужно сложить его с этим
101101
У нас получится:
111000
т.е. "1+1"= 0, "1+0"=1, "0+0"=0.
Самое интересное, что, если мы сложим результат, например со вторым значением, то получим первое. Это мы и должны использовать в задаче.
Получается, что:
uint64(_gateKey) будет равен
uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ uint64(0) - 1
Отсюда мы можем получить итоговый контракт для взлома:
contract Hack {
constructor (GatekeeperTwo _gates ) {
bytes8 key;
unchecked {
key = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ uint64(0) - 1);
}
_gates.enter(key);
}
}
P.S. unchecked нужен для Solidity 0.8, для защиты от overflow в uint64(0) - 1.
Задача может показаться сложной, если не знаешь про эти побитовые операции. А сколько людей вообще про них знает?!
#ethernaut
Отступление. Чем больше я разбираю задачи, тем яснее понимаю, что еще многого не знаю. Поэтому некоторые задачи я сейчас пропущу и вернусь к ним, как пройду остальные. Например, с gatekeeper 1 я вчера долго возился, разобрал, но не понял как объяснить ее, и значит "не понял".
Но вернемся к Gatekeeper 2.
В этой задаче нам нужно пройти через все проверки, чтобы наш адрес записался в переменную entrant.
Ссылка на задачу
В чем суть?
Первые два модификатора пройти достаточно просто. GateOne - сделан по примеру задачи Telephone, а gateTwo, с его "ужасным" assembly {x := extcodesize(caller())} - просит, чтобы байткод нашего контракта был равен 0, что бывает в том случае, если там нет никаких функций. Кстати, на extcodesize нельзя полагаться в вопросе безопасности! Не делайте так!
С обоими "воротами" мы можем справиться, вызвав constructor в нашем контракте на GatekeeperTwo.
С третьими воротами сложнее.
Требуется выполнить равенство:
uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1
Т.е. мы берем адрес вызывающего функцию, кодируем, получаем хеш через keccac256, потом берем первые 8 байтов. Все это оборачиваем в uint64 и... Приводим к равенству.
Но, что обозначает эта каретка "^". И тут, если не знаешь, то не пройдешь дальше.
А означает она - XOR, или сложение по модулю 2, или исключающее "или". Звучит замысловато, но по сути это побитовое сложение, когда отдельные разряды складываются независимо от других. Например, у нас есть такой порядок бит:
010101
И нужно сложить его с этим
101101
У нас получится:
111000
т.е. "1+1"= 0, "1+0"=1, "0+0"=0.
Самое интересное, что, если мы сложим результат, например со вторым значением, то получим первое. Это мы и должны использовать в задаче.
Получается, что:
uint64(_gateKey) будет равен
uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ uint64(0) - 1
Отсюда мы можем получить итоговый контракт для взлома:
contract Hack {
constructor (GatekeeperTwo _gates ) {
bytes8 key;
unchecked {
key = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ uint64(0) - 1);
}
_gates.enter(key);
}
}
P.S. unchecked нужен для Solidity 0.8, для защиты от overflow в uint64(0) - 1.
Задача может показаться сложной, если не знаешь про эти побитовые операции. А сколько людей вообще про них знает?!
#ethernaut
❤1
Логические побитовые операции
Я встречал уже несколько примеров с побитовым кодом, поэтому решил сделать небольшой пост об этом. Возможно, кому-то он тоже будет полезен.
Побитовое И - символ &
Побитовое И используется для выключения битов. Любой бит, установленный в 0, вызывает установку соответствующего бита результата также в 0.
11001010
11100010
11000010
Побитовое ИЛИ - символ |
Побитовое ИЛИ используется для включения битов. Любой бит, установленный в 1, вызывает установку соответствующего бита результата также в 1.
11001010
11100010
11101010
Побитовое НЕ - символ ~
Побитовое НЕ инвертирует состояние каждого бита исходной переменной.
11001010
00110101
Побитовое исключающее ИЛИ - символ ^
Исключающее ИЛИ устанавливает значение бита результата в 1, если значения в соответствующих битах исходных переменных различны.
11001010
11100010
00101000
Побитовые сдвиги
Операторы сдвига << и >> сдвигают биты в переменной влево или вправо на указанное число. При этом на освободившиеся позиции устанавливаются нули (кроме сдвига вправо отрицательного числа, в этом случае на свободные позиции устанавливаются единицы, так как числа представляются в двоичном дополнительном коде и необходимо поддерживать знаковый бит).
Сдвиг влево может применяться для умножения числа на два, сдвиг вправо — для деления.
x = 7 // 00000111 (7)
x = x >> 1 // 00000011 (3)
x = x << 1 // 00000110 (6)
x = x << 5 // 11000000 (-64)
x = x >> 2 // 11110000 (-16)
#побитовые #бит #операции #сдвиг #xor
Я встречал уже несколько примеров с побитовым кодом, поэтому решил сделать небольшой пост об этом. Возможно, кому-то он тоже будет полезен.
Побитовое И - символ &
Побитовое И используется для выключения битов. Любой бит, установленный в 0, вызывает установку соответствующего бита результата также в 0.
11001010
11100010
11000010
Побитовое ИЛИ - символ |
Побитовое ИЛИ используется для включения битов. Любой бит, установленный в 1, вызывает установку соответствующего бита результата также в 1.
11001010
11100010
11101010
Побитовое НЕ - символ ~
Побитовое НЕ инвертирует состояние каждого бита исходной переменной.
11001010
00110101
Побитовое исключающее ИЛИ - символ ^
Исключающее ИЛИ устанавливает значение бита результата в 1, если значения в соответствующих битах исходных переменных различны.
11001010
11100010
00101000
Побитовые сдвиги
Операторы сдвига << и >> сдвигают биты в переменной влево или вправо на указанное число. При этом на освободившиеся позиции устанавливаются нули (кроме сдвига вправо отрицательного числа, в этом случае на свободные позиции устанавливаются единицы, так как числа представляются в двоичном дополнительном коде и необходимо поддерживать знаковый бит).
Сдвиг влево может применяться для умножения числа на два, сдвиг вправо — для деления.
x = 7 // 00000111 (7)
x = x >> 1 // 00000011 (3)
x = x << 1 // 00000110 (6)
x = x << 5 // 11000000 (-64)
x = x >> 2 // 11110000 (-16)
#побитовые #бит #операции #сдвиг #xor
Ethernaut. Задача 15. Naught Coin
В этой задаче нам нужно вывести все токены со своего баланса.
Ссылка на задачу
В чем суть?
Задача не столько про кодинг, сколько про знание стандарта ERC20. При том, что на функцию transfer() стоит защита по времени в виде модификатора, все равно нам доступны и другие функции контракта ERC20.
В Ремиксе это можно увидеть, сделав деплой. Поэтому нам достаточно вызвать функцию approve() и затем transferFrom() для решения данной задачи. Краткая функция взлома может выглядеть так:
function hack() external{
balance = challenge.balanceOf(myAdd);
challenge.approve(myAdd, balance);
challenge.transferFrom(myAdd, otherAdd, balance);
}
где challenge это объект контракта.
При взломе контрактов, которые используют различные стандарты, сначала нужно изучить сам стандарт, чтобы понять все возможности для транзакций.
#ethernaut
В этой задаче нам нужно вывести все токены со своего баланса.
Ссылка на задачу
В чем суть?
Задача не столько про кодинг, сколько про знание стандарта ERC20. При том, что на функцию transfer() стоит защита по времени в виде модификатора, все равно нам доступны и другие функции контракта ERC20.
В Ремиксе это можно увидеть, сделав деплой. Поэтому нам достаточно вызвать функцию approve() и затем transferFrom() для решения данной задачи. Краткая функция взлома может выглядеть так:
function hack() external{
balance = challenge.balanceOf(myAdd);
challenge.approve(myAdd, balance);
challenge.transferFrom(myAdd, otherAdd, balance);
}
где challenge это объект контракта.
При взломе контрактов, которые используют различные стандарты, сначала нужно изучить сам стандарт, чтобы понять все возможности для транзакций.
#ethernaut
👍1
Ethernaut. Задача 17. Recovery
В этой задаче нам нужно вывести все токены контракта, который был создан, и чей адрес не записан. Другими словами нам нужно восстановить потерянный адрес сгенерированного контракта.
Ссылка на задачу
В чем суть?
Код задачи простой: по сути, нам нужно восстановить адрес и потом вызвать selfdestruct(), чтобы вернуть деньги. Все умещается в одной функции.
Давайте лучше поговорим, как генерируется адрес контракта, чтобы можно было решать подобные задачи в будущем.
Мы не будем углубляться в EVM и его процессы, а разберем базу для задачи.
Итак, есть два способы генерации адреса: старый и новый. Начнем с первого.
В старом способе генерация адреса контракта происходила с помощью данных о номере транзакции / количестве созданный контрактов из Фабрики (nonce) и адресе вызывающего. Также нам потребуется знать, что такое RLP.
Целью RLP (RECURSIVE-LENGTH PREFIX) является кодирование произвольно вложенных массивов двоичных данных. Это основной метод кодирования, используемый для сериализации объектов на исполнительном уровне Ethereum. Подробнее про него можно прочитать тут.
В случае 20 битового адреса RLP будет 0хd6 и 0х94.
Также, в задаче это был первый созданный контракт, поэтому nonce будет равен 1.
Код в Solidity выглядел так:
bytes1(0xd6), bytes1(0x94), address(sender), bytes1(0x01)
Все это нам нужно будет перевести в байткод, затем в хеш => uint265 =>uint160, отсюда получим адрес:
address lostContract = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), address(sender), bytes1(0x01))))));
Второй способ генерации адреса связан с opcode Create2, предложенный Виталиком Бутериным (EIP-1014).
Create2 использует другую формулу для генерации:
keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]
где:
address — адрес смарт-контракта, который будет вызывать CREATE2;
salt — случайное значение;
init_code — байт-код смарт-контракта для развертывания;
Таким образом гарантируется, что адрес, который мы предоставляем пользователю, действительно будет содержать желаемый байт-код.
Функция может выглядеть так:
function getAddress(bytes memory bytecode, uint _salt)
public view returns (address)
{
bytes32 hash = keccak256(
abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode))
);
return address(uint160(uint(hash)));
}
Полный пример с кодом можно посмотреть тут.
Я много гуглил по работе EVM и RLP, и мне стоило больших усилий понять, как это работает. Если вы захотите, после всех тем по безопасности, я могу написать отдельный пост про генерацию адресов с разбором RLP.
#ethernaut #generation #address #creat2
В этой задаче нам нужно вывести все токены контракта, который был создан, и чей адрес не записан. Другими словами нам нужно восстановить потерянный адрес сгенерированного контракта.
Ссылка на задачу
В чем суть?
Код задачи простой: по сути, нам нужно восстановить адрес и потом вызвать selfdestruct(), чтобы вернуть деньги. Все умещается в одной функции.
Давайте лучше поговорим, как генерируется адрес контракта, чтобы можно было решать подобные задачи в будущем.
Мы не будем углубляться в EVM и его процессы, а разберем базу для задачи.
Итак, есть два способы генерации адреса: старый и новый. Начнем с первого.
В старом способе генерация адреса контракта происходила с помощью данных о номере транзакции / количестве созданный контрактов из Фабрики (nonce) и адресе вызывающего. Также нам потребуется знать, что такое RLP.
Целью RLP (RECURSIVE-LENGTH PREFIX) является кодирование произвольно вложенных массивов двоичных данных. Это основной метод кодирования, используемый для сериализации объектов на исполнительном уровне Ethereum. Подробнее про него можно прочитать тут.
В случае 20 битового адреса RLP будет 0хd6 и 0х94.
Также, в задаче это был первый созданный контракт, поэтому nonce будет равен 1.
Код в Solidity выглядел так:
bytes1(0xd6), bytes1(0x94), address(sender), bytes1(0x01)
Все это нам нужно будет перевести в байткод, затем в хеш => uint265 =>uint160, отсюда получим адрес:
address lostContract = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), address(sender), bytes1(0x01))))));
Второй способ генерации адреса связан с opcode Create2, предложенный Виталиком Бутериным (EIP-1014).
Create2 использует другую формулу для генерации:
keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]
где:
address — адрес смарт-контракта, который будет вызывать CREATE2;
salt — случайное значение;
init_code — байт-код смарт-контракта для развертывания;
Таким образом гарантируется, что адрес, который мы предоставляем пользователю, действительно будет содержать желаемый байт-код.
Функция может выглядеть так:
function getAddress(bytes memory bytecode, uint _salt)
public view returns (address)
{
bytes32 hash = keccak256(
abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode))
);
return address(uint160(uint(hash)));
}
Полный пример с кодом можно посмотреть тут.
Я много гуглил по работе EVM и RLP, и мне стоило больших усилий понять, как это работает. Если вы захотите, после всех тем по безопасности, я могу написать отдельный пост про генерацию адресов с разбором RLP.
#ethernaut #generation #address #creat2
Подсказки по безопасности - 8
С помощью extcodesize мы можем проверить, кто делает вызов функции в нашем контракте: внешний контракт или пользователь. Однако, эту систему можно обмануть, создав пустой контракт с вызовом нужной функции в его конструкторе.
contract OnlyForEOA { ... function setFlag()...}
contract FakeEOA {
constructor(address _a) public {
OnlyForEOA c = OnlyForEOA(_a);
c.setFlag(1);
}
}
В этом случае, хоть вызов будет идти из внешнего контракта, в нашем контракте будет отображаться его пользователь (tx.origin).
#security #tip #st
С помощью extcodesize мы можем проверить, кто делает вызов функции в нашем контракте: внешний контракт или пользователь. Однако, эту систему можно обмануть, создав пустой контракт с вызовом нужной функции в его конструкторе.
contract OnlyForEOA { ... function setFlag()...}
contract FakeEOA {
constructor(address _a) public {
OnlyForEOA c = OnlyForEOA(_a);
c.setFlag(1);
}
}
В этом случае, хоть вызов будет идти из внешнего контракта, в нашем контракте будет отображаться его пользователь (tx.origin).
#security #tip #st
Подсказки по безопасности - 9
Удаление struct в котором есть mapping, не удаляет сам mapping. Это может быть использовано против вас.
struct BalancesStruct{
address owner;
mapping(address => uint) balances;
}
mapping(address => BalancesStruct) public stackBalance;
function remove() internal{
delete stackBalance[msg.sender];
}
В этом случае лучше использовать закрытие struct(lock), а не его удаление.
#security #tip #st
Удаление struct в котором есть mapping, не удаляет сам mapping. Это может быть использовано против вас.
struct BalancesStruct{
address owner;
mapping(address => uint) balances;
}
mapping(address => BalancesStruct) public stackBalance;
function remove() internal{
delete stackBalance[msg.sender];
}
В этом случае лучше использовать закрытие struct(lock), а не его удаление.
#security #tip #st
Оптимизация газа - 1
Компилятор Solidity считывает и выполняет функции по их селектору. Селектор, как мы знаем, располагается в первых четырех байтах сигнатуры функции из хеша kessac256. Например:
function tryThis(uint256 _value, string[] memory _names) external {}
В этом случае:
Сигнатура функции - tryThis(uint256,string[]);
Селектор функции - keccak256(signature) = 0x7f6ca090;
Компилятор Solidity располагает все функции в контракте по их селектору (в порядке hexadecimal) и проходит по каждой из них в тот момент, когда вызвана какая-либо из них. Проход по всем функциям в контракте стоит 22 газа.
Приведем к примеру такие функции:
red() => 2930cf24
white() => a0811074
yellow() => be9faf13
blue() => ed18f0a7
purple() => ed44cd44
green() => f2f1e132
Вызов green() будет стоит на 110 газа больше, чем вызов red(), только потому что она ниже.
Если вы наперед знаете, что какая-либо функция в вашем контракте будет вызываться чаще остальных, то старайтесь располагать ее выше остальных в контракте.
#gas #optimization #hint
Компилятор Solidity считывает и выполняет функции по их селектору. Селектор, как мы знаем, располагается в первых четырех байтах сигнатуры функции из хеша kessac256. Например:
function tryThis(uint256 _value, string[] memory _names) external {}
В этом случае:
Сигнатура функции - tryThis(uint256,string[]);
Селектор функции - keccak256(signature) = 0x7f6ca090;
Компилятор Solidity располагает все функции в контракте по их селектору (в порядке hexadecimal) и проходит по каждой из них в тот момент, когда вызвана какая-либо из них. Проход по всем функциям в контракте стоит 22 газа.
Приведем к примеру такие функции:
red() => 2930cf24
white() => a0811074
yellow() => be9faf13
blue() => ed18f0a7
purple() => ed44cd44
green() => f2f1e132
Вызов green() будет стоит на 110 газа больше, чем вызов red(), только потому что она ниже.
Если вы наперед знаете, что какая-либо функция в вашем контракте будет вызываться чаще остальных, то старайтесь располагать ее выше остальных в контракте.
#gas #optimization #hint
🔥3
Оптимизация газа - 2
Если взглянуть на таблицу стоимости газа, то мы заметим, что вызов переменной в первый раз стоит нам 2100 газа, а во второй - уже 100 газа. Эта разница может стать проблемой, особенно, когда мы работаем с динамическими массивами в циклах. Посмотрите на второй пример на скрине.
Работая с данными внутри функции стоит намного меньше газа, даже с учетом того, что добавились новые строчки кода. В данном примере мы экономим почти 2000 газа!
#gas #optimization #hint
Если взглянуть на таблицу стоимости газа, то мы заметим, что вызов переменной в первый раз стоит нам 2100 газа, а во второй - уже 100 газа. Эта разница может стать проблемой, особенно, когда мы работаем с динамическими массивами в циклах. Посмотрите на второй пример на скрине.
Работая с данными внутри функции стоит намного меньше газа, даже с учетом того, что добавились новые строчки кода. В данном примере мы экономим почти 2000 газа!
#gas #optimization #hint
🔥2
Оптимизация газа - 3
Изменение значения с "0" до любого-другого в сети Эфира стоит 20 000 газа (Gsset), в то время как обнуление значения может возвращает часть газа на баланс (Rsclear). Тут важно отметить, что вернуть можно только 20% от стоимости транзакции, которая превышает 24 000 газа.
Пример 1
У Алисы 10 токенов, а у Боба 0 токенов. Алиса пересылает 5 токенов Бобу. Таким образом баланс Алисы меняется с 10 токенов до 5, а у Боба с 0 до 5. Итого:
У Алисы - 5000 газа + у Боба - 20 000 газа. Всего 25 000 газа за транзакцию.
Пример 2
У Алисы 10 токенов, у Боба - 0. Алиса пересылает все 10 токенов Бобу, и ее баланс обнуляется. Получается:
У Алисы - 5000 газа + у Боба 20 000 газа = 25 000 газа. При этом Алисе вернут 4 800 газа обратно. Итого транзакция у нас выйдет в 20 200 газа.
Очевидно, что второй пример позволяет нам сэкономить немного газа, который может быть потрачен на другие операции в контракте.
#gas #optimization #hint
Изменение значения с "0" до любого-другого в сети Эфира стоит 20 000 газа (Gsset), в то время как обнуление значения может возвращает часть газа на баланс (Rsclear). Тут важно отметить, что вернуть можно только 20% от стоимости транзакции, которая превышает 24 000 газа.
Пример 1
У Алисы 10 токенов, а у Боба 0 токенов. Алиса пересылает 5 токенов Бобу. Таким образом баланс Алисы меняется с 10 токенов до 5, а у Боба с 0 до 5. Итого:
У Алисы - 5000 газа + у Боба - 20 000 газа. Всего 25 000 газа за транзакцию.
Пример 2
У Алисы 10 токенов, у Боба - 0. Алиса пересылает все 10 токенов Бобу, и ее баланс обнуляется. Получается:
У Алисы - 5000 газа + у Боба 20 000 газа = 25 000 газа. При этом Алисе вернут 4 800 газа обратно. Итого транзакция у нас выйдет в 20 200 газа.
Очевидно, что второй пример позволяет нам сэкономить немного газа, который может быть потрачен на другие операции в контракте.
#gas #optimization #hint
Оптимизация газа - 4
Хранение данных в calldata всегда стоит меньше газа, чем хранение в memory. Это с учетом того, что вы будете просто считывать данные в calldata, а не изменять их. Во втором случае, memory более разумный выбор.
#gas #optimization #hint
Хранение данных в calldata всегда стоит меньше газа, чем хранение в memory. Это с учетом того, что вы будете просто считывать данные в calldata, а не изменять их. Во втором случае, memory более разумный выбор.
#gas #optimization #hint
Оптимизация газа - 5
В Solidity существует 4 способа инкремента / декремента числа на 1.
Для каждой задачи используется разный опкод, потому стоимость газа будет слегка отличаться.
#gas #optimization #hint
В Solidity существует 4 способа инкремента / декремента числа на 1.
Для каждой задачи используется разный опкод, потому стоимость газа будет слегка отличаться.
#gas #optimization #hint
👍1
Оптимизация газа - 6
Solidity Optimizer прорабатывает две вещи в смарт контрактах: стоимость деплоя и стоимость вызова функций. Чем меньше "runs" установлено в Оптимизатор, чем меньше будет стоимость деплоя. С другой стороны, чем больше "runs" - тем меньше стоимость вызова функций.
Вы можете сами настраивать количество "runs", чтобы оптимизиваровать стоимость газа конкретно для вашего контракта.
#gas #optimization #hint
Solidity Optimizer прорабатывает две вещи в смарт контрактах: стоимость деплоя и стоимость вызова функций. Чем меньше "runs" установлено в Оптимизатор, чем меньше будет стоимость деплоя. С другой стороны, чем больше "runs" - тем меньше стоимость вызова функций.
Вы можете сами настраивать количество "runs", чтобы оптимизиваровать стоимость газа конкретно для вашего контракта.
#gas #optimization #hint
Оптимизация газа - 7
Функции помеченные как payable потребляют меньше газа, так как для них требуется меньше опкода для проверки, может ли другой контракт пересылать Эфир.
#gas #optimization #hint
Функции помеченные как payable потребляют меньше газа, так как для них требуется меньше опкода для проверки, может ли другой контракт пересылать Эфир.
#gas #optimization #hint
Оптимизация газа - 8
Когда вызову в контракте требуется больше 32kb памяти в одной транзакции, стоимость газа в разы возрастает. Посмотрите на пример.
Стоимость добавление 10 000 uint256 в память больше примерно в 10 раз больше добавления 1000 значений.
При этом стоимость добавления 20 000 значений уже больше в 4 раза стоимости добавления 10 000 uint256.
Чтобы избежать этого, вам стоит разбивать большие данные на более мелкие части и работать прицельно с ними.
#gas #optimization #hint
Когда вызову в контракте требуется больше 32kb памяти в одной транзакции, стоимость газа в разы возрастает. Посмотрите на пример.
Стоимость добавление 10 000 uint256 в память больше примерно в 10 раз больше добавления 1000 значений.
При этом стоимость добавления 20 000 значений уже больше в 4 раза стоимости добавления 10 000 uint256.
Чтобы избежать этого, вам стоит разбивать большие данные на более мелкие части и работать прицельно с ними.
#gas #optimization #hint
Оптимизация газа - 9
В выражениях, где используются операторы сравнения (<,>,<=,>=), дешевле будет использовать простые операторы - < или >, так как в случае с <= и >= компилятор сначала использует опкод "больше / меньше", а после опкод "iszero", чтобы проверить результат работы предыдущего сравнения.
#gas #optimization #hint
В выражениях, где используются операторы сравнения (<,>,<=,>=), дешевле будет использовать простые операторы - < или >, так как в случае с <= и >= компилятор сначала использует опкод "больше / меньше", а после опкод "iszero", чтобы проверить результат работы предыдущего сравнения.
#gas #optimization #hint
😱1
Оптимизация газа - 10
Когда вы используете require с двумя и более проверками, то в начало ставьте операторы && или | | для уменьшения стоимости газа. Например:
- require(A | | B) - если true, то компилятор не будет проверять остальные значения;
- require (A && B) - если false, то компилятор также остановит проверку дальше;
#gas #optimization #hint
Когда вы используете require с двумя и более проверками, то в начало ставьте операторы && или | | для уменьшения стоимости газа. Например:
- require(A | | B) - если true, то компилятор не будет проверять остальные значения;
- require (A && B) - если false, то компилятор также остановит проверку дальше;
#gas #optimization #hint
Оптимизация газа - 11
Указание правильной видимости функций влияет не только на безопасность ее исполнения, но и на экономию газа.
Например, создав external функцию, вы установите место хранения ее параметров, как calldata. Это позволит экономить газ каждый раз при ее вызове.
#gas #optimization #hint
Указание правильной видимости функций влияет не только на безопасность ее исполнения, но и на экономию газа.
Например, создав external функцию, вы установите место хранения ее параметров, как calldata. Это позволит экономить газ каждый раз при ее вызове.
#gas #optimization #hint
👍1
Оптимизация газа - 12
В Solidity некоторые data types дороже остальных. Вот несколько рекомендаций к их использованию:
- Тип uint лучше использовать вместо string, если это возможно;
- uint256 стоит меньше, чем uint8;
- bytes лучше использовать вместо byte[];
- Если длина bytes может быть ограничена, то лучше использовать наименьшие числа от bytes1 до bytes32;
- bytes32 дешевле, чем string;
#gas #optimization #hint
В Solidity некоторые data types дороже остальных. Вот несколько рекомендаций к их использованию:
- Тип uint лучше использовать вместо string, если это возможно;
- uint256 стоит меньше, чем uint8;
- bytes лучше использовать вместо byte[];
- Если длина bytes может быть ограничена, то лучше использовать наименьшие числа от bytes1 до bytes32;
- bytes32 дешевле, чем string;
#gas #optimization #hint
👍1