Solidity. Смарт контракты и аудит
2.63K subscribers
247 photos
7 videos
18 files
557 links
Обучение Solidity. Уроки, аудит, разбор кода и популярных сервисов
Download Telegram
Out of Gas

В Твиттере встретил код assembly, который пытается сохранить 1 бит очень "глубоко" в памяти, что приводит к перерасходу газа.

assembly {
mstore(not(0),1)
}

Вероятно, однажды код был использован в какой-либо атаке.

#assembly #gas
👍2
Интересный нюанс с call вызовом

В Твиттере у одного из аудиторов увидел пост о том, что call вызов может привести к атаке gas griefing через байты, которые он возвращает в качестве ответа. Смотрите, что получается:

(bool success,) = payable(receiver).call{gas: 3000, value: amount}(hex"");

В данном примере после запятой не указан еще один возвращаемый параметр, и это должно выглядеть так:

(bool success, bytes memory data)

Оба варианта верны и часто используются в контрактах. Bytes позволяет вернуть саму ошибку, которая может возникнуть в течении транзакции.

При этому bytes data, которая возвращается из return, будет скопирована в память. И, если размер data будет слишком большой, то стоимость записи в память может превысить лимиты и получится gas griefing.

Аудитор предлагает использовать assembly, который автоматически не копирует возвращаемые значения в память, например:

bool success;
assembly {
success := call(3000, receiver, amount, 0, 0, 0)
}

Не могу точно сказать, что это 100% уязвимость и нужно переходить на assembly всем, так как доказательств в документации я не нашел.

Тем не менее, данный вопрос поднимался не раз на GitHub, вот один из таких примеров.

С обучением разработки смарт контрактов, как в той пословице: "Чем глубже в лес, тем больше дров". Всегда найдется какое-нибудь "но", на которое потом будешь обращать внимание.

#assembly #call
👍2
Чуть больше о Yul (assembly)

Чем больше профессиональных контрактов я просматриваю, тем больше там встречается оптимизированного кода с assembly. Появляются даже целые библиотеки со сниппетами кода, который можно сразу вставлять к себе в контракт.

Именно поэтому понимать базовые основы для хорошего разработчика просто необходимо.

Я нашел интересную статью о yul для начинающих, хоть и на английском языке.

Очень длинная, но хорошо объясняющая базовые опкоды.

Оставлю ее здесь на самостоятельное изучение. А если захотите, сможем сделать день yul и разобрать статью по частям.

#yul #assembly #opcode
👍6
Return в Solidity и return в assembly

Знали ли вы, что return в assembly ведет себя по-другому, чем return в solidity?

В assembly return фактически является опкодом, который прекращает выполнение контекста и возвращает срез (часть информации) памяти.

Например, в функции:

function someLogic() external returns(bool success) {

assembly {
return(0x00, 0x20)
}
_someMoreLogic();
}

действие никогда не дойдет до _someMoreLogic(), прекратившись на участке assembly.

В solidity "return <value>" как бы говорит компилятору, что функция завершила свое выполнение и <value> должно быть возвращено для следующего контекста.

Для external функций это, по сути, означает вызов Return, а для internal - типа "просто возвращайся".

Return в solidity служит как полезная абстракция и позволяет нашим функциям прекращаться раньше, порой избегая другую логику исполнения, как например тут:

function someLogic() internal {
if (isOwner()) return;
uint fee = calculateFee();
_charheFee();
}

Если же мы хотим создать подобную логику с помощью assembly, нам потребуется использовать for циклы:

function someLogic() internal {

assembly{

for {} 1 {} {
if eq(caller(), sload(owner, slot)) {
break
}

let fee := calcFee()
break

}
}
}

В этом случае for {} 1 {} {} выступает эквивалентом while(true), и исполнение может прекратиться либо после первого if, при вополнении условий, либо уже в конце функции.

Пост переведен из данной ветки Твиттера от philogy.

Фух, я еще постигаю assembly и мне крайне интересно, как работает вся эта штуковина изнутри.

#return #assembly
5👍1
Solidity hints. Часть 19

Смотрим следующий пункт в репо и звучит он как:

30. For pure functions, the opcode STATICCALL is used, which does not guarantee that the state is not read, but at least that it is not modified. It is not possible to prevent functions from reading the state at the level of the EVM.

что в переводе:

Для чистых функций используется опкод STATICCALL, который не гарантирует, что память контракта не будет прочитана, но, по крайней мере, что она не будет изменена. Запретить функциям читать состояние на уровне EVM невозможно.

Когда вы пишите функции в контракте и помечаете их как view или pure, возможно, вы порой замечали, что компилятор может ругаться на некоторые варианты:

1. Когда вы с view функцией хотите изменить какую-либо переменную состояния;
2. Когда в с pure функцией хотите прочитать переменную состояния;

Они обе используют опкод STATICCALL "под капотом", который запрещает модифицировать память контракта, однако... он не запрещает читать его!

Тут я пока не нашел объяснения, на каком уровне идет запрет для pure чтения памяти контракта, если вы знаете ответ, поделитесь в комментариях.

И когда искал ответ но этот вопрос, наткнулся на интересный рабочий пример с pure функцией, где разработчик задавался вопросом, почему в этом случае pure может читать из памяти:

pragma solidity ^0.8.15;

contract Demo {

string internal _thing = "I am a string";

function thing() external pure returns (string memory) {

string storage ref;

assembly ("memory-safe") {
ref.slot := _thing.slot
}

return ref;
}
}


В обсуждениях пришли к выводу, что все дело в блоке assembly, который неявно конвертирует из storage в memory, как view:

The assembly in this function is pure. The problem is that the implicit conversion from storage to memory is view.

В последней версии Solidity 0.8.26 это все еще работает!

#assembly #pure #view
🤔3