Есть такая платформа блокчейн‑аналитики Dune, с помощью которой можно дергать различные данные из блокчейна. В декабре 2022го в коллаборации с Uniswap они запустили курс 12DaysOfDune, в котором я поучаствовал. Хочу поделиться с вами заданиями первой недели курса, и вариантами решений.

В результате вы узнаете, что представляет собой Dune, научитесь делать аналитику самостоятельно на примере протокола децентрализованного обмена Uniswap. Это протокол децентрализованного обмена и крупнейший сервис для обмена криптовалюты (децентрализованная биржа) с суточным объемом 1,27 млрд. долл (на 2.2.2023). Может быть, кто‑то заинтересуется заданиями второй недели, связанными с ликвидностью.

Dune — по сути, это набор таблиц с данными из блокчейна (поддерживаются Ethereum‑сети и недавно добавили BTC), к которым можно делать запросы на SQL, извлекать данные и строить графики, есть редактор дэшбордов. Таблицы бывают:

  1. Raw (сырые данные) — транзакции, события, трейсы (traces) — самый нижний уровень данных блокчейна.

  2. Decoded (декодированные) — созданные на основе сырых данных для более понятного представления, например uniswap_v3_ethereum.Pair_evt_Swap — таблица с событиями Swap пар Uniswap (то есть можно понять, когда был сделан обмен).

  3. Spellbooks — еще более высокоуровневые таблицы, которые заполняются периодически запускаемыми процессами. Например, это dex.prices, куда высчитываются и помещаются данные по ценам токенам на биржах.

  4. Community (сделанные сообществом) — таблицы от других провайдеров.

  5. User‑generated (пользовательские) — в принципе, любые открытые запросы (queries), созданные на платформе, которые можно использовать в своих запросах.

Квалификация

Чтобы писать запросы в Dune, нужно знать SQL, а также принципы работы EVM и Solidity, а данном случае еще и в Uniswap. Например, нужно понимать, что такое пул Uniswap, а также что есть смарт‑контракт Factory, который эмиттит событие PoolCreated, и какие в событии передаются параметры. Если такого понимания пока нет, то рекомендую почитать 1, 2, 3, 4. Хорошая новость в том, что если у вас есть опыт программирования, то разобраться будет не так сложно.

Задания

Результатом выполнения каждого задания является SQL‑запрос с данными. Фокус курса был на Uniswap V2, но я где только можно старался использовать V3 — более новую, но и более сложную версию. В V2 используется концепция пар обмена (pair) c фиксированной комиссией 0.3% (например, пара WETH/USDC). В V3 используется концепция пулов с разными комиссиями и ликвидностью в кастомных диапазонах. Во всех заданиях (кроме 3го) использую пул USDC/WETH с комиссией 0.05%.

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

День 1 — Посчитайте количество пар, содержащих токены USDC и/или WETH (начинающий).

Идея решения
  1. Получить адреса токенов USDC и WETH через etherscan.

  2. Использовать таблицу uniswap_v2_ethereum.Factory_evt_PairCreated или uniswap_v3_ethereum.Factory_evt_PoolCreated (таблица вызовов событий, событие вызывается при создании пары/пула). В таблице есть колонки token0, token1, сравнить их с адресами из п.1.

  3. Посчитать кол‑во созданных пар/пулов.

Обратите внимание, в V3 пара заменилась на пул, и пулов USDC/WETH м.б. несколько (различаются комиссии), поэтому DISTINCT.

Мое решение для V2 и V3.

День 2 — Постройте понедельный график количества обменов в V3 пуле USDC/WETH и их объем в долларах, разбивка по дням или неделям (начинающий).

Идея решения
  1. Найти адрес пула (можно в гугле или на info.uniswap.org)

  2. Смотрим таблицу uniswap_v3_ethereum.Pair_evt_Swap, куда записаны все успешные обмены (события Swap контракта Pair). Странно, что таблица называется Pair, хотя контракт называется Pool.

  3. Смотрим таблицу uniswap_v3_ethereum.Factory_evt_PoolCreated — получить адреса токенов в пуле.

  4. Смотрим таблицу prices.usd, куда записаны поминутные цены в долларах всех токенов, для чего нужно отбросить секунды функцией date_trunc.

  5. Немного математики — посчитать цену в долларах. Мы берем |amount0|, т.к. это кол‑во token0, добавленного или «вытащенного» из пула, и умножаем на стоимость token0 в долларах на минуту обмена.

  6. Группировка по дням (или неделям).

Мое решение, авторское. В результате получился вот такой красивый график.

Данные начинаются с 1 мая 2021, т.к. в начале мая был задеплоен первый Uniswap V3 SwapRouter.

День 3 — выведите список направлений обмена (TOK_A → TOK_B) и количество раз, когда в них была использована V2 пара WETH/USDC. Вывести первые 100 по количеству. Используйте UniswapV2Router02 (средний уровень).

Обмен может происходить через промежуточную пару. Например, обмен TOK_A → WETH → USDC → TOK_B использует 3 пары, где WETH/USDC — промежуточная. В результате должен появиться вот такая таблица:

Следует читать: при обмене WETH на HEX пара WETH/USDC была использована 28671 раз
Следует читать: при обмене WETH на HEX пара WETH/USDC была использована 28671 раз
Идея решения
  1. Чтобы выполнить обмен токенов, вызывается одна из 9 функций роутера (swapExactTokensForTokens, swapTokensForExactTokens, ...) – полный список в решении.

  2. В каждую из этих функций передается параметр path – массив адресов токенов, через которые пойдет обмен (пример выше).

  3. Запрашиваем таблицы uniswap_v2_ethereum.Router02_call_swapExactTokensForTokens, uniswap_v2_ethereum.Router02_call_swapTokensForExactTokens, ... где в path есть токены USDC,WETH, идущие друг за другом.

  4. Группируем по начальному/конечному токену в path, подсчитываем количество.

  5. Запрашиваем таблицу tokens.erc20, чтобы вывести символы токенов.

Мое, авторское решения. В процессе нашел баг в авторском решении.

День 4 — для V3 пула WETH/USDC определить объем в долларах и количество обменов, сделанных ботами (MEV) и обычными пользователями, вывести по дням (средний).

В результате должна получиться примерно такая картинка:

Идея решения

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

  1. Ищем, кто является выгодоприобретателем обмена через цепочку таблиц uniswap_v3_ethereum.Pair_evt_Swap — ethereum.transactions

  2. Запрашиваем таблицу labels.mev_ethereum, где есть уже размеченные данные от Dune (или etherscan?), какие аккаунты являются MEV‑ботами. Если запись не нашли — то считаем, что это не бот.

  3. Чтобы получить цену, используем цепочку uniswap_v3_ethereum.Pair_evt_Swap — uniswap_v3_ethereum.Factory_evt_PoolCreated — prices.usd

  4. Делаем серию группировок по дням (или неделям). Можно отфильтровать по дате, чтобы запрос был быстрее.

Реальный объем MEV трафика, конечно, больше.

Мое, авторское решения.

День 5 — Для V3 пула WETH/USDC определить объем в долларах и количество обменов, группированных по тому, кто вызвал обмен — кошелёк (EOA — externally owned account) или смарт‑контракт, вывести по неделям или дням (продвинутый).

Идея решения

Способ подсчета объема в долл и количества обменов есть в Дне 2.

Чтобы понять, кто инициировал обмен (EOA или смарт‑контракт) требуется

  1. Найти вызов UniswapV3Pool.swap() в ethereum.traces

  2. Найти, кто вызвал эту функцию (как правило, это другой смарт‑контракт, такой как роутер).

  3. Найти, его вызвал этот смарт‑контракт (EOA или смарт‑контракт) — это и будет группировкой.

Решение:

  1. Смотрим сделанные обмены в uniswap_v3_ethereum.Pair_call_swap

  2. Запрашиваем ethereum.traces (команды, из которых состоит транзакция). Для этого ищем по tx_hash и по trace_address команду, в которой был сделан swap (используем uniswap_v3_ethereum.Pair_call_swap). В результате на шаге 2 мы нашли в ethereum.traces строчку вызова метода UniswapV3Pool.swap() — назовем ее tr_swap.

  3. У каждой команды есть поля from (кто вызвал) и to (кого вызвали). Для найденной на предыдущем шаге команды ищем вызвавшую ее команду, назовем ее tr_swap_caller. Используем поля from, to, trace_address (tr_swap_caller.trace_address будет по длине меньше или равно tr_swap.trace_address). В результате шага 3 поле tr_swap_caller.from содержит искомый источник вызова.

  4. Запрашиваем таблицу ethereum.creation_traces для tr_swap_caller.from, и если такая запись там будет, значит, это смарт‑контракт, иначе, это EOA (кошелек).

  5. Далее запрашиваем prices.usd для цен в долларах.

  6. Бонусом можно подключить таблицу labels.contracts, где находятся имена смарт‑контрактов, по полю address = tr_swap_caller.from. Отсюда можно понять, относится ли контракт к Uniswap, Paraswap, Oneinch и тд (колонка source таблицы labels.contracts)

Мое, авторское решение. Видно, что большинство обменов сделаны кошельками (EOA) (за 2023 год):

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

Очень важная рекомендация — всегда проверять результаты своего запроса тестовым запросом. В данном случае метод swap() вызывает событие Swap() во всех успешных вызовах, и количество записей в uniswap_v3_ethereum.Pair_call_swap uniswap_v3_ethereum.Pair_evt_Swap должно быть одинаково, но как будто это не так.

Подсказки для оптимизации

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

  1. Поиск по хэшам (например, tx_hash) медленный, по возможности нужно включать в условие block_number или block_time (связано с хранением данных в формате паркет, когда в row groups хранится минимальное/максимальное значение block_number, поэтому происходит быстрое отсечение ненужных).

  2. В случае поиска на монотонном интервале, лучше отсекать ненужные данные условием «больше/меньше» (например, block_time >= DATE '2023-01-01' в случае, если вам нужны данные только за 2023 год), что опять же связано с особенностью хранения в паркетном формате.

Выводы

Итак, мы научились получать статистику по Uniswap в Dune Analytics. Если есть желание, можно продолжить изучение дальше, разобрать день 6 и задания со второй недели, связанные с ликвидностью.

Вообще, в целом Dune Analytics показала себя интересной платформой. С другой стороны, часто отваливались запросы — выполняющийся 30 секунд на следующий день мог выполняться 10 минут. Поэтому, если вам нужно написать запрос, смело умножайте оценочное время на пять, на всякий случай. Также возможны ошибки в данных, ресерч по которым может занимать довольно много времени. Связываю это с происходящей в текущей момент миграцией на третью версию движка (Dune SQL), платформа должна стабилизироваться. Хочу пожелать удачи ребятам в этом деле!

P. S. Минутка саморекламы — веду тг‑канал Web3 разработчик. Пишу небольшие заметки о задачах по блокчейну/крипте, которые решаю. Буду рад видеть среди подписчиков!

Комментарии (0)