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

Поговорим об окошках дебаггера.

Function Stack. Показывает все функции, которые задействованы в транзакции.

Solidity Locals. Показывает локальные переменные внутри функции.

Solidity State. Показывает все переменные состояния в контракте.

Opcodes. Показывает шаг и опкод, который используется в данный момент.

Step details. Показывает больше информации о шаге опкода.

Stack. Отображает стек EVM.

Memory. Отображает состояние памяти. Первые два слота, по правилам языка, всегда путые (до 0x40), третий слот (0х40) - указывает на пустое место в памяти, куда можно вести запись. Записи хранятся в формате Hex.

Также в памяти отображается три колонки: первая - место в памяти, вторая - hex значение, третье - расшифровка значения. Если в третьем стоят знаки "?", это означает, что значения не существует.

Storage. Постоянное хранилище.

Call Stack. Все вычисления с массивами данных тут называются Stack. Он может быть максимальным размером в 1024 элемента и содержать слова в 256 бит.

Call Data. Содержит параметры функции.

Return Value. Отображает то, что возвращает функция.

Full Storage Changes. Отображает состояние хранилища на момент завершения выполнения функции.

Global Variables. Глобальные переменные доступные на данный момент.

Далее поговорим о том, как его использовать в своей работе с контрактом.

#remix #debugger
👍1
Описание Remix Debugger. Часть 3

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

Отлавливание ошибок

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

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

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

Переменные

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

Слоты памяти

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

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

#remix #debugger
👍1
Чтение событий в mainnet

Нашел интересный пример для чтения событий (event) в контрактах, которые уже были загружены в сеть. Для этого используются такие сервисы как Alchemy или Infura. Если я правильно понял, то для этого случая, все бесплатно.

Чтобы подключить прослушивание событий в сети, вам нужно зарегистрировать в одном из этих сервисов, создать API и получить ссылку WebSocket. Все это будет в вашем личном кабинете.

Также нам потребуется адрес контракта и его ABI, которые можно найти на etherscan. В нижеследующем примере был взят адрес контракта USDT. 

Далее в js файле потребуется написать такой код: 

const ethers = require("ethers");
const usdtAbi = require("pathToAbiFile");

async function main() {
const usdtAddress = "tokenAdress";
const provider = new ethers.providers.WebSocketProvider("linkToSocket");

const contract = new ethers.Contract (usdtAddress, usdtAbi, provider);
contract.on("Transfer", (from, to, value, event) => {
  let info = {
   from: from,
   to: to,
   value: ethers.utils.formatUnits(value, 6),
   data: event
  };
 
  console.log(JSON.stringify(info, null, 4));
});
}

main();

Теперь, как только будет происходить события transfer, мы будем получать сообщение об этом.

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

#ethers #ethersjs #alchemy #event
👍1
Лезем в опкод!

Со вчерашнего дня у меня в голове засела идея узнать больше про опкод в EVM. К тому же еще та задача из ethernaut не давала покоя. Поэтому я решил покопаться в этой теме чуть глубже.

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

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

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

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

Видео: Демистификация опкода EVM

Приятного просмотра!

#evm #opcode
👍1
Опкод из байткода

Понравился этот слайд из видео, где объясняется конструкция опкода.

Если говорить кратко, то код нашего смарт контракта в сети Эфира представлен в виде байткода. Например, сложение (add) выглядит так:

0000 0001

В опкодах мы берем каждые 4 символа бинарного кода и приравниваем его к 1 символу в hex, и добавляем "0х", для указания EVM на то, что это конкретно hex код, а не какой-нибудь другой. И получается:

0х01

В итоге, все опкоды равны 1 байту, или 2 hex символам. Более того, каждому опкоду (этому hex) соответствует понятное для разработчика название.

Opcode 01 = name "Add"

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

#evm #opcode
Магические 6080604052

Уверен, никто не прочитал число в заголовке поста. А зря...

Это число является байткодом, с которого начинается байткод любого смарт контракта. Ну, иногда с легкими изменениями в 6060604052. Хотите узнать что это такое?

И это тоже относится к опкоду EVM. В другом формате это можно было бы записать так:

PUSH1 0x60 PUSH1 0x80 MSTORE

Как мы можем убедиться из таблицы опкодов, PUSH1 = 60, MSTORE = 52.

PUSH1 (0x60) - кладет 0х60 в Stack (мы же помним о форматах памяти?);
PUSH1 (0x60) - потом кладет 0х80 в Stack;
MSTORE (0x52) - берет 0х60 из памяти и перемещает в слот 0х80;

Если выполнить все эти действия по порядку, получится именно 6080604052.

Далее чуть сложнее.

0х80 и 0х60 не могут быть использованы, как простые числа 80 или 60. Так как они hexadecimal, то в переводе в decimal 60 будет ровняться 96, а 80 - 128.

Короче говоря, PUSH1 0x60 PUSH1 0x80 MSTORE берет 96 байтов памяти и перемещает указатель в начало 128 байта. Именно поэтому в памяти первые 64 байта всегда пустые, следующие 32 являются указателем, и потом уже идет запись контракта в саму память.

#evm #opcode
👍1
Задача из Ethernaut - Magiс Number. Часть 1

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

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

Итак, целью задачи было написать код смарт контракта, чтобы он возвращал нам число "42", при этом уложившись в 10 байтов. Это можно сделать только с помощью опкода. Поехали.

Мы помним, что 1 опкод = 1 байту, а число 42 в hex = 0х2а.

Нам потребуется два набора байткода:

1. Байткод инициализации: тот, который подготовит смарт контракт и вернет runtime байткод.

2. Runtime байткод: тот, который используется после создания контракта, и где лежат все наши функции.

Посмотри на runtime сперва.

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

Кладем значение в память:

1. 0x60 - PUSH1 --> PUSH(0x2a) --> 0x602a
2. 0x60 - PUSH1 --> PUSH(0x80) --> 0x6080
3. 0x52 - MSTORE --> MSTORE --> 0x52

И возвращаем его из памяти:

1. 0x60 - PUSH1 --> PUSH(0x20) --> 0x6020
(размер значение в 32 байтах)
2. 0x60 - PUSH1 --> PUSH(0x80) --> 0x6080
3. 0xf3 - RETURN --> RETURN --> 0xf3

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

В итоге runtime байткод будет выглядеть так:

602a60805260206080f3

Все "0х" мы удаляем, не забыли?

#evm #opcode
👍1
Задача из Ethernaut - Magiс Number. Часть 2

Теперь разберем байткод инициализации. Он отвечает за загрузку нашего runtime кода в память и возвращает его в EVM.

Для этого нам потребуется скопировать код с помощью опкода CODECOPY, который принимает 3 значения:

1. Слот назначения, куда код будет помещен в памяти, для примера возьмем 0х00.
2. Текущая позиция опкода runtime, которую мы не знаем в данный момент.
3. Размер нашего кода в байтах, и его длина сейчас ровно 10 байтов.

Вот так это выглядит:

1. 0x60 - PUSH1 --> PUSH(0x0a) --> 0x600a
(0x0a - это размер нашего кода - 10 байтов)
2. 0x60 - PUSH1 --> PUSH(0x??) --> 0x60??
(?? - позиция, которую мы еще не знаем)
3. 0x60 - PUSH1 --> PUSH(0x00) --> 0x6000
(0x00 выбранный нами ранее слот памяти)
4. 0x39 - CODECOPY --> CODECOPY --> 0x39

Далее возвращаем этот код:

1. 0x60 - PUSH1 --> PUSH(0x0a) --> 0x600a
(размер нашего опкода в 10 байтов)
2. 0x60 - PUSH1 --> PUSH(0x00) --> 0x6000
(значение было сохранено в слоту 0х00)
3. 0xf3 - RETURN --> RETURN --> 0xf3
(возвращает значение в 0х00 и длинной в 0х0а)

Если сложить текущий опкод, мы получим 600a60__600039600a6000f3. Он будет равняться 12 байтам. Это означает, что мы нашли недостающее значение, которое мы ранее пометили, как ??.

12 или 0x0c в hex позволяет закончить создание нашего кода:

600a600c600039600a6000f3

Теперь можно сложить два байткода и получить то, что потребуется нам для решения:

602a60805260206080f3 + 600a600c600039600a6000f3 = 600a600c600039600a6000f3602a60505260206050f3

Говоря кратно, решение задачи можно теперь представить так:

bytes memory code = "\x60\x0a\x60\x0c\x60\x00\x39\x60\x0a\x60\x00\xf3\x60\x2a\x60\x80\x52\x60\x20\x60\x80\xf3";
address solver;
assembly {
solver := create(0, add(code, 0x20), mload(code))
}

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

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

#evm #opcode
👍2🔥1
Задача на день

Ну, и небольшая задача-вопрос на вечер: где уязвимость в данном коде?

Решение

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

#task
3👍1
Из чего состоит транзакция? Часть 1

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

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

0xb3e9dad434ce64f0f886e5b3bafced3bc72fee467cf1739d0f8f5a141784fbad

В правом углу транзакции будет небольшое окошко меню, где можно найти дополнительные параметры, одним из которых будет Get Raw Tx Hex. Откроется окно и мы увидим более длинную запись, типа такой:

0xf86d822c858502eeae9847826b6c94388c818ca8b9251b393131c08a736a67ccb192978732bdda0cd1511e8026a0220a666d6dd188222bba14b0f77e378aac1910bba8d646edcd0a90d937cf2f15a07d8396243c8b0ced7d7c955ee27b67a4989e49eaf41aa8396191e0a5a40064db

Все это пригодится нам в разборе транзакции на составные части.

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

1. Информация о транзакции
2. Подпись

Генерируется она по формуле:

Keccak256(RLP(nonce,gasPrice,gasLimit,to,value,data,v,r,s))

Техническую часть о транзакциях можно прочесть в Yellow Pages и EIP-155.

Далее подробнее поговорим подробнее о каждой части.

#transaction #rlp #ecdsa
👍5
Из чего состоит транзакция? Часть 2

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

- Nonce. Значение, которое равняется количеству транзакций, отправленных с данного адреса.

- Gas Price. Цена за газ в Wei.

- Gas Limit. Максимальное количество газа, которое может использоваться в этой транзакции.

- To. Адрес назначения транзакции.

- Amount. Количество Эфира отправляемого в транзакции.

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

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

Далее поговорим о данных v, r и s в формуле.

#transaction #rlp #ecdsa
1👍1
Из чего состоит транзакция? Часть 3

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

Символы v, r и s отвечают за подпись транзакции для ее авторизации / подтверждения. Для генерации подписи используется специальный алгоритм Elliptic Curve Digital Signature Algorithm (ECDSA).

С помощью ECDSA формируются значения для R и S по формуле:

C = kG, r=Cx, s = (e + rd) / k

где k - случайное число, g - точка генерации, e - данные для подписи, d - приватный ключ.

Подробное описание расчетов можно найти в этой статье.

Значение для V можно найти в документации для EIP-155 и рассчитывается по формуле v = CHAIN_ID * 2 + 35.

Chain_Id для всех сетей разный. Так для mainnet он равен 1, для Goerly - 5. Поддерживаемые сети можно найти все в том же документе EIP-155.

Теперь для проведения транзакции у нас есть все значения.

#transaction #rlp #ecdsa
👍2
Из чего состоит транзакция? Часть 4

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

Для этого на помощь приходит Recursive Length Prefix (RLP). Помните же формулу генерации транзакции Keccak256(RLP(...))?

RLP - это специальный алгоритм для кодирования объектов в чистые байты. Не уверен на все 100%, но в одной из статей я прочитал, что для кодировки в RLP данные уже заранее должны быть преобразованы в HEX формат.

И вот, когда мы кодируем информацию о нашей транзакции через RLP мы получаем Raw Tx Hex, то длинное значение из etherscan.

А, если прогнать его через kessac256 мы получаем хеш транзакции из 32 байтов.

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

#transaction #rlp #ecdsa
👍3
Из чего состоит транзакция? Часть 5

Узнать публичный ключ не составляет труда, если с аккаунта была хоть раз отправленная какая-либо транзакция, так как raw tx hex уже содержит всю необходимую информацию. Для этих целей мы использовали библиотеку ethereumjs-tx, а также я делился отдельным сервисом, который все это может делать онлайн.

С приватным ключом дела обстоят сложнее.

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

Разбивая каждую транзакцию на детали (nonce, gasPrice, gasLimit, to, value, data, v, r, s), можно было заметить, что в некоторых из них значения R совпадали. Это могло происходить из-за того, что в формуле случайное число (или nonce) было одинаковым.

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

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

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

#transaction #rlp #ecdsa
👍3
Ролевая система (access control)

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

Как я понял, это разбор примера контракта от open zeppelin, поэтому его безопасность уже будет на хорошем уровне. Кстати, там по ссылке можно найти примеры и других контрактов для управления доступом: AccessControlCrossChain, AccessControlEnumerable и Ownable2Step.

Видео урок

Всем приятного просмотра и легкой пятницы!
👍3
Обзор пройденного

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


Решение задач

Решение задач Ethernaut (1-25)
Поиск по хештегам #ethernaut

Решение задач Damn Vulnerable Defi (1-10)
Поиск по хештегам #dvd #DamnVulnerableDefi

Решение задач Capture The Ethers (1-20)
Поиск по хештегам #capture #cte


Безопасности и оптимизация газа

Безопасность и взлом контрактов v1.0

Подсказки по безопасности (9 постов)
Поиск по хештегам #security #tip #st

Оптимизация газа (18 постов)
Поиск по хештегам #gas #optimization #hint

Уязвимость в struct

Опасность tx.origin

Защити свой Метамаск

Гайд по проверке контракта (pdf)


Работа с памятью

Структура / хранение данных

Динамические массивы и мэппинги в storage


Разбор нюансов

Лезем в опкод! (5 постов)

Из чего состоит транзакция? (5 постов)

Описание Remix Debugger (3 поста)


Другое

Логические побитовые операции

Что такое динамические NFT?

MetaMask Flask and Snaps

Чтение событий в mainnet

Ролевая система (access control)

Если захотите поделиться где-нибудь одним из постов, пожалуйста, оставляйте ссылки на канал.

Приятного обучения.
👍6
Движемся дальше

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

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

В целом, продолжаем совершенствовать наши знания.

Всем приятной недели и легкого обучения!
Мини задача

И сразу интересный пример из Твиттера.

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

Небольшая подсказка: тут потребуется знания стандарта ERC20.

#security #erc20
👍1
Как создать адрес в сети Эфир на js

Попалась интересная статья о том, как с помощью ethers создать валидный адрес с приватным ключом, который потом можно импортировать, например, в Метамаск.

Давайте для начала разберемся, как вообще создается подобный адрес.

1. В начале необходимо сгенерировать приватный ключ 64 hex (256 бит / 32 байта).

2. Затем с помощью ECDSA и нашего приватного ключа создается публичный ключ (128 hex, 64 байта).

3. В конце, к нашему публичному ключу применяется keccak256, из чего получается строка в 64 символа (32 байта). К последним 40 символов (20 байт) этой строки добавляется префикс "0х" и получается адрес.

Для того чтобы сгенерировать адрес на JavaScript нам потребуется установка node и библиотеки ethers.

Как обычно перед стартом проекта мы заходим в нужную папку, открываем из нее консоль и прописываем npm init. Далее создаем файл, например address.js.

В файле пишем код (можно скопипастить этот):

var ethers = require('ethers'); 
var crypto = require('crypto');

var id = crypto.randomBytes(32).toString('hex');
var privateKey = "0x"+id;
console.log("SAVE BUT DO NOT SHARE THIS:", privateKey);

var wallet = new ethers.Wallet(privateKey);
console.log("Address: " + wallet.address

Сохраняем изменения в файле и прописываем в консоли node address.

Если появилась информация с приватным ключом и адресом, то все ок. В противном случае, решfем ошибки.

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

#address #metamask #ethers
👍4
Два расширения для браузера

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

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

Довольно удобная штука.

Как установить?

Идете по этой ссылке и скачиваете репозиторий. Затем распаковываете его на свой компьютер.

Далее заходите в браузер (в моем случае Chrome), в правом верхнем углу в меню выбираете пункт Дополнительные инструменты -> Расширения,и уже там кликаете на Загрузить распакованное расширение. Откроется окошко с выбором папки.

После всех манипуляций, у вас будет два расширения: для проверки адресов, и для проверки транзакций.

#extentions #tools #toolbox
👍1
А что вы думаете о будущем web3?

Этот топик совсем не по теме канала, но меня немного бомбит со вчерашнего дня.

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

Вообще мне было обидно, что в моем кругу есть настолько узкомыслящие люди, ограничивающие web3 и блокчейн только игрой на бирже и криптой, как таковой. Мои доводы, что текущая "крипто зима" никак не отразилась, например, на IPFS, Chainlink, Immunify и других компаниях не были приняты всерьез.

Вот мне и захотелось спросить на канале, а верите ли вы сами в будущее web3, в то, что данная профессия будет востребована?
👍5