Всем привет! Меня зовут Павел Найданов. Начинал фронтенд-разработчиком, но вот уже несколько лет, как сменил специализацию: теперь работаю в отделе Solidity-разработки компании MetaLamp. На протяжении нескольких лет мы вместе с коллегами изучаем различные аспекты разработки DeFi приложений и собираем наши наработки и инсайты в статьи, которые помогут новичкам быстрее понять, как устроен web3.
Перед вами третий материал, который мы подготовили в рамках цикла статей о децентрализованных финансах. Поскольку DeFi занимают одно из ключевых мест в мире web3, рекомендую начать погружение в предметную область именно с изучения подобных материалов. А если вам нужен путеводитель по миру web3, можно совершенно бесплатно воспользоваться нашей картой развития, выложенной на GitHub: так изучение новой предметной области будет более системным и продуктивным.
В этой статье я расскажу про несколько важных аспектов в работе DeFi приложений. Рассмотрим на примерах:
проблему ценообразования при выполнении транзакций;
опасность потери прибыли для поставщиков ликвидности;
виды атак при выполнении транзакций
Знание этих вопросов поможет начинающим web3 разработчикам понять, с какими вызовами придётся столкнуться при работе с реальными проектами. Да и просто пользователям пригодится знание нюансов работы DeFi приложений.
Price slippage
Во время обмена токенов обычно происходит следующая история. Между ожидаемой ценой перед совершением транзакции и фактической ценой после ее выполнения есть разница. Эту разницу мы называем проскальзыванием цены (slippage).
Неопытного пользователя может обескуражить это несовпадение. Но факт остаётся фактом: такая ситуация почти всегда сопутствует любому обмену токенов в DEX на базе AMM.
При совершении сделки в децентрализованном обменнике ожидаемая цена может заметно отличаться от реальной. Так происходит потому, что ожидаемая цена зависит от прошлого состояния блокчейна. А оно может успеть измениться между созданием транзакций и их выполнением.
Пример проскальзывания цены
Предположим, у нас есть децентрализованный обменник, основанный на уже знакомом принципе AMM. В этом обменнике поставщики ликвидности создали ликвидную пару.
Представим, что Алиса и Боб хотят купить 1 ETH за USDT в нашем импровизированном обменнике. Стартовая цена 1 ETH на момент обмена — 1 400 USDT. Соответственно, и Алиса, и Боб отправляют в обменник 1 400 USDT.
Поскольку обменник децентрализованный и работает на основе блокчейн-технологии, то операции проводятся атомарно. Из-за пошагового выполнения операций, транзакция Боба совершается раньше транзакции Алисы. Но транзакция не просто выполнилась раньше, она ещё и повлияла на цену ETH – он подорожал. После покупки Боба цена ETH подросла относительно его цены до момента покупки. Все по правилам AMM.
Как и почему это произошло? Цена актива зависит от соотношения количества токенов в пуле ликвидности. Если одного токена становится меньше, то его цена незначительно возрастает относительно другого токена.
Получается, что на момент исполнения транзакции Алисы, цена ETH уже изменилась и стала равна 1540 USDT. Но так как Алиса отправила USDT меньше, то эфира она получит тоже меньше. Другими словами, эфир стал для неё дороже. Это и есть то самое проскальзывание фактической цены относительно ожидаемой.
Важно! Проскальзывание часто выражается в процентах, которые указывают, насколько может изменится цена токена.
Фактическая цена обмена (то количество токена, которое ты получишь в результате) зависит от двух факторов:
1) объёма токенов в ликвидной паре,
2) порядка транзакций на обмен в этой паре.
Итак, на стоимость обмена будет влиять место твоей транзакции в очереди обменов среди других пользователей и количество ликвидности в пуле.
Почему возникает slippage?
Пример Алисы и Боба позволяет нам выделить две основные причины проскальзывания: ликвидность и волатильность.
-
Волатильность – это изменчивость стоимости актива за определенный промежуток времени. Любой токен подвержен изменчивости.
Чем больше цена токена подвержена изменению, тем чаще будет проявляться проскальзывание в промежутке между началом сделки и её реальным исполнением. Причиной такого несовпадения становятся колебания цены, возникающие в ходе выполнения заявки из-за обменов одних токенов на другие, совершаемых другими пользователями.
Каждая покупка токена увеличивает его стоимость, поскольку его остаётся меньше в пуле. Каждая продажа токена уменьшает его стоимость, так как его в пуле станет больше.
Обычно волатильность считают в процентах за определённый промежуток времени: год, месяц или день. Волатильность токена измеряют отклонением oт среднего значения стоимости. Чeм выше отклонение, тeм выше уровень волатильности. Получается, чем больше меняется цена токена за промежуток времени, тем больше токен волатилен.
На изображении ниже представлены три вида волатильности цены токена на временном таймлайне.
Ликвидность – способность токенов легко конвертироваться в другие активы по рыночной цене. Некоторые токены торгуются реже из-за низкой популярности или новизны по сравнению с другими токенами. Это значит, что в пулах недостаточно ликвидности для поддержания стабильной цены, поэтому покупка большого количества токенов может существенно повлиять на его текущую стоимость.
Давай предположим, что мы хотим приобрести 5 ETH.
Левый график. Ликвидность низкая. В пуле осталось 10 ETH. График показывает значительное смещение по кривой соотношения количества токенов. Покупка всего 5 ETHсильно изменит стоимость оставшихся 5 ETH в пуле. Согласно математике AMM, опустошить пул невозможно. Просто оставшиеся эфиры будут стоить очень и очень дорого.
Правый график. Ликвидность высокая. В пуле — 1000 токенов. Согласно графику видно, что покупка 5 ETH окажет гораздо меньшее влияние на количество эфира в пуле. Соответственно, стоимость ETH поменяется незначительно. То есть цена изменится меньше, если кто-то нас опередит и тоже купит 5 ETH.
Обратите внимание: чем больше ликвидности в пуле, тем меньше риска проскальзывания цены. Ожидаемая стоимость токена будет практически совпадать с фактической.
Как Uniswap демонстрирует slippage?
С проскальзыванием работают следующие закономерности:
Чем больше токенов участвует в сделке, тем больше она влияет на цену.
Чем меньше токенов в пуле ликвидности, тем больше проскальзывание.
Увидеть эти зависимости на практике можно, если поиграть с параметрами сделок в приложении Uniswap. Можно выбрать в приложении токены для обмена и попробовать изменять их количество, которое готов отдать в пул ликвидности.
В дополнительных опциях свопа есть параметр "Price impact". Он наглядно показывает влияние цены сделки на стоимость покупаемого токена.
Посмотрим, что будет, если взять какой-нибудь токен с очень ограниченным пулом ликвидности. Попробуем обменять токен USDT на ASM – токен, который поддерживает платформу Assemble.
В модальном окне свопа Uniswap заблаговременно предупредит о том, что сделка окажет критическое влияние на цену токена. Изменение цены превысит 91%, что нарушит баланс токенов в пуле ликвидности. В данном случае это сигнал того, что мы выкупим большую часть ликвидности.
О плюсах проскальзывания
Проскальзывание — это изменение цены токена в момент сделки. И интересно то, что оно может быть не только отрицательным.
Ожидаемая цена может измениться в обе стороны: быть ниже или выше ожидаемой. Соответственно и проскальзывания бывают двух видов: положительные и отрицательные.
Если фактическая цена исполнения ниже, чем ожидаемая цена сделки на покупку, это считается положительным проскальзыванием, поскольку даёт нам более выгодную цену, чем мы изначально предполагали.
Если фактическая цена исполнения выше, чем ожидаемая цена сделки на покупку, это считается отрицательным проскальзыванием, поскольку дает нам менее выгодную цену, чем мы изначально ожидали.
Это работает как для покупки, так и для продажи токена.
Как Uniswap работает со slippage?
На некоторых обменниках можно в ручном режиме установить величину проскальзывания (0.5%, 1%, 5%). Так, децентрализованный обменник Uniswap в настройках свопа дает возможность указать slippage.
Эта величина напрямую влияет на время, необходимое для обмена токенами. Если установить низкий уровень проскальзывания, обмен может занять много времени и даже вообще не выполниться. Если установить слишком высокое значение, другой пользователь или бот может увидеть нашу отложенную сделку и опередить нас.
Для пользователя это является потенциальной проблемой или даже угрозой.
Для предупреждения потенциальных проблем, Uniswap прямо в интерфейсе подсказывает пользователю, что может произойти во время обмена. На скриншоте с сайта Uniswap ниже приведён пример установленного низкого проскальзывания.
На следующем скриншоте я выставил высокое проскальзывание и Uniswap предупредил меня о потенциальной угрозе frontrun.
Боты могут опередить транзакции пользователя и тем самым повлиять на фактическую цену сделки. Для пользователя это означает дополнительные издержки. Именно так работает frontrun — одна из самых распространённых атак ботов, о которой мы поговорим позже.
Impermanent loss
Любой пул ликвидности полагается на поставщиков ликвидности, которые уменьшают волатильность токенов. Интерес поставщика заключается в получении вознаграждения за предоставление своих токенов в пул. Гарантированная награда, что может быть лучше? Но не всё так просто.
Когда мы предоставляем ликвидность в пул, цена внесённого актива может измениться. Это временное несоответствие между текущей ценой токенов в пуле и рыночной и называется непостоянными потерями. Эти потери возможные и незафиксированные. Постоянными и зафиксированными они станут только в момент вывода средств из пула.
Проще говоря, чем сильнее изменяется цена актива, тем большим потерям может подвергнуться пользователь, если решит забрать средства. Непостоянные потери – это та разница в прибыли, которую он недополучает из-за того, что предоставил токены в пул, а не хранит на кошельке.
Пример возникновения непостоянных потерь
Условие: Предположим, что я хочу стать поставщиком ликвидности для пула токенов USDT/ETH. Договоримся на берегу:
добавить токены нам будет необходимо в соотношении 1:1
цена 1 ETH равна 100 USDT
договоримся, что 1 ETH равен 100$
всего в пуле ликвидности на данный момент 10 ETH и 1000 USDT
Процесс:
-
Я вношу в пул ликвидности 1 ETH и 100 USDT.
Согласно договорённостям, стоимость 1 ETH и 100 USDT, внесённых в пул ликвидности, будет равна 200$.
Высчитаем нашу долю внесённых токенов относительно всех токенов пула и получим 10%. Это значит, что мы будем получать 10% комиссий за обмены из нашего пула.
Вспомним про механизм AMM с постоянным уравнением: X * Y = K, где X — количество ETH, Y — количество USDT, K — постоянное значение.
В данном случае 10 ETH * 1000 USDT = 10 000. Это значение должно оставаться постоянным до и после любых обменов в пуле.
-
Стоимость 1 ETH поднимается до 400$.
Да, это произошло, но соотношение токенов в пуле также говорит, что цена эфира — 100 USDT. Арбитражные боты воспользуются этой возможностью. Они выявляют расхождения в ценах на разных обменниках и организуют процесс «купить в одном месте дешевле, продать в другом месте дороже». Прибыль кладут себе в карман. Подробнее об арбитражных ботах мы поговорим чуть позже.
-
После работы ботов в пуле останется 5 ETH и 2000 USDT.
Таким образом, сохранится наша постоянная K, которая равна 10 000. Также нивелируется разница в стоимости 1 ETH в пуле и за пределами обменника.
-
А теперь пришло время забрать 10% вложенной нами ликвидности.
Это 0,5 ETH и 200 USDT. В долларовом эквиваленте это — 400$. В два раза больше первоначально вложенной суммы! Также не стоит забывать про комиссионные сборы с пользователей за использование нашего пула ликвидности для обмена токенами. Предположим, что наше вознаграждение составило 20$.
Мы заработали 220$ в чистом виде! Приятно? Безусловно!
Однако, если бы мы просто держали свои ETH и USDT на кошельке, у нас был бы 1 ETH стоимостью 400$ и 100 USDT стоимостью 100$. Вместе это — 500$.
Разница между возможной стоимостью в 500$ и полученной стоимостью в 420$ и есть те самые непостоянные потери.
Заметим, что потери непостоянны и не зафиксированы, пока мы не выведем токены из пула. К тому же, комиссионные выплаты должны частично или полностью свести на нет потери, а в идеале даже вывести нас в плюс.
А есть ли способ избежать непостоянных потерь?
К сожалению, поставщик ликвидности не может полностью избежать непостоянных потерь. Но можно снизить риск, если использовать пулы со стейблкоинами и менее волатильными токенами.
Для оценки непостоянных потерь при изменении цены токенов можно использовать графическое представление.
Согласно графику мы можем сделать вывод, что:
1,25-кратное изменение цены — убыток 0,6%
1,50-кратное изменение цены — убыток 2,0%
1,75-кратное изменение цены — убыток 3,8%
2-кратное изменение цены — убыток 5,7%
3-кратное изменение цены — убыток 13,4%
4-кратное изменение цены — убыток 20,0%
5-кратное изменение цены — убыток 25,5%
Чтобы быстро и удобно считать непостоянные потери ты можешь воспользоваться специальным сервисом. Попробуй этот калькулятор непостоянных потерь. Любой из подобных сервисов предложит ввести стоимость токенов на момент добавления ликвидности в пул и на момент вывода.
Arbitrage. Mev. Gas auction
Давайте разберёмся с арбитражем и распространенными атаками ботов.
Цены на один и тот же актив могут различаться на разных обменниках. Когда мы покупаем токены на одном обменнике и продаём их на другом с целью получения выгоды — это и есть арбитраж.
На самом деле, это не просто концепция «купи дешевле, продай дороже», а образ мышления, направленный на оценку ценности токена и спроса на него. Чтобы получить прибыль, очень часто нужно выстроить сложную цепочку сделок.
Не стоит забывать, что пользователь всегда может понести убытки, если цена токена упала в момент сделки. Заработать на арбитраже в ручном режиме сложно, поэтому процесс автоматизируется программно.
Арбитражные боты влияют на рыночные цены и на других участников рынка. Они уравновешивают курсы на обмен токенов по всему рынку, повышая его общую эффективность. Это благоприятно отражается на пользователях. В этом плане, арбитраж полезен, так как увеличивает объём торговли и доход от комиссии.
Но не все боты полезны или хотя бы безвредны. Существует целый ряд ботов, которые в стремлении получить прибыль могут оказывать отрицательный эффект на работу DEX.
Чем опасны боты?
Чтобы понять, как работают боты-злоумышленники, необходимо понимать устройство блокчейна и работу майнеров.
Отправленная транзакция в блокчейн исполняется не сразу. Она попадает в mempool или memory pool. Это такая небольшая база данных неподтверждённых или ожидающих выполнения транзакций. Когда транзакция подтверждается включением в блок, она удаляется из mempool.
Майнеры для составления блока выбирают транзакции из mempool на основе наибольшей выгоды, которую они могут получить. Для этого они могут включать, исключать и менять порядок транзакций на своё усмотрение в создаваемом блоке. Процесс извлечения такой прибыли называется Miner Extractable Value (MEV).
Вместе с майнерами за mempool следят и боты, так как содержимое mempool видно всем (специфика блокчейна). Боты могут успевать совершать собственные транзакции на опережение или после исполнения целевой транзакции.
Например, видя целевую транзакцию о пополнение пула ликвидности значительной суммой токенов, которая повлияет на увеличение цены, бот-злоумышленник может успеть:
Провести покупку токенов по маленькой цене.
Выждать транзакцию на ожидание.
Продать токены по новой выросшей цене.
Провести свою транзакцию бот может путём искусственного повышения цены за газ. Из-за этого майнер будет брать из mempool эту транзакцию в первую очередь – из-за желания извлечь максимальный MEV.
Самое интересное здесь то, что ботов-злоумышленников может быть несколько. И тогда между ними начинается конкуренция по предложению наибольшей цены за газ. Такое явление называется войной за газ. Минус явления: такая война увеличивает комиссию для обычных пользователей.
Стоимость обмена токенов в DEX на AMM зависит от порядка транзакций. А этим порядком можно манипулировать, если предлагать большую плату за газ. Боты стараются направить свою транзакцию в точно определённое место среди других транзакций в ожидании. Таким образом создаются атаки, которые эксплуатируют майнеров.
Рассмотрим самые распространённые атаки ботов:
-
Front-running
Бот наблюдает за транзакциями и выбирает подходящую, которая принесёт ему прибыль. Он инициирует конкурирующую транзакцию с более высокой ценой за газ и ожидает, что его транзакция будет подтверждена раньше транзакции-жертвы.
Например, Алиса хочет приобрести много токенов. Это приведёт к повышению цены, так как токена останется в обороте меньше, а спрос на него останется прежним. Боту выгодно купить токен раньше Алисы. Бот это понимает и проводит атаку типа front-running.
Согласно схеме срабатывает следующий сценарий:
Алиса инициирует покупку токена и её транзакция попадает в mempool.
Бот видит транзакцию Алисы и применяет на ней свою стратегию заработка. Стратегия заключается в поиске транзакций, которые могут существенно повлиять на стоимость токена. После нахождения такой транзакции, бот должен первым купить токен по ещё не изменившейся цене, чтобы удержать её или продать дороже в будущем.
Транзакция Алисы полностью подходит для бота. Он может на ней заработать, если купит токены раньше. Тогда бот начинает действовать. Он инициирует свою транзакцию на покупку токена, которая также попадает в mempool. Но бот предлагает майнеру цену за газ больше, чем предложила Алиса. Однако, он не предлагает слишком большую цену. Чтобы транзакция выполнилась очень быстро, ему нужно учитывать накладные расходы за свои действия и соблюдать баланс, иначе можно уйти в минус.
Всё это время майнер занимается своими прямыми обязанностями и составляет блок с транзакциями. Действует он, как мы уже говорили, согласно принципу MEV, пытаясь извлечь максимальную прибыль. В mempool огромное количество транзакций. Поэтому всегда есть большая вероятность, что транзакция жертвы ещё не выполнилась и все описанные выше манипуляции бота с транзакциями были успешно проведены.
-
Так как транзакция бота предлагает для майнера больше газа, логично, что он вставляет её в блок раньше транзакции Алисы. Это значит, что транзакция бота будет выполнена раньше транзакции Алисы.
Чем опасна эта атака? Алиса ведь всё равно купит свой токен рано или поздно? Дело в том, что транзакция бота спровоцирует проскальзывание цены. Алиса будет неприятно удивлена, когда увидит разницу между фактической и реальной ценой покупки.
Транзакция бота, также как и любая другая покупка токена, приведёт к повышению его стоимости. А это означает, что Алиса заплатит больше ожидаемого за покупку и получит меньше взамен, так как бот купит токен раньше неё.
-
Back-running
Атака похожа на front-running с той лишь разницей, что бот выполняет собственную транзакцию сразу после целевой транзакции.
Например, бот отслеживает mempool на появление новых пулов ликвидности. Если он находит новый пул, то скупает как можно больше токенов. Однако скупает он не все из них, поскольку у пользователей должна оставаться возможность купить токены.
Затем бот ждёт, пока цена вырастет и другие пользователи начнут покупать токены. В этот момент бот продаёт токены по более высокой цене. Смысл стратегии в том, чтобы как можно раньше купить токен с целью продать его по более высокой цене.
Согласно этой схеме происходит следующий сценарий:
Бот ищет новый, недавно появившийся токен и покупает его. Очень часто цена таких токенов низкая.
Алиса тоже узнаёт про этот токен и желает его купить. Отметим, что между ботом и Алисой есть и другие пользователи, которые купили этот токен. В данном случае Алиса просто является сигналом для бота.
Алиса инициирует покупку токена. Её транзакция попадает в mempool.
Бот видит транзакцию Алисы и применяет на ней свою стратегию заработка. Стратегия заключается в ожидании, когда цена токена достаточно вырастет. Напомню, когда Алиса купит токен, его стоимость вырастет. Это будет существенный рост, так как Алиса состоятельный покупатель и всегда покупает в больших объёмах.
Транзакция Алисы полностью подходит для бота, потому что она поднимет стоимость и бот сможет на ней заработать. Тогда бот инициирует свою транзакцию на продажу токена. Транзакция также попадает в mempool. Но в этот раз бот предлагает майнеру цену за газ меньше, чем предложила Алиса. Слишком маленькую цену бот не выставит, так как ему важно, чтобы транзакция исполнилась максимально близко к транзакции Алисы.
Все это время майнер занимается своими прямыми обязанностями и составляет блок с транзакциями. Поэтому сначала он добавляет в блок транзакцию Алисы на покупку токена, а потом транзакцию бота на продажу токена.
Ты можешь спросить, в чём здесь опасность для Алисы? Ведь она купит раньше бота. Да, раньше, поэтому это не front-running, а back-running. После того, как Алиса купит свои токены, бот продаст свои. Помнишь, что для большей выгоды он скупил большую часть токенов?
Продажа огромного количества токенов может сильно обвалить стоимость нового, не окрепшего токена. А в результате получится, что Алиса будет держать на руках токены, которые сильно упали в цене и превратились в фантики.
Sandwich
Эта атака комбинирует в себе front-running и back-running атаки.
Перейдём сразу к примеру. Алиса хочет купить токен на децентрализованной бирже, которая использует модель автоматизированного маркет-мейкера.
Бот, который видит транзакцию Алисы, создает две собственные транзакции, которые он вставит до и после транзакции Алисы. Первая транзакция бота покупает токен, что увеличивает цену транзакции Алисы, а вторая транзакция — это продаже токена по более высокой цене с прибылью.
Согласно этой схеме происходит следующий сценарий:
Стратегия бота заключается в поиске транзакции, которая повысит стоимость токена достаточно для заработка.
Алиса инициирует покупку токена. Транзакция попадает в mempool.
Бот, который постоянно мониторит mempool, видит транзакцию Алисы и применяет свою стратегию заработка.
Транзакция Алисы полностью подходит для бота: она поднимет стоимость и бот сможет на ней заработать. Тогда бот инициирует две транзакции. Первая на покупку токена — бот выставляет цену газа больше Алисы. Вторая на продажу токена — бот выставляет цену газа меньше Алисы.
Обе транзакции также попадают в mempool.
-
Все это время майнер занимается своими прямыми обязанностями и составляет блок с транзакциями. Из-за правильно предложенных цен за газ первая транзакция бота будет выполнена майнером раньше транзакции Алисы, а вторая после.
В данном случае неприятность для Алисы — это проскальзывание цены. То есть фактическая цена покупки для Алисы будет больше, а токена в таком случае она купит меньше, чем ожидала.
Бот смог заработать на разнице покупки и продажи. Он купил первой транзакцией токен дешевле. Второй транзакцией продал дороже, чем купил. Профит положил себе в карман.
Вывод
На этом все! Мы изучили много особенностей DEX. Почему цена за токены может проскальзывать и как можно на это повлиять, опасности добавления ликвидности в пул, связанные с непостоянной потерей. Отдельно поговорили о том, как работают арбитражеры и какие основные типы атак бывают.
Ты, конечно же, хочешь спросить, для чего мне может понадобится эта теория?
Если ты продолжишь погружение в мир децентрализованных финансов, то тебе придётся решать проблемы, о которых мы говорили выше в своих собственных проектах. А тут, как говорится, предупрежден, значит вооружен! И, кто знает, может быть именно тебе удастся создать децентрализованный обменник без проскальзывания цены, непостоянных потерь и с самой современной защитой от атак ботов.
vassabi
а есть возможность делать отдельный mempool для доверенных участников?
(т.е. чтобы если транзакция прошла - то она была в общем блокчейне, но чтобы исключить ботов. Понятно, что так меньше прибыли для майнера, но если это майнер биржи\торговой площадки - может недополученная прибыль не будет настолько большой?)
pnaydanovgoo Автор
Это в принципе уже частично так и работает. Mempool нельзя рассматривать, как общую буферную зону для всех транзакций перед тем, как они попадут в блокчейн. Каждая нода настраивает свои правила для мемпула. А после попадания транакции в мемпул ноды должна происходить своего рода синхронизация с другими нодами сети. Получается, что может быть некая абстрактная нода, которая первой получила транзакцию в свой mempool и не распространила ее на остальную сеть. Более того все ноды могут иметь значительно отличные мемпулы из-за задержек сети например.
Поэтому, я думаю, технически ограничить доверенных участников внутри сети реально. На сколько это сложно и практикуется ли в таком виде я к сожалению не подскажу. Если кто-то знает пишите в комментарии.
Я думаю, что для подобной задачи, больше подходит приватный блокчейн с доверенными узлами и менее децентрализованным консенсусом. Например, proof of authority. И видимо mempool должен быть закрытым, чтобы боты не могли считывать транзакции на ожидании и успевать манипулировать ценой.