⚡️⚡️⚡️ Менее 12 часов до закрытия продаж! Курс для разработчиков ⚡️⚡️⚡️
Сегодня последний день продаж доступа на первый и второй модули курса. Если опоздаете, то придется ждать следующего года, так как третий и последующие модули будут доступны только текущим ученикам и тем, кто уже изучил базовый материал по Solidity.
Напомню, что на этом модуле мы будет проходить взаимодействие контрактов в сети: с другими контрактами и библиотеками, а также поговорим о самых популярных стандартах ERC для создания токенов и NFT. Вы научитесь не только создавать свои токены, но и понимать весь смысл кода контракта, в том числе аспекты его безопасности.
У нас уже набралась достаточно большая группа учеников, поэтому с понедельника, с началом курса, продаж не будет совсем!
Успейте занять свое место!
Программа курса! Описание курса
Условия и оплата
Старт 14 августа!
#курс
Сегодня последний день продаж доступа на первый и второй модули курса. Если опоздаете, то придется ждать следующего года, так как третий и последующие модули будут доступны только текущим ученикам и тем, кто уже изучил базовый материал по Solidity.
Напомню, что на этом модуле мы будет проходить взаимодействие контрактов в сети: с другими контрактами и библиотеками, а также поговорим о самых популярных стандартах ERC для создания токенов и NFT. Вы научитесь не только создавать свои токены, но и понимать весь смысл кода контракта, в том числе аспекты его безопасности.
У нас уже набралась достаточно большая группа учеников, поэтому с понедельника, с началом курса, продаж не будет совсем!
Успейте занять свое место!
Программа курса! Описание курса
Условия и оплата
Старт 14 августа!
#курс
Есть запросы?
Фух, после запуска курса как-то быстро время пошло, не успеваю уделять время на изучение новых постов и статей.
Вообще, за последнее время я немного разобрался со своими проблемными зонами в аудите и Solidity.
Во-первых, составил небольшой план проведения аудита, с учетом тех нюансов, которые я пропускал в прошедших конкурсах. Спасибо с4 за доступы к репортам, благодаря которым учишься быстрее. Теперь, надеюсь, поиск багов будет более эффективным. Узнаем на новых конкурсах!
Во-вторых, в Solidity моей слабой зоной оказался assembly и побитовые операции. И если со вторыми, посидев над задачей, я разобраться смогу, то с yul...
С assembly мне часто приходится обращаться к одному из участников канала с просьбой помочь разобраться. Все эти низкоуровневые запросы к памяти, порой абсолютно непонятны. Но после его объяснений, можно работать с кодом дальше. Очень надеюсь, что все сложится, и он станет одним из преподавателей на курсе!
Также хотел бы спросить, есть ли какие-нибудь запросы по темам от участников канала?
Этот канал ведется уже более года и тут поднималась и описывалась куча разных тем, и что самое главное, практически без повторов. Поэтому хотелось бы услышать, чего-то что не было еще.
Пожелания пишите в комментариях!
#offtop
Фух, после запуска курса как-то быстро время пошло, не успеваю уделять время на изучение новых постов и статей.
Вообще, за последнее время я немного разобрался со своими проблемными зонами в аудите и Solidity.
Во-первых, составил небольшой план проведения аудита, с учетом тех нюансов, которые я пропускал в прошедших конкурсах. Спасибо с4 за доступы к репортам, благодаря которым учишься быстрее. Теперь, надеюсь, поиск багов будет более эффективным. Узнаем на новых конкурсах!
Во-вторых, в Solidity моей слабой зоной оказался assembly и побитовые операции. И если со вторыми, посидев над задачей, я разобраться смогу, то с yul...
С assembly мне часто приходится обращаться к одному из участников канала с просьбой помочь разобраться. Все эти низкоуровневые запросы к памяти, порой абсолютно непонятны. Но после его объяснений, можно работать с кодом дальше. Очень надеюсь, что все сложится, и он станет одним из преподавателей на курсе!
Также хотел бы спросить, есть ли какие-нибудь запросы по темам от участников канала?
Этот канал ведется уже более года и тут поднималась и описывалась куча разных тем, и что самое главное, практически без повторов. Поэтому хотелось бы услышать, чего-то что не было еще.
Пожелания пишите в комментариях!
#offtop
👍6
Diamond Proxy
Делал тут небольшую презентацию про прокси контракты и вспомнил, что на канале просто упоминал про Diamond паттерн. Пора сделать чуть более подробное описание этого необычного прокси.
Итак, этот EIP был впервые представлен в феврале 2020 года и получил свой финальный вид в феврале 2020 года. Он определяет стандарт, так называемых, модульных контрактов и методов хранения информации в нем.
Кратко описывая этот паттерн, можно сказать, что у нас есть главный Diamond контракт, который хранит всю пользовательскую информацию, как и обычный Proxy (transparent, uups), а также данные об external функциях вспомогательных контрактов.
Вспомогательные контракты называются Facets.
Когда в Diamond контракт приходит вызов какой-либо функции, то он попадает в fallback, в котором идет запрос к хранилищу Diamond и ищется селектор нужной функции. По селектору определяется Facet и вызов через delegatecall перенаправляется туда.
Также существует, скажем так, главный Facet, который содержит функцию diamondCut. С помощью нее мы можем добавлять новые селекторы функций и другие адреса Facets в наш Diamond контракт.
Вообще, с помощью этого паттерна нам не нужно заботится о размере контрактов, так как с помощью такой модульной системы мы можем строить реально большие протоколы! Это бывает полезно, когда мы не знаем, насколько большим будет наш Dapp, и сколько функций потребуется в итоге.
Очень интересно организована система storage в Diamond прокси. Но об этом более подробно можно прочитать по этой прекрасной ссылке!
#diamond #proxy
Делал тут небольшую презентацию про прокси контракты и вспомнил, что на канале просто упоминал про Diamond паттерн. Пора сделать чуть более подробное описание этого необычного прокси.
Итак, этот EIP был впервые представлен в феврале 2020 года и получил свой финальный вид в феврале 2020 года. Он определяет стандарт, так называемых, модульных контрактов и методов хранения информации в нем.
Кратко описывая этот паттерн, можно сказать, что у нас есть главный Diamond контракт, который хранит всю пользовательскую информацию, как и обычный Proxy (transparent, uups), а также данные об external функциях вспомогательных контрактов.
Вспомогательные контракты называются Facets.
Когда в Diamond контракт приходит вызов какой-либо функции, то он попадает в fallback, в котором идет запрос к хранилищу Diamond и ищется селектор нужной функции. По селектору определяется Facet и вызов через delegatecall перенаправляется туда.
Также существует, скажем так, главный Facet, который содержит функцию diamondCut. С помощью нее мы можем добавлять новые селекторы функций и другие адреса Facets в наш Diamond контракт.
Вообще, с помощью этого паттерна нам не нужно заботится о размере контрактов, так как с помощью такой модульной системы мы можем строить реально большие протоколы! Это бывает полезно, когда мы не знаем, насколько большим будет наш Dapp, и сколько функций потребуется в итоге.
Очень интересно организована система storage в Diamond прокси. Но об этом более подробно можно прочитать по этой прекрасной ссылке!
#diamond #proxy
👍8🔥2❤1
Небольшая заметка про Arbitrum
Сейчас смотрел конкурсный аудит от Shell и заметил, что проект работает в сети Arbutrum. Я никогда ранее напрямую не работал с этой сетью, в плане аудита или деплоя контрактов, поэтому мне захотелось узнать чуть больше о ее технической части.
Вообще, когда вы планируете разработку своего протокола на нескольких сетях, то лучше всего не полагаться на, так называемую, "похожесть" EVM сетей, а смотреть документации и искать, где может быть проблема.
В доках Арбитрума есть специальный раздел, где описывается разница между двумя сетями, но можно назвать всего пару основных, на которые необходимо обращать внимание.
1. Запрос block.number вернет номер блока примерно приближенного к сети L1, а не самого Арбитрума. Для запроса номеров блока в Арбитруме есть свои методы.
2. Один блок Эфира может включать в себя несколько блоков Арбитрума.
3. Разные блоки могут иметь одинаковый timestamp. Тут есть описание проблемы.
4. Push0 также еще не поддерживается на этой сети
Если хотите прочитать про остальные различия, то они лучше всего описаны в этом посте, а про поддержку Solidity можно узнать тут.
P.S. Если знаете еще какие-то моменты, на которые следует обращать внимание, то дайте знать в комментариях.
UPD. Спасибо @Deathstore за ссылку http://evmdiff.com/. Тут можно посмотреть различия в сетях.
#arbitrum
Сейчас смотрел конкурсный аудит от Shell и заметил, что проект работает в сети Arbutrum. Я никогда ранее напрямую не работал с этой сетью, в плане аудита или деплоя контрактов, поэтому мне захотелось узнать чуть больше о ее технической части.
Вообще, когда вы планируете разработку своего протокола на нескольких сетях, то лучше всего не полагаться на, так называемую, "похожесть" EVM сетей, а смотреть документации и искать, где может быть проблема.
В доках Арбитрума есть специальный раздел, где описывается разница между двумя сетями, но можно назвать всего пару основных, на которые необходимо обращать внимание.
1. Запрос block.number вернет номер блока примерно приближенного к сети L1, а не самого Арбитрума. Для запроса номеров блока в Арбитруме есть свои методы.
2. Один блок Эфира может включать в себя несколько блоков Арбитрума.
3. Разные блоки могут иметь одинаковый timestamp. Тут есть описание проблемы.
4. Push0 также еще не поддерживается на этой сети
Если хотите прочитать про остальные различия, то они лучше всего описаны в этом посте, а про поддержку Solidity можно узнать тут.
P.S. Если знаете еще какие-то моменты, на которые следует обращать внимание, то дайте знать в комментариях.
UPD. Спасибо @Deathstore за ссылку http://evmdiff.com/. Тут можно посмотреть различия в сетях.
#arbitrum
👍10
Необычное использование прокси
В еще одном конкурсном аудите обнаружил для себя необычный подход к использованию прокси контрактов, которое немного перевернуло мое представление об этом паттерне.
Смотрите, в обычной реализации мы делаем как? Мы пишем прокси контракт, а затем добавляем к нему контракт Логики. Все данные хранятся в прокси, а Логику мы можем менять хоть каждый день.
В проекте Sparkn по-другому.
P.S. Репо, возможно, будет открыто только на время конкурса.
Здесь три контракта: proxy, distributor и proxyFactory.
Proxy - самая простая реализация прокси паттерна с одной лишь fallback функцией.
Distributor - контракт Логики для прокси с разными функциями, основная из которых это перечисление средств пользователям.
ProxyFactory - основной контракт для взаимодействия.
Смотрите, что получается. Контракты proxyFactory и distributor постоянные. Т.е. distributor хоть и является контрактом Логики для прокси, но в данном случае он не обновляемый, так как функционал для этого не заложен.
В proxyFactor есть функция для деплоя прокси контракта и определения его адреса наперед, основываясь на определенных параметрах, в том числе на адресе пользователя.
Итак, суть в том, что пользователь может заранее вычислить свой адрес прокси и отправить туда некоторую сумму для распределения пользователям. Затем он вызывает функцию деплоя proxy, сразу после чего идет туда вызов и с помощью delegatecall вызывается функция из distributor, которая делает рассылку активов для установленных пользователей.
Другими словами, каждый взятый пользователь может сделать деплой прокси, на котором будут лежать токены, и уже с него через фиксированный контракт distributor сделать рассылку.
Два контракта постоянных, и неограниченное количество индивидуальных контрактов для пользователей.
На мой взгляд сделано очень круто!
#proxy
В еще одном конкурсном аудите обнаружил для себя необычный подход к использованию прокси контрактов, которое немного перевернуло мое представление об этом паттерне.
Смотрите, в обычной реализации мы делаем как? Мы пишем прокси контракт, а затем добавляем к нему контракт Логики. Все данные хранятся в прокси, а Логику мы можем менять хоть каждый день.
В проекте Sparkn по-другому.
P.S. Репо, возможно, будет открыто только на время конкурса.
Здесь три контракта: proxy, distributor и proxyFactory.
Proxy - самая простая реализация прокси паттерна с одной лишь fallback функцией.
Distributor - контракт Логики для прокси с разными функциями, основная из которых это перечисление средств пользователям.
ProxyFactory - основной контракт для взаимодействия.
Смотрите, что получается. Контракты proxyFactory и distributor постоянные. Т.е. distributor хоть и является контрактом Логики для прокси, но в данном случае он не обновляемый, так как функционал для этого не заложен.
В proxyFactor есть функция для деплоя прокси контракта и определения его адреса наперед, основываясь на определенных параметрах, в том числе на адресе пользователя.
Итак, суть в том, что пользователь может заранее вычислить свой адрес прокси и отправить туда некоторую сумму для распределения пользователям. Затем он вызывает функцию деплоя proxy, сразу после чего идет туда вызов и с помощью delegatecall вызывается функция из distributor, которая делает рассылку активов для установленных пользователей.
Другими словами, каждый взятый пользователь может сделать деплой прокси, на котором будут лежать токены, и уже с него через фиксированный контракт distributor сделать рассылку.
Два контракта постоянных, и неограниченное количество индивидуальных контрактов для пользователей.
На мой взгляд сделано очень круто!
#proxy
👍5
Еще один пример работы с Error
В протоколе Dopex встретил хороший пример работы с ошибками (require / error) в контракте.
Была создана специальная функция для валидации входящих условий:
function _validate(bool _clause, uint256 _errorCode) internal pure {
if (!_clause) revert RdpxV2CoreError(_errorCode);
}
которая проверяет условие в родительской функции, и если не true, то порождает Error с нужным кодом. Сам Error выглядит максимально просто:
error RdpxV2CoreError(uint256);
ну, и в коде, в комментариях, есть список ошибок по номеру, например:
// ERROR CODES
// E1: "Insufficient bond amount",
// E2: "Bond has expired",
// E3: "Invalid parameters"
Как это работает? Например, у нас есть функция, которая должна принимать адрес в качестве аргумента:
function getAddress(address addr) external {...}
и нам нужно проверить, чтобы этот адрес не был нулевым, поэтому мы передаем условие в функцию _validate():
function getAddress(address addr) external {
_valudate(addr != address(0), 4);
}
Идет проверка, если адрес окажется нулевым, по код выдаст ошибку под номером 4, из чего мы узнаем о проваленной проверке.
Вот такое простое решение.
#error
В протоколе Dopex встретил хороший пример работы с ошибками (require / error) в контракте.
Была создана специальная функция для валидации входящих условий:
function _validate(bool _clause, uint256 _errorCode) internal pure {
if (!_clause) revert RdpxV2CoreError(_errorCode);
}
которая проверяет условие в родительской функции, и если не true, то порождает Error с нужным кодом. Сам Error выглядит максимально просто:
error RdpxV2CoreError(uint256);
ну, и в коде, в комментариях, есть список ошибок по номеру, например:
// ERROR CODES
// E1: "Insufficient bond amount",
// E2: "Bond has expired",
// E3: "Invalid parameters"
Как это работает? Например, у нас есть функция, которая должна принимать адрес в качестве аргумента:
function getAddress(address addr) external {...}
и нам нужно проверить, чтобы этот адрес не был нулевым, поэтому мы передаем условие в функцию _validate():
function getAddress(address addr) external {
_valudate(addr != address(0), 4);
}
Идет проверка, если адрес окажется нулевым, по код выдаст ошибку под номером 4, из чего мы узнаем о проваленной проверке.
Вот такое простое решение.
#error
👍5
Снимоку.PNG
2.6 KB
1000+ участников!
С таким активным летом на события в жизни, совсем пропустил, что канал перевалил за 1000 участников! Потихоньку мы идем к цели и становимся самым большим каналом в русскоязычном сегменте, который посвящен Solidity и аудиту!
800+ информационных постов, 350+ ссылок на различные ресурсы, 2 активных обучающих модуля и 3 в плане, а также куча разных подборок - все это и делает наше сообщество одним из самых крутых!
С нового учебного года мы продолжим наш путь в тонкости языка и проблем безопасности современных смарт контрактов, будем говорить и про различные тулзы, которые стали появляться чуть ли не каждую неделю, и про опкоды и yul, и, возможно, попробуем залезть в ноду!
Ожидается много интересного!
А пока, гуляем последние дни лета, готовимся к новому сезону и копим силы!
Поздравляю всех с 1К!
С таким активным летом на события в жизни, совсем пропустил, что канал перевалил за 1000 участников! Потихоньку мы идем к цели и становимся самым большим каналом в русскоязычном сегменте, который посвящен Solidity и аудиту!
800+ информационных постов, 350+ ссылок на различные ресурсы, 2 активных обучающих модуля и 3 в плане, а также куча разных подборок - все это и делает наше сообщество одним из самых крутых!
С нового учебного года мы продолжим наш путь в тонкости языка и проблем безопасности современных смарт контрактов, будем говорить и про различные тулзы, которые стали появляться чуть ли не каждую неделю, и про опкоды и yul, и, возможно, попробуем залезть в ноду!
Ожидается много интересного!
А пока, гуляем последние дни лета, готовимся к новому сезону и копим силы!
Поздравляю всех с 1К!
🎉36🔥3
Есть ли авторы?
Слушайте, сейчас у меня пошел какой-то нереальный загруз в работе, что я не успеваю подготавливать хороший материал по темам языка и аудита для канала.
Я подумал, может кто-то из вас хотел бы попробовать написать несколько постов для канала? Может у вас есть какие-то любимые темы, которыми вы хотели бы поделиться, и которых еще не было на канале?
Также хотелось бы видеть авторские посты, а не копипаста с других каналов или статей. Посты с пруфами, ссылками и репо.
Есть желающие?
Слушайте, сейчас у меня пошел какой-то нереальный загруз в работе, что я не успеваю подготавливать хороший материал по темам языка и аудита для канала.
Я подумал, может кто-то из вас хотел бы попробовать написать несколько постов для канала? Может у вас есть какие-то любимые темы, которыми вы хотели бы поделиться, и которых еще не было на канале?
Также хотелось бы видеть авторские посты, а не копипаста с других каналов или статей. Посты с пруфами, ссылками и репо.
Есть желающие?
Пишешь тесты на Foundry?
Еще один вопрос на сегодня: есть ли среди участников канала те, кто хорошо умеет работать с Foundry и писать тесты?
Важно умение писать тесты для токенов, NFT, Vaults, связки с uniswap и другими defi, варианты с флешзаймами и т.д. Т.е. прям хорошо владеющие этим навыком!
Хочу попробовать один проект запустить осенью или зимой на зарубежный рынок.
Ниже будет опрос, кликните, кто на скиле)
Еще один вопрос на сегодня: есть ли среди участников канала те, кто хорошо умеет работать с Foundry и писать тесты?
Важно умение писать тесты для токенов, NFT, Vaults, связки с uniswap и другими defi, варианты с флешзаймами и т.д. Т.е. прям хорошо владеющие этим навыком!
Хочу попробовать один проект запустить осенью или зимой на зарубежный рынок.
Ниже будет опрос, кликните, кто на скиле)
👍6
Пишешь тесты на Foundry
Anonymous Poll
14%
Да, уже писал тесты и хорошо в них разбираюсь
19%
Да, на среднем уровне, нужно подучить
43%
Нет, но хочу научиться
24%
Нет, больше по hardhat
Пишешь тесты на Foundry 2
Пока выдалась свободная минута, хочу продолжить мысль про последние посты.
Из опроса видно, что 15 участников чата уверены в своих силах для написания тестов на Foundry. Честно говоря, я думал, что будет куда меньше!
Зачем я проводил опрос?
При общении с зарубежными разработчиками, я понял, что есть некоторый интерес к коммерческому написанию тестов для смарт контрактов, без разработки самих протоколов. Другими словами, вам дают контракт и вы пишите тесты для них.
И я планирую вывести эту услугу осенью-зимой, вкупе с парой других сопутствующих.
Нужны 3-4 человека, которым интересная эта тема и которые будут готовы к такой работе.
Если хотите поучаствовать, то напишите мне в личку @zaevlad. Желательно приложить ссылку на свой репо, где уже есть написанные тесты.
На данный момент точно по срокам не скажу, разбираю свои текущие задачи. Сейчас просто собираю желающих и проверяю интерес.
Всем спасибо за пройденный опрос!
Для тех, кто не знает Foundry, могу найти хорошего преподавателя, который даст уроки. Но это уже в конце года, сейчас еще идет текущий курс.
#foundry
Пока выдалась свободная минута, хочу продолжить мысль про последние посты.
Из опроса видно, что 15 участников чата уверены в своих силах для написания тестов на Foundry. Честно говоря, я думал, что будет куда меньше!
Зачем я проводил опрос?
При общении с зарубежными разработчиками, я понял, что есть некоторый интерес к коммерческому написанию тестов для смарт контрактов, без разработки самих протоколов. Другими словами, вам дают контракт и вы пишите тесты для них.
И я планирую вывести эту услугу осенью-зимой, вкупе с парой других сопутствующих.
Нужны 3-4 человека, которым интересная эта тема и которые будут готовы к такой работе.
Если хотите поучаствовать, то напишите мне в личку @zaevlad. Желательно приложить ссылку на свой репо, где уже есть написанные тесты.
На данный момент точно по срокам не скажу, разбираю свои текущие задачи. Сейчас просто собираю желающих и проверяю интерес.
Всем спасибо за пройденный опрос!
Для тех, кто не знает Foundry, могу найти хорошего преподавателя, который даст уроки. Но это уже в конце года, сейчас еще идет текущий курс.
#foundry
🔥7
Активное выдалось лето
Я тут вчера рефлексировал по прошедшему лету и удивлялся, что оно так быстро прошло. В последние дни все чаще появлялось чувство усталости (может и из-за сезонной аллергии) и ощущение, что я немного торможу свое собственное обучение.
Потом я решил посмотреть, а чем же я занимался все лето, и вот, что получилось:
1. За три месяца я поучаствовал в 7 конкурсных аудитах на Code4rena;
2. Заходил на 3 конкурса на новой платформе CodeHawks;
3. Запустил и написал материалы для 2 модулей своего курса для начинающих разработчиков;
4. Пошел отбор и стажировался в крутой зарубежной компании, занимающейся безопасностью и аудитом;
5. На канале стало 1000+ участников!
Начинается новый сезон, и я думаю, взять небольшой отпуск от конкурсных аудитов. Они занимаю очень много времени.
Хочу пересмотреть и изучить материалы, которые все это время сохранял к себе в закладки, провести 3 модуль курса и подготовить почву для дальнейших проектов.
Это так, просто делюсь с вами текущими событиями.
P.S. Все также еще актуален отклик на тестировщика Foundry!
Я тут вчера рефлексировал по прошедшему лету и удивлялся, что оно так быстро прошло. В последние дни все чаще появлялось чувство усталости (может и из-за сезонной аллергии) и ощущение, что я немного торможу свое собственное обучение.
Потом я решил посмотреть, а чем же я занимался все лето, и вот, что получилось:
1. За три месяца я поучаствовал в 7 конкурсных аудитах на Code4rena;
2. Заходил на 3 конкурса на новой платформе CodeHawks;
3. Запустил и написал материалы для 2 модулей своего курса для начинающих разработчиков;
4. Пошел отбор и стажировался в крутой зарубежной компании, занимающейся безопасностью и аудитом;
5. На канале стало 1000+ участников!
Начинается новый сезон, и я думаю, взять небольшой отпуск от конкурсных аудитов. Они занимаю очень много времени.
Хочу пересмотреть и изучить материалы, которые все это время сохранял к себе в закладки, провести 3 модуль курса и подготовить почву для дальнейших проектов.
Это так, просто делюсь с вами текущими событиями.
P.S. Все также еще актуален отклик на тестировщика Foundry!
🔥24👍5
Реентранси, рекурсия и ресмысление...
Каждый, кто начинает свой путь в аудите и безопасности смарт контрактов, одной из первых атак изучает Реентранси, грубо говоря, повторное вхождение в одну функцию несколько раз.
Я уже не раз писал на канале о этом, находил сам в задачах и аудитах, но что-то никогда не задумывался, как она работает "под капотом".
И вот вчера, меня немного поставили в ступор простым кодом:
Вопрос был простой: "Почему не работает реентранси?".
И код написан правильно. Но вот, почему не проходит...
Я пошел сравнивать этот код с кодом примеров данной атаки, которые были в разных статьях в поиске гугл. Достаточно большое количество ресурсов, в том числе solidity-by-example, показывали пример атаки, где в итоге баланс пользователя обнуляется, а не минусуется, как в данном примере. Другими словами, вместо:
balances[msg.sender] -= _amount;
было
balances[msg.sender] = 0;
И вот если в данном контракте также обнулять контракт, то атака работает!
Мне потребовалось некоторое время и помощь коллег, чтобы разобраться с этим.
Скажу сразу, я не правильно понимал ход выполнения атаки реентранси. До сего дня я полагал, что функция будет заходить в withdraw(), доходить до call вызова, перебрасываться в контракт хакера в receive(), а оттуда снова в withdraw() и так по кругу, пока не будет выполнено условие if(address(walletRe).balance >= SUM) {}, после чего call наконец пройдет, и исполнение функции дойдет до balances[msg.sender] -= _amount.
Короче, это не так.
На самом деле функция заходит в некую рекурсию, т.е. выполняется withdraw() полностью, но в своем программном порядке.
Лучше всего о рекурсии можно узнать из этого видео, спасибо @elawbek за наводку!
Смотрите, что получается. Допустим у нас есть 10 Эфиров на контракте Wallet, которые мы хотим увести. На контракте хакера 1 Эфир для атаки.
Сначала мы делаем депозит и переводим 1 Эфир с контракта хакера, на контракт Wallet, и так теперь 11 Эфира. После чего мы запускаем функцию attack().
Мы попадаем в withdraw(), происходит рекурсия, которая под капотом исполняет данную функцию 11 раз, так как всего там 11 Эфира лежит. И когда рекурсия начинает "сворачиваться", то оказывается, что успешная пересылка Эфира прошла всего один раз, и тогда уже сработало balances[msg.sender] -= _amount. В каждый последующий раз мы вычитали 1 Эфир из balances[msg.sender], на котором уже был ноль. Происходило переполнение и call вызов возвращал "External call returned false".
Именно поэтому атака реентранси тут и стопорилась!
Но почему в некоторых статьях также можно встретить описание атаки, где присутствует balances[msg.sender] -= _amount? Как, например, тут.
Объяснение просто: все дело в pragma и версии Solidity. До 0.8 версии в математических расчетах не было проверки на overflow. Система не выдавала ошибку, когда программа пыталась вычесть какое-либо число из 0.
Кстати, задача Ethernaut именно на этом и построена!
Каждый, кто начинает свой путь в аудите и безопасности смарт контрактов, одной из первых атак изучает Реентранси, грубо говоря, повторное вхождение в одну функцию несколько раз.
Я уже не раз писал на канале о этом, находил сам в задачах и аудитах, но что-то никогда не задумывался, как она работает "под капотом".
И вот вчера, меня немного поставили в ступор простым кодом:
contract Wallet {
mapping(address => uint) public balances;
function deposit(address _to) public payable {
balances[_to] = balances[_to] + msg.value;
}
function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
(bool result,) = msg.sender.call{value:_amount}("");
require(result, "External call returned false");
balances[msg.sender] -= _amount;
}
}
receive() external payable {}
}
contract AttacWallet {
uint constant SUM = 1 ether;
Wallet walletRe;
constructor(address payable _wallet) {
walletRe = Wallet(_wallet);
}
function depositAttack() public payable {
walletRe.deposit{value: SUM}(address(this));
}
function attack() external payable {
walletRe.withdraw(SUM);
}
receive() external payable {
if(address(walletRe).balance >= SUM) {
walletRe.withdraw(SUM);
}
}
}
Есть два контракта: Wallet и AttacWalet, второй предназначен для взлома первого.Вопрос был простой: "Почему не работает реентранси?".
И код написан правильно. Но вот, почему не проходит...
Я пошел сравнивать этот код с кодом примеров данной атаки, которые были в разных статьях в поиске гугл. Достаточно большое количество ресурсов, в том числе solidity-by-example, показывали пример атаки, где в итоге баланс пользователя обнуляется, а не минусуется, как в данном примере. Другими словами, вместо:
balances[msg.sender] -= _amount;
было
balances[msg.sender] = 0;
И вот если в данном контракте также обнулять контракт, то атака работает!
Мне потребовалось некоторое время и помощь коллег, чтобы разобраться с этим.
Скажу сразу, я не правильно понимал ход выполнения атаки реентранси. До сего дня я полагал, что функция будет заходить в withdraw(), доходить до call вызова, перебрасываться в контракт хакера в receive(), а оттуда снова в withdraw() и так по кругу, пока не будет выполнено условие if(address(walletRe).balance >= SUM) {}, после чего call наконец пройдет, и исполнение функции дойдет до balances[msg.sender] -= _amount.
Короче, это не так.
На самом деле функция заходит в некую рекурсию, т.е. выполняется withdraw() полностью, но в своем программном порядке.
Лучше всего о рекурсии можно узнать из этого видео, спасибо @elawbek за наводку!
Смотрите, что получается. Допустим у нас есть 10 Эфиров на контракте Wallet, которые мы хотим увести. На контракте хакера 1 Эфир для атаки.
Сначала мы делаем депозит и переводим 1 Эфир с контракта хакера, на контракт Wallet, и так теперь 11 Эфира. После чего мы запускаем функцию attack().
Мы попадаем в withdraw(), происходит рекурсия, которая под капотом исполняет данную функцию 11 раз, так как всего там 11 Эфира лежит. И когда рекурсия начинает "сворачиваться", то оказывается, что успешная пересылка Эфира прошла всего один раз, и тогда уже сработало balances[msg.sender] -= _amount. В каждый последующий раз мы вычитали 1 Эфир из balances[msg.sender], на котором уже был ноль. Происходило переполнение и call вызов возвращал "External call returned false".
Именно поэтому атака реентранси тут и стопорилась!
Но почему в некоторых статьях также можно встретить описание атаки, где присутствует balances[msg.sender] -= _amount? Как, например, тут.
Объяснение просто: все дело в pragma и версии Solidity. До 0.8 версии в математических расчетах не было проверки на overflow. Система не выдавала ошибку, когда программа пыталась вычесть какое-либо число из 0.
Кстати, задача Ethernaut именно на этом и построена!
👍9❤2👏2😁1
Теперь же происходит переполнение и функция откатывается. Это можно легко проверить и в 0.8 версии поместив вычитание в unchecked:
В общем, такое получилось небольшое расследование. Если все так же не очень понятен смысл поста, то рекомендую сначала посмотреть видео про рекурсию, а потом еще раз почитать про реентранси.
P.S. Если захотите увидеть, как все происходит внутри, добавьте этот тест в Foundry:
forge test --match-contract TestReentrancy -vvvv
Надеюсь, некоторым, как и мне вчера, стало чуть более понятна эта атака.
#reentrancy
unchecked {
balances[msg.sender] -= _amount;
}
Реентранси снова пройдет! В общем, такое получилось небольшое расследование. Если все так же не очень понятен смысл поста, то рекомендую сначала посмотреть видео про рекурсию, а потом еще раз почитать про реентранси.
P.S. Если захотите увидеть, как все происходит внутри, добавьте этот тест в Foundry:
contract TestReentrancy is Test {
using stdStorage for StdStorage;
Wallet public wallet;
AttacWallet public attacWallet;
function setUp() public{
wallet = new Wallet();
attacWallet = new AttacWallet(payable(wallet));
vm.deal(address(wallet), 10 ether);
vm.deal(address(attacWallet), 2 ether);
}
function test_testSetup() public {
attacWallet.depositAttack();
attacWallet.attack();
console.log(address(attacWallet).balance);
}
}
И запустите команду в терминале:forge test --match-contract TestReentrancy -vvvv
Надеюсь, некоторым, как и мне вчера, стало чуть более понятна эта атака.
#reentrancy
👍13
Письменное задание в Spearbit
Для тех, кто не в курсе, Spearbit крутая зарубежная компания, которая занимается безопасностью смарт контрактов. Пару раз в год они набирают к себе аудиторов. Для этого требуется пройти тест из 4 вариантов ответа на время и после технического интервью еще выполнить письменное задание.
Предлагаю вашему вниманию одно из таких заданий от февраля 2022 года. Прекрасная практика для аудиторов и тех, кто любит решать задачи.
Есть два контракта: прокси и Логики. Деплой логики делается только однажды для всех пользователей. Прокси - для каждого свой.
Пользователи держат все свои активы на прокси контракте и, в случае необходимости, посылают вызовы на контракт Логики.
Тут есть критическая уязвимость. Задание: найти ее и дать свои рекомендации по ее устранению.
Вы получите дополнительный бонус, если в рекомендациях расскажите о нишевом решении для предотвращения уязвимости. Еще один бонус получите, если в рекомендациях расскажите, как можно убрать уязвимость, убрав два слова в контракте, и изменив всего одно.
Вот ссылка на репо задания: https://github.com/spearbit-audits/writing-exercise
Удачи в поисках!
#proxy #bug #spearbit
Для тех, кто не в курсе, Spearbit крутая зарубежная компания, которая занимается безопасностью смарт контрактов. Пару раз в год они набирают к себе аудиторов. Для этого требуется пройти тест из 4 вариантов ответа на время и после технического интервью еще выполнить письменное задание.
Предлагаю вашему вниманию одно из таких заданий от февраля 2022 года. Прекрасная практика для аудиторов и тех, кто любит решать задачи.
Есть два контракта: прокси и Логики. Деплой логики делается только однажды для всех пользователей. Прокси - для каждого свой.
Пользователи держат все свои активы на прокси контракте и, в случае необходимости, посылают вызовы на контракт Логики.
Тут есть критическая уязвимость. Задание: найти ее и дать свои рекомендации по ее устранению.
Вы получите дополнительный бонус, если в рекомендациях расскажите о нишевом решении для предотвращения уязвимости. Еще один бонус получите, если в рекомендациях расскажите, как можно убрать уязвимость, убрав два слова в контракте, и изменив всего одно.
Вот ссылка на репо задания: https://github.com/spearbit-audits/writing-exercise
Удачи в поисках!
#proxy #bug #spearbit
👍11❤1🔥1
Что за адрес 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE?
Это специальный адрес, который работает как идентификатор для Эфира, нативной валюты. Подобно адресам токенов ERC20, данный адрес используется для представления Эфира в контрактах, как если он был токеном.
Его можно посмотреть на Etherscan тут.
#ether
Это специальный адрес, который работает как идентификатор для Эфира, нативной валюты. Подобно адресам токенов ERC20, данный адрес используется для представления Эфира в контрактах, как если он был токеном.
Его можно посмотреть на Etherscan тут.
#ether
👍7
Побитовые в Chainlink
На этой неделе заканчивается конкурсный аудит в Chainlink. И в одном из контрактов были использованы побитовые операции, которые я и хочу сегодня разобрать с вами.
Вообще, побитовые операции отдельная моя боль. Я пока что еще не научился понимать их на интуитивном уровне, и каждый код с ними занимает некоторое время, чтобы полноценно понимать его работу.
Контракты конкурсного аудита можно посмотреть тут, а тут ссылка на контракт и функцию из примера.
Итак, посмотрим на функцию с побитовой операцией:
(uint224(uint112(latestPrincipal)) << 112) - сначала значение latestPrincipal уменьшается до uint112, а затем увеличивается до uint224, для того чтобы вместить значение для операции сдвига влево.
uint224(uint112(latestStakedAtTime)) - берем значение latestStakedAtTime и также приводим его к uint112 в начале, и к uint224 позже.
Побитовая операция OR (вот эта палочка - "|" между значениями) служит для объединения ранее сдвинутого latestPrincipal со latestStakedAtTime.
В результате получается, что latestPrincipal занимает верхние 112 бит, а latestStakedAtTime - нижние 112 бит.
Для себя и тех, кто забыл, напомню, как работает побитовое OR (ИЛИ).
Допустим у нас есть два значения:
Это делается для того, чтобы работа со схожими по смыслу значениями была более эффективной и занимала меньше места в памяти контракта.
Прекрасное компактное решение от команды Chainlink!
#bit #or #shift
На этой неделе заканчивается конкурсный аудит в Chainlink. И в одном из контрактов были использованы побитовые операции, которые я и хочу сегодня разобрать с вами.
Вообще, побитовые операции отдельная моя боль. Я пока что еще не научился понимать их на интуитивном уровне, и каждый код с ними занимает некоторое время, чтобы полноценно понимать его работу.
Контракты конкурсного аудита можно посмотреть тут, а тут ссылка на контракт и функцию из примера.
Итак, посмотрим на функцию с побитовой операцией:
function _updateStakerHistory(s_checkpointId - это просто уникальный идентифкатор для ведения учета, который увеличивается на +1 при каждом вызове функции.
Staker storage staker,
uint256 latestPrincipal,
uint256 latestStakedAtTime
) internal {
staker.history.push(
s_checkpointId++,
(uint224(uint112(latestPrincipal)) << 112) | uint224(uint112(latestStakedAtTime))
);
}
(uint224(uint112(latestPrincipal)) << 112) - сначала значение latestPrincipal уменьшается до uint112, а затем увеличивается до uint224, для того чтобы вместить значение для операции сдвига влево.
uint224(uint112(latestStakedAtTime)) - берем значение latestStakedAtTime и также приводим его к uint112 в начале, и к uint224 позже.
Побитовая операция OR (вот эта палочка - "|" между значениями) служит для объединения ранее сдвинутого latestPrincipal со latestStakedAtTime.
В результате получается, что latestPrincipal занимает верхние 112 бит, а latestStakedAtTime - нижние 112 бит.
Для себя и тех, кто забыл, напомню, как работает побитовое OR (ИЛИ).
Допустим у нас есть два значения:
а 1011010101Если хотябы одно значение будет равно "1", то и результат будет равен "1". Отсюда получаем:
b 0111010111
с 1111010111Т.е. вы поняли теперь как работает функция в chainlink? Мы берем значение, обрезаем его до uint112 и тут же увеличивает до uint224, освобождая место при помощи сдвига влево (<<) для другого значения, которое мы и записываем на освободившееся пространство.
Это делается для того, чтобы работа со схожими по смыслу значениями была более эффективной и занимала меньше места в памяти контракта.
Прекрасное компактное решение от команды Chainlink!
#bit #or #shift
👍4🔥3
Плагин для VSCode
Некоторое время назад подбирал для себя простое решения для того, чтобы отмечать файлы и контракты, аудит которых уже провел.
Многие из плагинов были слегка накрученные и с множеством дополнительных функций, что порой, скорее, мешало работе, чем помогало ей.
Вот нашел для себя простой плагин Mark files:
После установки, кликаете правой кнопкой мыши на древе файлов и в появившемся меню выбираете "Mark \ unmark selected file". Радом с файлом или папкой появится маленький значок.
В общем, крайне простой инструмент.
#plaggin
Некоторое время назад подбирал для себя простое решения для того, чтобы отмечать файлы и контракты, аудит которых уже провел.
Многие из плагинов были слегка накрученные и с множеством дополнительных функций, что порой, скорее, мешало работе, чем помогало ей.
Вот нашел для себя простой плагин Mark files:
После установки, кликаете правой кнопкой мыши на древе файлов и в появившемся меню выбираете "Mark \ unmark selected file". Радом с файлом или папкой появится маленький значок.
В общем, крайне простой инструмент.
#plaggin
👍7
Return в Solidity и return в assembly
Знали ли вы, что return в assembly ведет себя по-другому, чем return в solidity?
В assembly return фактически является опкодом, который прекращает выполнение контекста и возвращает срез (часть информации) памяти.
Например, в функции:
В solidity "return <value>" как бы говорит компилятору, что функция завершила свое выполнение и <value> должно быть возвращено для следующего контекста.
Для external функций это, по сути, означает вызов Return, а для internal - типа "просто возвращайся".
Return в solidity служит как полезная абстракция и позволяет нашим функциям прекращаться раньше, порой избегая другую логику исполнения, как например тут:
Пост переведен из данной ветки Твиттера от philogy.
Фух, я еще постигаю assembly и мне крайне интересно, как работает вся эта штуковина изнутри.
#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
Сборник советов по оптимизации газа
RareSkills выпустил отличный сборник с рекомендациями и примерами по оптимизации газа. Да, большинство из них уже будут находиться сканирующими ботами, но все же важно понимать как это устроено.
Всего приведено около 70 примеров! В общем, интересное чтиво в свободное время.
Сборник от RareSkills
Будет также полезно тем, кто пишет своего бота и детекторы на скан контрактов!
#gas
RareSkills выпустил отличный сборник с рекомендациями и примерами по оптимизации газа. Да, большинство из них уже будут находиться сканирующими ботами, но все же важно понимать как это устроено.
Всего приведено около 70 примеров! В общем, интересное чтиво в свободное время.
Сборник от RareSkills
Будет также полезно тем, кто пишет своего бота и детекторы на скан контрактов!
#gas
🔥4👍1
Баг в протоколе Lybra
Интересная находка была в конкурсном аудите протокола Lybra, в наборе которого были прокси контракты.
При всей очевидности проблемы, найти ее смог только один аудитор. Почему очевидной? Просто потому, что этому обучают всех с момента знакомства с прокси контрактами.
Для протокола был создан отдельный контракт конфигуратор, который так и называется LybraConfigurator. По своей сути, это был Логический контракт для прокси контракта.
Проблема заключалась в том, что тут был конструктор, с помощью которого устанавливались переменные в память, что и было ошибкой.
Как мы знаем, конструктор исполняется только один раз, во время деплоя контракта, и позволяет сделать изначальную установку переменных в контракт. Но запись идет в память данного контракта, а не в память прокси, как это положено в данном случае. Поэтому на момент? когда были бы развернуты контракты в сети, то две переменные в прокси остались бы с нулевым значением.
Вот такой очевидный и не очень баг.
Более детально о нем можно прочитать тут, в отчете code4rena.
Будьте внимательны при работе с прокси!
#proxy #constructor
Интересная находка была в конкурсном аудите протокола Lybra, в наборе которого были прокси контракты.
При всей очевидности проблемы, найти ее смог только один аудитор. Почему очевидной? Просто потому, что этому обучают всех с момента знакомства с прокси контрактами.
Для протокола был создан отдельный контракт конфигуратор, который так и называется LybraConfigurator. По своей сути, это был Логический контракт для прокси контракта.
Проблема заключалась в том, что тут был конструктор, с помощью которого устанавливались переменные в память, что и было ошибкой.
Как мы знаем, конструктор исполняется только один раз, во время деплоя контракта, и позволяет сделать изначальную установку переменных в контракт. Но запись идет в память данного контракта, а не в память прокси, как это положено в данном случае. Поэтому на момент? когда были бы развернуты контракты в сети, то две переменные в прокси остались бы с нулевым значением.
Вот такой очевидный и не очень баг.
Более детально о нем можно прочитать тут, в отчете code4rena.
Будьте внимательны при работе с прокси!
#proxy #constructor
👍4