Solidity. Смарт контракты и аудит – Telegram
Solidity. Смарт контракты и аудит
2.62K subscribers
246 photos
7 videos
18 files
547 links
Обучение Solidity. Уроки, аудит, разбор кода и популярных сервисов
Download Telegram
Чем отличается джун от мидла?

Несколько раз этот вопрос был задан в чате в контексте того, что вакансий для джунов сейчас мало и берут, в основном, только мидлов и сеньоров. Так как же стать тем самым мидлом без опыта работы?!

На одной из моих старух работ в студии, у нас была ходила типа поговорка: "Джун знает, мидл практикует, сеньор знает, где погуглить". Думаю, она и сейчас не плохо отражает суть индустрии.

Как я сейчас вижу в вакансиях, для мидлов и джунов практически схожие требования: знание Solidity, понимание работы EVM, умение писать тесты, способность работать с современными стандартами EIP, некоторые знания в безопасности смарт контрактов, ну, и может еще в некоторых случаях знание js, react - для подключения фронта.

На собеседовании вопросы будут задавать практически идентичные для обоих кандидатов, но джун "посыпется", когда попросят показать портфолио / реализованные проекты, в общем любой написанный вами код.

В некотором роде, эталоном хорошего разработчика можно считать его GitHub профиль и загруженные туда проекты.

Вы можете начать наполнять его с самого начала своего обучения. Это не только будет отличным бонусом к прохождению собеседования, но хорошей практикой работы с Git (а это чаще всего, также является одним из требований на работу).

Что можно сохранять на начальном уровне обучения?

1. Практика с функциями и типами данных. Это просто, но это покажет, что вы серьезно подходили к своему обучению. На первом модуле курса было два практикума, и во втором были упражнения со вложенными маппингами и структурами. Он может стать прекрасным примером того, что вы умеете работать со сложными типами данных.

2. Ваш первый токен или nft. Написали токен? Создайте файл "ERC20 practice" и тоже загрузите на Git.

3. По сути, изучая каждый новый ERC (подписи, vaults, прокси, и т.д.) вы можете создавать практический проект. Не волнуйтесь за качество своего кода. Так или иначе 100% безопасным его вы не сделаете, но при этом обязательно добавляйте комментарии! Это прям очень важно!

4. Когда вы изучите базис, то можете глянуть этот пост, где я предлагаю несколько проектов для практики: https://news.1rj.ru/str/solidityset/1112 Каждый из них покажет потенциальному работодателю ваши знания и навыки в программировании. А если еще к ним будут приложены хотя бы простые тесты - это сильно повысит шансы на получение работы.

На текущем модуле мы делаем акцент именно на практику. Если вы будете действительно выполнять каждое задание и практикумы, то в итоге в вашем GitHub может появиться более 14 практических проектов! И это за два месяца!

Работы будет очень много! Это будет серьезная прокачка навыков за лето!

Старт уже 1 июля!

#курс
👍12🔥21
А есть ли интенсивы для мидлов и сеньоров?

Тут на днях гуглил какие-нибудь обучающие программы для продвинутых разработчиков и аудиторов, и обнаружил, что их просто нет. Или я их не нашел?

За весь период своего обучения я погружался во множество различных тем web3 и аудита: от простых CTF до расчетов в DeFi протоколах. Ну, т.е. я хочу сказать, что много знаний уже есть на более-менее базовом уровне. Но вот мне потребовалось на выходных найти информацию по работе с Gnosis Safe: подключение, нюансы безопасности, общая архитектура протокола. И в большинстве статей поисковой выдачи было что-то типа: "Первые упоминания о гречке датированы 8 веком до нашей эры...". Пришлось копаться в официальных доках.

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

А ведь сколько пользы было бы, если бы зарубежные компании выпускали небольшие курсы по определенным темам (например, как RareSkills). Вот мне было бы интересно узнать:

1. Как работает Gnosis Safe;
2. Как подключать OpenSea;
3. Тики Uniswap V3 и хуки Uniswap V4;
4. Абстрактные аккаунты и их безопасность;
5. Особенности деплоя контрактов на разные L2 сети;
6. Безопасные ликвидации в DeFi протоколах;
7. Что такое блобы и их практическое применение сегодня;

и еще много других нишевых тем с разбором документации, кода и вопросов безопасности.

С одной стороны, крутым разработчикам и аудиторам нафиг это не надо записывать интенсивы, но что насчет компаний, которые проводят стримы и обучающие сессии, те же RareSkills, Alchemy, Updraft?

В общем, если вы знаете какие-нибудь ресурсы, где проводятся нишевые обучающие программы, поделитесь в комментариях.

А я пошел дальше работать над предстоящим модулем, старт которого уже на следующей неделе!

#обучение
🔥9👍21
Обучение в потоке

Хочу еще поделиться с вами некоторыми неожиданными открытиями, которые я получил, проведя уже 5 модулей - 4 от первого потока и 1 весенний.

Я получал много сообщений в личку, где ученики делились своими переживаниями и достижениями, задавали вопросы и интересовались "а что дальше?". Каждая история ученика - это небольшая борьба: за новые знания, против лени, за крутую работу и необычные перемены в жизни.

Из их обратной связи мне стало понятно, почему многие так любят обучение в группах и на разных курсах. Интересно, найдете ли вы для себя что-то:

1. "Сроки обучения не бесконечны." - мое первое открытие из этой серии. Действительно, когда мы учимся сами по видео на ютуб или задействуем какой-никакой роадмап, мы понятия не имеем, сколько времени этой займет: месяц или полгода. В наших представлениях учеба является каким-то бесконечным процессом.

А на курсе же мы точно знаем, что, например, через 8 недель это закончится. И уже легче планировать свое время и нагрузку.

2. "Внимание на вопросы от других учеников". От нескольких учеников я получал вопросы, где они просили рассказать подробнее о том или ином моменте из урока, который они как бы поняли, но позже прочитали вопрос от другого ученика в чате и "зависли". Другими словами, вопросы в чате помогают как самим задающим его, так и остальным участникам обратить внимание на некоторые нюансы языка и разработки.

3. "Мотивация быть в актуальных уроках". Для небольшого количества учеников играла роль небольшая конкуренция на модуле. В том плане, что если ты немного отстал от группы и затем увидел вопрос по актуальному уроку, то был заряд мотивации догнать остальных и идти в ногу.

4. "Профессиональное сообщество". Для многих играло значительную роль, что все они были на одном уровне в изучении Solidity и могли задавать вопросы друг другу. Никто не отвечал "свысока или снисходительно" и каждый для себя находил поддержку в группе.

5. "Структура и экономия времени!". Также для большинства учеников сыграло ключевую роль структурирование информации; многие говорили, что покупают курс только поэтому. Они не хотели сами составлять себе программу, искать информацию по темам и понятия не имели, как практиковаться с кодом. "Зачем я буду тратить половину времени на поиск, когда тут уже все сделано за меня?" - железный аргумент!

Здорово, что у нас получилось собрать столько учеников и пройти этот путь вместе!

У вас все еще есть время присоединиться к потоку!

Программа курса

Старт уже 1 июля!

#курс
👍142👎1
На сколько будет доступ к каналу и материалам?

Забыл вчера осветить этот вопрос, а он оказался достаточно популярным.

Как мне объяснил один из учеников, на некоторых других курсах, что он проходил, доступ к материалам был ограничен. Порой это был всего один месяц после завершения модуля, чаще - 3-6 месяцев. И если ты не успевал, то все - жди следующего запуска и покупай заново.

Ну, и, конечно, понятно волнение многих: "А что будет, если я не успею пройти модуль вместе со всеми? У меня же еще работа (учеба, дом, семья и т.д.). Могу ли в своем темпе проходить?".

Именно поэтому отвечаю на все эти вопросы в одном отдельном посте.

Канал и чат остаются с вами навсегда. Еще ни один из каналов курса не был удален, и это могут подтвердить ученики самого первого модуля, который проводился в июле прошлого года.

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

Кроме того, практикумы можно будет сдавать до Нового года. Поэтому вы можете проходить модуль в своем темпе и сдать работу по завершению.

Надеюсь, теперь вам будет проще решиться для себя попробовать этот модуль.

Программа курса

Старт уже в понедельник!

#курс
🔥14👍5
Solidity hints. Часть 5

Спасибо всем, кто пережил неделю продаж и остался на канале, понимаю что для многих это действительно испытание. Мы возвращаемся в рабочий ритм и потихоньку продолжаем разбирать репо от Chinmaya, который делал свои заметки по Solidity во время своего обучения. И следующий пункт:

6. enum types are not part of the ABI, they are just a solidity abstraction

Тип данных enum не является частью ABI, это всего лишь абстракция Solidity.

Действительно, enum - это один из способов создания пользовательского типа данных в Solidity. Они явно преобразуются во все целочисленные типы и из них. При этом во время выполнения преобразования проверяется, что значение лежит внутри диапазона перечисления, и в противном случае возникает ошибка Panic.

Enum требуют наличия хотя бы одного значения и не могут иметь более 256 значений, что фактически означает равенство к типу данных uint8 (так он и будет отображаться в ABI).

Когда мы создаем enum, например:

enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;


а затем хотим получить значение choice:

function getChoice() public view returns (ActionChoices) {
return choice;
}


в некотором роде, функция будет выглядеть как

getChoice() returns (uint8)

Значения в enum начинаются с 0, и если не установлено какое-либо другое значение, то функция или запрос к переменной вернем именно ноль. За этим необходимо следить в функциях, где от условия enum зависит ход ее исполнения.


7. Delete keyword is simply a reassignment of elements to their default values (ie.zero)

Ключевой метод delete в функциях фактически обнуляет значение, а не удаляет его.

При изучении Solidity нам часто говорили, что тут нельзя создать ни один тип данных с пустым или несуществующим значением. Например:

- boolean: false

- string: ""

- int / uint / enum: 0

- address: 0x0000000000000000000000000000000000000000 (or address(0))


Не помню, чтобы я встречал какие-либо критические уязвимости в последнее время, связанные с этим пунктом, так что, думаю, этот пункт можно просто запомнить.

#enum #default
👍4
Solidity hints. Часть 6

О, сегодня будет один интересный пункт, который порой упускают из виду даже опытные разработчики, и связан он с темой низкоуровневого вызова call.

8. call bypasses function existence check, type checking and argument packing

Вызов call пропускает проверку существования функции, проверку типа данных и упаковку аргументов. Что это означает?

Вообще, call - это такая "зараза", которую нужно контролировать вдоль и поперек. Уже не первый десяток уязвимостей было найдено, только потому что разработчики забыли сделать какую-либо проверку после вызова этого опкода.

Отправь его и он уйдет без вопросов...

Даже в самой документации есть специальная сноска по этому поводу:

The low-level functions call, delegatecall and staticcall return true as their first return value if the account called is non-existent, as part of the design of the EVM. Account existence must be checked prior to calling if needed.

Грубо говоря, что низкоуровневые языки всегда будут возвращать true как первое возвратное значение, даже если аккаунт, на который идет вызов, не существует. Так заложено в EVM и проверка на существование аккаунта ложится на плечи разработчика.

Также и с функциями. Посмотрите на два контракта:

contract Caller {

function testCallFoo(address payable _addr) public payable {
(bool success, bytes memory data) = _addr.call{
value: msg.value,
gas: 5000
}(abi.encodeWithSignature("foo(string,uint256)", "call foo", 123));

}

function testCallDoesNotExist(address payable _addr) public payable {
(bool success, bytes memory data) = _addr.call{value: msg.value}(
abi.encodeWithSignature("doesNotExist()")
);

}
}

contract Receiver {
event Received(address caller, uint256 amount, string message);

fallback() external payable {
emit Received(msg.sender, msg.value, "Fallback was called");
}

function foo(string memory _message, uint256 _x)
public
payable
returns (uint256)
{
emit Received(msg.sender, msg.value, _message);

return _x + 1;
}
}


Из контракта caller мы делаем вызовы во второй контракт. И что самое удивительное, обе функции сработают с call, учитывая то, что в receiver нет функции doesNotExist(), которую мы пытаемся вызвать через testCallDoesNotExist().

А если убрать fallback(), куда падает вызов несуществующих функций, наш call все равно не откатится.

Более того, call все равно как вы упаковываете типы данных. Это также проблема разработчиков правильно передавать данные в другой контракт или функцию.

Кстати, для этих целей есть abi.encodeCall - который практически тоже самое, что и abi.encode, но только добавляет проверку типов данных на соответствие функции, которая была указана в аргументах. Результат кодирования схож с abi.encodeWithSelector.

Подводя итоги можно сказать сказать общепринятые правила в использовании call:

1. Всегда проверять успешность возвращаемого значения;
2. Знать наверняка или самому контролировать вопрос существования адреса, куда отправляется вызов;
3. Проверять правильность кодирования данных при отправке;

Это если и не избавит вас на 100% от ошибок и проблем с call вызовов, но значительно сократить их шанс.

Будьте аккуратны с низкоуровневыми вызовами!

#call
3🔥2
Solidity hints. Часть 7

Сегодня поговорим о таком интересном опкоде, как extcodesize и его роли во внешних вызовах. Разберем два пункта из репо:

9. The evm considers a call to non-existing contract to always succeed, so there is a check of extcodesize > 0 when making an external call. But call, staticcall, delegatecall, send, transfer do not include this check

11. extcodesize > 0 check is skipped by the compiler if the function call expects return data. the ABI decoder will catch the case of a non-existing contract Because such calls are followed up by abi decoding the return data, which has a check for returndatasize is being at least a non-zero number. So for empty contracts, they would always revert in the end.


Для начала необходимо разобраться вообще за что отвечает excodesize.

excodesize - это специальный опкод EVM, который проверяет, есть ли код у данного адреса или нет. Если код есть, то данный адрес является контрактом.

P.S. Далее прошу более опытных читателей канала поправить меня, если будут неточности.

Когда мы используем низкоуровневые вызовы, такие как call(), delegatecall(), send() и transfer() проверка на существование контракта опускается, т.е. excodesize не исполняется где-то в середине вызова, и EVM считает, что этот вызов по-любому успешный.

При этом, когда мы дополняем такие вызовы проверкой на успешность (bool success,), мы получаем специальные методы, которые могут дать понять, что вызов обвалился на каком-либо этапе. Делается это с помощью другого опкода returndatasize, который отвечает за возвращаемые данные после вызова.

Нужно отметить, что разработчики не должны полагаться на excodesize для проверки на существование контракта, так как она может легко обойтись хакером. Например, у нас есть два контракта:

contract Victim {

bool public tricked;

function isContract(address _addToEval) public view returns(bool){
// The code is only stored at the end of the
// constructor execution.
//Thus extcodesize returns 0 for contracts in construction
uint32 size;
assembly {
size := extcodesize(_addToEval)
}
return (size > 0);
}

function supposedToBeProtected() external {
require(!isContract(msg.sender), "caller is not an EOA");
tricked = true;
}

}

contract Attacker {

bool public successfulAttack;
Victim v;

constructor(address _v) {
v = Victim(_v);
// address(this) doesn't have code, yet. Thus, it will bypass
//isContract() check
v.supposedToBeProtected();
//tricked was set to true on the above execution
successfulAttack = v.tricked();
}
}


Функцию supposedToBeProtected() должен вызывать только пользовательский адрес. Но с помощью конструктора хакер может обойти эту проверку.

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

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

#excodesize
👍3
Немного наблюдений из мира web3

"Привет всем аллергикам! Я с вами!" - именно так хочется начать этот пост, потому что уже несколько дней вместе с адской жарой в 36-37 градусов приходит и сезонная аллергия. Помимо того, что у тебя постоянно забит нос, так еще и фокусирование на информации нет никакого.

Пытаюсь вникнуть в код - и такой "а что такое uint?..", читаю репорты и просто не могу связать А с В. Короче, в следующем году надо планировать отпуск на весь этот сезон.

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

Вначале пара слов про тестирование смарт контрактов.

Осенью прошлого года я писал, что с развитием Foundry и любовью разработчиков к нему, придет новая профессия - тестировщик. И здесь я имел ввиду не разработчик - тестировщик, а именно отдельную специальность; людей, который будут только лишь писать разнообразные тесты для протоколов: юнит, фазз, инфариант и fv. В некотором роде, я оказался прав.

В компаниях, которые работали над собственным проектом, тесты писали и пишут сами разработчики. Но появились другие компании, тот же Trail of Bits, которые стали предлагать написание тестов, как услугу. Позже к ним присоединились и частные разработчики, которые предлагают нишевое тестирование: инварианты или формальную верификацию.

Открылся проект - Recon - от мощных аудиторов, который предлагает сервис по написанию инвариант тестов вкупе с программами Медуза и Foundry.

Тем не менее, с развитием предлагаемых услуг, я не увидел какого-то ажиотажа со стороны разработчиков к сложным тестам. Они все также остались на юнит и фазз тестах в своей основе.

Или может инварианты и формальная верификации прерогатива крупных протоколов с хорошим финансированием?

Также, большую часть выходящих на конкурсные аудиты протоколов, составляют проекты в сфере DeFi. А в Твиттере в постах разработчиков и безопасников чаще появляются посты на тему расчетов, токеномики и финансовых механизмов.

Я бы сказал, что простые токены или NFT уже не так сильно интересны сообществу, как год-два назад. Многие хотят каким-либо образом "прикрутить" DeFi или даже GameFi к своим протоколам.

P.S. Я за игровой индустрией не очень слежу, поэтому приветствуются комментарии на эту тему.

В DeFi сейчас осваиваются темы интеграций с другими крупными протоколами, типа Uniswap, Lido, Aave, а также базовые механики ликвидаций и стейкинга ликвидности. Реже, когда проекты смотрят на деривативы или опционы.

Будет интересно понаблюдать, как будет развиваться сервисы тестирования с DeFi протоколами, ведь после точечных взломов, когда хакеры подбирали необходимые значения чисел для атаки на протокол, должно появиться что-то для предотвращения таких случаев.

В конкурсных аудитах также есть некоторые обновления.

Если раньше на конкурс выходили более простые протоколы, то сейчас стали появляться все чаще миллионники: то Euler, то MakerDao, то еще пара штук в прошлом году. Да, и более профессиональные протоколы, как Uniswap или Chainlink, облюбовали этот способ аудита.

Только на мой взгляд, это, скорее, не конкурсный аудит, а багбаунти ограниченное по времени. Такие протоколы зачастую прошли не один профессиональный аудит от топовых фирм (Spearbit, ToB, etc), поисправляли все что можно, и теперь хотят собрать массовый фидбек.

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

А что вы замечаете на рынке контрактов и аудитов? Куда, думаете, движется сфера web3?

#trend
👍7🔥21👏1
Solidity hints. Часть 7

Понемногу выходим из состояния выходного дня и возвращаемся к работе, несмотря на лето и дару в 38...

Сегодня у нас по плану очередной пункт из репо Chinmaya и звучит он как:

10. On receiving funds from a selfdestruct / coinbase miner reward, the contract can not react to it, and it doesn’t require a contract to have receive or fallback functions.

В общем плане можно перевести как, контракт так или иначе получает Эфир от самоуничтожающихся контрактов и майнеров, и не может не принять его, даже если отсутствуют функции fallback или receive. Давайте разберем более детально этот пункт.

Для того, чтобы контракт мог принимать нативный токен - Эфир - на свой счет, в нем должны быть функции receive или fallback, или какая-либо другая с модификатором payable. Однако существуют способы пополнить счет контракта, даже если он напрямую запрещает это.

Первый вариант - это получение награды за майнинг. Майнер может указать любой адрес в сети для получения оплаты за свою работу, и тот так или иначе получит Эфир на свой счет.

Второй вариант - уничтожение контракта через selfdestruct. Другими словами, вы можете уничтожить свой контракт, на котором есть Эфир на балансе, и указать адрес другого контракта в сети. В этом случае тот также получит активы на свой счет, даже если нет необходимых функций.

Интересный факт о selfdestruct. Несмотря на то, что он был фактически упразднен при Шанхайском обновлении и его использование не рекомендуется в текущих версиях Solidity, начиная с 0.8.24 эту функцию можно использовать только в момент создания контракта (EIP-6780).

Звучит немного странно, но объясню, что это значит.

Скажем вы создаете контракт и выполняете там какие-либо действия. Если транзакция завершится, то selfdestruct вы уже не сможете вызвать. Однако, если вы создаете контракт, выполняете другие действия и уничтожаете его в одной транзакции, то все пройдет нормально.

На самом деле, я пока не встречал практического применения создания/уничтожения контракта в одной функции в реальных протоколах, но уже были найдены уязвимости на этом поле! Удивительно, но факт!

Как думаете, в каких случаях можно использовать selfdestruct?

#selfdestruct
👍2
Программа Smart Contract Security Specialist

Друзья из Statemind попросили поделиться информацией об их fellowship программе Smart Contract Security Specialist, на которую сейчас проходит отбор.

Для того чтобы попасть в программу, вам нужно будет заполнить анкету и пройти небольшой тест. Он не для новичков в Solidity и в программировании в целом. Скорее подойдет для тех, кто с математикой на короткой ноге.

Немного о самом обучении:

• Обучение онлайн
• Длительность 4 недели
• Обучение на английском/русском языках
• Отбор проводим по входному тесту (в форме)
• После успешного прохождения программы проводим интервью и по результатам предлагаем возможность присоединиться к нашей команде в качестве интерна (мы работаем удаленно)
• Обучение с нашей стороны бесплатное, потому что основная наша задача - найти единомышленников, c которыми мы в дальнейшем будем работать над интересными проектами
• Помогаем с релокацией
• Поддерживаем с обучением и развитием (английский язык, ресерчи, тулзы)
В программе курса
• Introduction to blockchain
• DeFi primitives
• DeFi security

Подробная информация и форма для заполнения для тех, кому будет интересно
https://docs.google.com/forms/d/1fo1FtehBqyIzhp-8FVcfSiZbUZWap1nexAP7UYXT9hc/edit

P.S. В прошлом году они также проводили набор и пригашали участников из нашего чата. Если вы проходили программу, будем рады услышать ваши отзывы и комментарии!


#fellowship
👍6
Solidity hints. Часть 8

Сегодняшний пункт также немного нов для меня, поэтому если у вас будут уточнения по информации, пишите комментарии с вашим мнением.

12. Always send 1 wei to precompiled contracts to activate them when testing in private blockchains, otherwise it may lead to OOG

Всегда отправляйте 1 wei на precompiled контракты в приватных сетях, чтобы активировать их и избежать ошибки OOG.

Для начала нужно разобраться, что такое precompiled контракты и OOG.

OOG - out of gas - ошибка, которая возникает в транзакции, когда расходуется больше газа, чем было разрешено.

Далее чуть сложнее, обратившись в yellow pages сети мы можем узнать, что

Precompiled контракты - это контракты, предназначенные для предварительной разработки архитектуры сети, которая впоследствии может стать родным расширением. Четыре контракта в адресах 1, 2, 3 и 4 выполняют функцию восстановления открытого ключа по эллиптической кривой, 256-битную хэш-схему SHA2, 160-битную хэш-схему RIPEMD и функцию идентификации соответственно.

Как я понял, на данный момент всего 9 таких контрактов:

1. Recovery of ECDSA signature
2. Hash function SHA256
3. Hash function RIPEMD160
4. Identity
5. Modular exponentiation (EIP 198)
6. Addition on elliptic curve alt_bn128 (EIP 196)
7. Scalar multiplication on elliptic curve alt_bn128 (EIP 196)
8. Checking a pairing equation on curve alt_bn128 (EIP 197)
9. BLAKE2b hash function (EIP 152)

А в этой статье можно прочитать больше о первых 4 контрактах:

https://medium.com/@rbkhmrcr/precompiles-solidity-e5d29bd428c4

А тут обо всех 9:

https://www.rareskills.io/post/solidity-precompiles

Далее по пункту, что мы разбираем, можно найти упоминание в официальной документации Solidity:

При выполнении функций sha256, ripemd160 или ecrecover на приватном блокчейне вы можете столкнуться с проблемой Out-of-Gas. Это происходит потому, что эти функции реализованы как "прекомпилированные контракты" и реально существуют только после получения первого сообщения (хотя код их контрактов жестко закодирован). Сообщения несуществующим контрактам стоят дороже, и поэтому при выполнении может возникнуть ошибка Out-of-Gas. Обходной путь решения этой проблемы - сначала отправить Wei (например, 1) каждому из контрактов, прежде чем использовать их в своих реальных контрактах.

Как я понял, precompiled контракты - это не совсем обычные контракты, которые мы с вами пишем на Solidity. Это специальные контракты, которые являются частью самой сети, и используются для каких-либо конкретных действий. Например, для восстановления подписи через ecrecover!

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

#precompile
👍51
Solidity hints. Часть 9

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

13. Functions called from within an unchecked block do not inherit the property. Bitwise operators do not perform overflow or underflow checks

Функции, вызываемые из unchecked блока, не наследуют это свойство. Побитовые операторы не выполняют проверку на переполнение или недополнение.

Unchecked блок по своей задаче не выполняет проверки на overflow, поэтому если мы в функции:

    function g(uint a, uint b) pure public returns (uint) {
return a - b;
}


вычтем 3-4, то вернется огромное число приближенное к максимальному значению uint256.

Однако... Посмотрите на этот вариант:

contract Test {
function unsafe_subtract(uint a, uint b) pure public returns (uint) {
unchecked {
return subtract(a,b);
}
}

function subtract(uint a, uint b) pure public returns (uint) {
return a - b;
}
}


в этом случае, несмотря на то, что исполнение функции subtract() происходит в unchecked блоке, проверка на переполнение будет все равно выполнена. Другими словами функция в блоке не будет наследовать от unchecked игнор проверки overflow.

Также, если мы используем побитовые операции или исполнение функции в assembly блоках, то в них НЕ будет проверки на переполнение.

#overflow
🔥3
Solidity hints. Часть 10

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

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

А на канале мы продолжаем разбирать пункты из репо Chinmaya, и сегодня у нас по счету уже 14:

14. After a failed call, Do not assume that the error message is coming directly from the called contract: The error might have happened deeper down in the call chain and the called contract just forwarded it (bubbling up of errors)

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

После неудачного вызова не думайте, что сообщение об ошибке пришло непосредственно от вызванного контракта: ошибка могла произойти глубже в цепочке вызовов, а вызванный контракт просто переслал ее.

В простых протоколах обычно вызовы могут идти в "соседний" контракт, например:

Контракт А => Контракт В

В более сложных протоколах, каких-нибудь DeFi, эти вызовы могут проходить через несколько контрактов, или даже протоколов:

А => В => С => А => Е

и т.д. И вот если мы вызываем из контракта А такую функцию и на каком-то этапе происходит откат транзакции и нам возвращается ошибка, не стоит предполагать, что вернул ее контракт В.

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

15. Calling a function on a different contract (instance) will perform an EVM function call and thus switch the context such that state variables in the calling contract are inaccessible during that call (except if you use delegatecall)

и перевод:

Вызов функции на другом контракте выполнит вызов функции EVM и, таким образом, переключит контекст так, что переменные состояния в вызывающем контракте будут недоступны во время этого вызова (не считая использования delegatecall)

Тут также все достаточно просто. Например, если мы делаем вызов из контракта А в контракт В, то переменные контракта А будут недоступны для изменения в контракте В, так как вызов "переключится" контекстом на В.

Еще проще, допустим у нас есть переменная owner в А. Мы делаем вызов из А в В и там вызываем функцию для изменения адреса владельца.

И несмотря на то, что вызов идет через А, переменная будет изменения в В.

При этом, если мы используем функция с delegatecall в А, делая вызов в В, то мы "как бы захватываем" функцию из В, переносим ее в А и изменяем уже состояние в памяти А.

Звучит немного запутано, но стоит разобраться с call() и delegatecall(), как все встанет на свои места!

#solidity #call #delegatecall
👍3
Solidity hints. Часть 11

Фух, лето... В моем городе на солнце уже +42, работать даже под кондиционером достаточно сложно. Но мы все равно понемногу повторяем азы Solidity, и переходим к следующему пункту:

16. After contract creation, The deployed code does not include the constructor code or internal functions only called from the constructor

После создания контракта, его код не включает в себя конструктор или внутренние функции, которые были использованы в нем

Как вы можете уже знать, что при деплое контракта его код разделяется на две части: init code и runtime code. Например, возьмем самый простой контракт:

pragma solidity 0.8.17;// optimizer: 200 runscontract Minimal {
constructor() payable {

}
}


Когда мы выполним деплой контракта, его байткод будет следующим:

0x6080604052603f8060116000396000f3fe6080604052600080fdfea2646970667358221220d03248cf82928931c158551724bebac67e407e6f3f324f930c4cf1c36e16328764736f6c63430008110033

И можно разделить его на:

init code - 0x6080604052603f8060116000396000f3fe

runtime code - 6080604052600080fdfea2646970667358221220d03248cf82928931c158551724bebac67e407e6f3f324f930c4cf1c36e16328764736f6c63430008110033

P.S. Весь байткод, по сути, это перечисление опкодов и значений для него, но это слишком сложная тема для обсуждения в данном посте.

Вот этот вот runtime code появляется после выполнения init code и является уже нашим смарт контрактом, в котором мы можем вызывать различные функции.

В обсуждаемом пункте говорится о том, что действия контракта в конструкторе не включаются в итоговый код runtime, а используются исключительно в init code, в моменте создания самого контракта.

Более того, если в нашем контракте присутствуют internal функции, которые используются только в конструкторе, то они также не будут включены в итоговый runtime код!

Вообще, деплой контракта достаточно интересная тема, и если вы хотите чуть больше об этом узнать, то крайне рекомендую прочитать эту статью:

Ethereum smart contract creation code

#creationcode #runtime #init
4👍4
Solidity hints. Часть 12

Сегодня чуть припозднился с постом. Иногда в репо встречаются какие-то обрывки из правил, и я трачу время, чтобы понять:

1) О чем вообще идет речь?
2) Откуда это он взял?

И вот сейчас пункт звучит:

17. Dont use this.f inside constructor

Кто это вообще использует? Я за все время ни разу не встречал в конструкторах this, а знаете почему?

Потому что его вообще нельзя использовать там!

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

Ну, в общем, рассказываю, что-о-чем тут.

Он читал официальные доки Solidity и встретил комментарий к коду:

State variables are accessed via their name and not via e.g. `this.owner`. Functions can be accessed directly or through `this.f`, but the latter provides an external view to the function. Especially in the constructor, you should not access functions externally, because the function does not exist yet.

Немного запутано написано, но я понял, что this, в этом случае, как бы выполняет вызов в функцию из вне создаваемого контракта, и так как последняя еще не существует, он выдает об этом ошибку.

При этом, если использовать туже функцию, но без this, то все работает нормально. Например:

contract TestThis {

uint256 private num;

// не работает
constructor() {
num = this.increment();
}

function increment() internal view returns(uint256){
return num + 5;
}
}

contract TestThis {

uint256 private num;

// работает
constructor() {
num = increment();
}

function increment() internal view returns(uint256){
return num + 5;
}
}


В общем, не особо понял зачем этот пункт, раз все равно нельзя так сделать, но раз есть в списке, то разбираем и его.

Ну, и еще один пункт напоследок:

18. Internal is the default visibility level for state variables.

Тут все просто, у каждого типа данных в Solidity есть некое значение по-умолчанию. У uint - это 0, у bool - false и т.д.

И у каждой переменной состояния есть область видимости: public, external, internal и private. Если мы не указываем нужную нам область, то по умолчанию устанавливается internal, что запрещает обращаться к ней из других внешних контрактов.

#constructor #visibility
👍2
Solidity hints. Часть 13

Понемногу движемся дальше и сегодня на очереди у нас:

19. Internal function calls do not create an EVM message call. They are called using simple jump statements. Same for functions of inherited contracts.

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

Internal функции не создают EVM вызов, а используют внутренние опкоды jump. Тоже самое актуально и для наследуемых контрактов.

Тема может быть немного сложной для новичков в Solidity, так как связана с работой EVM и его низкоуровневых инструкций - опкодов. Но постараюсь объяснить простыми словами.

У нас есть 4 области видимости для функций: public, external, internal и private. Первые две - открытые для доступа других контрактов, вторые - закрыты.

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

Доступ к внутренним функциям возможен только внутри текущего контракта или контрактов, вытекающих из него. Они не могут быть доступны извне. Поскольку они не открыты для внешнего доступа через ABI контракта, они могут принимать параметры внутренних типов, таких как mapping или storage references.

Внутренние функции вызываются через инструкции опкоды JUMP / JUMPI, просто перепрыгивая в другую точку текущего кода. Это имеет смысл, потому что внутренние функции не меняют контекст (то есть остаются внутри одного и того же контракта при вызове).

Другими словами, вы можете вызвать внутреннюю функцию только в том случае, если вы выполняете код внутри самого контракта и этот вызов внутренней функции не требует (да и не позволяет) напрямую обращаться к ней через EVM, а только через Jump.

Думаю, можно подытожить: если мы можем вызывать функцию извне, то это EVM вызов, если же она предназначена только для внутреннего использования - то это работа опкодов.

Следующий пункт:

20. If you have a public state variable of array type, then you can only retrieve single elements of the array via the generated getter function.

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

Если у вас есть публичная переменная состояния типа массив, то вы можете получить только отдельные элементы массива через сгенерированную функцию getter. Этот механизм существует для того, чтобы избежать высоких газовых затрат при возврате целого массива. Вы можете использовать аргументы, чтобы указать, какой отдельный элемент возвращать, например myArray(0). Если вы хотите вернуть весь массив за один вызов, то вам нужно написать функцию, например:

contract arrayExample {

uint[] public myArray;
function myArray(uint i) public view returns (uint) {
return myArray[i];
}

function getArray() public view returns (uint[] memory) {
return myArray;
}
}


Также немного запутанный пункт и документация для начинающих разработчиков.

Смотрите, когда мы создаем переменную с областью видимости public, EVM также создает для нее специальный геттер, по которому можно прочитать значение этой переменной. Проще всего это будет понять, попытавшись создать в Ремиксе контракт с двумя переменными public и internal.

Для первой переменной у вас появится кнопка в левой панели Ремикса, для того чтобы посмотреть ее значение, но для internal - ее не будет.

Так вот, если мы создадим массив с этой областью видимости, то обращаться через геттер к значениям хранящимся в нем, мы сможем только по специальному индексу. И у нас не получится вернуть весь массив сразу.

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

Хмм, вот сейчас перечитал пост и понял, что для новичков он вообще ничего не скажет, а для продвинутых - это и так само собой разумеющееся... Но надеюсь, хоть кому-то это будет полезно.

#array #getter #jump #external
👍2💯2🌭1
Solidity hints. Часть 14

На этих выходных жара настолько разошлась, что город ввел веерные отключения света по всем районам, и несколько вышек, которые отвечают за интернет в моем ЖК просто "полетели"... Выходные я провел на мобильном интернете, который едва тянул, чтобы отправить сообщение в мессенджеры. Никогда бы не подумал, что буду скучать контенту, в котором "и так ничего нет"...

А мы продолжаем изучать нюансы Solidity и сегодня на очереди следующие пункты из репо Chinmaya:

21. Without a payable keyword in function declaration, it will auto reject all ether sent to it. It will revert.

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

Без ключевого модификатора payable в функции, она не сможет принимать Эфир и будет откатывать транзакцию.

Вообще, удивительно, что при таком легком правиле, которое учится на самых первых уроках по Solidity, даже профессиональные протоколы могут упускать это из виду.

Вы можете сами зайти на сайт с подбором багов из контестов - Solodit - и ввести в поиск missing payable. На данный момент там 67 багов по этой теме...

Чаще всего разработчики забывают установить этот модификатор во внешних вызовах между контрактами. Например, из одного контракта мы отправляем Эфир в другой, там в функции делаем расчеты с msg.value, но забываем payable.

Тут единственное, что можно порекомендовать проверять наличие payable в функциях, где есть обработка msg.value, а также отслеживать движение Эфира и токенов между контрактами. Так мы сможете обнаружить и другие возможные баги.

22. Modifiers can also be defined in libraries but their use is limited to functions of the same library

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

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

Вообще библиотеки такая странная штука в Solidity, что она порой вызывает недоумение и у опытных разработчиков.

Библиотеки не могут хранить Эфир, не имеют своего storage, а то как они работают с вашим контрактом при наличии internal / external функций и говорить не стоит...

А тут еще и модификаторы подоспели.

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

#modifier #payable #library
👍3
Solidity hints. Часть 15

Мы уже разобрали более 20 пунктов из репо и я подумываю сделать недельный перерыв от них. В том плане, что хочется изучить или рассмотреть что-то новое, и вот смотрю я на Account Abstraction. Может разберем его на следующей неделе?

Было несколько годных статей на Alchemy по этой теме, да и Патрик Коллинс выпустил 4 часовое видео. Для первого касания хватит, а там посмотрим чего нам не будет хватать для понимания темы.

Ну, а пока что, еще пара пунктов:

23. Multiple modifiers are applied to a function by specifying them in a whitespace-separated list and are evaluated in the order presented. Modifier Order Matters

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

Несколько модификаторов применяются к функции путем указания их в списке, разделенном пробелами, и оцениваются в указанном порядке. Порядок модификаторов имеет значение.

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

contract MyToken is ERC20, ERC20Permit, Ownable {
}


и если мы нарушим этот порядок и поставим ERC20Permit перед основным контрактом ERC20, то сам компилятор будет выдавать ошибку.

С модификаторами чуточку сложнее. В большинстве случаев компилятор на покажет вам предупреждений или ошибок по вопросу их порядка в функции. Однако сама логика работы функции может быть нарушена.

Если вам интересно разобраться с работой модификаторов, то вот прекрасный пример для головоломки:

pragma solidity ^0.4.18;

contract modifierTest {
uint public modState1;
uint public modState2;
uint public modState3;

modifier modA() {
modState1 = modState1 + 1;
_;
}

modifier modB() {
modState2 = modState2 + 1;
_;
modState2 = modState2 + 1;
_;
}

function func() public modA modB {
modState3 = modState3 + 1;
}
}


Как думаете, как значения получатся после выполнения функции? И ответ на этот вопрос моно будет найти в следующем пункте:

24. The _ symbol can appear in the modifier multiple times. Each occurrence is replaced with the function body. Symbols introduced in the modifier are not visible in the function

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

Символ _ может встречаться в модификаторе несколько раз. Каждое появление заменяется телом функции. Символы, введенные в модификатор, не видны в функции.

Думаю, тут будет лучше самим перенести код в Ремикс и попробовать "поиграться" с функцией, так будет намного понятнее, как происходит работа внутри и на что влияет порядок.

#modifier
🔥3👍2
Solidity hints. Часть 16

Последний пост перед небольшим перерывом с циклом Solidity Hints, так как на следующей неделе мы поговорим про Account Abstractions.

Итак:

25. For values of immutable variables, 32 bytes are reserved in the code, even if they would fit in fewer bytes
26. The compiler does not reserve a storage slot for constant and immutable variables, and every occurrence is replaced by the respective value directly in the code.


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

25. Для значений immutable переменных всегда резервируется 32 байта памяти, не смотря на то, что сами значения могут по итогу заниматься меньше места.
26. Компилятор не резервирует слот для хранения постоянных и неизменяемых переменных, и каждое их вхождение заменяется соответствующим значением непосредственно в коде.

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

Если вы хоть раз интересовались работой памяти в Solidity, то наверняка слышали рекомендацию по упаковке в правильный порядок переменных состояния контракта, чтобы они занимали меньше места. Например,

uint256 - занимает весь слот памяти в 32 байта;

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

address занимает 20 байтов, а bool - всего 1.

Зная размерность наших переменных мы может сгруппировать их таким образом, чтобы они компактно размещались в памяти.

При этом два вида переменных, constant и immutable - размещаются не в памяти контракта, а в его байткоде.

Значение переменной константы, при деплое контракта, просто копируется во все места в самом контракте, где к ней обращаются. Для значений immutable переменной происходит тоже самое, только для нее резервируется 32 байта, даже если значение занимает гораздо меньше места. Именно поэтому константы иногда могут быть дешевле по газу, чем immutable переменные.

#immutable #constant #storage #bytecode
👍4
Account abstraction (ERC-4337). Часть 1

С этого дня начнем разбирать AA и для начала я решил прочитать серию статей с Alchemy, перевод чего и буду делать тут. Если у вас есть еще какие интересные материалы по этой теме, делитесь в комментариях.

Мне понравилась эта статья тем, что автор разбирает шаги, которые проходило сообщество прежде чем сформировать идею АА, а также множество примеров кода. Далее пойдет уже перевод первой части статьи.

Для начала давайте придумаем, как защитить наши самые ценные активы. Мы хотим иметь возможность подписывать большинство транзакций одним закрытым ключом (как в обычном аккаунте кошельке), и наш бесценный Courage NFT можно будет передать, только если мы подпишем его вторым ключом, который мы закроем в банковском хранилище, охраняемом трехголовым псом.

И вот какой будет наша первая задача.

Каждый счет Ethereum - это либо смарт-контракт, либо счет, принадлежащий внешнему владельцу (EOA), причем последний управляется off-chain с помощью закрытого ключа. Должен ли счет, на котором хранятся эти активы, быть смарт-контрактом или EOA?

На самом деле, держателем активов должен быть смарт-контракт. Если бы это был EOA, то активы всегда можно было бы передать с помощью транзакций, подписанных закрытым ключом EOA, что не обеспечит нужную нам безопасность.

Поэтому, в отличие от большинства людей, наше присутствие onchain будет представлено смарт-контрактом, а не EOA, который мы будем называть кошельком смарт-контракта или просто «кошельком».

Нам нужен способ отдавать команды этому смарт-контракту, чтобы он выполнял нужные нам действия. В частности, нам нужно иметь возможность приказать смарт-контракту выполнить любой вид call()/transfer(), который я мог бы отправить из EOA.

P.S. Каждому пользователю, желающему защитить свои активы таким образом, потребуется собственный смарт-контракт. Не может быть одного большого контракта, в котором хранятся активы нескольких человек, потому что остальная экосистема предполагает, что один адрес представляет одно лицо, и не сможет различать отдельных пользователей.

Например, если кто-то захочет отправить NFT кому-то в контракте с объединенным кошельком, API передачи NFT позволит отправителю указать только адрес объединенного кошелька, но не отдельного пользователя в нем.


Пользовательские операции

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

Назовем данные, представляющие действие, которое я хочу, чтобы кошелек выполнил, пользовательской операцией или user op.

Итак, контракт кошелька выглядит следующим образом:

contract Wallet {
function executeOp(UserOperation op);
}


‍Что входит в пользовательскую операцию?

Во-первых, нам нужны все параметры, которые мы обычно передаем в eth_sendTransaction:

struct UserOperation {
address to;
bytes data;
uint256 value; // Amount of wei sent
uint256 gas;
// ...
}


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

Для нашего кошелька, защищающего NFT, для большинства пользовательских операций мы передадим подпись остальной части операции, подписанную нашим главным ключом.

Но если пользовательская операция заключается в передаче нашего сверхценного NFT Carbonated Courage, то кошельку потребуется передать подписи остальной части операции, подписанные каждым из двух наших ключей.

Мы также добавим nonce для предотвращения атак повторного воспроизведения, когда кто-то может повторно отправить предыдущую пользовательскую операцию, чтобы запустить ее снова:

struct UserOperation {
// ...
bytes signature;
uint256 nonce;
}


Это то, что нам нужно! Пока мой Carbonated Courage NFT находится под действием этого контракта, он не может быть передан без двух подписей.
2
P.S. Хотя кошелек может интерпретировать поля подписи и nonce по своему усмотрению, я ожидаю, что почти все кошельки будут использовать поле подписи для получения некой подписи поверх всех остальных полей, чтобы предотвратить подделку или фальсификацию операции неавторизованными лицами. Аналогично, я ожидаю, что почти любой кошелек будет отклонять операции с уже виденным им nonce.

Далее поговорим о том, что может вызывать наш кошелек - смарт контракт.

#accountabstraction
👍4