Минимальные требования для работы
Вчера со мной поделились вакансией на позицию "Аудитор смарт контрактов". Хоть я и не ищу ни работы, ни заказов сейчас, было интересно прочитать современные требования для соискателей. В итоге я подумал, что вообще мне следовало бы погуглить открытие вакансии и посмотреть, что сейчас актуально, что рассказать об этом на канале.
В целом я заметил некую тенденцию, которая меня сильно раздражала в описании вакансий в России и СНГ в целом. Иногда складывалось впечатление, что описания пишутся HR, которые только на Ютубе слышали о существовании web3. Они стараются в требования впихнуть вообще все, что только можно.
Грубо говоря, на позицию мидла ищут разработчика, который знает:
- Python, C+, JS (включая 3-4 популярных фрейма типа TypeScript, Next, React, Vue и т.д.);
- Solidity, Rust, Ton;
- Hardhat, Foundry, Truffle (!!!);
- Имеет степень бакалавра компьютерных наук;
и т.д.
Вроде как, мы ищем спеца на все руки, собеседование вы не пройдете, а если случится обратное - ну, что же, простенький сайтик с подключенным смарт контрактов написать дадим.
И это не мировые компании типа того же Uniswap, Open Zeppelin, Aave и других. О многих из них никто даже не слышал.
Я всегда бы за четкое разделение знаний на позициях. Если у компании нет денег на оплату двух позиций: Фронтенда и Web3 разработчика, то с зп вероятнее всего будут проблемы.
Разработчик смарт контрактов может должен знать язык под задачу (Solidity, Rust), уметь ответить за свою работу (написать тесты и показать, что все работает) и постоянно изучать новое в сфере блокчейна. Ему не нужно знать, как написать лендинг и связать его.
Фронтенд разработчик должен знать свой язык и набор библиотек для связи с блокчейном. Ему нафиг не нужно уметь писать DeFi протоколы!
Это хорошо, когда разработчик знает другой язык и может выполнить некоторые действия (устанавливать плагины на Python, подключать библиотеки, читать конфиги и т.д.), но он вовсе не обязан быть сеньором во всех языках, как требуют того некоторые вакансии.
Я пишу этот пост с обращением ко всем соискателям. Не нужно искать работу в web3, где требуется "мастер на все руки". Многих это может напугать, а другим сбить фокус - и вы начнете изучать все, чтобы соответствовать требованиям.
Для хорошей работы Solidity разработчиком нужно всего три вещи:
1. Отличное знание Solidity и современных паттернов разработки;
2. Навыки тестирования смарт контрактов;
3. Базовые аспекты безопасности кода;
Да, в процессе обучения вы познакомитесь с JS и с Python, но тут не потребуется "навыков работы 3+ лет".
В общем, делайте фокус именно на смарт контрактах, и не старайтесь изучить вообще все, чтобы получить работу в непонятной компании.
#job
Вчера со мной поделились вакансией на позицию "Аудитор смарт контрактов". Хоть я и не ищу ни работы, ни заказов сейчас, было интересно прочитать современные требования для соискателей. В итоге я подумал, что вообще мне следовало бы погуглить открытие вакансии и посмотреть, что сейчас актуально, что рассказать об этом на канале.
В целом я заметил некую тенденцию, которая меня сильно раздражала в описании вакансий в России и СНГ в целом. Иногда складывалось впечатление, что описания пишутся HR, которые только на Ютубе слышали о существовании web3. Они стараются в требования впихнуть вообще все, что только можно.
Грубо говоря, на позицию мидла ищут разработчика, который знает:
- Python, C+, JS (включая 3-4 популярных фрейма типа TypeScript, Next, React, Vue и т.д.);
- Solidity, Rust, Ton;
- Hardhat, Foundry, Truffle (!!!);
- Имеет степень бакалавра компьютерных наук;
и т.д.
Вроде как, мы ищем спеца на все руки, собеседование вы не пройдете, а если случится обратное - ну, что же, простенький сайтик с подключенным смарт контрактов написать дадим.
И это не мировые компании типа того же Uniswap, Open Zeppelin, Aave и других. О многих из них никто даже не слышал.
Я всегда бы за четкое разделение знаний на позициях. Если у компании нет денег на оплату двух позиций: Фронтенда и Web3 разработчика, то с зп вероятнее всего будут проблемы.
Разработчик смарт контрактов может должен знать язык под задачу (Solidity, Rust), уметь ответить за свою работу (написать тесты и показать, что все работает) и постоянно изучать новое в сфере блокчейна. Ему не нужно знать, как написать лендинг и связать его.
Фронтенд разработчик должен знать свой язык и набор библиотек для связи с блокчейном. Ему нафиг не нужно уметь писать DeFi протоколы!
Это хорошо, когда разработчик знает другой язык и может выполнить некоторые действия (устанавливать плагины на Python, подключать библиотеки, читать конфиги и т.д.), но он вовсе не обязан быть сеньором во всех языках, как требуют того некоторые вакансии.
Я пишу этот пост с обращением ко всем соискателям. Не нужно искать работу в web3, где требуется "мастер на все руки". Многих это может напугать, а другим сбить фокус - и вы начнете изучать все, чтобы соответствовать требованиям.
Для хорошей работы Solidity разработчиком нужно всего три вещи:
1. Отличное знание Solidity и современных паттернов разработки;
2. Навыки тестирования смарт контрактов;
3. Базовые аспекты безопасности кода;
Да, в процессе обучения вы познакомитесь с JS и с Python, но тут не потребуется "навыков работы 3+ лет".
В общем, делайте фокус именно на смарт контрактах, и не старайтесь изучить вообще все, чтобы получить работу в непонятной компании.
#job
👍32🥰4❤3
Media is too big
VIEW IN TELEGRAM
Пробы записи видео и разбор функций
Вот выдалось немного свободного времени потренироваться с записью видео, и решил, что некоторым будет интересно узнавать об интересных функциях или реализациях в смарт контрактах, что я встречаю на конкурсных аудитах.
Вообще у меня по задумке два направления на этот год: необычные функции и реализации, а также "по следам аудита", где хочу проходить по уже подтвержденным багам. Там и я сам буду проводить хорошую работу над ошибками и, возможно, начинающим аудиторам будет полезно узнавать моменты, на которые стоит обращать внимание.
Вообще пишу я лучше, чем говорю, поэтому видео формат для меня настоящий челлендж. Это первый такой, тренировочный, ролик, чтобы узнать ваше мнение.
Сейчас пара вопросов:
1. Как вообще видео по формату? Удобно ли смотреть с телефона, не раздражает ли что, все ли четко и по делу?
2. Что надо улучшить (субтитры, крупнее код, подсвечивать код или что-то другое)?
Я только учусь, поэтому любой фидбек будет ценен для меня.
По данному видео, вот ссылки на протокол и функцию из видео.
#video
Вот выдалось немного свободного времени потренироваться с записью видео, и решил, что некоторым будет интересно узнавать об интересных функциях или реализациях в смарт контрактах, что я встречаю на конкурсных аудитах.
Вообще у меня по задумке два направления на этот год: необычные функции и реализации, а также "по следам аудита", где хочу проходить по уже подтвержденным багам. Там и я сам буду проводить хорошую работу над ошибками и, возможно, начинающим аудиторам будет полезно узнавать моменты, на которые стоит обращать внимание.
Вообще пишу я лучше, чем говорю, поэтому видео формат для меня настоящий челлендж. Это первый такой, тренировочный, ролик, чтобы узнать ваше мнение.
Сейчас пара вопросов:
1. Как вообще видео по формату? Удобно ли смотреть с телефона, не раздражает ли что, все ли четко и по делу?
2. Что надо улучшить (субтитры, крупнее код, подсвечивать код или что-то другое)?
Я только учусь, поэтому любой фидбек будет ценен для меня.
По данному видео, вот ссылки на протокол и функцию из видео.
#video
❤11👍4🔥3
Тестирование != тестирование
Сегодня хотел поделиться кейсом, который встретился в одном из недавних аудитов, а также обсудить тему правильного тестирования.
В конкурсе протокола Diva, который проходил на платформе CodeHawks, был занятный баг, который пропускался даже в тестах, не смотря на то, что был в конструкторе контракта.
Суть его была в том, что в одном контракте в конструкторе также создавался и другой:
Обратите внимание, что аргументы передаются в не совсем верном порядке: адреса aave и diva перепутаны местами.
Хоть его и засчитали как Medium (по предварительным результатам), мне кажется, что правильнее было бы установить как Low. Контракты сразу после деплоя были бы не рабочими и, вроде как, ни к каким другим последствиям, кроме как ре-деплой, это бы не привело. Но примечательно другое.
Разработчики написали тесты, которые успешно проходили!
В общем, они были написаны правильно: тестировались необходимые функции и ветки, но что-то все же пропустилось... И это случилось у хороших разработчиков.
Разработчикам, которые только начинают писать тесты для своих контрактов, бывает сложно понять саму суть проводимых тестов. Они пишут unit тесты и стараются получить 100% coverage в итоге, не представляя, что тестирование может быть вне рамок кода.
Например, когда мы пишем тест для проверки реверта в require, в итоге нам нужно получить не сообщение об ошибке, которое порождается этой проверкой, а такие состояния кода, при которых она возникает. А это может быть не совсем директивное поведение пользователя.
Или также дело обстоит с проверками if/else. Тут тесты пишутся не только на два условия, но и для всех состояний, ролей и временных отрезков, которые могут повлиять на эту проверку.
Именно поэтому, на 1000 строк кода контракта мы можем встречать 5000+ строк тестов.
Если вы хотите стать лучше в этом деле, то уже с момента написания самого контракта, начните вести файл, куда будете записывать проверки. Пишем "pragma ..." - думаем на каких сетях это будет работать, какие проблемы есть в текущей версии языка. Пишем "MyContract is..." - думаем над порядком наследований и передачей параметров, пишем переменную - думаем на размерностью и слотом в памяти.
Это сложно, но так вы и сами сможете написать более безопасный код и более подробную документацию, которая поможем аудиторам в скором будущем.
Пишите правильные тесты!
#testing
Сегодня хотел поделиться кейсом, который встретился в одном из недавних аудитов, а также обсудить тему правильного тестирования.
В конкурсе протокола Diva, который проходил на платформе CodeHawks, был занятный баг, который пропускался даже в тестах, не смотря на то, что был в конструкторе контракта.
Суть его была в том, что в одном контракте в конструкторе также создавался и другой:
constructor(address _aaveV3Pool, address _diva, address _owner) AaveDIVAWrapperCore(_aaveV3Pool, _diva, _owner) {}
constructor(address diva_, address aaveV3Pool_, address owner_) Ownable(owner_) {}
Обратите внимание, что аргументы передаются в не совсем верном порядке: адреса aave и diva перепутаны местами.
Хоть его и засчитали как Medium (по предварительным результатам), мне кажется, что правильнее было бы установить как Low. Контракты сразу после деплоя были бы не рабочими и, вроде как, ни к каким другим последствиям, кроме как ре-деплой, это бы не привело. Но примечательно другое.
Разработчики написали тесты, которые успешно проходили!
В общем, они были написаны правильно: тестировались необходимые функции и ветки, но что-то все же пропустилось... И это случилось у хороших разработчиков.
Разработчикам, которые только начинают писать тесты для своих контрактов, бывает сложно понять саму суть проводимых тестов. Они пишут unit тесты и стараются получить 100% coverage в итоге, не представляя, что тестирование может быть вне рамок кода.
Например, когда мы пишем тест для проверки реверта в require, в итоге нам нужно получить не сообщение об ошибке, которое порождается этой проверкой, а такие состояния кода, при которых она возникает. А это может быть не совсем директивное поведение пользователя.
Или также дело обстоит с проверками if/else. Тут тесты пишутся не только на два условия, но и для всех состояний, ролей и временных отрезков, которые могут повлиять на эту проверку.
Именно поэтому, на 1000 строк кода контракта мы можем встречать 5000+ строк тестов.
Если вы хотите стать лучше в этом деле, то уже с момента написания самого контракта, начните вести файл, куда будете записывать проверки. Пишем "pragma ..." - думаем на каких сетях это будет работать, какие проблемы есть в текущей версии языка. Пишем "MyContract is..." - думаем над порядком наследований и передачей параметров, пишем переменную - думаем на размерностью и слотом в памяти.
Это сложно, но так вы и сами сможете написать более безопасный код и более подробную документацию, которая поможем аудиторам в скором будущем.
Пишите правильные тесты!
#testing
🔥11👍3
Ловушка аудитора и недостаток валидации
Хочу поделиться с вами небольшими результатами своего "переобучения" в качестве аудитора за последние пару месяцев.
Как вы знаете, я решил научиться аудировать протоколы заново и с конца ноября экспериментировал с заметками. Основной целью я ставил перед собой - в короткое время научиться разбирать протокол и понимать, что за что отвечает.
В январе-начале февраля была немного другая цель - быстро генерировать потенциальные проблемы в контракте и отправлять максимум репортов. "Максимум" складывался за счет поиска паттернов, на основе прочитанных репортов и догадок, в ущерб валидации.
Что же можно сказать по итогам этого периода?
В общем плане, за два-три месяца скорость понимания кода значительно повысилась. Если раньше на 1000 строк могло уходить до 4-5 дней, то сейчас уже 1-2 дня. Когда я говорю про "понимание", то имею ввиду, что я могу, смотря на функцию, хорошо знать:
- что она делает;
- какие служебные вызывает;
- какие состояния контракта изменяет;
- куда и откуда переводятся токены;
- какие проверки есть или нет;
- есть ли зеркальные функции;
и т.д.
Заниматься по 3-4 часа в день, вполне адекватная нагрузка.
С репортами и поиском багов стало немного хуже.
За 4 недели (с 13.01 по 05.02) я посмотрел 5 конкурсных аудитов и написал 28 отчетов. Результатов еще официальный нет, но предварительные не особо радуют. Примерно 10 штук - это "дизайн протокола" - когда протокол понимает, что этот код может привести к проблемам, но берет ответственность за свои действия и не будет ничего менять в коде, около 6 репортов - уже известные проблемы, около 4 - из Med в Low/Info, еще 4 - валидные и остальные - не валидные.
Другими словами, из 28 отчетов будут приняты около 3-4.
Когда я провожу реальный аудит, я читаю документацию, повторяю EIP, которые используются в протоколе, смотрю тесты и комментарии, построчно иду по коду и часто пишу заказчикам, чтобы удостовериться в каком-либо моменте. Если не могу доказать баг тестами, то в репорте веду отдельный блок с комментариями по коду, на что нужно обратить внимание.
В конкурсных же аудитах я "забил" на валидацию в пользу количества репортов и затраченного времени.
Как вы сами можете видеть, это не окупается ни по работе, ни по оплате.
Короче - не делайте так. Репорты с недостаточной валидацией - отстой!
На ближайшее время хочу попробовать другой подход: вместо поиска паттернов и максимума отчетов, генерировать возможные пути атаки и валидировать их. Это сложно объяснить, но, например, вместо поиска слабых мест в контракте и его логике, искать способы атаки на эти места. Если не могу на 100% доказать такой факт, то репорт - мимо.
⚡️ И небольшое предложение для всех:
Если тоже учитесь аудитам и уже пробовали себя в конкурсах, то предлагаю "поковырять" один небольшой баг баунти с Immunefi. Создадим закрытый чат, скину репо и каждый день будем в чате обмениваться ходом аудита.
Что скажете, кто-нибудь хочет поучаствовать?
#audit
Хочу поделиться с вами небольшими результатами своего "переобучения" в качестве аудитора за последние пару месяцев.
Как вы знаете, я решил научиться аудировать протоколы заново и с конца ноября экспериментировал с заметками. Основной целью я ставил перед собой - в короткое время научиться разбирать протокол и понимать, что за что отвечает.
В январе-начале февраля была немного другая цель - быстро генерировать потенциальные проблемы в контракте и отправлять максимум репортов. "Максимум" складывался за счет поиска паттернов, на основе прочитанных репортов и догадок, в ущерб валидации.
Что же можно сказать по итогам этого периода?
В общем плане, за два-три месяца скорость понимания кода значительно повысилась. Если раньше на 1000 строк могло уходить до 4-5 дней, то сейчас уже 1-2 дня. Когда я говорю про "понимание", то имею ввиду, что я могу, смотря на функцию, хорошо знать:
- что она делает;
- какие служебные вызывает;
- какие состояния контракта изменяет;
- куда и откуда переводятся токены;
- какие проверки есть или нет;
- есть ли зеркальные функции;
и т.д.
Заниматься по 3-4 часа в день, вполне адекватная нагрузка.
С репортами и поиском багов стало немного хуже.
За 4 недели (с 13.01 по 05.02) я посмотрел 5 конкурсных аудитов и написал 28 отчетов. Результатов еще официальный нет, но предварительные не особо радуют. Примерно 10 штук - это "дизайн протокола" - когда протокол понимает, что этот код может привести к проблемам, но берет ответственность за свои действия и не будет ничего менять в коде, около 6 репортов - уже известные проблемы, около 4 - из Med в Low/Info, еще 4 - валидные и остальные - не валидные.
Другими словами, из 28 отчетов будут приняты около 3-4.
Когда я провожу реальный аудит, я читаю документацию, повторяю EIP, которые используются в протоколе, смотрю тесты и комментарии, построчно иду по коду и часто пишу заказчикам, чтобы удостовериться в каком-либо моменте. Если не могу доказать баг тестами, то в репорте веду отдельный блок с комментариями по коду, на что нужно обратить внимание.
В конкурсных же аудитах я "забил" на валидацию в пользу количества репортов и затраченного времени.
Как вы сами можете видеть, это не окупается ни по работе, ни по оплате.
Короче - не делайте так. Репорты с недостаточной валидацией - отстой!
На ближайшее время хочу попробовать другой подход: вместо поиска паттернов и максимума отчетов, генерировать возможные пути атаки и валидировать их. Это сложно объяснить, но, например, вместо поиска слабых мест в контракте и его логике, искать способы атаки на эти места. Если не могу на 100% доказать такой факт, то репорт - мимо.
Если тоже учитесь аудитам и уже пробовали себя в конкурсах, то предлагаю "поковырять" один небольшой баг баунти с Immunefi. Создадим закрытый чат, скину репо и каждый день будем в чате обмениваться ходом аудита.
Что скажете, кто-нибудь хочет поучаствовать?
#audit
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍9
Программа интенсива по Foundry для новичков
По прошедшему ранее опросу, 35 участников канала хотели бы пойти на интенсив и еще 25 - подумают об этом! Это прекрасное количество, чтобы провести модуль немного раньше, чем я планировал.
Сегодня хочу представить вам программу интенсива, которая подойдет для тех, кто только учится тестированию или хотел бы перейти с Hardhat.
Я постарался структурировать информацию из открытого цикла постов по Foundry, который уже был на канале пару дет назад, обновив и дополнив многие моменты. Кроме того, добавлена практическая часть для получения навыков работы с системой приложений Foundry и написанием современных тестов.
Интенсив рассчитан на 3 недели. На 4 неделе будет пара бонусных постов и финальный практикум.
Хочу подчеркнуть, что данный интенсив для новичков в тестировании и работе с Foundry. Более продвинутые темы, такие как инвариант тестирование, формальная верификации, работа с обновляемыми контрактами, фаззинг значений из служебных функций, подключение сторонних плагинов - еще в разработке и, скорее всего, будет выпущен в конце года.
А пока, вот список тем по неделям:
Неделя 1
1. Установка Foundry: что входит в программу и для чего это нужно
2. Знакомство с cast командами. Чем они нужны и как работают
3. Сложные команды cast, которые пригодятся разработчику
4. Работа с chisel. Побитовые сдвиги и форк сети
5. Настройки Foundry: профили, библиотеки, пути
Неделя 2
6. Какие тесты бывают: правила написания и формирования папки проекта
7. Создание setUp() функции для удобного написания тестов. Роли пользователей, управление временем в тестах
8. Логика assert условий
9. Работа с ошибками и событиями в тестах
10. Логирование результатов теста и вывод в консоль
Неделя 3
11. Фазз тесты
12. Форк тесты. Интеграции с другими протоколами
13. Мутационные тесты
14. Тестирование подписей в контрактах
15. Деплой в разные сети. Написание простых скриптов
Неделя 4. Бонусные уроки
16. Интеграция с Hardhat
17. Хранение приватных ключей
18. Современные программы тестирования
19. Финальный практикум
Какие знания потребуются для прохождения интенсива:
- Знание Solidity на среднем уровне;
- Понимание популярных ERC - ERC20, ERC721, ERC712, ERC4626;
- Навыки работы с консолью / терминалом;
- Умение подключать зависимости в протокол;
- Знакомство с популярными протоколами типа Uniswap/Chainlink;
По стоимости будет 3000 рублей для тех, кто присоединялся хотя бы к одному из основных модулей курса, или 4000 рублей для всех других желающих.
Начать думаю в первых числах марта. О старте продаж будет еще отдельный пост на канале.
Если есть какие-либо вопросы, буду рад ответить в комментариях!
#foundry
По прошедшему ранее опросу, 35 участников канала хотели бы пойти на интенсив и еще 25 - подумают об этом! Это прекрасное количество, чтобы провести модуль немного раньше, чем я планировал.
Сегодня хочу представить вам программу интенсива, которая подойдет для тех, кто только учится тестированию или хотел бы перейти с Hardhat.
Я постарался структурировать информацию из открытого цикла постов по Foundry, который уже был на канале пару дет назад, обновив и дополнив многие моменты. Кроме того, добавлена практическая часть для получения навыков работы с системой приложений Foundry и написанием современных тестов.
Интенсив рассчитан на 3 недели. На 4 неделе будет пара бонусных постов и финальный практикум.
Хочу подчеркнуть, что данный интенсив для новичков в тестировании и работе с Foundry. Более продвинутые темы, такие как инвариант тестирование, формальная верификации, работа с обновляемыми контрактами, фаззинг значений из служебных функций, подключение сторонних плагинов - еще в разработке и, скорее всего, будет выпущен в конце года.
А пока, вот список тем по неделям:
Неделя 1
1. Установка Foundry: что входит в программу и для чего это нужно
2. Знакомство с cast командами. Чем они нужны и как работают
3. Сложные команды cast, которые пригодятся разработчику
4. Работа с chisel. Побитовые сдвиги и форк сети
5. Настройки Foundry: профили, библиотеки, пути
Неделя 2
6. Какие тесты бывают: правила написания и формирования папки проекта
7. Создание setUp() функции для удобного написания тестов. Роли пользователей, управление временем в тестах
8. Логика assert условий
9. Работа с ошибками и событиями в тестах
10. Логирование результатов теста и вывод в консоль
Неделя 3
11. Фазз тесты
12. Форк тесты. Интеграции с другими протоколами
13. Мутационные тесты
14. Тестирование подписей в контрактах
15. Деплой в разные сети. Написание простых скриптов
Неделя 4. Бонусные уроки
16. Интеграция с Hardhat
17. Хранение приватных ключей
18. Современные программы тестирования
19. Финальный практикум
Какие знания потребуются для прохождения интенсива:
- Знание Solidity на среднем уровне;
- Понимание популярных ERC - ERC20, ERC721, ERC712, ERC4626;
- Навыки работы с консолью / терминалом;
- Умение подключать зависимости в протокол;
- Знакомство с популярными протоколами типа Uniswap/Chainlink;
По стоимости будет 3000 рублей для тех, кто присоединялся хотя бы к одному из основных модулей курса, или 4000 рублей для всех других желающих.
Начать думаю в первых числах марта. О старте продаж будет еще отдельный пост на канале.
Если есть какие-либо вопросы, буду рад ответить в комментариях!
#foundry
🔥11👍1
Обязательный Foundry
В прошлую среду я предложил разобрать один bug bounty и этот процесс натолкнул меня на написание этого поста.
Сам протокол написан с использованием обновляемых контрактов UUPS, да еще и с Beacon Proxy. При том, что я понимаю как работают паттерны этих прокси, но в данном случае была какая-то авторская разработка и мне пришлось потратить пару часов, чтобы разобраться как все там работает.
В процессе разбора были такие моменты, когда казалось, что "Вот! Здорово! Баг найден! Это по-любому приведет к взлому и можно писать отчет." А для отчета нужен PoC...
Сидишь такой, пишешь тесты, и оказывается, что бага нет. В какой-то момент происходит реверт, отрабатывая require в коде, или переменная состояния не позволяет выполнить действие. И казалось бы 100% баг перестает быть таковым... Обидно, досадно, но ладно.
В целом, я не особо люблю писать тесты в конкурсных протоколах, так как это занимает время самого аудита. И мы возвращаемся к описанной ранее в постах проблеме недостаточной валидации.
Foundry давно стал стандартом написания тестов в мире аудита. Immunefi, портал для bug bounty, требует к каждому репорту добавлять функциональный PoC. Платформа для конкурсов Cantina стала требовать у своих аудиторов с определенной бальной системой добавление тестов к отчетам. Скоро и другие платформы сделают это обязательным условием для участия в конкурсах, либо будут резать выплаты находкам без тестов.
Тесты - это как железобетонные доказательства. Для разработчиков - что функция отрабатывает так как надо, для аудиторов - что баг действительно есть.
Пару лет назад, в вакансиях доминировали Truffle и Hardhat, потом основным стал Hardhat и Truffle и немного Foundry. Потом все начали забывать о Truffle и в вакансиях требовались знания или/или - Hardhat/Foundry. Теперь же Foundry чуть больше доминирует на рынке из-за своего более простого освоения.
Foundry уже не опция для знаний разработчика, а обязанность. Даже если вы мастер JavaScript и можете без проблем написать тесты на Hardhat.
Уже в прошлом году стали появляться компании, которые выводят Foundry на новый уровень, упрощая разработку инвариант тестов и проверок формальной верификации. В этом году будет еще больше "движений" в эту сторону.
И если вы будете откладывать изучение Foundry, то в какой-то момент нужно будет учить его не по чуть-чуть, вместе с развитием программы, а сразу большим объемом.
#foundry
В прошлую среду я предложил разобрать один bug bounty и этот процесс натолкнул меня на написание этого поста.
Сам протокол написан с использованием обновляемых контрактов UUPS, да еще и с Beacon Proxy. При том, что я понимаю как работают паттерны этих прокси, но в данном случае была какая-то авторская разработка и мне пришлось потратить пару часов, чтобы разобраться как все там работает.
В процессе разбора были такие моменты, когда казалось, что "Вот! Здорово! Баг найден! Это по-любому приведет к взлому и можно писать отчет." А для отчета нужен PoC...
Сидишь такой, пишешь тесты, и оказывается, что бага нет. В какой-то момент происходит реверт, отрабатывая require в коде, или переменная состояния не позволяет выполнить действие. И казалось бы 100% баг перестает быть таковым... Обидно, досадно, но ладно.
В целом, я не особо люблю писать тесты в конкурсных протоколах, так как это занимает время самого аудита. И мы возвращаемся к описанной ранее в постах проблеме недостаточной валидации.
Foundry давно стал стандартом написания тестов в мире аудита. Immunefi, портал для bug bounty, требует к каждому репорту добавлять функциональный PoC. Платформа для конкурсов Cantina стала требовать у своих аудиторов с определенной бальной системой добавление тестов к отчетам. Скоро и другие платформы сделают это обязательным условием для участия в конкурсах, либо будут резать выплаты находкам без тестов.
Тесты - это как железобетонные доказательства. Для разработчиков - что функция отрабатывает так как надо, для аудиторов - что баг действительно есть.
Пару лет назад, в вакансиях доминировали Truffle и Hardhat, потом основным стал Hardhat и Truffle и немного Foundry. Потом все начали забывать о Truffle и в вакансиях требовались знания или/или - Hardhat/Foundry. Теперь же Foundry чуть больше доминирует на рынке из-за своего более простого освоения.
Foundry уже не опция для знаний разработчика, а обязанность. Даже если вы мастер JavaScript и можете без проблем написать тесты на Hardhat.
Уже в прошлом году стали появляться компании, которые выводят Foundry на новый уровень, упрощая разработку инвариант тестов и проверок формальной верификации. В этом году будет еще больше "движений" в эту сторону.
И если вы будете откладывать изучение Foundry, то в какой-то момент нужно будет учить его не по чуть-чуть, вместе с развитием программы, а сразу большим объемом.
#foundry
❤5👍3🔥3💯1
Проблемы с обучением и прохождением курсов
Вчера в разговоре со знакомым аудитором речь зашла о возрасте аудиторов и их успехах на поприще поиска багов в конкурсах и баг баунти. Я жаловался, что не получается уделять больше 3-4 часов в день на изучение протоколов, так как помимо этого есть еще и другие дела ежедневно требующие внимания. Легко в этом плане некоторым молодым людям, до 25 лет, у которых еще нет стабильной работы, семьи, детей, и т.д. Они ищут себя, могут много учиться и уделять время тому, что действительно зажигает. Многих могут так или иначе поддерживать родители, и не нужно думать, где взять денег на оплату квартиры и еды.
Не хочу никого обидеть в возрастном плане или в жизненной ситуации, просто говорю, что, если у человека меньше повседневных забот, он может больше времени уделять своему обучению и профессии. Намного проще и быстрее стать хорошим аудитором или разработчиком, если можете без труда уделять 8-10 часов в день своему обучению или аудиту.
Мне повезло, что на момент своего переобучения в web3 я, во-первых, уже был разработчиком, и во-вторых, мог в течение года уделять достаточно времени обучению.
Но вчера я понял, что изучение Solidity/Foundry и чего-то еще может быть трудной задачей, когда ты работаешь на тяжелой (эмоционально и физически) работе, по паре часов тратишь время на дорогу, вечером нужно провести время с семьей, детьми или просто второй половинкой. К тому же постоянно приходят счета на оплату, случаются мелкие неприятности и т.д. И это все намного важнее web3 и порой "выбивает из времени и пространства"...
Я пишу посты на двух каналах и только сейчас задумался об этом.
Что могло бы помочь вам в обучении? Может какой-то формат постов, которые можно читать в метро или автобусе? Мелкие задания на закрепления материалов?
Если и делать далее какие-нибудь модули, то так, чтобы было удобно и практично в любой момент.
Можете поделиться своими способами обучения? Как вам удобнее всего изучать новый материал?
У нас тут большой канал с техническими постами, и, думаю, есть много участников, которые постоянно чему-то учатся. Буду рад услышать ваши лайфхаки.
#learn
Вчера в разговоре со знакомым аудитором речь зашла о возрасте аудиторов и их успехах на поприще поиска багов в конкурсах и баг баунти. Я жаловался, что не получается уделять больше 3-4 часов в день на изучение протоколов, так как помимо этого есть еще и другие дела ежедневно требующие внимания. Легко в этом плане некоторым молодым людям, до 25 лет, у которых еще нет стабильной работы, семьи, детей, и т.д. Они ищут себя, могут много учиться и уделять время тому, что действительно зажигает. Многих могут так или иначе поддерживать родители, и не нужно думать, где взять денег на оплату квартиры и еды.
Не хочу никого обидеть в возрастном плане или в жизненной ситуации, просто говорю, что, если у человека меньше повседневных забот, он может больше времени уделять своему обучению и профессии. Намного проще и быстрее стать хорошим аудитором или разработчиком, если можете без труда уделять 8-10 часов в день своему обучению или аудиту.
Мне повезло, что на момент своего переобучения в web3 я, во-первых, уже был разработчиком, и во-вторых, мог в течение года уделять достаточно времени обучению.
Но вчера я понял, что изучение Solidity/Foundry и чего-то еще может быть трудной задачей, когда ты работаешь на тяжелой (эмоционально и физически) работе, по паре часов тратишь время на дорогу, вечером нужно провести время с семьей, детьми или просто второй половинкой. К тому же постоянно приходят счета на оплату, случаются мелкие неприятности и т.д. И это все намного важнее web3 и порой "выбивает из времени и пространства"...
Я пишу посты на двух каналах и только сейчас задумался об этом.
Что могло бы помочь вам в обучении? Может какой-то формат постов, которые можно читать в метро или автобусе? Мелкие задания на закрепления материалов?
Если и делать далее какие-нибудь модули, то так, чтобы было удобно и практично в любой момент.
Можете поделиться своими способами обучения? Как вам удобнее всего изучать новый материал?
У нас тут большой канал с техническими постами, и, думаю, есть много участников, которые постоянно чему-то учатся. Буду рад услышать ваши лайфхаки.
#learn
🔥13❤4🤔2🙏2
Постфикс и префикс в Solidity
Продолжаю свои попытки в видео формат. В этот раз добавил субтитры, на случай если ролик будут смотреть без звука.
Сложно подобрать какие-либо визуальные выделения кода на видео (обводка функций, подчеркивание, увеличение "под лупу"). Одной программы не достаточно, а при нескольких переводов из одной программы в другую, сильно падает качество видео. И если, скажем, для съемок природы или кошко-девочки это будет не критично, то для отображения кода в редакторе - просто жесть...
Но это так, просто делюсь процессом и трудностями.
Если захотите покопаться в коде протокола с видео, то вот ссылка на контаркт:
https://github.com/code-423n4/2025-01-liquid-ron/blob/main/src/ValidatorTracker.sol#L36
Всем приятной пятницы и отличных выходных!
P.S. Буду также признателен комментариям по формату видео: что можно улучшить, что добавить или вообще убрать.
#video
Продолжаю свои попытки в видео формат. В этот раз добавил субтитры, на случай если ролик будут смотреть без звука.
Сложно подобрать какие-либо визуальные выделения кода на видео (обводка функций, подчеркивание, увеличение "под лупу"). Одной программы не достаточно, а при нескольких переводов из одной программы в другую, сильно падает качество видео. И если, скажем, для съемок природы или кошко-девочки это будет не критично, то для отображения кода в редакторе - просто жесть...
Но это так, просто делюсь процессом и трудностями.
Если захотите покопаться в коде протокола с видео, то вот ссылка на контаркт:
https://github.com/code-423n4/2025-01-liquid-ron/blob/main/src/ValidatorTracker.sol#L36
Всем приятной пятницы и отличных выходных!
P.S. Буду также признателен комментариям по формату видео: что можно улучшить, что добавить или вообще убрать.
#video
🔥10❤3
Обучающий модуль по Foundry?
Новый месяц, новые знания и цели!
Прежде всего, ниже будет еще один опрос для желающих пройти интенсив по работе с Foundry, и прошу всех, кто точно планирует зайти на него, проголосовать.
Если будет небольшое количество желающих, то мы перенесем его на более поздний срок. Если же группа наберется, то мы начнем уже со следующей неделе, а продажи откроем в эту среду.
Больше информации о самом интенсиве можно найти в этом посте:
https://news.1rj.ru/str/solidityset/1310
Я провожу опрос, чтобы распределить свою нагрузку на этот месяц: либо отменю часть работ и большую часть времени буду уделять ученикам и ответам на вопросы, либо сам больше буду принимать участие в bug bounty программах и конкурсах по аудиту, а на канале будем разбирать интересные темы с delegatecall.
Также напоминаю, что я еще веду параллельно небольшой канал по Defi:
https://news.1rj.ru/str/defisolset
Недавно мы закончили делать разбор протокола Torando Cash, а после рассмотрели темы процентной ставки AAVE V3 и Compound, а также ликвидации и залога. Сейчас знакомимся с Aave v3.
Это, скорее, канал для продвинутых разработчиков, которые уже знают Solidity, и которым интересна тема DeFi протоколов.
Всем приятной недели и легкого обучения!
#offtop
Новый месяц, новые знания и цели!
Прежде всего, ниже будет еще один опрос для желающих пройти интенсив по работе с Foundry, и прошу всех, кто точно планирует зайти на него, проголосовать.
Если будет небольшое количество желающих, то мы перенесем его на более поздний срок. Если же группа наберется, то мы начнем уже со следующей неделе, а продажи откроем в эту среду.
Больше информации о самом интенсиве можно найти в этом посте:
https://news.1rj.ru/str/solidityset/1310
Я провожу опрос, чтобы распределить свою нагрузку на этот месяц: либо отменю часть работ и большую часть времени буду уделять ученикам и ответам на вопросы, либо сам больше буду принимать участие в bug bounty программах и конкурсах по аудиту, а на канале будем разбирать интересные темы с delegatecall.
Также напоминаю, что я еще веду параллельно небольшой канал по Defi:
https://news.1rj.ru/str/defisolset
Недавно мы закончили делать разбор протокола Torando Cash, а после рассмотрели темы процентной ставки AAVE V3 и Compound, а также ликвидации и залога. Сейчас знакомимся с Aave v3.
Это, скорее, канал для продвинутых разработчиков, которые уже знают Solidity, и которым интересна тема DeFi протоколов.
Всем приятной недели и легкого обучения!
#offtop
🔥4
Foundry откладывается, но будет что-то интересное!
К сожалению, проголосовавших за участие в модуле не набралось достаточного количества, поэтому я принял решение немного повременить с данным интенсивом. Скорее всего, теперь он будет включен в большой Летний Практический модуль. Те, кто хотел зайти на него в этот раз, просто смогут докупить его отдельно.
При этом дела у нас всегда есть: на канале будут дальше выходить посты про Solidity, я по не многу буду записывать видео про всякие интересные штуки в коде, и подумываю сделать долгий цикл постов про создание dApp.
За все три года, что ведется этот канал, я никогда не поднимал тему, как создавать свои проекты, которые будут взаимодействовать с блокчейном: как написать сайт на react, как подключить регистрацию через кошелек, как делать вызовы в сеть и отслеживать результат, и т.д.
И решил, что было бы полезно написать цикл постов, в котором буду показывать как создать простой проект: от идеи до запуска. Мы будем обсуждать идеи, решения и проблемы безопасности. Вы увидите полный цикл создания dApp.
Основной идеей будет сделать так, чтобы все стало понятно новичку: самый базовый код JS, бэкенд и все остальное, чтобы вы могли по моим стопам делать свой собственный продукт.
Это цикл постов на полгода-год, так как буквально будет включать каждый этап разработки. Выходить посты будут по мере возможностей, так как делать их буду в свободное от основной работы время.
Пока как-то так! Возвращаемся в "задротное русло".
#offtop
К сожалению, проголосовавших за участие в модуле не набралось достаточного количества, поэтому я принял решение немного повременить с данным интенсивом. Скорее всего, теперь он будет включен в большой Летний Практический модуль. Те, кто хотел зайти на него в этот раз, просто смогут докупить его отдельно.
При этом дела у нас всегда есть: на канале будут дальше выходить посты про Solidity, я по не многу буду записывать видео про всякие интересные штуки в коде, и подумываю сделать долгий цикл постов про создание dApp.
За все три года, что ведется этот канал, я никогда не поднимал тему, как создавать свои проекты, которые будут взаимодействовать с блокчейном: как написать сайт на react, как подключить регистрацию через кошелек, как делать вызовы в сеть и отслеживать результат, и т.д.
И решил, что было бы полезно написать цикл постов, в котором буду показывать как создать простой проект: от идеи до запуска. Мы будем обсуждать идеи, решения и проблемы безопасности. Вы увидите полный цикл создания dApp.
Основной идеей будет сделать так, чтобы все стало понятно новичку: самый базовый код JS, бэкенд и все остальное, чтобы вы могли по моим стопам делать свой собственный продукт.
Это цикл постов на полгода-год, так как буквально будет включать каждый этап разработки. Выходить посты будут по мере возможностей, так как делать их буду в свободное от основной работы время.
Пока как-то так! Возвращаемся в "задротное русло".
#offtop
1👍13🔥5❤2❤🔥1😱1
Calldata Encoding
Знаете то чувство в процессе своего обучения, когда ты вроде бы понял тему и можешь ее рассказать, но спустя какое-то время возвращаешься к ней и пытаешься разобраться вновь?
У меня так происходит с некоторыми задачами (Ethernaut, DVD и другими). Вот я решил задачу, написал контракт, понял, как это делается, но вот что-то непонятное так и "не зацепилось". Сейчас я хочу с вами еще раз пройти одну тему и после разобрать задачу на видео. Может после этого, все сложится как надо.
Итак, у нас есть простая функция:
Вы знаете, как будут располагаться данные в аргументах функции? Если да - то вообще здорово, если нет - давайте разбираться.
В Solidity существуют статичные типы данных:
uint, int, address, bool, bytes - n, tuple
Они представлены в шестнадцатеричном формате, дополненные нулями, чтобы занять весь слот в 32 байта.
Например:
А также есть динамические типы данных:
string, bytes, array
Для них кодирование calldata будет выглядеть так:
- Первые 32 байта - смещение;
- Следующие 32 байта - длинна;
- Затем - значения;
Например, для байтов:
где:
Для строк:
где:
И для массивов:
где:
С примерами кода посты получаются большими, поэтому в следующем мы поговорим про offset.
#calldata
Знаете то чувство в процессе своего обучения, когда ты вроде бы понял тему и можешь ее рассказать, но спустя какое-то время возвращаешься к ней и пытаешься разобраться вновь?
У меня так происходит с некоторыми задачами (Ethernaut, DVD и другими). Вот я решил задачу, написал контракт, понял, как это делается, но вот что-то непонятное так и "не зацепилось". Сейчас я хочу с вами еще раз пройти одну тему и после разобрать задачу на видео. Может после этого, все сложится как надо.
Итак, у нас есть простая функция:
function flipSwitch(bytes memory _data) public onlyOff {
(bool success, ) = address(this).call(_data);
require(success, "call failed :(");
}Вы знаете, как будут располагаться данные в аргументах функции? Если да - то вообще здорово, если нет - давайте разбираться.
В Solidity существуют статичные типы данных:
uint, int, address, bool, bytes - n, tuple
Они представлены в шестнадцатеричном формате, дополненные нулями, чтобы занять весь слот в 32 байта.
Например:
Input: 23 (uint256)
Output:
0x0000000000000000000000000000000000000000000000000000000000000017
Input: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f (address of Uniswap)
Output:
0x000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f
А также есть динамические типы данных:
string, bytes, array
Для них кодирование calldata будет выглядеть так:
- Первые 32 байта - смещение;
- Следующие 32 байта - длинна;
- Затем - значения;
Например, для байтов:
Input: 0x123
Output:
0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000
где:
offset:
0000000000000000000000000000000000000000000000000000000000000020
length(the value is 2 bytes length = 4 chrs):
0000000000000000000000000000000000000000000000000000000000000002
value(the value of string and bytes starts right after the length):
1234000000000000000000000000000000000000000000000000000000000000
Для строк:
Input: “GM Frens”
Output:
0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008474d204672656e73000000000000000000000000000000000000000000000000
где:
offset:
0000000000000000000000000000000000000000000000000000000000000020
length:
0000000000000000000000000000000000000000000000000000000000000008
value(“GM Frens” in hex):
474d204672656e73000000000000000000000000000000000000000000000000
И для массивов:
Input: [1,3,42] → uint256 array
Output:
0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000002a
где:
offset:
0000000000000000000000000000000000000000000000000000000000000020
length (3 elements in the array):
0000000000000000000000000000000000000000000000000000000000000003
first element value(1):
0000000000000000000000000000000000000000000000000000000000000001
second element value(3):
0000000000000000000000000000000000000000000000000000000000000003
third element value(42):
000000000000000000000000000000000000000000000000000000000000002a
С примерами кода посты получаются большими, поэтому в следующем мы поговорим про offset.
#calldata
🔥8
Calldata Encoding. Часть 2
Что такое смещение - offset?
Смещение указывает на начало данных. Данные формируются из длины и значения. В нашем примере смещение было 20 в шестнадцатеричном формате, что равно 32 в десятичном. Это означает, что наши данные начинаются после первых 32 байт от начала кодировки.
Рассмотрим пример функции calldata, которая имеет как статические, так и динамические параметры:
Calldata в этом случае будет выглядеть так:
И ее можно разобрать на:
Как видно из этого примера, с помощью смещения можно переместить содержимое данных (длину и значение) после параметра адреса(to).
И такое формирование calldata может приводить к взлому вашего протокола!
Как? Постараюсь рассказать в ближайшем видео!
#calldata
Что такое смещение - offset?
Смещение указывает на начало данных. Данные формируются из длины и значения. В нашем примере смещение было 20 в шестнадцатеричном формате, что равно 32 в десятичном. Это означает, что наши данные начинаются после первых 32 байт от начала кодировки.
0000000000000000000000000000000000000000000000000000000000000020
^
| -> counting 32 bytes from here
0000000000000000000000000000000000000000000000000000000000000004
^
| so this is the actual start
20606e1500000000000000000000000000000000000000000000000000000000
Рассмотрим пример функции calldata, которая имеет как статические, так и динамические параметры:
pragma solidity 0.8.19;
contract Example {
function transfer(bytes memory data, address to) external;
}
data: 0x1234
to: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
Calldata в этом случае будет выглядеть так:
0xbba1b1cd00000000000000000000000000000000000000000000000000000000000000400000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f00000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000
И ее можно разобрать на:
0x
function selector (transfer):
Bba1b1cd
offset of the 'data' param (64 in decimal):
0000000000000000000000000000000000000000000000000000000000000040
address param 'to':
0000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f
length of the 'data' param:
0000000000000000000000000000000000000000000000000000000000000002
value of the 'data' param:
1234000000000000000000000000000000000000000000000000000000000000
Как видно из этого примера, с помощью смещения можно переместить содержимое данных (длину и значение) после параметра адреса(to).
И такое формирование calldata может приводить к взлому вашего протокола!
Как? Постараюсь рассказать в ближайшем видео!
#calldata
👍6❤1🤯1
Проект на виду. Часть 1. Что по донатам?
На прошлой неделе я писал, что хочу, здесь на канале, создать web3 проект и показать весь процесс от и до: от идеи и до первой транзакции в сети. И сегодня первый пост в этом большом цикле.
Я долго думал, что же такое реализовать, чтобы было и интересно, и полезно, и показывало бы разработку с разных сторон, включая фронт, бек, смарт контракты, тестирование и т.д. У меня были как простые идеи, вроде, аукциона, и более сложные: полноценный DeFi протокол.
В первом случае, мне самому это было бы не интересно, во втором - я не так хорошо дружу с математикой, чтобы писать формулы, а копировать юнисваповский x*y=k как-то не хочется.
Решающую роль в выборе проекта сыграло мое увлечение настольной игрой ДНД.
Я подписан на канал одного прекрасного мастера, который постоянно делает контент с разборами механик и выкладывает кучу полезной информации по механикам игры. И в очередной раз, когда он выложил подборку своих файлов, я был очень впечатлен и в благодарность отправил ему донат. А потом вдруг понял...
Я регулярно поддерживаю нескольких авторов, которые постоянно делятся свои авторским контентом: ДНД, озвучка, психология, ВПН, игры и пару других. Это не большие суммы: от 100 до 500 рублей.
И я подумал, что через проект с донатом криптой я смогу также поддержать авторов и, кроме того, немного поспособствовать общему распространению знаний о web3.
Хочется создать что-то очень простое: чтобы авторы могли и без кошельков начать принимать донаты, и для других пользователей - отправить донат без долгих разборов в UI/UX сайта.
Кроме того, здесь не будет каких-то супер навороченных расчетов, но при этом присутствуют все этапы разработки, о которых писал выше: фронт, бек, смарт контракты и тестирование.
А что вы думаете про донаты и поддержку авторов? Есть ли у вас действующие подписки или поддерживали ли авторов когда-нибудь?
#openproject
На прошлой неделе я писал, что хочу, здесь на канале, создать web3 проект и показать весь процесс от и до: от идеи и до первой транзакции в сети. И сегодня первый пост в этом большом цикле.
Я долго думал, что же такое реализовать, чтобы было и интересно, и полезно, и показывало бы разработку с разных сторон, включая фронт, бек, смарт контракты, тестирование и т.д. У меня были как простые идеи, вроде, аукциона, и более сложные: полноценный DeFi протокол.
В первом случае, мне самому это было бы не интересно, во втором - я не так хорошо дружу с математикой, чтобы писать формулы, а копировать юнисваповский x*y=k как-то не хочется.
Решающую роль в выборе проекта сыграло мое увлечение настольной игрой ДНД.
Я подписан на канал одного прекрасного мастера, который постоянно делает контент с разборами механик и выкладывает кучу полезной информации по механикам игры. И в очередной раз, когда он выложил подборку своих файлов, я был очень впечатлен и в благодарность отправил ему донат. А потом вдруг понял...
Я регулярно поддерживаю нескольких авторов, которые постоянно делятся свои авторским контентом: ДНД, озвучка, психология, ВПН, игры и пару других. Это не большие суммы: от 100 до 500 рублей.
И я подумал, что через проект с донатом криптой я смогу также поддержать авторов и, кроме того, немного поспособствовать общему распространению знаний о web3.
Хочется создать что-то очень простое: чтобы авторы могли и без кошельков начать принимать донаты, и для других пользователей - отправить донат без долгих разборов в UI/UX сайта.
Кроме того, здесь не будет каких-то супер навороченных расчетов, но при этом присутствуют все этапы разработки, о которых писал выше: фронт, бек, смарт контракты и тестирование.
А что вы думаете про донаты и поддержку авторов? Есть ли у вас действующие подписки или поддерживали ли авторов когда-нибудь?
#openproject
🔥19❤4
Calldata Encoding. Часть 3
Вот и обещанное видео с решением одной из задач Ethernaut, для которой мы разбирали ранее вопросы с calldata.
Саму задачу можно найти по ссылке:
https://ethernaut.openzeppelin.com/level/29
А тест для нее вот:
Если вы хотели поэкспериментировать с передачей данных в виде calldata между контрактами, то это отличная возможность.
P.S. Буду рад комментариям и отзывам по видео. Все еще экспериментирую со стилем и созданием формата.
#calldata
Вот и обещанное видео с решением одной из задач Ethernaut, для которой мы разбирали ранее вопросы с calldata.
Саму задачу можно найти по ссылке:
https://ethernaut.openzeppelin.com/level/29
А тест для нее вот:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../src/Switch.sol";
import {Test, console} from "forge-std/Test.sol";
contract SwitchTest is Test {
Switch switchContract;
function setUp() public {
switchContract = new Switch();
}
function test_Switch() external {
bytes memory callData = hex"30c13ade0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000020606e1500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000476227e1200000000000000000000000000000000000000000000000000000000";
(bool success, ) = address(switchContract).call(callData);
require (success, "Call failed!");
assertTrue(switchContract.switchOn());
}
}
Если вы хотели поэкспериментировать с передачей данных в виде calldata между контрактами, то это отличная возможность.
P.S. Буду рад комментариям и отзывам по видео. Все еще экспериментирую со стилем и созданием формата.
#calldata
👍7
Solidity 0.8.29
Вчера выпустили новую версию Solidity, вот несколько ключевых изменений, а также пара ссылок.
1. В версии 0.8.29 появилась экспериментальная поддержка EVM Object Format (EOF). Обратите внимание, что эта функция может быть включена только при компиляции для версии EVM Osaka, которая еще не была развернута в mainnet или testnets.
Чуть больше об этом можно узнать тут:
https://x.com/uttam_singhk/status/1830526179105001771
В связи с экспериментальным характером функции, не все синтаксические различия покрываются проверками анализа на данный момент, и в некоторых случаях вы можете столкнуться с внутренними ошибками компилятора при попытке их использования.
Кроме того, компиляция в EOF может быть выполнена только через IR и только при включенном оптимизаторе. Текущая реализация, однако, не включает никаких низкоуровневых оптимизаций, что может привести к увеличению размера кода в некоторых случаях.
Чтобы опробовать ее на своем контракте, используйте --experimental-eof-version 1 в командной строке или settings.eofVersion: 1 в стандартном JSON и не забудьте выбрать версию EVM, которая ее поддерживает (--evm-version osaka/settings.evmVersion: «osaka»).
2. В этом выпуске появился синтаксис для перемещения переменных хранения контракта в произвольное место.
Поддержка указания местоположения хранилища - один из самых старых и обсуждаемых запросов в трекере проблем Solidity, но множество вариантов использования и потенциально противоречивые требования до сих пор не позволяли прийти к какому-то конкретному решению. С включением EIP-7702: Set EOA account code в обновление Pectra, это стало критичным для безопасной реализации абстракции учетных записей, и разработчики решили сделать этот вариант использования приоритетным.
В настоящее время синтаксис очень ограничен: базовое местоположение может быть только буквальным выражением и применяется ко всему дереву наследования.
Чуть больше о EIP7702 можно прочитать тут:
https://cantina.xyz/introduction/pectra-competition-resources/eip-7702
3. Начальная поддержка ethdebug. Этот релиз также представляет первый экспериментальный шаг к поддержке ethdebug - формата отладочных данных, подходящего для смарт-контрактов.
Текущая реализация поддерживает генерацию инструкций и диапазонов исходных текстов. Эта начальная версия поддерживает только неоптимизированную компиляцию через IR и все еще не имеет многих важных возможностей.
Если вы хотите попробовать, вы можете включить вывод ethdebug в командной строке с помощью команды:
Чтобы запросить артефакты ethdebug в стандартном JSON, добавьте
в settings.outputSelection (обратите внимание, что символ «*» не включает его). Также не забывайте, что settings.viaIR: true/--via-ir необходим для работы функции.
4. Также были исправлены несколько проблем с SMTChecker, Error Reporting, Yul Optimizer, а также перешил с C++17 на C++20.
#solidity
Вчера выпустили новую версию Solidity, вот несколько ключевых изменений, а также пара ссылок.
1. В версии 0.8.29 появилась экспериментальная поддержка EVM Object Format (EOF). Обратите внимание, что эта функция может быть включена только при компиляции для версии EVM Osaka, которая еще не была развернута в mainnet или testnets.
Чуть больше об этом можно узнать тут:
https://x.com/uttam_singhk/status/1830526179105001771
В связи с экспериментальным характером функции, не все синтаксические различия покрываются проверками анализа на данный момент, и в некоторых случаях вы можете столкнуться с внутренними ошибками компилятора при попытке их использования.
Кроме того, компиляция в EOF может быть выполнена только через IR и только при включенном оптимизаторе. Текущая реализация, однако, не включает никаких низкоуровневых оптимизаций, что может привести к увеличению размера кода в некоторых случаях.
Чтобы опробовать ее на своем контракте, используйте --experimental-eof-version 1 в командной строке или settings.eofVersion: 1 в стандартном JSON и не забудьте выбрать версию EVM, которая ее поддерживает (--evm-version osaka/settings.evmVersion: «osaka»).
2. В этом выпуске появился синтаксис для перемещения переменных хранения контракта в произвольное место.
contract C layout at 2**255 - 42 {
uint x;
}Поддержка указания местоположения хранилища - один из самых старых и обсуждаемых запросов в трекере проблем Solidity, но множество вариантов использования и потенциально противоречивые требования до сих пор не позволяли прийти к какому-то конкретному решению. С включением EIP-7702: Set EOA account code в обновление Pectra, это стало критичным для безопасной реализации абстракции учетных записей, и разработчики решили сделать этот вариант использования приоритетным.
В настоящее время синтаксис очень ограничен: базовое местоположение может быть только буквальным выражением и применяется ко всему дереву наследования.
Чуть больше о EIP7702 можно прочитать тут:
https://cantina.xyz/introduction/pectra-competition-resources/eip-7702
3. Начальная поддержка ethdebug. Этот релиз также представляет первый экспериментальный шаг к поддержке ethdebug - формата отладочных данных, подходящего для смарт-контрактов.
Текущая реализация поддерживает генерацию инструкций и диапазонов исходных текстов. Эта начальная версия поддерживает только неоптимизированную компиляцию через IR и все еще не имеет многих важных возможностей.
Если вы хотите попробовать, вы можете включить вывод ethdebug в командной строке с помощью команды:
--ethdebug/--ethdebug-runtime.
Чтобы запросить артефакты ethdebug в стандартном JSON, добавьте
evm.bytecode.ethdebug»/«evm.deployedBytecode.ethdebug
в settings.outputSelection (обратите внимание, что символ «*» не включает его). Также не забывайте, что settings.viaIR: true/--via-ir необходим для работы функции.
4. Также были исправлены несколько проблем с SMTChecker, Error Reporting, Yul Optimizer, а также перешил с C++17 на C++20.
#solidity
1🔥9❤1
Понимание метаданных смарт-контракта. Часть 1
Нашел интересную статью о метаданных в смарт контрактах. Я, вроде как, за три года ведения канала ни разу толком о них не писал, поэтому разберем эту тему на простых примерах.
Когда solidity генерирует байткод для развертываемого смарт-контракта, он добавляет метаданные о компиляции в конец байткода. Мы рассмотрим данные, содержащиеся в этом байткоде.
Давайте посмотрим на вывод компилятора простейшего смарт-контракта solidity:
Контракт буквально ничего не делает. Мы можем скомпилировать его, чтобы увидеть код инициализации с помощью
Мы получим следующий вывод:
Это кажется ужасно большим для контракта, который ничего не делает, верно? Давайте разберемся, что представляет собой весь этот байткод.
Когда мы компилируем код с помощью
solc --optimize-runs 1000 --bin --no-cbor-metadata contractFile.sol
мы получаем следующий результат:
Это гораздо меньше! Но что же это за дополнительная информация?
Метаданные Solidity
По умолчанию компилятор Solidity добавляет метаданные в конец «фактического» initcode, который сохраняется в блокчейне после завершения выполнения конструктора. Ниже приведен «дополнительный» код:
Последние два байта 0033 означают «посмотрите назад на 0x33 байта, это метаданные». Это относится ко всему коду между ведущим fe (который является операционным кодом INVALID) и конечным 0033. Мы можем проверить, что это действительно 0x33 байта.
Так что же это за строка 0x33 (51 decimal)?
Мы можем получить подсказку, если внесем в исходный код одно крошечное, казалось бы, несущественное изменение. Это изменение - буквально дополнительный комментарий:
На скриншоте выше показаны результаты до и после.
А как они расшифровываются, узнаем из следующего поста.
#metadata
Нашел интересную статью о метаданных в смарт контрактах. Я, вроде как, за три года ведения канала ни разу толком о них не писал, поэтому разберем эту тему на простых примерах.
Когда solidity генерирует байткод для развертываемого смарт-контракта, он добавляет метаданные о компиляции в конец байткода. Мы рассмотрим данные, содержащиеся в этом байткоде.
Давайте посмотрим на вывод компилятора простейшего смарт-контракта solidity:
//SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
contract Empty {
constructor() payable {}
}
Контракт буквально ничего не делает. Мы можем скомпилировать его, чтобы увидеть код инициализации с помощью
solc --optimize-runs 1000 --bin contractFile.sol
Мы получим следующий вывод:
======= contractFile.sol:Empty =======
Binary:
6080604052603e80600 f5f395ff3fe60806040525f80fdfea26469706673582212203082dbb4f4db7e5d53b235f44d3e38f839dc82075e2cda9df05b88e6585bca8164736f6c63430008140033
Это кажется ужасно большим для контракта, который ничего не делает, верно? Давайте разберемся, что представляет собой весь этот байткод.
Когда мы компилируем код с помощью
solc --optimize-runs 1000 --bin --no-cbor-metadata contractFile.sol
мы получаем следующий результат:
======= contractFile.sol:Empty =======
Binary:
6080604052600880600 f5f395ff3fe60806040525f80fd
Это гораздо меньше! Но что же это за дополнительная информация?
Метаданные Solidity
По умолчанию компилятор Solidity добавляет метаданные в конец «фактического» initcode, который сохраняется в блокчейне после завершения выполнения конструктора. Ниже приведен «дополнительный» код:
fea26469706673582212203082dbb4f4db7e5d53b235f44d3e38f839dc82075e2cda9df05b88e6585bca8164736f6c63430008140033
Последние два байта 0033 означают «посмотрите назад на 0x33 байта, это метаданные». Это относится ко всему коду между ведущим fe (который является операционным кодом INVALID) и конечным 0033. Мы можем проверить, что это действительно 0x33 байта.
# fe and 0033 are not included
>>>hex(len('a26469706673582212203082dbb4f4db7e5d53b235f44d3e38f839dc82075e2cda9df05b88e6585bca8164736f6c6343000814') // 2)
# '0x33'
Так что же это за строка 0x33 (51 decimal)?
Мы можем получить подсказку, если внесем в исходный код одно крошечное, казалось бы, несущественное изменение. Это изменение - буквально дополнительный комментарий:
//SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
contract Empty {
// nothing
constructor() payable {}
}
На скриншоте выше показаны результаты до и после.
А как они расшифровываются, узнаем из следующего поста.
#metadata
1👍6❤1
Понимание метаданных смарт-контракта. Часть 2
Выходные прошли и пора возвращаться к рабочим ритмам.
С прошлым постом я забыл дать прикрепить ссылку к оригинальной статье, поэтому исправляюсь и выделяю ее отдельно:
https://www.rareskills.io/post/solidity-metadata
Вообще у RareSkills очень хороший всегда материал и, при изучении каакой-либо темы, рекомендую искать подборки там.
А сейчас мы продолжаем разговор о metadata.
Расшифровка metadata
Давайте посмотрим на гекс в синей рамке скрина из предыдущего поста:
Далее рассмотрим код в красном поле:
Это дает нам подсказку о том, что содержат эти данные: хэш IPFS и версию компилятора solidity.
Хэш IPFS
Раздел, подчеркнутый желтым, вместе с бирюзовой рамкой можно поместить в следующий python-скрипт (обратите внимание, что мы используем версию кода с комментарием // nothing):
Qm...RTEa - это IPFS-хэш файла метаданных, созданного компилятором. Этот участок кода (бирюзовый и желтый) кодируется иначе, чем поля выше. В частности, IPFS-хэш (бирюзовый и желтый) представляет собой закодированную в base58 версию шестнадцатеричных данных «1220...RTEa».
Это хэш IPFS, который вы получите, если поместите JSON-файл из компилятора Solidity на IPFS. На скрине этого поста файл JSON, о котором идет речь.
Мы можем сохранить JSON-файл как реальный файл, а затем проверить, что хэш совпадает с тем, который мы создали в python выше. Вам понадобится установленный инструмент командной строки ipfs (как установить).
Это совпадает с хэшем, полученным ранее.
Не приведет ли это к коллизиям хэшей?
Если два контракта с идентичным исходным кодом и конфигурацией компилятора хранят свой проверенный исходный код на IPFS, хэши IPFS будут сталкиваться (clash), но это желательно, потому что это экономит место storage. Смарт-контракты однозначно идентифицируются по комбинации идентификатора цепочки и их адреса, а не по содержимому IPFS.
Получение версии solidity
Наконец, если мы преобразуем секцию в оранжевой рамке, то получим версию solidity.
Зачем нужны метаданные смарт-контракта?
Эти метаданные добавляют дополнительные 53 байта к стоимости развертывания, что означает дополнительные 10 600 газа (200 на байткод) + стоимость calldata (16 газа на ненулевой байт, 4 газа на нулевой байт). Это означает до 848 дополнительных газа к стоимости calldata.
Так зачем это включать?
Это позволяет строго проверять код смарт-контракта. В метаданные JSON, которые выводит компилятор, входит хэш исходного кода. Поэтому если исходный код немного изменится, то изменится и JSON-файл метаданных, и его IPFS-хэш изменится.
Один странный трюк для снижения расхода газа через хэш IPFS
Один из очевидных способов оптимизировать затраты на газ во время развертывания - использовать опцию --no-cbor-metadata. Но если вам это нужно для проверки контракта, то вы все равно можете снизить стоимость газа, добывая хэши IPFS, в которых много нулевых байтов.
Когда контракт будет развернут, нулевые байты уменьшат стоимость calldata. Поскольку исходный код хэшируется, включая комментарии, это означает, что можно добывать комментарии, которые приводят к газоэффективным хэшам IPFS, которые будут добавлены к контракту. Обратите внимание, это означает, что мы хотим, чтобы в шестнадцатеричном представлении хэша были нули, а не в кодировке base58.
#metadata
Выходные прошли и пора возвращаться к рабочим ритмам.
С прошлым постом я забыл дать прикрепить ссылку к оригинальной статье, поэтому исправляюсь и выделяю ее отдельно:
https://www.rareskills.io/post/solidity-metadata
Вообще у RareSkills очень хороший всегда материал и, при изучении каакой-либо темы, рекомендую искать подборки там.
А сейчас мы продолжаем разговор о metadata.
Расшифровка metadata
Давайте посмотрим на гекс в синей рамке скрина из предыдущего поста:
>>> bytes.fromhex("69706673").decode("ASCII")
'ipfs'Далее рассмотрим код в красном поле:
>>> bytes.fromhex("736f6c63").decode("ASCII")
'solc'Это дает нам подсказку о том, что содержат эти данные: хэш IPFS и версию компилятора solidity.
Хэш IPFS
Раздел, подчеркнутый желтым, вместе с бирюзовой рамкой можно поместить в следующий python-скрипт (обратите внимание, что мы используем версию кода с комментарием // nothing):
import base58
hex_ipfs_hash = "12206a68b6b8bcc01ba559ec3adf7a387b6c4210a5dc69a05d038e9d17cae3fa373b"
bytes_str = bytes.fromhex(hex_ipfs_hash)
print(base58.b58encode(bytes_str).decode("utf-8"))
# QmVW2XyafSxDtiSqirJRauuT5SaQtGnQYsxxyYHrFmRTEa
Qm...RTEa - это IPFS-хэш файла метаданных, созданного компилятором. Этот участок кода (бирюзовый и желтый) кодируется иначе, чем поля выше. В частности, IPFS-хэш (бирюзовый и желтый) представляет собой закодированную в base58 версию шестнадцатеричных данных «1220...RTEa».
Это хэш IPFS, который вы получите, если поместите JSON-файл из компилятора Solidity на IPFS. На скрине этого поста файл JSON, о котором идет речь.
Мы можем сохранить JSON-файл как реальный файл, а затем проверить, что хэш совпадает с тем, который мы создали в python выше. Вам понадобится установленный инструмент командной строки ipfs (как установить).
mkdir out
solc --optimize-runs 1000 --bin --metadata C.sol --output-dir out
# Compiler run successful. Artifact(s) can be found in directory "out".
ipfs add -qr --only-hash out/Empty_meta.json
# QmVW2XyafSxDtiSqirJRauuT5SaQtGnQYsxxyYHrFmRTEa
Это совпадает с хэшем, полученным ранее.
Не приведет ли это к коллизиям хэшей?
Если два контракта с идентичным исходным кодом и конфигурацией компилятора хранят свой проверенный исходный код на IPFS, хэши IPFS будут сталкиваться (clash), но это желательно, потому что это экономит место storage. Смарт-контракты однозначно идентифицируются по комбинации идентификатора цепочки и их адреса, а не по содержимому IPFS.
Получение версии solidity
Наконец, если мы преобразуем секцию в оранжевой рамке, то получим версию solidity.
>>> 0x00 # solidity is version 0
0
>>> 0x08 # major version
8
>>> 0x14 # minor version
20
# correct, we used solidity 0.8.20
Зачем нужны метаданные смарт-контракта?
Эти метаданные добавляют дополнительные 53 байта к стоимости развертывания, что означает дополнительные 10 600 газа (200 на байткод) + стоимость calldata (16 газа на ненулевой байт, 4 газа на нулевой байт). Это означает до 848 дополнительных газа к стоимости calldata.
Так зачем это включать?
Это позволяет строго проверять код смарт-контракта. В метаданные JSON, которые выводит компилятор, входит хэш исходного кода. Поэтому если исходный код немного изменится, то изменится и JSON-файл метаданных, и его IPFS-хэш изменится.
Один странный трюк для снижения расхода газа через хэш IPFS
Один из очевидных способов оптимизировать затраты на газ во время развертывания - использовать опцию --no-cbor-metadata. Но если вам это нужно для проверки контракта, то вы все равно можете снизить стоимость газа, добывая хэши IPFS, в которых много нулевых байтов.
Когда контракт будет развернут, нулевые байты уменьшат стоимость calldata. Поскольку исходный код хэшируется, включая комментарии, это означает, что можно добывать комментарии, которые приводят к газоэффективным хэшам IPFS, которые будут добавлены к контракту. Обратите внимание, это означает, что мы хотим, чтобы в шестнадцатеричном представлении хэша были нули, а не в кодировке base58.
#metadata
👍2❤1
Проект на виду. Часть 2. Нейросети
Продолжаю делиться процессом создания проекта, о котором говорил в предыдущих постах - Пост 1 и Пост 2.
В последнее время я все чаще натыкался на посты/статьи/вопросы по нейросетям и редакторам кода на их основе. Очень долгое время я сопротивлялся этой технологии и специально игнорировал ее наличие...
И вот только две недели назад я установил свою первую нейросеть - DeepSeek. Во-первых, она была доступна для пользователей из РФ без ВПН, а во-вторых, не требовалась регистрация с телефоном.
Я поигрался в ней около недели и не совсем тогда понял, в чем прикол. Ну, в целом, я умею хорошо гуглить и искать нужную мне информацию и не понимал, что может дать мне простой чат.
На прошлой неделе я решил зайти еще чуть дальше и экспериментировал с остальными нейронками: ChatGPT, Claude, Grok 3, Gemini и даже скачивал редакторы кода: Cursor и Windsurf. Также смотрел много видео по этой теме: как писать промты, какие лимиты есть, для каких целей какие сети предназначены и т.д.
В итоге, после более-менее правильной работе с ними и адекватными запросами, я был впечатлен результатами. В особенности, их возможности объяснить код на разных языках.
И после этого, первым вопросом в моей голове было:
"Как лучше поступить с учениками на курсе: взять обещание не использовать нейронки хотя бы на первых модулях или наоборот: подсказать, как лучше работать с ними?"
Но это еще в процессе осознания...
А пока мне интересно было бы еще научиться самому работать с сетями и попробовать написать задуманный проект с ними.
Что бы вы понимали, в плане редакторов и кода я немного "динозавр" что ли... За все время, что я писал код (js, php) в своей карьере, я использовал Notepad++ - без плагинов и какой-никакой подсветки кода. Только за год-два до того как переключиться на Solidity, я писал проекты в VS Code.
Для меня знание кода и процесса его исполнения всегда было превыше плагинов-помощников. Зачем мне доп программы, когда можно просто посмотреть документацию или погуглить ответ на stackoverflow...
В этот раз будет по другому. Интересно будет посмотреть, сколько времени уйдет на фронтенд, бекэнд и много ли кода я буду исправлять.
В конечном итоге, если все получится, как задумываю, то базовые идеи/проекты можно будет выпускать буквально каждую неделю.
Будем практиковаться!
Также мне будет интересно узнать о вашем опыте работы с нейронками: лайфхаки, уроки, полезные ресурсы и т.д.
Всем приятного дня!
Продолжаю делиться процессом создания проекта, о котором говорил в предыдущих постах - Пост 1 и Пост 2.
В последнее время я все чаще натыкался на посты/статьи/вопросы по нейросетям и редакторам кода на их основе. Очень долгое время я сопротивлялся этой технологии и специально игнорировал ее наличие...
И вот только две недели назад я установил свою первую нейросеть - DeepSeek. Во-первых, она была доступна для пользователей из РФ без ВПН, а во-вторых, не требовалась регистрация с телефоном.
Я поигрался в ней около недели и не совсем тогда понял, в чем прикол. Ну, в целом, я умею хорошо гуглить и искать нужную мне информацию и не понимал, что может дать мне простой чат.
На прошлой неделе я решил зайти еще чуть дальше и экспериментировал с остальными нейронками: ChatGPT, Claude, Grok 3, Gemini и даже скачивал редакторы кода: Cursor и Windsurf. Также смотрел много видео по этой теме: как писать промты, какие лимиты есть, для каких целей какие сети предназначены и т.д.
В итоге, после более-менее правильной работе с ними и адекватными запросами, я был впечатлен результатами. В особенности, их возможности объяснить код на разных языках.
И после этого, первым вопросом в моей голове было:
"Как лучше поступить с учениками на курсе: взять обещание не использовать нейронки хотя бы на первых модулях или наоборот: подсказать, как лучше работать с ними?"
Но это еще в процессе осознания...
А пока мне интересно было бы еще научиться самому работать с сетями и попробовать написать задуманный проект с ними.
Что бы вы понимали, в плане редакторов и кода я немного "динозавр" что ли... За все время, что я писал код (js, php) в своей карьере, я использовал Notepad++ - без плагинов и какой-никакой подсветки кода. Только за год-два до того как переключиться на Solidity, я писал проекты в VS Code.
Для меня знание кода и процесса его исполнения всегда было превыше плагинов-помощников. Зачем мне доп программы, когда можно просто посмотреть документацию или погуглить ответ на stackoverflow...
В этот раз будет по другому. Интересно будет посмотреть, сколько времени уйдет на фронтенд, бекэнд и много ли кода я буду исправлять.
В конечном итоге, если все получится, как задумываю, то базовые идеи/проекты можно будет выпускать буквально каждую неделю.
Будем практиковаться!
Также мне будет интересно узнать о вашем опыте работы с нейронками: лайфхаки, уроки, полезные ресурсы и т.д.
Всем приятного дня!
🔥7❤4