Solidity. Смарт контракты и аудит
2.63K subscribers
246 photos
7 videos
18 files
555 links
Обучение Solidity. Уроки, аудит, разбор кода и популярных сервисов
Download Telegram
Chainlink Connect to API. Часть 1

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

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

Как это все работает на chainlink?

Сейчас есть два способа работы с API:

1) Создать свой external adapter (далее Внешний Адаптер), куда определить нужное вам API. И уже оттуда получать информацию в свой смарт контракт;

2) Использовать доступные Адаптеры на узлах Chainlink, которые, при подключении вашего контракта, могут выдать информацию, на которую они преднастроены. Другими словами, вам нужно узнать узел, понять, что он отдает и нужно ли это, и тогда подключать его.

К слову сказать, раньше там был поиск по, так называемым, шаблонам работ на различных узлах. Вы могли в своем контракте прописать любую ссылку API стороннего ресурса и отправить ее на выполнение запроса на понравившейся узел. Так... это сложно звучит... Попробую схематически объяснить.

У вас есть смарт контракт, и вы хотите получать в него текущий курс биткойна. Далее вам нужно найти сторонний ресурс, например, биржу обмена валют, у которого на сайте есть API.

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

Как было раньше:

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

Как сейчас:

Первый вариант.

Лавочку прикрыли. На маркете теперь есть официальные узлы, которые могут предоставить вам данные с API, которые уже установлены в них. Доступные узлы и информацию можно посмотреть тут.

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

Я зашел на этот канал, но выглядит он как-то глухо. Не, люди там есть, но в основном просто висят вопросы или новости.

Второй вариант.

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

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

#chainlink #api #adapter
Chainlink Connect to API. Часть 2

Для начала разберем несколько HTTP Get запросов, которые представлены в chainlink. В некотором роде, разделение запросов на различные типы служат еще и для экономии газа транзакций, так как в получаемом ответе содержится только конкретная информация.

Single Word Response

Результатом такого запроса будет всего одна единица данных: одно число uint или int, булево значение, строка или bytes32. Например, как в этой ссылке мы получаем только значение USD числом.

Multi-Variable Responses

Результатом этого запроса будет несколько значений одной единицы данных. Представьте себе, как если бы в рамках одного контракта и одной функции мы отправляли бы несколько Single Word Response.

Array Response

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

Large Responses

В этом случае мы сможем получить большие по объему данные, в рамках блокчейна, как например, такой.

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

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

Далее перейдем к коду.

#chainlink #api #adapter
Chainlink Connect to API. Часть 3

Пройдемся по коду, представленному в актуальной документации chainlink.

В целом код у всех видов запросов очень похож.

В самом начале подключаются два контракта, от которых наследуется наш:

import '@chainlink/contracts/src/v0.8/ChainlinkClient.sol';
import '
@chainlink/contracts/src/v0.8/ConfirmedOwner.sol';

Далее подключается библиотека для запросов:

using Chainlink for Chainlink.Request;

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

Далее в конструкторе устанавливается владелец, а также передаются контракты токена, оракула, jobId и fee.

Fee - это стоимость выполнения запроса адаптером.
JobId - это уникальный идентификатор узла, по которому он выполняет данный запрос.

Позже я покажу, где их можно найти, и как они появляются.

Так же тут есть одинаковая для всех функция withdrawLink() для возврата неиспользованных Link.

Переходим к интересному.

Функция fulfill(). Она вызывается извне, когда адаптер готов прислать нам ответ на запрос. Типа как fallback функция. Она может принимать различные аргументы на входе для фиксирования событий (event). И возвращает результат запроса в нужном типе данных: uint, int, bool, bytes или string.

Далее функция request(). Более точное название меняется от контракта к контракту, но это скорее сделано, чтобы назвать функцию в зависимости от данных, которая она будет запрашивать.

Идем построчно.

Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);

Создаем временную переменную на основе ранее подключенной библиотеки, куда передаем jobId, адрес нашего контракта, а также this.funcName.selector - где funcName - это имя callback функции, которая будет принимать ответ в нашем контракте.

В данных примерах это fulfil, а так может быть любой, как, например, fulfilResult.

req.add('get', 'httpLink');

Здесь указывается API ссылка.

req.add('path', 'name');

Далее указываем ключ в json, значение которого мы хотим получить. Например, в "image: 'http://'", image - это ключ.

sendChainlinkRequest(req, fee);

В конце вызываем функцию из подключенного контракта, куда передаем параметры запроса и fee.

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

#chainlink #api #adapter
Chainlink Connect to API. Часть 4

Давайте для начала поговорим о предустановленных API на узлах.

Я уже ранее давал ссылку на маркет chainlink, а теперь поговорим чуть конкретнее и возьмем, к примеру, узел Tiingo.

Это сторонний сервис, предоставляющих информацию о крипто финансах.

Перейдя по ссылке, и открыв вкладку "Integrations", вы найдете всю необходимую информацию по подключению: ссылку на контракт токена, оракула и jobid.

Именно эти параметры и нужно указывать в своем контракте в конструкторе.

При этом, в функции request() уже не нужно давать API ссылку, так как она вшита в Адаптер узла.

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

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

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

#chainlink #api #adapter
Chainlink Connect to API. Часть 5

Давайте, наконец, рассмотрим процесс создания своего собственного Внешнего Адаптера. Напомню, с помощью него можно подключать API с любого сайта и передавать данные в смарт контракт.

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

Поэтому тут я просто разберу код.

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

git clone https://github.com/thodges-gh/CL-EA-NodeJS-Template.git ExternalAdapterProject

Chainlink специально создал этот шаблон, чтобы пользователям было легче создавать свои адаптеры. Использование данного репозитария бесплатное.

После это, на всякий случай, выполняем команду "npm install" для загрузки пакетов для работы проекта. Мало ли, их не окажется у вас.

После этого проект будет готов к работе.

По сути, мы будем редактировать только два файла: index.js и .env. Остальные могут потребоваться для настройки работы с узлом.

#chainlink #api #adapter
Chainlink Connect to API. Часть 6

Открываем index.js.

const { Requester, Validator } = require('@chainlink/external-adapter')

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

Дальше в customError вы можете настроить обработку пользовательских ошибок, но можно и оставить без изменений.

В customParams мы записываем все параметры, которые можно будет запрашивать в смарт контракте.

Например в шаблоне есть строка:

base: ['base', 'from', 'coin']

которую можно заменить на

city: ['city', 'town']

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

req.add("city", "boston");

Так же тут можно указать необязательные параметры, которые, возможно, будут в API ссылке, как, например, тут:

https://api.openweathermap.org/data/2.5/weather?q=<CITY_NAME>&appid=<YOUR_API_KEY>

где weather - и есть endpoint.

Endpoint, грубо говоря, это некая точка запроса на стороне сервера, которая выдает определенные параметры. В случае с weather она может отдавать температуру в int, а, например, с storm - есть ли угроза торнадо в регионе в булевом значении.

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

Далее идет функция создания запроса createRequest. И тут обратим внимание на код внутри:

const validator = new Validator(callback, input, customParams)

создает объект Валидатора, куда передается callback (об этом позже), input от пользователя в контракте, параметры, которые мы прописали выше.

const jobRunID = validator.validated.id

Здесь функция получает jobId. Если забыли, что это такое, то прочитайте посты выше.

const endpoint = validator.validated.data.endpoint || 'price'

еще раз проверяем endpoint и через символ "||" указываем значение по умолчанию,

const url = `https://min-api.cryptocompare.com/data/${endpoint}`

и передаем ссылку API.

const appid = prosess.anv.API_KEY;

Некоторые API для подключения требуют регистрации на сервисе и получения уникального ключа доступа API_KEY, его можно хранить, как переменную среды в файле .env.

В конце мы создаем переменную, куда помещаем данные о параметрах запроса, в нашем случае это city:

const cityData = validator.validated.data.city.toUpperCase()

Приведение к одному регистру нужно для дополнительной корректности передачи значений.

Далее в const params мы передаем cityData и appid.

Переменная const config будет работать типа axios, это такой способ передачи данный через url. И тут не нужно указывать дополнительные значения типа "get" или "headers".

После подготовки запроса мы создаем функции отправки запроса - Requester.request(), где в качестве аргументов передаются config и customErros.

response.data.result = Requester.validateResultNumber(response.data, ["path", "data"])

В этой строке особое внимание заслуживает ["path", "data"] - где определяется путь в json файле по ссылке до нужной нам информации. Например:

Из файла {"region": {"city":"boston"} - нужно будет передать ['region', 'city']

И затем вызывается callback функция:

callback(response.status, Requester.success(jobRunID, response))

В принципе, это все. Дальше код нам не интересен, так как он выполняет скорее служебные функции.

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

#chainlink #api #adapter
Chainlink NodeJS IPFS External Adapter

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

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

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

#chainlink #api #ipfs #adapter
Solodit запускает свое API

Самый популярный сайт для поиска уязвимостей по отчетам запускает свое API для тех, кто создает ботов и ИИ агентов по анализу смарт контрактов. Довольно горячая тема на сегодняшний день, поэтому команда прекрасно попадает в тренды запросов. По словам разработчика из команды, за первый день они собрали уже более 400 заявок на закрытое бета тестирование!

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

Во-первых, качество отчетов. На текущий момент на Solodit я нашел всего 43 486 результатов:

1. High - 6859 results
2. Medium - 12263 results
3. Low - 21032 results
4. Gas - 3332 results

Заметьте, что Low составляет большую часть. С уверенностью могу сказать, что половина Medium - будет либо повторами, либо мусором (вспомните хотя бы Medium уязвимости, которые были таковыми в 2023 году и сейчас уже в статусе QA), а также около четверти от High - (повторы и "натянутые" Medium, от аудиторов, которые хотели доказать свою значимость). В итого остается около 10 000 адекватных отчетов. Можно также смело убрать из них половину тех, где будет не качественное описание уязвимостей, без примеров кода и POC.

Я уже делал небольшой обзор на эту проблему в этом посте: https://xn--r1a.website/solidityset/1482

Во-вторых, проблема поиска. Как они решили проблему поиска и ранжирования уязвимостей? Да, понятно можно поискать по ключевым словам: reentrancy, replay, defi. В результате будет определенное количество уязвимостей. И да, мы можем проранжировать их через опцию High/Medium/Low, отсеяв, например, последние два. Но тогда вероятные релевантные баги из Medium будут отброшены! А это все может стать хорошей подсказкой к поиску других багов в протоколе, который вы аудируете сейчас.

В-третьих, доступы по API. С большой долей уверенности скажу, что будут немного бесплатных запросов в месяц для всех пользователей, и увеличенные лимиты - для платных подписчиков. Делать все открытым и бесплатным - значит обречь себя не нескончаемый поток запросов, каких может быть тысячи в секунду! А это большая нагрузка и на сервер и на ожидание других пользователей. Остается вопрос цены. Не думаю, что будет большой, но все же.

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

А вы будете пользоваться новым сервисом API для поиска багов? Как бы вы решили эти проблемы?

#api #bugs
👍5🔥1