Solidity. Смарт контракты и аудит – Telegram
Solidity. Смарт контракты и аудит
2.62K subscribers
246 photos
7 videos
18 files
547 links
Обучение Solidity. Уроки, аудит, разбор кода и популярных сервисов
Download Telegram
Чтение событий в 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
Задача на день

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

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

Решение

Посмотрите на модификатор и его условия вначале. Затем на функцию statcall(). Вспомните, как можно передать функцию в низкоуровневых вызовах.

#task