MEV игра с положительной суммой

Идеальная сделка против реальной сделки

Содержание

  • MEV — это игра с нулевой суммой или, возможно, с отрицательной суммой
  • Carbon — сэндвич MEV
  • Запретный сэндвич: теория MEV-устойчивых транзакций CFMM
  • Оптимальный сэндвич: как эксплуатировать блокчейн-энтузиастов с произвольной точностью
  • Как предотвратить MEV-атаки на AMM

MEV — это игра с нулевой суммой

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

Как люди теряют деньги на одном свопе

Uniswap: предупреждение о влиянии на цену

Когда люди обменивают слишком большие суммы токенов по сравнению с текущей ликвидностью в пуле, они теряют деньги.
Они получают меньше токенов, чем должны были получить. Крупнейшая DEX, Uniswap, имеет это красивое предупреждающее сообщение в своем пользовательском интерфейсе выше, но все же некоторые люди совершают обмен и теряют деньги.

Типичные случаи:

  • Относительно крупный своп токенов с низкой капитализацией (например, 1 ETH на <какую-нибудь дерьмовую монету>)
  • Своп на пул с очень низкой ликвидностью (например, общая ликвидность 1 ETH и 2000 DAI)

Вы можете свести к минимуму это негативное влияние на цену, используя агрегатор DEX, такой как 1inch. Они делят торговлю между несколькими пулами. Тем не менее, некоторые пулы не индексируются на 1inch, и вы все равно можете пострадать от негативного влияния на цену, обменяв крупный токен с низкой капитализацией.

1 дюйм: предупреждение о влиянии на цену

Как пользователи MEV зарабатывают после негативного влияния цены

Как пользователи MEV зарабатывают после негативного влияния цены
Когда кто-то страдает от негативного влияния на цену и теряет деньги, есть возможность заработать деньги сразу после сделки. В этом посте я называю первую транзакцию «trigger tx», а последующие транзакции MEV — «backrun tx».

Вот реальный пример из Arbitrum:

  1. Триггер TX
  2. Обратный ход TX1
  3. Backrun TX2

Пользователь (0x36528721ee15c46f2d24Fb6bfc5b580029749c5a) обменял 52 500 XIRTAM на 0,021535 ETH с помощью универсального маршрутизатора Uniswap и примерно потерял 0,0136 ETH (25,8 доллара США) из-за негативного влияния на цену. В то время как в двух обратных транзакциях первый заработал 0,01228 (23,05 доллара США) ETH, а второй — 0,000425 ETH (0,80 доллара США).

Обычно обмен на Uniswap является хорошей идеей, но когда вы обмениваете токен с низкой капитализацией, вы должны быть осторожны, и я рекомендую вам взглянуть на агрегатор DEX перед обменом.
В этом случае из графика ниже видно, что пулы на Uniswap имели гораздо меньшую ликвидность по сравнению с SushiSwap. Таким образом, этот пользователь не только пострадал от негативного влияния на цену, но и создал возможности для заработка для тех, кто ищет MEV.

размер ликвидности для ETH/XIRTAM

Если бы пользователь совершил обмен на SushiSwap, он получил бы 0,0351 ETH и не создал бы никаких возможностей для пользователей MEV. 1inch, вероятно, предложит этот путь, потому что пулы ETH/XIRTAM uniswap не отображаются на 1inch на момент написания этой статьи.

Или даже пользователь мог бы получить больше, разделив своп на 92% на SushiSwap и 8% на Uniswap. Пользователь получил бы 0,03516457 ETH по этому маршруту, как показано на графике ниже.

Идеальная сделка vs Фактическая сделка

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

Идеальная сделка против реальной сделки

Заключение

Мне просто не нравится, когда люди теряют кучу денег на одном свопе и отдают их поисковикам. Я надеюсь, что этот пост поможет людям понять, как работает MEV и как они могут минимизировать негативное влияние на цену.

Если у вас есть какая-либо торговая стратегия, которую вы хотите попробовать, но не знаете, как ее реализовать, пожалуйста, свяжитесь со мной в Twitter. Я могу помочь вам создать ваших ботов за ~$1,000.

Спасибо, что прочитали?

Ссылки

Carbon — сэндвич MEV

При обсуждении углерода вы часто услышите утверждение, что «на углероде нет МЭВ» или что «углерод устойчив к МЭВ, особенно к сэндвич-атакам». Как обычно, это сложнее, чем позволяют эти простые утверждения, поэтому я хочу обсудить эту тему здесь более подробно. TLDR — типичная стратегия Carbon действительно устойчива к сэндвич-атакам, которые являются наиболее распространенной формой MEV. Однако — не все стратегии таковы. Это, конечно, неудивительно, поскольку стратегии Carbon являются строгим надмножеством позиций Uniswap v3, а позиции Uniswap v3 уязвимы.

Как работает Carbon

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

Каждая стратегия Carbon состоит из двух ордеров, одного ордера на покупку (например, покупка ETH за USDC) и одного ордера на продажу (например, продажа ETH за USDC), и, конечно же, покупка должна происходить по более низкой цене (для владельца стратегии), чем продажа, иначе стратегия будет быстро опустошена рынком. Вот почему углеродную стратегию часто называют стратегией «покупай-дешево-продавай-дорого».

Углеродная «стратегия»: покупай дешево, продавай дорого

Ордера могут быть ордерами с одной ценой, также известными как лимитные ордера (покупка ETH за USDC по цене 2000 USDC за ETH) или диапазонные ордера (то же самое между 2000 и 1800 USDC за ETH, начиная с 2000). Объем ордера всегда определяется продаваемым токеном, и для не слишком широких диапазонов можно аппроксимировать диапазонные ордера более простыми для обоснования наборами ордеров одинакового размера с одной ценой (купить ETH на сумму 10 USDC в 2000 году, 10 в 1999 году, 10 в 1998 году, …, 10 в 1900 году). Стратегия может быть либо любой комбинацией двух противоположных лимитных или диапазонных ордеров, т.е. повторяющейся стратегией, либо она может быть исполнена только один раз либо на покупку, либо на продажу, опять же в диапазоне или лимитном формате, а затем стоп, т.е. быть одноразовой стратегией.

Range Sell Order: Продать (приблизительно) одно и то же количество на каждом тике в диапазоне от 100 до 105

MEV, арбитражи и сэндвич-атаки

Раньше MEV расшифровывалось как «Miner Extractable Value» («Извлекаемая ценность майнера»), но это было несколько неэлегантно изменено на «Maximum Extractable Value», когда люди поняли, что майнеры не единственные, кто может влиять на производство блоков и, в частности, на последовательность транзакций. MEV соответствует ценности, которую игроки в цепочке производства блоков могут извлечь из отправленных транзакций, выбирая, где и когда включать их в блок, и какие другие транзакции включать до или после них.

Возможно, двумя наиболее важными сделками, совместимыми с MEV, в отношении онлайн-трейдинга являются арбитражные снайпы и сэндвич-атаки. Случай с арбитражными бекасами был хорошо описан Дэном Робинсоном в его посте в Dark Forest Medium. Арбитраж (ончейн) по определению является транзакцией, которые могут быть представлены всеми и которые приносят гарантированную прибыль. Справиться с ними сложно, и Carbon не предлагает никакой особой защиты в этом отношении.

Бесплатные сэндвич-атаки

Более интересным случаем являются сэндвич-атаки. Это сродни фронтраннингу на традиционных рынках. Их механика заключается в следующем: предположим, что трейдер отправляет транзакцию на продажу 1 000 TKN за USDC на AMM с постоянным произведением, таком как Uniswap v2 или Bancor. Далее предположим, что это (а) в настоящее время единственная такая транзакция в мемпуле, (б) комиссия не взимается, и (в) текущая цена после проскальзывания равна 100, поэтому трейдер получает 100 000 USDC. Атака-сэндвич будет выполнять эту транзакцию спереди и сзади, что означает, что он (1) продаст большое количество TKN на бирже, затем (2) вставит транзакцию в этот «сэндвич», а затем (3) купит такое же количество TKN обратно.

Сделка на продажу (1) приведет к снижению цены в AMM, и мы предполагаем, что (d) сделка (2) теперь выполняется по цене 90 после проскальзывания, а не 100, поэтому трейдер получает только 90 000 USDC. Поскольку после транзакции (3) AMM находится в точно таком же состоянии, как и после выполнения транзакции без сэндвича, это игра с нулевой суммой, поэтому убыток трейдера в размере 10 000 USDC является прибылью в размере 10 000 USDC от сэндвича.

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

Сэндвич-атаки в присутствии газа

В присутствии газа транзакции (1) и (3) не являются издержками, а вместо этого влекут за собой постоянные затраты, т.е. затраты, не зависящие от объема торгов. Поскольку выигрыш от сэндвич-атаки всегда пропорционален объему, ясно, что атака нежизнеспособна ниже определенного минимального объема. Для стандартного случая с постоянным продуктом этот объем очень мал и, вероятно, будет превышен, но для других случаев, обсуждаемых ниже, он может отличаться.

Сэндвич-атаки с концентрированной ликвидностью

При условии, что у злоумышленника достаточно ликвидных средств — флэш-кредиты здесь не работают, потому что все три транзакции подписаны индивидуально, и кредит может быть возвращен только в транзакции (3) — выигрыш в сценарии постоянного продукта в принципе неограничен: при достаточной ликвидности злоумышленник может подтолкнуть цену к точке, где трейдер продает свой токен (почти) за бесценок, таким образом, злоумышленник получает (почти) всю сумму, которую трейдер отправлял в AMM.

Концентрированная ликвидность AMM исчерпывает ликвидность по конечной и ненулевой цене, поэтому максимальная сумма, которую злоумышленник может внести в соответствии с пунктом (1), такова, что атакуемая транзакция (2) просто не завершается сбоем. Он устанавливает минимальную или максимальную цену исполнения для данной сделки, а следовательно, и максимальную прибыль, которая может быть достигнута с помощью сэндвич-атаки.

Сэндвич-атаки при наличии комиссий

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

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

Сэндвич-атаки на углерод

Углеродные стратегии и сборы

Выше мы обсудили, как работает Carbon, в частности, как работают повторяющиеся стратегии, которые перерабатывают ликвидность между кривыми продажи и покупки. Для простоты мы сначала рассмотрим стратегии, состоящие из одноценовых ордеров, где все покупки и продажи происходят по одной цене. Кроме того, сначала мы рассматриваем довольно узкую стратегию, например, «покупай по 99, продавай по 101» или даже «покупай по 99,9, продавай по 100,1». Понятно, что для трейдера, торгующего против этой стратегии, это сделки «на 100» с комиссией 1% или 0,1% соответственно. Конечно, нет никакой качественной разницы между стратегией 99/101 и стратегией 50/150 или даже 1/199. Математически все они могут быть выражены как средняя цена плюс/минус комиссия — опять же, только для трейдера-тейкера. Таким образом, хотя эти стратегии обычно не рассматриваются как «средние плюс/минус комиссии», математически они таковыми и являются.

Для карбоновых линеек это по-прежнему более или менее справедливо. Если мы придерживаемся интерпретации, что диапазон — это «$1 при 100, $1 при 100.1 и $1 при 100.2» и т.д., то мид и комиссия в некоторой степени зависят от того, как диапазон был исчерпан/заполнен, т.е. мид и комиссия не постоянны, а меняются со временем при изменении состояния стратегии. Это более актуально для более широких диапазонов — например, «покупай 99–89,9, продавай по 100–100,1» — это, по сути, все еще диапазон со средним значением 100 и комиссией около 1%. Подводя итог — представление средней плюс/минус комиссии по-прежнему применяется, за исключением того, что оно меняется со временем в зависимости от состояния стратегии.

Сэндвич-атаки на углерод

Как мы только что видели, углеродные стратегии можно рассматривать как концентрированные AMM ликвидности с, как правило, очень большими комиссиями. Как мы видели выше, для концентрированных позиций ликвидности с большими комиссиями сэндвич-атаки, как правило, нежизнеспособны. Итак, краткая версия такова: типичные диапазоны Carbon не могут быть эффективно атакованы с помощью сэндвич-атак; тем не менее, очень узкие перекрывающиеся диапазоны, которые возможны в Carbon, но не очень «Carbon’y», все еще могут быть восприимчивы к сэндвич-атакам.

Стоит рассмотреть ряд конкретных примеров. Первая — это очень «углеродная» стратегия, скажем, покупка ETH за USDC по цене 1 800–1 500 и продажа обратно по цене 2 200–2 500, и мы предполагаем, что стратегия в настоящее время полностью финансируется в USDC. В этом случае первый USDC, конвертированный в ETH, будет находиться на уровне 1 800, и он будет размещен на кривой, продавая его обратно по цене 2 200. Во-первых, владелец стратегии здесь мейкер, поэтому он вообще не может быть впереди. Они выразили «намерение» покупать и продавать в двух конкретных диапазонах, и это намерение либо будет выполнено, либо нет. Конечно, им может больше не нравиться это намерение — скажем, если ETH упадет до 1 000, то покупка от 1 800 до 1 500 выглядит плохой идеей в ретроспективе — но это ничем не отличается от любого другого торгового решения.

Вопрос, на который мы пытаемся ответить, заключается в том, можно ли зажать тейкеров, торгующих против углеродных стратегий. Тейкеры в приведенной выше модели продают первый доллар ETH по цене 1 800, а последний — по 1 500. Другими словами, их цена становится все хуже, и кто-то, кто их опережает, забирает у них деньги. Проблема заключается в том, что любой, кто опережает их, то есть продает ETH раньше них на шаге (1), должен выкупить этот ETH на шаге (3). В этой ситуации ETH выставляется обратно на продажу по цене 2 200, т.е. с разницей в цене 400 на начальном этапе и 1 000 в конце. Эта разница, которая является издержкой для злоумышленника, слишком велика, чтобы компенсировать увеличение затрат, наложенное на тейкера на шаге 2, и поэтому атака «сэндвич» нежизнеспособна.

Однако, если у нас есть стратегия покупки на уровне 2000–1500 и продажи на уровне 1501–2001 (это означает, что диапазоны покупки и продажи перекрываются, а подразумеваемая комиссия составляет около 5 б..), то это ведет себя очень похоже на диапазон Uniswap v3 в 5 б.., и возможна сэндвич-атака.

Заключение

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

Углеродные стратегии можно математически рассматривать как стратегии AMM с текущей средней ценой и комиссией, при этом цена и комиссия изменяются, когда сделки происходят против стратегии. Для типичных стратегий Carbon эти параметры таковы, что сэндвич-атаки нежизнеспособны. Для других, в частности, для тех, кто имитирует позиции Uniswap v3, которые являются подмножеством стратегий Carbon, сэндвич-атаки, тем не менее, жизнеспособны и, следовательно, возможны.

Изображение на обложке

Запретный сэндвич: теория MEV-устойчивых транзакций CFMM

Знакомство

В своей последней статье я представил основы сэндвич-атаки, с помощью которой эксплойтер может выкачивать ценность из наивного свопа токенов, создавая серию транзакций во время создания блока, которые обрамляют транзакции своей жертвы. Процесс происходит в три этапа: 1) предварительный запуск сделки пользователя, создание модифицированного состояния пула ликвидности с произвольно сниженной котировкой по отношению к токену, который пользователь пытается продать, 2) разрешение на торговлю пользователя по сниженной ставке и 3) обратный запуск предыдущих двух шагов, возвращая пул ликвидности в относительно нормальное состояние. Было продемонстрировано, что весь процесс финансово неотличим от эффективной кражи злоумышленником, за которой следует значительное повышение комиссии за своп в пуле ликвидности перед обменом того, что осталось от токенов пользователя. При желании представленные там формулы, в том числе класс питонов OptimumSandwich , являются благодатной почвой для продолжения самостоятельного изучения. Я завершил статью, предложив читателю вывести математическое описание «не-сэндвичной» сделки. То есть, чтобы алгебраически показать, что для любого набора переменных состояния и пользовательских входных данных, в котором может быть выполнена сэндвич-атака, существует аналогичный набор, в котором изменение не более одного из этих элементов сделает сэндвич-атаку невозможной. Целью данной статьи является описание «не-сэндвичного» множества.

Здесь используются те же соглашения, что и ранее; Чтобы различать входы и выходы, принадлежащие a ttacker и u ser, я продолжу использовать индексы aи uсоответственно. Кроме того, x будет последовательно представлять маркер, отправляемый в пул ликвидности либо злоумышленником, либо пользователем, в то время как y неизменно будет представлять маркер, который передается из пула ликвидности пользователю или злоумышленнику. Строчная греческая буква δ обозначает комиссию за своп пула ликвидности, а прописная форма Δ обозначает количество сделок. Предположим, что x, y, δ, Δ x, Δ y всегда являются положительными действительными числами, где Δ x и Δ y — это количество токенов, которые берутся и добавляются в кошельки пользователя или злоумышленника соответственно (подразумевается, что они добавляются и удаляются из балансов пула ликвидности соответствующим образом). Разница между количеством токенов, полученных злоумышленником от бэкраннинг трейда, и тем же токеном, отправленным в пул ликвидности во время фронтраннинг трейдинга, обозначается буквой Q.

Поломка автомата по продаже сэндвичей

Важнейшая информация из предыдущей статьи заключается в том, что прибыль злоумышленника, Q, оптимальна при точном количестве сделки Δ x ₐ, учитывая резерв токенов пула ликвидности, x, уровень комиссии, δ и количество токенов, которые пользователь пытается обменять, Δ x u. Оптимальная передняя торговая величина, Δxₐ, может быть выражена как один из корней четвертичного многочлена. В то время как предыдущая цель состояла в том, чтобы определить значение Δ x ₐ, рассматривая другие переменные как константы, цель здесь состоит в том, чтобы определить значение для других переменных, x, δ и Δxu, когда оптимальная сделка атакующего равна нулю. Другими словами, какая комбинация состояния пула и входных данных пользователя заставляет злоумышленника решить ничего не делать и оставить транзакцию пользователя в покое? По сравнению с предшественницей этой статьи, эти решения найти заметно проще. Возьмем ранее определенную четверть и установим неопределенное значение Δxₐ равным нулю. Это приводит к тому, что все, кроме постоянного члена (т.е. коэффициента D), исключаются (eqn1).

Несмотря на то, что член x по-прежнему является четвертичным, два его корня тривиальны (см. факторизацию ниже), а два других являются решениями относительно безобидного квадратичного числа. Оба члена Δx uи δ являются кубическими, но, опять же, один корень тривиален, а другой может быть выведен из применения квадратичной формулы. Как и прежде, я представляю только те корни, которые имеют отношение к контексту. То есть, для infimum δ: 0 < δ < 1, для supremum Δ x u: Δ x u > 0 и infimum x: x > 0, где оптимальная сделка атакующего спереди равна Δ xₐ ≤ 0 (уравнения 2–4). Для большей части читателей смысл этих выражений можно понимать следующим образом:

  1. При условии постоянного пула ликвидности продукта с резервом токенов x ETH, где пользователь номинировал обмен Δxu ETH на токен-аналог, минимальная комиссия за своп, которая сводит на нет всю ценность сэндвич-атаки, составляет inf δ (уравнение 2). Таким образом, любая δ  inf δ также сделает невозможной атаку «сэндвича» (рис. 1).
  2. При условии постоянного пула ликвидности продукта с резервом токенов x ETH и комиссией за своп в размере δмаксимальное количество ETH, которое пользователь может обменять, прежде чем раскрыть возможность сэндвич-атаки, составляет sup Δ x u (eqn 3). Следовательно, любые Δ x u≤ sup Δx uтакже сделают невозможной сэндвич-атаку (рис. 2).
  3. При условии постоянного пула ликвидности продукта с комиссией за своп в размере δ, где пользователь номинировал обмен Δ x u ETH на токен-аналог, минимальный резерв токенов ETH в пуле ликвидности постоянного продукта, необходимый для аннулирования сэндвич-атаки, составляет infx(уравнение 4). Таким образом, любые x ≥ inf x также сделают невозможным сэндвич-атаку (рисунок 3).
Рисунок 1: Анализ inf δ по отношению к x и Δxu. В контексте постоянного пула ликвидности продукта, содержащего резерв токенов в размере x ETH, когда пользователь решает обменять Δxu ETH на его эквивалентный токен, самая низкая комиссия за своп, которая делает сэндвич-атаку бесполезной, представлена inf δ (уравнение 2). Комиссия за своп больше или равна inf δ защиты от возможности сэндвич-атаки. Визуализации представляют собой: а) трехмерный (3D) график поверхности слева и б) соответствующую тепловую карту справа.
Рисунок 2: Анализ sup Δxu по отношению к x и δ. При условии постоянного пула ликвидности продукта с резервом токенов x ETH и комиссией за своп в размере δ, максимальная сумма ETH, которую пользователь может обменять, не рискуя подвергнуться сэндвич-атаке, представлена sup Δxu (уравнение 3). Любой Δxu меньше или равен sup Δxu обеспечивает иммунитет от сэндвич-атаки. Визуализации представляют собой: а) трехмерный (3D) график поверхности слева и б) соответствующую тепловую карту справа.
Рисунок 3: Анализ inf x по отношению к δ и Δxu. В пуле ликвидности постоянного продукта, работающем с комиссией за своп в размере δ, когда пользователь решает обменять Δxu ETH на связанный токен-аналог, наименьший резерв токенов ETH, необходимый в пуле ликвидности для нейтрализации потенциала сэндвич-атаки, изображается inf x (уравнение 4). Резервы x, превышающие или равные inf x, гарантируют, что сэндвич-атака неосуществима. Ось inf x представлена в масштабе log10. Визуализации представляют собой: а) трехмерный (3D) график поверхности слева и б) соответствующую тепловую карту справа.

Беглое рассмотрение уравнений 3 и 4 позволяет свести размерности x и Δ x u к одной переменной r =x/ Δx uЭто интуитивное упрощение; Важен не абсолютный размер сделки пользователя, а ее относительный размер по сравнению с резервом токенов ликвидности пула (уравнение 5).

После этого можно определить новые минимальные значения, описывающие сэндвич-устойчивую к атакам торговлю, inf δ и inf r (уравнения 6 и 7).

Исследование сюжета inf δ против r бросает вызов моей интуиции (рис. 4). Очевидно, что для x, Δ xu∈ R+ предел r = ∞ как x → ∞, предел r = 0 как Δ x u → ∞ и предел r = 1 как Δ x u → x. С геометрической точки зрения r = 1 является «серединой» диапазона, так как 0 и ∞ в некотором смысле равноудалены от предела в точках Δ x u →xПока ничего удивительного. Пределы inf δ также тривиальны; Предел inf δ = 0 как r → ∞, предел inf δ = 1 как r → 0, а естественная «середина» диапазона равна δ = 1/2. Я ожидал, что эти средние точки совпадут друг с другом, но этого не произошло. Значение r, соответствующее δ = 1/2, равно r = 1/√3, а значение δ, соответствующее r = 1, равно δ = (9 — √33)/8. В этом факте нет ничего полезного; Я поднимаю его только из любопытства. Однако анализ продолжает приносить свои плоды. Функция, определяющая inf δ также проявляет асимптотически ограничивающее поведение. Функция inf δ асимптотически эквивалентна 1 — √r по мере того, как r становится сколь угодно большим. Оно также асимптотически эквивалентно 2/(2 r + 3), поскольку r становится сколь угодно близким, но большим 0 (уравнения 8 и 9). Для удобства читателя предусмотрен интерактивный сюжет через desmos. Первый (уравнение 8) имеет более практическое значение, так как мы редко ожидаем, что пользователь попытается совершить своп с количеством токенов, превышающим весь резерв пула.

Рисунок 4: Анализ inf δ относительно r, где r = x/Δxu. В контексте постоянного пула ликвидности продукта, содержащего резерв токенов в размере x ETH, когда пользователь решает обменять Δxu ETH на его эквивалентный токен, и где частное x и Δxu обозначается как r, самая низкая комиссия за своп, которая делает сэндвич-атаку бесполезной, представлена inf δ (уравнение 6). Комиссия за своп больше или равна inf δ защиты от возможности сэндвич-атаки. Визуализации представляют собой графики логарифмического масштаба, показывающие взаимосвязь между r и inf δ и демонстрирующие а) ключевые точки пересечения, соответствующие эвристическим средним точкам функциональной области, и б) асимптотическую аппроксимацию inf δ.

Синтез несъедобного бутерброда

Для единообразия в этой демонстрации будут повторно использованы сценарии, представленные в разделах «Ожидаемое поведение» и «Вкусный бутерброд для одного» из предыдущей статьи «Оптимальный сэндвич: как использовать энтузиастов блокчейна с произвольной точностью». Предположим, что существует пул ликвидности с 500 ETH (x) и 1 000 000 USDC (y), что представляет собой общую стоимость около 2 миллионов долларов США, из которых можно сделать вывод о рыночной цене ETH около 2 000 долларов США. Кроме того, предположим, что уровень комиссии пула, δ, зафиксирован на уровне 0,003 (т.е. 0,3% или 30 базисных пунктов) в стандартном случае.

Цель этого раздела состоит в том, чтобы пройти через откровения, вытекающие из предыдущего дискурса. Однако я должен подчеркнуть, что наше путешествие сюда в значительной степени носит схоластический характер. Моя главная цель – улучшить понимание читателем принципов, изложенных выше, а также предыдущей статьи. Не следует упускать из виду существующие методы, такие как minReturn, которые представляют собой достаточно эффективные решения против сэндвич-атак. Я обещаю более подробно рассмотреть критерий minReturn в следующей статье. А пока давайте ограничимся рамками этого раздела, рассмотренными до сих пор. Посмейтесь надо мной.

Во-первых, рассмотрим случай, когда пользователь решает обменять 20 ETH на USDC:

  1. Пользователь наблюдает пул с 500.000000 токенов ETH, x и 1000000.000000 токенов USDC, y и комиссией за своп 0.300000%, δ.
  2. Пользователь выбирает обмен 20,000000 токенов ETH, Δxu, и ожидает получить 38346,153846 токенов USDC, Δyu.
  3. Во-первых, фронт злоумышленника управляет торговлей пользователя, обменивая 681,367696 токенов ETH, Δxₐ, на 575031,461640 токенов USDC, Δyₐ.
  4. Затем сделка пользователя разрешена; пользователь обменивает 20.000000 токенов ETH, Δxu, на 7053.521318 токенов USDC, Δyu.
  5. Наконец, злоумышленник выполняет обе предыдущие сделки, обменивая 575031,461640 токенов USDC, Δxₐ, на 693,644385 токенов ETH, Δyₐ.
  6. Таким образом, злоумышленник извлек из транзакции пользователя в общей сложности 12,276689 токенов ETH, Q.
  7. Общий процесс эквивалентен тому, что пользователь отдает 12,276689 токенов ETH (Q) злоумышленнику, а затем обменивает оставшиеся 7,723311 токенов ETH, Δxu, с пулом.
  8. В дополнение к принесенному в жертву количеству токенов ETH, Q, комиссия пула также, по-видимому, увеличилась с 0,300000% до 53,630805%, δ* (т. е. увеличение на 17776,934872%).
  9. В конце процесса пул ликвидности содержит 507,723311 токенов ETH, x и 992946,478682 токенов USDC, y.
  10. Потери пользователя составляют -81.605662% по отношению к ожидаемому результату.
  11. Максимальная неатакующая сделка на уровне комиссии 0,300000% — это обмен 1,506781 токенов ETH, sup Δxu, чтобы получить 2995,493230 токенов USDC, Δyu.
  12. В качестве альтернативы, если бы уровень комиссии был изменен на 3,773612%, inf δ, пользователь мог бы обменять все 20,000000 токенов ETH, Δxu, на 37010,149326 токенов USDC, Δyu, без риска атаки.
  13. Скорректированный уровень комиссии составляет всего -3,484064% по сравнению с наивным свопом и +424,704579% по сравнению с атакованной транзакцией.

Даже будучи автором этого анализа, я не перестаю удивляться кажущемуся ложному парадоксу, обнаруживаемому при его проведении. На рисунке выше показано, что при установке комиссии пула в 0,3% стоимость выполнения сэндвич-атаки достаточно низка, чтобы позволить эксплойтеру уменьшить начальную стоимость транзакции на отвратительные 81%. Тем не менее, увеличение комиссии пула с 0,300% до 3,774% (inf δ оценивается как x = 500, Δx u= 20) делает транзакцию бесполезной для потенциального эксплойтера, и ценность транзакции улучшается для пользователя на 424,705% по сравнению с ее эксплуатируемой альтернативой. Разница колоссальна, за пределами всего, что, как я подозреваю, можно было бы получить, просто догадываясь. Это служит напоминанием о том, что, хотя эвристика и интуиция играют значительную роль в разработке прочной теоретической основы, наступает момент, когда необходимость выполнения конкретных расчетов становится неизбежной. Приведенные выше данные сведены в таблицу на рисунке 5.

Рисунок 5: Табличные результаты сэндвич-атаки. a) Детали сэндвич-атаки и б) скорректированное количество свопа или настройка комиссии пула, которые предотвратили бы ее возникновение.

Верхний предел неатакуемого обмена токенами, sup Δxu, на первый взгляд может показаться обыденным. И уравнение 3, и рисунок 2 дают полное представление о том, как переменные x и δ влияют на его стоимость, и последующие финансовые последствия кажутся очевидными. Тем не менее, есть еще кое-что, что заслуживает более пристального внимания.

Метод переменной комиссии, использующий динамическое вычисление inf δ, представляет собой головоломку. Приведенный выше пример, в свете почти бесконечного диапазона пользовательского ввода, является неполным. Обращаясь к кривой комиссии на рисунке 4, обратите внимание, что когда значения r ничтожно малы — это означает, что Δ x u значительно превосходит резерв пула ликвидности токена,x— комиссия пула приближается к 100%. По мере того, как r стремится к нулю, как функция inf δ, так и ее асимптотическое приближение стремятся к единице, как показано в уравнениях 6 и 9.

Признавая эти особенности, можно утверждать, что этот механизм не может поддерживать пользовательскую ценность для исключительно больших размеров свопов, возможно, даже для умеренно больших. Когда значения inf δ близки к 100% для существенных свопов, логично сделать вывод, что токены, переданные из пула ликвидности пользователю, сократятся почти до нуля. Парадокс, по-видимому, заключается в том, что Δxu стремится к бесконечности, а Δyu тяготеет к нулю. Однако то же самое верно, когда Δxu практически не существует. Проще говоря, обмен бесконечного количества ETH в пул дает тот же результат, что и обмен почти ничего — практически никакой отдачи. Однако, как уже было показано, существуют определенные значения Δxu, которые дают очень разумные результаты для Δyu, предполагая, что существует определенное количество ETH, которое максимизирует доходность USDC под действием inf δ. Это может быть доказано алгебраически (уравнения 10–13).

На первый взгляд, всеобъемлющая функция swap (уравнение 10) кажется более сложной, чем то, к чему мы привыкли традиционно. К счастью, тонкости реализации не имеют значения, учитывая исследовательский характер этого упражнения. Несколько устрашающая производная в частных производных (уравнение 11) упрощается при оценке при Δxu = 0 до известной формулы предельной цены AMM с постоянным произведением (уравнение 12), как и ожидалось. Производная также имеет легко идентифицируемый корень при Δ x u = 2x. Это точное количество ETH, которое можно обменять, чтобы максимизировать USDC, полученный от пула (т. е. точку, в которой дополнительная, бесконечно малая сумма ETH не может принести обменщику дополнительные USDC).

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

Чтобы углубиться и по-настоящему оценить широту этого метода, я подверг его значениям r до 0,001, что соответствует объемам торгов, в 500 раз превышающим совокупную стоимость обоих резервов токенов в пуле. Представьте себе, в качестве примера, совершение ошеломляющей сделки на 1 миллиард долларов ETH в пуле, содержимое которого составляет всего 2 миллиона долларов, разделенных поровну между ETH и USDC. Это самый крайний случай, рассматриваемый в настоящее время (рис. 6).

Рисунок 6: Анализ Δyu по отношению к Δxu с использованием минимальной комиссии пула, устойчивой к сэндвич-атакам inf δ. Когда пользователь решает обменять Δxu ETH на эквивалентный токен, потенциальная ценность, полученная пользователем, Δyu, представлена в нормальных условиях, когда не выполняется сэндвич-атака (белая трасса), когда выполняется оптимальная сэндвич-атака (красная линия) на уровне комиссии 0,3% и когда минимальная комиссия пула, устойчивого к сэндвич-атакам, inf δ, трудоустроен. Все три условия предполагают наличие резервного баланса пула ликвидности в размере 500 ETH. Визуализации а) масштабируются по логарифму, до 500 входов Δxu включительно× резервного баланса пула ликвидности ETH (т. е. 500 000 ETH), и б) линейны в обоих измерениях, до входных данных Δxu включительно, равных 1× резервному балансу пула ликвидности ETH (т. е. 500 ETH). Локальные максимумы метода inf δ (синяя трасса) и сэндвич-атаки (красная линия), а также точка пересечения этих двух кривых изображаются ломаными линиями и помечены в соответствии с их x- и y-координатами (Δxu и Δyu соответственно).

Во-первых, важно признать, что использование inf δ вместо δ, направленное на предотвращение сэндвич-атаки, повышает скорость удержания ценности пользователем с ускоренной скоростью. Это происходит с неожиданным постоянством по мере увеличения значений Δxu, но только до определенного момента. Пик при Δ x u =2x, выведенный выше (уравнение 13), отчетливо выделяется. Аналогичный пик можно наблюдать и для кривой транзакций с зажатыми транзакциями, что ранее не обсуждалось. Но суть нашего наблюдения заключается в сближении синих и красных следов. На данном этапе попадание в жертву бутерброда с комиссией 0,03% совпадает с результатом упреждающего скачка комиссии. Для любого свопа, превышающего этот порог, ирония очевидна: поддаться сэндвич-атаке становится более экономично, чем ее предотвратить.

К сожалению, если локальные максимумы вокруг красного следа или точки его пересечения с синей кривой можно описать алгебраически, то в настоящее время это не под силу. Я подозреваю, что это невозможно, но я не могу быть уверен, не потратив на этот вопрос больше времени, чем оно того стоит. Я предложу награду в 100 USDC первому человеку, который сможет предоставить чисто символическое решение этой проблемы или доказательство того, что его не может быть. Численно определены красные максимумы трассы (Δ x u = 49 518,49922993397 ETH, Δ y u = 57 986,60716050453 USDC) и пересечение красно-синего следа (Δxu = 181 608,08402209895 ETH, Δyu = 50 907,34540591974 USDC).

В этом анализе рассматривается только ситуация, когда и пользователь, и злоумышленник соблюдают одинаковый уровень комиссии пула, либо де-факто 0,3% в случае эмуляции сэндвич-сделки, либо любое рассчитанное значение inf δ, подходящее для того, чтобы свести атаку на нет. Несмотря на то, что используемый здесь метод переменной платы называется «динамическим», следует подчеркнуть, что это относится к самой модели, а не к гипотетическому дизайну CFMM. Несмотря на то, что динамика расчета платы «на лету» и особенно ее влияние на сэндвич-MEV достаточно интересна, чтобы оправдать дальнейшее исследование, такое глубокое погружение остается за рамками настоящего обсуждения.

Заключение

Представленная здесь теория мотивирована желанием получить более надежную аналитическую основу для описания сэндвич-атак в отношении произвольных уровней комиссий, глубины ликвидности и размеров пользовательских транзакций. Ничто из представленного здесь не должно быть связано с основами проектирования CFMM. Как отмечалось выше, minReturn является вполне исправным ответом на проблему отражения сэндвич-атак и будет в центре внимания будущего анализа. Вместо этого позвольте вашему вниманию стать предметом вашего внимания распределение стоимости между поставщиками ликвидности пула, его своппером и его противником. Во всяком случае, эти модели проливают новый свет на старую проблему и проливают свет на ранее нехарактерную, ограниченную природу самого популярного эксплойта в отрасли.

Постскриптум

Эта статья появилась вместе с недавним исследованием Штефана Лёша о том, как Carbon, торговый протокол Bancor, противостоит атакам сэндвичей. Лёш углубляется в практическую сторону этих атак и комментирует их целесообразность при различных структурах сборов. Его выводы подтверждают выводы, представленные в настоящем анализе.

Обновленный блок кода: класс OptimumSandwich (python)

Класс OptimumSandwich (см. ниже) обновлен по сравнению с предыдущим выпуском и выводит маркированный текст из предыдущего раздела и табличные данные на рисунке 5 в текстовый файл.class OptimumSandwich:
def __init__(
self,
x: Decimal = Decimal(‘500’), # the {x_ticker} token balance of the constant product liquidity pool.
y: Decimal = Decimal(‘1_000_000’), # the {y_ticker} token balance of the constant product liquidity pool.
d: Decimal = Decimal(‘0.003’), # the fee level (decimal, 0.003 = 0.3% = 30 bps) of the constant product liquidity pool.
Dx_u: Decimal = Decimal(’20’), # the number of {x_ticker} tokens elected by the user to swap with the pool for {y_ticker} tokens.
print_analysis: bool = False, # prints a bulleted list and tabulated summary of the class.
x_ticker: str = ‘ETH’,
y_ticker: str = ‘USDC’
):
self.x = x
self.y = y
self.d = d
self.Dx_u = Dx_u
self.A = self.calculate_coefficient_A()
self.B = self.calculate_coefficient_B()
self.C = self.calculate_coefficient_C()
self.D = self.calculate_coefficient_D()
self.gamma = self.calculate_gamma()
self.nu = self.calculate_nu()
self.omega = self.calculate_omega()
self.phi = self.calculate_phi()
self.tau = self.calculate_tau()
self.mu = self.calculate_mu()
self.eta = self.calculate_eta()
self.rho = self.calculate_rho()
self.beta = self.calculate_beta()
self.alpha = self.calculate_alpha()
self.Dx_a = self.calculate_Dx_a()
self.Dy_a = self.calculate_Dy_expected(self.Dx_a, self.d)
self.Q = self.calculate_Q()
self.Dy_u_expected = self.calculate_Dy_expected(self.Dx_u, self.d)
self.Dy_u_obtained = self.calculate_Dy_u_obtained()
self.d_u = self.calculate_d_u()
self.sup_Dx_u = self.calculate_sup_Dx_u()
self.Dy_u_sup_Dx_u = self.calculate_Dy_expected(self.sup_Dx_u, self.d)
self.inf_d = self.calculate_inf_d()
self.Dy_u_inf_d = self.calculate_Dy_expected(self.Dx_u, self.inf_d)
if print_analysis:
self.print_analysis(x_ticker, y_ticker)

def calculate_coefficient_A(self):
return(
+ Decimal(‘2’)*self.d*(
+ Decimal(‘2’)
— self.d
)*(
+ self.Dx_u*(
+ self.d
+ (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
)
+ Decimal(‘2’)*self.x
)/(
+ Decimal(‘1’)
— (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
)
)

def calculate_coefficient_B(self):
return(
+ (
+ self.d*(
+ self.Dx_u*(
+ self.Dx_u
+ Decimal(‘6’)*self.x
— (
+ Decimal(‘1’)
— self.d
)*(
+ self.Dx_u
— (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)*(
+ Decimal(‘2’)*self.x
+ self.Dx_u
)
+ (
+ Decimal(‘1’)
— self.d
)*(
+ Decimal(‘3’)*self.x
— self.Dx_u
)
)
)
+ Decimal(‘6’)*self.x**Decimal(‘2’)*(
+ Decimal(‘2’)
— self.d
)
)
)/(
+ Decimal(‘1’)
— (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
)
)

def calculate_coefficient_C(self):
return(
+ (
+ Decimal(‘2’)*self.x*(
+ self.x
+ self.d*self.Dx_u
)*(
+ self.Dx_u*(
+ (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
— self.d
)
— Decimal(‘2’)*self.d*self.x*(
+ Decimal(‘2’)
— self.d
)
)
)/(
+ Decimal(‘1’)
— (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
)
)

def calculate_coefficient_D(self):
return(
+ self.x*(
+ self.x
+ self.d*self.Dx_u
)*(
+ (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)*(
+ self.x
+ self.Dx_u
)**Decimal(‘2’)
— self.x*(
+ self.x
+ self.d*self.Dx_u
)
)/(
+ Decimal(‘1’)
— (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
)
)

def calculate_gamma(self):
return(
+ (
+ self.B
— (
+ (
+ Decimal(‘3’)*self.A**Decimal(‘2’)/Decimal(‘8’)
)
)
)/Decimal(‘6’)
)

def calculate_nu(self):
return(
+ (
+ self.A*self.C/Decimal(‘4’)
)
)

def calculate_omega(self):
return(
+ self.gamma*(
+ self.nu
+ self.B*(
self.A/Decimal(‘4’)
)**Decimal(‘2’)
— Decimal(‘3’)*(
+ self.A/Decimal(‘4’)
)**Decimal(‘4’)
— self.D
)
)

def calculate_phi(self):
return(
+ (
+ (
+ (
+ self.A/Decimal(‘2’)
)**Decimal(‘3’)
— (
+ self.A*self.B
)/Decimal(‘2’)
— self.C
)/Decimal(‘4’)
)**Decimal(‘2’)
)

def calculate_tau(self):
return(
+ self.D
— self.A*self.C/Decimal(‘4’)
— self.B**Decimal(‘2′)/Decimal(’12’)
)

def calculate_mu(self):
return(
+ (
+ (
+ (
+ self.tau
)/Decimal(‘3’)
)**Decimal(‘3’)
+ (
+ (
+ self.omega
— self.phi
— self.gamma**Decimal(‘3’)
)**Decimal(‘2’)
)
)**(
+ Decimal(‘1’)/Decimal(‘2’)
)
)

def calculate_eta(self):
return(
+ (
+ self.mu
+ self.gamma**Decimal(‘3’)
+ self.phi
— self.omega
)**(
+ Decimal(‘1’)/Decimal(‘3’)
)
)

def calculate_rho(self):
return(
+ Decimal(‘2’)*self.tau/(
+ Decimal(‘3’)*self.eta
)
)

def calculate_beta(self):
return(
+ (
+ (
+ self.A/Decimal(‘2’)
)**Decimal(‘2’)
— (
+ Decimal(‘2’)*self.B
)/Decimal(‘3’)
— self.rho
+ Decimal(‘2’)*self.eta
)**(
+ Decimal(‘1’)/Decimal(‘2’)
)
)

def calculate_alpha(self):
return(
+ (
+ self.A**Decimal(‘2’)/Decimal(‘2’)
— (
+ Decimal(‘4’)*self.B
)/Decimal(‘3’)
— (
+ self.A**Decimal(‘3’)/Decimal(‘4’)
— self.A*self.B
— Decimal(‘2’)*self.C
)/self.beta
— Decimal(‘2’)*self.eta
+ self.rho
)**(
+ Decimal(‘1’)/Decimal(‘2’)
)
)

def calculate_Dx_a(self):
return(
+ (
+ self.alpha
+ self.beta
)/Decimal(‘2’)
— self.A/Decimal(‘4’)
)

def calculate_Q(self):
return(
+ (
+ self.Dx_a*(
+ self.Dx_u*(
+ Decimal(‘1’)
— self.d
)*(
+ self.Dx_a
+ self.x
— self.Dx_a*(
+ Decimal(‘1’)
— self.d
)
)
— (
+ self.Dx_a
+ self.x
)*(
+ self.Dx_a
+ self.Dx_u
+ self.x
)
+ (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)*(
+ self.Dx_a
+ self.Dx_u
+ self.x
)**Decimal(‘2’)
)
)/(
+ self.Dx_u*(
+ Decimal(‘1’)
— self.d
)*(
+ self.Dx_a*(
+ Decimal(‘1’)
— self.d
)
— self.Dx_a
— self.x
)
+ (
+ self.Dx_a
+ self.x
)*(
+ self.Dx_a
+ self.Dx_u
+ self.x
)
)
)

def calculate_Dy_expected(self, Dx, d):
return(
+ Dx*self.y*(
+ Decimal(‘1’)
— d
)/(
+ self.x
+ Dx
)
)

def calculate_Dy_u_obtained(self):
return(
+ self.Dx_u*self.y*(
+ Decimal(‘1’)
— self.d
)*(
+ self.Dx_a*self.d
+ self.x
)/(
+ (
+ self.Dx_a
+ self.x
)*(
+ self.Dx_a
+ self.Dx_u
+ self.x
)
)
)

def calculate_d_u(self):
return(
+ Decimal(‘1’)
— self.Dy_u_obtained/self.y
— self.Dy_u_obtained*self.x/(
+ (
+ self.Dx_u
— self.Q
)*self.y
)
)

def calculate_sup_Dx_u(self):
return(
+ self.x*(
+ self.d*(
+ Decimal(‘5’)
— Decimal(‘2’)*self.d
)
+ (
+ (
+ Decimal(‘2’)
— self.d
)*(
+ Decimal(‘2’)
+ Decimal(‘4’)*self.d**Decimal(‘2’)
— Decimal(‘5’)*self.d
)
)**(
+ Decimal(‘1’)/Decimal(‘2’)
)
— Decimal(‘2’)
)/(
+ Decimal(‘2’)*(
+ self.d
— Decimal(‘1’)
)**Decimal(‘2’)
)
)

def calculate_inf_d(self):
return(
+ (
+ (
+ self.Dx_u
+ Decimal(‘2’)*self.x
)*(
+ Decimal(‘2’)*self.Dx_u
+ self.x
)
— (
+ self.x*(
+ self.Dx_u
+ Decimal(‘2’)*self.x
)*(
+ Decimal(‘4’)*self.Dx_u**Decimal(‘2’)
+ Decimal(‘5’)*self.Dx_u*self.x
+ Decimal(‘2’)*self.x**Decimal(‘2’)
)
)**(
+ Decimal(‘1’)/Decimal(‘2’)
)
)/(
+ Decimal(‘2’)*(
+ self.Dx_u
+ self.x
)**Decimal(‘2’)
)
)

def print_analysis_tables(self, f, x_ticker, y_ticker):
table_1_data = [
[f»{x_ticker} tokens in pool», f»{self.x:.6f}»],
[f»{y_ticker} tokens in pool», f»{self.y:.6f}»],
[f»Swap fee (%)», f»{(self.d*100):.6f}»],
[f»User’s {x_ticker} tokens to swap», f»{self.Dx_u:.6f}»],
[f»User’s expected {y_ticker} tokens in return», f»{self.Dy_u_expected:.6f}»]
]

table_2_data = [
[f»Attacker’s front running {x_ticker} tokens swapped», f»{self.Dx_a:.6f}»],
[f»{y_ticker} tokens received by attacker», f»{self.Dy_a:.6f}»],
[f»User’s {x_ticker} tokens swapped», f»{self.Dx_u:.6f}»],
[f»{y_ticker} tokens received by user», f»{self.Dy_u_obtained:.6f}»]
]

table_3_data = [
[f»{y_ticker} tokens attacker swaps back», f»{self.Dy_a:.6f}»],
[f»{x_ticker} tokens attacker ends up with», f»{(self.Dx_a + self.Q):.6f}»],
[f»{x_ticker} tokens extracted from user’s transaction», f»{self.Q:.6f}»]
]

table_4_data = [
[f»Pool’s final {x_ticker} tokens», f»{(self.x + self.Dx_u — self.Q):.6f}»],
[f»Pool’s final {y_ticker} tokens», f»{(self.y — self.Dy_u_obtained):.6f}»],
[f»User’s losses (%)», f»{-(Decimal(‘1’) — self.Dy_u_obtained/self.Dy_u_expected)*100:.6f}»],
[f»New effective pool fee (%)», f»{self.d_u*100:.6f}»],
[f»Pool fee increase (%)», f»{100*(self.d_u/self.d — 1):.6f}»]
]

table_5_data = [
[f»Maximum unattackable {x_ticker} tokens to swap», f»{self.sup_Dx_u:.6f}»],
[f»{y_ticker} tokens user would receive», f»{self.Dy_u_sup_Dx_u:.6f}»],
[f»New fee level for unattackable trade (%)», f»{self.inf_d*100:.6f}»],
[f»{y_ticker} tokens user would receive at new fee», f»{self.Dy_u_inf_d:.6f}»],
[f»Difference compared to naive swap (%)», f»{(-(self.Dy_u_expected — self.Dy_u_inf_d)/self.Dy_u_expected*100):.6f}»],
[f»Difference compared to attacked transaction (%)», f»{((self.Dy_u_inf_d — self.Dy_u_obtained)/self.Dy_u_obtained*100):.6f}»]
]

f.write(«\n»)
f.write(«Initial Pool State and User’s Trade Decision\n»)
f.write(tabulate(table_1_data, headers=[«Description», «Value»], tablefmt=’pretty’) + «\n»)
f.write(«\nAttacker’s Trade and User’s Actual Trade Result\n»)
f.write(tabulate(table_2_data, headers=[«Description», «Value»], tablefmt=’pretty’) + «\n»)
f.write(«\nAttacker’s Back Running and Extraction Result\n»)
f.write(tabulate(table_3_data, headers=[«Description», «Value»], tablefmt=’pretty’) + «\n»)
f.write(«\nSummary of User’s Losses and Pool’s Final State\n»)
f.write(tabulate(table_4_data, headers=[«Description», «Value»], tablefmt=’pretty’) + «\n»)
f.write(«\nUnattackable Trade and Adjusted Fee Level\n»)
f.write(tabulate(table_5_data, headers=[«Description», «Value»], tablefmt=’pretty’) + «\n»)
return None

def print_analysis(self, x_ticker, y_ticker):
with open(‘sandwich_analysis.txt’, ‘w’) as f:
f.write(f»1. The user observes a pool with {self.x:.6f} {x_ticker} tokens and {self.y:.6f} {y_ticker} tokens, and a swap fee of {(self.d*100):.6f}%.\n»)
f.write(f»2. The user elects to swap {self.Dx_u:.6f} {x_ticker} tokens and expects to receive {self.Dy_u_expected:.6f} {y_ticker} tokens.\n»)
if self.Dx_a > Decimal(‘0’):
f.write(f»3. First, the attacker front runs the user’s trade by swapping {self.Dx_a:.6f} {x_ticker} tokens for {self.Dy_a:.6f} {y_ticker} tokens.\n»)
f.write(f»4. Then, the user’s trade is allowed through; the user swaps {self.Dx_u:.6f} {x_ticker} tokens for {self.Dy_u_obtained:.6f} {y_ticker} tokens.\n»)
f.write(f»5. Finally, the attacker back runs both of the previous trades by swapping {self.Dy_a:.6f} {y_ticker} tokens for {(self.Dx_a + self.Q):.6f} {x_ticker} tokens.\n»)
f.write(f»6. Therefore, the attacker has extracted a total of {self.Q:.6f} {x_ticker} tokens from the user’s transaction.\n»)
f.write(f»7. The overall process is equivalent to the user giving away {self.Q:.6f} {x_ticker} tokens to the attacker, then swapping the remaining {(self.Dx_u — self.Q):.6f} {x_ticker} tokens with the pool.\n»)
f.write(f»8. In addition to the sacrificed {x_ticker} token quantity, the pool fee also appears to be increased from {(self.d*100):.6f}% to {(self.d_u*100):.6f}% (i.e. {100*(self.d_u/self.d — 1):.6f}% increase).\n»)
f.write(f»9. At the end of the process, the liquidity pool contains {(self.x + self.Dx_u — self.Q):.6f} {x_ticker} tokens, and {(self.y — self.Dy_u_obtained):.6f} {y_ticker} tokens.\n»)
f.write(f»10. The user’s losses are -{(Decimal(‘1’) — self.Dy_u_obtained/self.Dy_u_expected)*100:.6f}% with respect to the expected outcome.\n»)
f.write(f»11. The maximum unattackable trade at a {(self.d*100):.6f}% fee level is to swap {self.sup_Dx_u:.6f} {x_ticker} tokens to receive {self.Dy_u_sup_Dx_u:.6f} {y_ticker} tokens.\n»)
f.write(f»12. Alternatively, if the fee level was changed to {(self.inf_d*100):.6f}%, the user could have swapped all {self.Dx_u:.6f} {x_ticker} tokens for {self.Dy_u_inf_d:.6f} {y_ticker} tokens with no risk of attack.\n»)
f.write(f»13. The adjusted fee level translates to a mere -{((self.Dy_u_expected — self.Dy_u_inf_d)/self.Dy_u_expected*100):.6f}% difference compared to the naive swap, and a +{((self.Dy_u_inf_d — self.Dy_u_obtained)/self.Dy_u_obtained*100):.6f}% difference compared to the attacked transaction.\n»)
self.print_analysis_tables(f, x_ticker, y_ticker)
else:
f.write(‘3. Any attempted sandwich attack on this trade will not benefit the exploiter in any way.’)
return None

Оптимальный сэндвич: как эксплуатировать блокчейн-энтузиастов с произвольной точностью

Знакомство

У меня часто есть возможность обсудить различные аспекты нашей отрасли с прилежной и увлеченной аудиторией. Как ни странно, атаки «сэндвичей» являются одной из наиболее часто запрашиваемых тем для обсуждения. Я подозреваю, что интерес к этому специфическому вектору атаки обусловлен его зловещим и угрожающим характером. Действительно, сэндвич-атаки являются одним из наиболее правдоподобных страхов, разделяемых сообществом DeFi; Тем не менее, примечательно, что лишь немногие из его членов могут точно сформулировать, что такое сэндвич-атака, не говоря уже о том, чтобы вывести уравнения, описывающие, как она выполняется. Я вынужден признать, что должен взять на себя хотя бы часть вины за разочаровывающий уровень эрудиции, демонстрируемый типичным пешеходом DeFi. Опыт показывает, что мне не удалось привести способную и желающую аудиторию к пониманию, о котором они вежливо просили и которого по праву заслуживают. Моя решимость состоит в том, чтобы представить здесь полный маршрут выполнения сэндвич-атаки, а также математические методы, которые позволяют ведущим эксплуататорам отрасли выжать все доступные вей ценности (в буквальном смысле) из уязвимых сделок. При любых других обстоятельствах может возникнуть разумный вопрос этики в отношении публикации информации, которую так легко использовать в качестве оружия. К несчастью для нас, те, кто хотел бы использовать его в качестве оружия, уже сделали это, и поэтому я не вижу никаких негативных последствий для опустошения черных ящиков наших противников и совместного изучения их содержимого.

Ожидаемое поведение

Рискуя показаться наивным, я хочу начать с рассмотрения прототипа случая, когда пользователь торгует против общего пула ликвидности с постоянным продуктом и ожидает, что ему будут доставлены токены, которые он предположил на основе алгоритмов, используемых в смарт-контрактах, с которыми он взаимодействует. То есть результат уже знакомой функции swap (уравнение 1). Чтобы различать входы и выходы, связанные с a ttacker и u ser, я буду использовать индексы aи uсоответственно. Кроме того, пусть x всегда обозначает токен, который отправляется в пул ликвидности либо от злоумышленника, либо от пользователя, а y всегда обозначает маркер, который отправляется из пула ликвидности либо злоумышленнику, либо пользователю. Я также буду соблюдать старую традицию обозначать комиссию за своп пула ликвидности строчной греческой буквой δ, а суммы сделок — прописной формой Δ. Предположим, что x, y, δ, Δ x, Δ y всегда являются положительными действительными числами, где Δ x и Δ y — это количество токенов, вычитаемых и добавленных в кошельки пользователя или злоумышленника соответственно (и, как следствие, добавленных и вычтенных из балансов пула ликвидности соответственно).

Предположим, что существует пул ликвидности с 500 ETH и 1 000 000 USDC, что представляет собой общую стоимость около 2 миллионов долларов США, из которых можно сделать вывод о рыночной цене ETH около 2 000 долларов США. Для удобства предположим, что уровень комиссии пула, δ, зафиксирован на уровне 0,003 (т.е. 0,3% или 30 базисных пунктов). Затем предположим, что пользователь мотивирован обменять 20 ETH (т.е. ~$40 тыс. номинальной стоимости) с пулом. Ожидаемый результат после эффекта проскальзывания цены и после учета торговой комиссии представлен на рисунке 1.

Рисунок 1: (наивный) ожидаемый результат. 20 ETH обмениваются на 38 346,1538 USDC из пула ликвидности постоянного продукта, состоящего из 500 ETH и 1 000 000 USDC, а также общей ликвидности около 2 миллионов долларов США.

То, что изображено выше, является желаемым результатом, если позволить себе сомнительное предположение, что пользователь знаком с основами маркет-мейкинга с постоянной функцией (CFMM).

Вкусный бутерброд для одного

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

Шаг 1: Проведите сделку

Наблюдая за транзакцией пользователя в мемпуле, злоумышленник может очень быстро рассчитать точное количество токенов для торговли до ожидаемого обмена пользователя, чтобы максимизировать эффективность эксплойта. На данный момент поверьте мне на слово, что наиболее эффективным эксплойтом является опережение сделки пользователя на 20 ETH со свопом в том же направлении на 575 031,4616 USDC (рисунок 2). Для непосвященных это может показаться довольно импульсивным поступком. По моему опыту, новички в DeFi, и особенно те, кто не имеет предварительных знаний в CeFi, находятся в заблуждении, что фронтраннинг сродни тому, чтобы быть побежденным по привлекательной цене другим участником, который так же, если не больше, заинтересован в покупке того же токена, что и они. Позвольте мне положить конец этой фантазии. За исключением исчезающего меньшинства случаев, человек, который находится впереди вас, абсолютно не заинтересован в токене, который вы пытаетесь купить.

Рисунок 2: Шаг 1 — Фронт-ран сделки. Злоумышленник совершает крупную сделку в том же направлении, что и пользователь, и следит за тем, чтобы его своп опережал своп пользователя в последовательности транзакций; 681,3677 ETH обменивается злоумышленником на 575 031,4616 USDC.

Шаг 2: Разрешите своп пользователя продолжить в подтасованном пуле ликвидности

Теперь, когда цены внутри пула ликвидности соответствующим образом скорректированы, чтобы подготовить почву для эксплойта, теперь критически важно, чтобы своп-транзакция пользователя была выполнена сразу после предварительного запуска (рисунок 3). Это было бы рискованной затеей, если бы не тот факт, что эти эксплойты обычно выполняются во время создания блока. То есть, человек, определяющий порядок транзакций в блоке, находится в привилегированном положении, чтобы решить судьбу попытки эксплойта; Если эксплойт их собственный, они могут гарантировать его успех. Если и существует запас случайности, то он достаточно мал, чтобы им можно было пренебречь.

Рисунок 3: Шаг 2 — Разрешите торговлю пользователя. Злоумышленник принудительно совершает сделку пользователя сразу после первой сделки. 20 ETH обменивается пользователем на 7 053,5213 USDC; ранее, в неэксплуатируемом примере, пользователь получил 38 346,1538 USDC за ту же сделку в том же пуле ликвидности (таким образом, снижение на 81,6%).

Шаг 3: Обратный запуск сделки

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

Рисунок 4: Шаг 3 — Обратный запуск сделки. Злоумышленник принудительно выполняет свою собственную транзакцию сразу после того, как сделка пользователя была исполнена. 575 031,4616 USDC, приобретенные на первом этапе, полностью обмениваются обратно на 693,6444 ETH, что приносит злоумышленнику 12,2767 ETH в оба конца.

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

Общий результат

Шаги, описанные выше, подробно описывают механизм атаки (рис. 2–4), и для многих из вас на данный момент будет предложено очень мало новой информации. Тем не менее, я надеюсь, что следующая точка зрения, касающаяся всего процесса, достаточно нова, чтобы заслуживать вашего внимания, независимо от того, были ли вы знакомы с этой темой.

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

  1. Пользователь решает обменять ETH на USDC в пуле ликвидности со ставкой комиссии 0,3% и ликвидностью почти в 2 миллиона долларов.
  2. Они рассчитывают отправить 20 ETH в пул ликвидности и получить 38 346,1538 USDC, а затем совершить транзакцию.
  3. Приходит незнакомец и забирает 12,2767 ETH без предупреждения и без причины.
  4. Оставшиеся 7,7233 ETH отправляются в пул ликвидности для выполнения свопа.
  5. Затем пул ликвидности немедленно изменяет свою комиссию до 53,63% (т.е. увеличение на 17 776,9349%).
  6. Обмен выполняется, и пользователь получает 7 053,5213 USDC (в отличие от 38 346,1538 USDC, которые он ожидал).
  7. Наконец, пул ликвидности немедленно возвращает свой уровень комиссии к уровню до обмена пользователя (т.е. обратно к 0,3%).

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

Рисунок 5: Общий результат. Пользователь отправляет 20 ETH, которые перехватывает злоумышленник, который удаляет из транзакции 12,2767 ETH, оставляя для сделки только 7,7233 ETH. Затем пул ликвидности меняет уровень комиссии с 0,3% до 53,63%, прежде чем обработать обмен. Затем пользователь получает 7 053,5213 USDC в соответствии со знакомым алгоритмом свопа (eqn 1) на новом уровне комиссии.

Точное выяснение сэндвичей

Описанная выше сэндвич-атака оптимальна для состояния в том виде, в котором она представлена, за исключением отсутствия знаков после запятой. То есть 681,3677 ETH (на самом деле 681,367696126344533020 ETH), используемые в опережающей транзакции, не являются приблизительными, а добытые злоумышленником 12,27688934466993032 ETH — это абсолютный максимум достижимых.

Пусть Q — общее количество ETH, добытое злоумышленником до того, как будет рассмотрен газ. Относительно легко вложить три уравнения свопа вместе, чтобы рассчитать Q с точки зрения комиссии за своп, δ, двух резервов токенов пула ликвидности, x и y, количества токенов ETH, отправленных в пул ликвидности пользователем, Δ x u, и количества токенов ETH, отправленных впул ликвидности злоумышленником во время предварительного запуска. Δxₐ (уравнение 2). Однако результат получается немного некрасивым.

Уменьшение уравнения 2 и отмена слагаемых y дает уравнение 3.

График Q в сравнениис Δ xₐ показывает профиль прибыли от атаки по отношению к количеству токенов, зафиксированных во время шага фронтраннинга. На первый взгляд, визуальный осмотр графика кажется непоказательным; однако изображение размера Δxₐ в логарифмической шкале помогает преувеличить интересующий объект (рис. 6). Для удобства читателя предусмотрен интерактивный сюжет через desmos.

Рисунок 6: Q в сравнении с Δxₐ. Оба графика получены из уравнения 3; а) изображается в линейных масштабах и б) размер Δxₐ в логарифмическом масштабе. Пик, изображенный на рисунке b), обозначает оптимальное количество токенов Δxₐ для торговли во время фронтраннингового компонента атаки.

Локальные максимумы вокруг пика, показанного на рисунке 5 b), могут быть вычислены из частной производной уравнения 3 по отношению к Δxₐ, вычисленной как 0 (уравнение 4). Коэффициенты ABCD заменяют другие члены (x, δ и Δxu), которые рассматриваются как постоянные для вычисления опережающей транзакции (уравнения 5–8).

Заметим, что каждый из коэффициентов A-D имеет один и тот же знаменатель 1-(1-δ)², который является артефактом деления на ведущий коэффициент для принудительного приведения к типу монического уравнения (т.е. ведущий коэффициент = 1). К счастью, уравнение 4 является четвертичным, поэтому решение для Δxₐ не является невозможным, хотя и неудобным. Для всех реалистичных значений (например, комиссия за своп, баланс токенов, суммы свопа больше нуля) уравнение 4 имеет два действительных и два комплексных корня. Здесь представлен только корень в точке Δxₐ > 0 (т.е. часть, необходимая для выполнения эксплойта), если он существует. Обратите внимание, что в зависимости от уровня комиссии, δ, глубины ликвидности, x и суммы сделки пользователя, Δ x u, некоторые сэндвич-атаки просто неприбыльны для любого объема сделки Δ xₐ.

Как это характерно для квартиков, раствор лучше всего разбить на более мелкие кусочки для читабельности и наглядности изложения. Таким образом, раствор разбивается на десять составных частей, представленных греческими буквами гамма (γ), ню (ν), омега (ω), фи (φ), тау (τ), му (µ), эта (η), ро (ρ), бета (β) и альфа (α) (уравнения 9–18). Эти метки не имеют стандартного значения — они являются рудиментом метода, который я использовал для решения и упрощения квартика.

Оптимальная операция фронт-раннинга, определяющая общую отдачу от сэндвич-атаки, Q, может быть рассчитана путем составления уравнения 19 из четвертичных промежуточных соединений γνωφτµηρβ и α (уравнения 9–18) и коэффициентов ABC и D (уравнения 5–8):

Метод, описанный здесь, является общим. Например, реализация приведенных выше формул для Δ x u = 12,345678910, x = 123,45678910 и δ = 0,012345678910 дает оптимальную опережающую сделку Δxₐ = 121,983219009865770262 и чистую прибыль Q = 6,075819297992183389 токенов. Как правило, чем больше предполагаемая сделка пользователя по сравнению с глубиной пула ликвидности и чем ниже комиссия за своп, тем выгоднее эксплойт. При δ = 0 эксплойт становится асимптотическим при Q =Δ x uкак Δxₐ → ∞, как и ожидалось. Корректировка потребления газа тривиальна, так как смоделированная стоимость газа может быть просто вычтена из Q, чтобы завершить анализ с точки зрения злоумышленника.

Расчет токенов пользователя, полученных из пула после атаки, Δyu, относительно прост (уравнение 20).

«Эффективный» уровень комиссии, δ*, который представляет собой кажущуюся комиссию, взимаемую с суммы свопа после учета рейка атакующего, Δx u-Q, также тривиален для расчета (уравнение 21).

Я избавлю вас от хлопот, связанных с расшифровкой этих уравнений, если вы пообещаете не использовать их во зло. Класс OptimumSandwich (см. ниже) вычисляет оптимальную переднюю сделку, Δxₐ, и печатает анализ, достаточный для воссоздания рисунков 1–5. Я считаю, что недостаток документации, тестов и обработки ошибок в приведенном ниже блоке кода должен быть адекватно компенсирован контекстом и комментариями, представленными в этой статье. Иди ко мне (jk).

Заключение

Цель этой статьи — дать авторитетный обзор сэндвич-атак и удовлетворить несколько эгоистичную потребность иметь удобный флаг в литературе, на который я могу ссылаться в будущих обсуждениях. Однако это только самый фундаментальный случай; Существуют и более сложные методы, которые выходят за рамки данной статьи. Например, изощренные эксплойты, при которых злоумышленник контролирует большую часть предложения токенов lp, принадлежащих пулу ликвидности, в котором выполняется атака, преднамеренная «приманка» путем создания привлекательной цены по сравнению с конкурирующими пулами ликвидности и тактика одновременного сэндвича и рыночного арбитража являются обычным явлением, но не обсуждаются здесь явно. Как бы то ни было, представленный материал является полным в отношении масштабов дискуссии и должен удовлетворять потребности практически любого информированного разговора на эту тему.

Я хочу закончить эту статью вызовом читателю — как в духе поиска луча надежды, так и в духе моих собственных педагогических амбиций. Учитывая представленные здесь математические описания, можно вывести выражение для «не-сэндвичной» сделки. То есть, учитывая состояние пула (x, δ), существует максимальное количество токенов, обмениваемых пользователем, Δ x u, для которого любая попытка сэндвич-атаки принесет эксплойтеру нулевую прибыль (т.е. Q = 0 и, следовательно, Δ xₐ = 0). Само собой разумеется, что такая сумма сделки может и должна быть доступна пользователям через интерфейс для приложений DeFi, работающих с типичным постоянным продуктом CFMM на уровне смарт-контрактов. Если вы зашли так далеко, то задача, которую я вам бросаю, заключается в следующем:

  1. Для общего пула ликвидности с резервным балансом x ETH и комиссией за своп в δ, какое количество ETH, Δ x u, пользователь можетобменять, не подвергая риску сэндвич-атаки?
  2. Для общего пула ликвидности с резервным балансом x ETH, где пользователь намерен торговать определенным количеством ETH, Δxu, какая настройка комиссии за своп δ сведет на нет возможность сэндвич-атаки?

Постскриптум

Эта статья примыкает к недавней статье Стефана Лёша (Stefan Loesch) о устойчивости Carbon, торгового протокола от Bancor, к сэндвич-атакам. В статье Лёша обсуждается жизнеспособность «сэндвич-атак» в контексте подразумеваемых уровней комиссий в парадигме концентрированной ликвидности и напрямую затрагиваются концепции, заложенные в приведенных выше проблемных вопросах.

Блок кода: класс OptimumSandwich (python)

from decimal import Decimal, getcontext
getcontext().prec = 100

class OptimumSandwich:
«»»
Examples:
>>> OptimumSandwich()
1. The user observes a pool with 500.000000 ‘x’ tokens and 1000000.000000 ‘y’ tokens, and a swap fee of 0.003000.
2. The user elects to swap 20.000000 ‘x’ tokens and expects to receive 38346.153846 ‘y’ tokens.
3. First, the attacker front runs the user’s trade by swapping 681.367696 ‘x’ tokens for 575031.461640 ‘y’ tokens.
4. Then, the user’s trade is allowed through; the user swaps 20.000000 ‘x’ tokens for 7053.521318 ‘y’ tokens.
5. Finally, the attacker back runs both of the previous trades by swapping 575031.461640 ‘y’ tokens for 693.644385 ‘x’ tokens.
6. Therefore, the attacker has extracted a total of 12.276689 ‘x’ tokens from the user’s transaction.
7. The overall process is equivalent to the user giving away 12.276689 ‘x’ tokens to the attacker, then swapping the remaining 7.723311 ‘x’ tokens with the pool.
8. In addition to the sacrificed ‘x’ token quantity, the pool fee also appears to be increased from 0.003000 to 0.536308 (i.e. 17876.934872% increase).
9. At the end of the process, the liquidity pool contains 507.723311 ‘x’ tokens, and 992946.478682 ‘y’ tokens.
10. The user’s losses are -81.605662% with respect to the expected outcome.

>>> OptimumSandwich(
x = Decimal(‘123.45678910’),
y = Decimal(‘123456.78910’),
d = Decimal(‘0.012345678910’),
Dx_u = Decimal(‘12.345678910’)
)
1. The user observes a pool with 123.456789 ‘x’ tokens and 123456.789100 ‘y’ tokens, and a swap fee of 0.012346.
2. The user elects to swap 12.345679 ‘x’ tokens and expects to receive 11084.784657 ‘y’ tokens.
3. First, the attacker front runs the user’s trade by swapping 121.983219 ‘x’ tokens for 60600.286699 ‘y’ tokens.
4. Then, the user’s trade is allowed through; the user swaps 12.345679 ‘x’ tokens for 2973.112594 ‘y’ tokens.
5. Finally, the attacker back runs both of the previous trades by swapping 60600.286699 ‘y’ tokens for 128.059038 ‘x’ tokens.
6. Therefore, the attacker has extracted a total of 6.075819 ‘x’ tokens from the user’s transaction.
7. The overall process is equivalent to the user giving away 6.075819 ‘x’ tokens to the attacker, then swapping the remaining 6.269860 ‘x’ tokens with the pool.
8. In addition to the sacrificed ‘x’ token quantity, the pool fee also appears to be increased from 0.012346 to 0.501727 (i.e. 4063.984954% increase).
9. At the end of the process, the liquidity pool contains 129.726649 ‘x’ tokens, and 120483.676506 ‘y’ tokens.
10. The user’s losses are -73.178436% with respect to the expected outcome.
«»»
def __init__(
self,
x: Decimal = Decimal(‘500’), # the ‘x’ token balance of the constant product liquidity pool.
y: Decimal = Decimal(‘1_000_000’), # the ‘y’ token balance of the constant product liquidity pool.
d: Decimal = Decimal(‘0.003’), # the fee level (decimal, 0.003 = 0.3% = 30 bps) of the constant product liquidity pool.
Dx_u: Decimal = Decimal(’20’) # the number of ‘x’ tokens elected by the user to swap with the pool for ‘y’ tokens.
):
self.x = x
self.y = y
self.d = d
self.Dx_u = Dx_u
self.A = self.calculate_coefficient_A()
self.B = self.calculate_coefficient_B()
self.C = self.calculate_coefficient_C()
self.D = self.calculate_coefficient_D()
self.gamma = self.calculate_gamma()
self.nu = self.calculate_nu()
self.omega = self.calculate_omega()
self.phi = self.calculate_phi()
self.tau = self.calculate_tau()
self.mu = self.calculate_mu()
self.eta = self.calculate_eta()
self.rho = self.calculate_rho()
self.beta = self.calculate_beta()
self.alpha = self.calculate_alpha()
self.Dx_a = self.calculate_Dx_a()
self.Dy_a = self.calculate_Dy_expected(self.Dx_a)
self.Q = self.calculate_Q()
self.Dy_u_expected = self.calculate_Dy_expected(self.Dx_u)
self.Dy_u_obtained = self.calculate_Dy_u_obtained()
self.d_u = self.calculate_d_u()
self.print_analysis()

def calculate_coefficient_A(self):
return(
+ Decimal(‘2’)*self.d*(
+ Decimal(‘2’)
— self.d
)*(
+ self.Dx_u*(
+ self.d
+ (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
)
+ Decimal(‘2’)*self.x
)/(
+ Decimal(‘1’)
— (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
)
)

def calculate_coefficient_B(self):
return(
+ (
+ self.d*(
+ self.Dx_u*(
+ self.Dx_u
+ Decimal(‘6’)*self.x
— (
+ Decimal(‘1’)
— self.d
)*(
+ self.Dx_u
— (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)*(
+ Decimal(‘2’)*self.x
+ self.Dx_u
)
+ (
+ Decimal(‘1’)
— self.d
)*(
+ Decimal(‘3’)*self.x
— self.Dx_u
)
)
)
+ Decimal(‘6’)*self.x**Decimal(‘2’)*(
+ Decimal(‘2’)
— self.d
)
)
)/(
+ Decimal(‘1’)
— (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
)
)

def calculate_coefficient_C(self):
return(
+ (
+ Decimal(‘2’)*self.x*(
+ self.x
+ self.d*self.Dx_u
)*(
+ self.Dx_u*(
+ (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
— self.d
)
— Decimal(‘2’)*self.d*self.x*(
+ Decimal(‘2’)
— self.d
)
)
)/(
+ Decimal(‘1’)
— (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
)
)

def calculate_coefficient_D(self):
return(
+ self.x*(
+ self.x
+ self.d*self.Dx_u
)*(
+ (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)*(
+ self.x
+ self.Dx_u
)**Decimal(‘2’)
— self.x*(
+ self.x
+ self.d*self.Dx_u
)
)/(
+ Decimal(‘1’)
— (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)
)
)

def calculate_gamma(self):
return(
+ (
+ self.B
— (
+ (
+ Decimal(‘3’)*self.A**Decimal(‘2’)/Decimal(‘8’)
)
)
)/Decimal(‘6’)
)

def calculate_nu(self):
return(
+ (
+ self.A*self.C/Decimal(‘4’)
)
)

def calculate_omega(self):
return(
+ self.gamma*(
+ self.nu
+ self.B*(
self.A/Decimal(‘4’)
)**Decimal(‘2’)
— Decimal(‘3’)*(
+ self.A/Decimal(‘4’)
)**Decimal(‘4’)
— self.D
)
)

def calculate_phi(self):
return(
+ (
+ (
+ (
+ self.A/Decimal(‘2’)
)**Decimal(‘3’)
— (
+ self.A*self.B
)/Decimal(‘2’)
— self.C
)/Decimal(‘4’)
)**Decimal(‘2’)
)

def calculate_tau(self):
return(
+ self.D
— self.A*self.C/Decimal(‘4’)
— self.B**Decimal(‘2′)/Decimal(’12’)
)

def calculate_mu(self):
return(
+ (
+ (
+ (
+ self.tau
)/Decimal(‘3’)
)**Decimal(‘3’)
+ (
+ (
+ self.omega
— self.phi
— self.gamma**Decimal(‘3’)
)**Decimal(‘2’)
)
)**(
+ Decimal(‘1’)/Decimal(‘2’)
)
)

def calculate_eta(self):
return(
+ (
+ self.mu
+ self.gamma**Decimal(‘3’)
+ self.phi
— self.omega
)**(
+ Decimal(‘1’)/Decimal(‘3’)
)
)

def calculate_rho(self):
return(
+ Decimal(‘2’)*self.tau/(
+ Decimal(‘3’)*self.eta
)
)

def calculate_beta(self):
return(
+ (
+ (
+ self.A/Decimal(‘2’)
)**Decimal(‘2’)
— (
+ Decimal(‘2’)*self.B
)/Decimal(‘3’)
— self.rho
+ Decimal(‘2’)*self.eta
)**(
+ Decimal(‘1’)/Decimal(‘2’)
)
)

def calculate_alpha(self):
return(
+ (
+ self.A**Decimal(‘2’)/Decimal(‘2’)
— (
+ Decimal(‘4’)*self.B
)/Decimal(‘3’)
— (
+ self.A**Decimal(‘3’)/Decimal(‘4’)
— self.A*self.B
— Decimal(‘2’)*self.C
)/self.beta
— Decimal(‘2’)*self.eta
+ self.rho
)**(
+ Decimal(‘1’)/Decimal(‘2’)
)
)

def calculate_Dx_a(self):
return(
+ (
+ self.alpha
+ self.beta
)/Decimal(‘2’)
— self.A/Decimal(‘4’)
)

def calculate_Q(self):
return(
+ (
+ self.Dx_a*(
+ self.Dx_u*(
+ Decimal(‘1’)
— self.d
)*(
+ self.Dx_a
+ self.x
— self.Dx_a*(
+ Decimal(‘1’)
— self.d
)
)
— (
+ self.Dx_a
+ self.x
)*(
+ self.Dx_a
+ self.Dx_u
+ self.x
)
+ (
+ Decimal(‘1’)
— self.d
)**Decimal(‘2’)*(
+ self.Dx_a
+ self.Dx_u
+ self.x
)**Decimal(‘2’)
)
)/(
+ self.Dx_u*(
+ Decimal(‘1’)
— self.d
)*(
+ self.Dx_a*(
+ Decimal(‘1’)
— self.d
)
— self.Dx_a
— self.x
)
+ (
+ self.Dx_a
+ self.x
)*(
+ self.Dx_a
+ self.Dx_u
+ self.x
)
)
)

def calculate_Dy_expected(self, Dx):
return(
+ Dx*self.y*(
+ Decimal(‘1’)
— self.d
)/(
+ self.x
+ Dx
)
)

def calculate_Dy_u_obtained(self):
return(
+ self.Dx_u*self.y*(
+ Decimal(‘1’)
— self.d
)*(
+ self.Dx_a*self.d
+ self.x
)/(
+ (
+ self.Dx_a
+ self.x
)*(
+ self.Dx_a
+ self.Dx_u
+ self.x
)
)
)

def calculate_d_u(self):
return(
+ Decimal(‘1’)
— self.Dy_u_obtained/self.y
— self.Dy_u_obtained*self.x/(
+ (
+ self.Dx_u
— self.Q
)*self.y
)
)

def print_analysis(self):
print(f»1. The user observes a pool with {self.x:.6f} ‘x’ tokens and {self.y:.6f} ‘y’ tokens, and a swap fee of {self.d:.6f}.»)
print(f»2. The user elects to swap {self.Dx_u:.6f} ‘x’ tokens and expects to receive {self.Dy_u_expected:.6f} ‘y’ tokens.»)
if self.Dx_a > Decimal(‘0’):
print(f»3. First, the attacker front runs the user’s trade by swapping {self.Dx_a:.6f} ‘x’ tokens for {self.Dy_a:.6f} ‘y’ tokens.»)
print(f»4. Then, the user’s trade is allowed through; the user swaps {self.Dx_u:.6f} ‘x’ tokens for {self.Dy_u_obtained:.6f} ‘y’ tokens.»)
print(f»5. Finally, the attacker back runs both of the previous trades by swapping {self.Dy_a:.6f} ‘y’ tokens for {(self.Dx_a + self.Q):.6f} ‘x’ tokens.»)
print(f»6. Therefore, the attacker has extracted a total of {self.Q:.6f} ‘x’ tokens from the user’s transaction.»)
print(f»7. The overall process is equivalent to the user giving away {self.Q:.6f} ‘x’ tokens to the attacker, then swapping the remaining {(self.Dx_u — self.Q):.6f} ‘x’ tokens with the pool.»)
print(f»8. In addition to the sacrificed ‘x’ token quantity, the pool fee also appears to be increased from {self.d:.6f} to {self.d_u:.6f} (i.e. {100*(self.d_u/self.d — 1):.6f}% increase).»)
print(f»9. At the end of the process, the liquidity pool contains {(self.x + self.Dx_u — self.Q):.6f} ‘x’ tokens, and {(self.y — self.Dy_u_obtained):.6f} ‘y’ tokens.»)
print(f»10. The user’s losses are -{(Decimal(‘1’) — self.Dy_u_obtained/self.Dy_u_expected)*100:.6f}% with respect to the expected outcome.»)
else:
print(‘3. Any attempted sandwich attack on this trade will not benefit the exploiter in any way.’)
return None

Как предотвратить MEV-атаки на AMM

Фон

Ключевые формулы, с которыми мы здесь работаем:

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

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

  • Q — прибыль, которую злоумышленник получает от сэндвич-атаки, основанная на других параметрах, приведенных ниже, и
  • Δxₐ — это размер опережающей сделки, которая приводит к этому конкретному значению Q. Параметры торговли:
  • Δxu, который представляет собой размер пользовательской сделки в терминах токенов,
  • x, который представляет собой размер пула в тех же единицах, что и Δx (виртуальный размер в случае пулов с кредитным плечом, но этот анализ игнорирует застрявшие на границе последствия ликвидности с кредитным плечом), и, что самое важное,
  • δ представляет собой процентную комиссию пула (в десятичном выражении, например, 10bp = 0.001)

Ключевое уравнение, которое мы здесь рассмотрим, — это второе уравнение, приведенное выше. Она получается из первого, сначала выводя относительно Δ x ₐ, а затем требуя, чтобы производная Q по Δ xобращалась в нуль при Δxₐ=0. Это условие гарантирует, что Δxₐ=0 является оптимальным для потенциального сэндвич-атаки, другими словами, арбитражная прибыль отсутствует.

Упрощение формулы

Мы видим, что приведенная выше формула, как написано, состоит из трех слагаемых, первые два из которых тривиальны, потому что они дают решения, которые не представляют интереса с финансовой точки зрения. Один из этих терминов показывает, что пустой пул (x=0) не допускает сэндвич-атак, а другое решение имеет неоправданно большое значение Δxu и поэтому может быть отброшено. Таким образом, у нас остается операционная часть уравнения, которая выглядит следующим образом

Оперативная часть второго уравнения выше

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

  • δ действительно δ, как и любая плата >δ также предотвратит сэндвич
  • Δ x u на самом деле является sup Δ x u, так как любая сделка <Δxuтакже предотвратит сэндвич, и
  • x на самом деле inf x, так как любая ликвидность пула >также предотвратит сэндвич

Марк в своей статье решил приведенное выше уравнение для δ, Δ xuи x, получив следующие формулы

Формулы из статьи 2

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

Уровни комиссий, которые не допускают сэндвич-атаку

Эта формула выглядит разочаровывающе сложной, но, к счастью, она имеет хорошую асимптотику для больших значений r=x/Δxu (т.е. небольших сделок):

Асимптотическое поведение минимального уровня сопротивления комиссии для небольших сделок (r=x/Δx)

На графике ниже красная линия — это фактическая кривая, синяя линия — асимптотика степенного закона 1/r, а зеленая линия — (значительно) улучшенная асимптотика 2/2r+3

Минимальная плата за отсутствие сэндвича в зависимости от r=x/Δx

(Базовый калькулятор Desmos см. здесь )

В то время как r=x/Δ xuлучше подходит для формул, с финансовой точки зрения более интуитивной величиной является нормализованный ликвидностью размер сделки 1/r = Δ x u/x. Важно иметь в виду, что это также процентное проскальзывание, т.е. величина, на которую сделка размера Δx отрицательно толкает цену пула размера x.

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

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

Небольшие сделки (1% от размера пула или меньше) не могут быть атакованы сэндвич-атаками, если уровень комиссии больше, чем проскальзывание (которое также является нормализованным размером сделки с ликвидностью), т.е. δ>Δ x u/x. Для чуть более крупных сделок — примерно до 10% проскальзывания — хорошо работает приближение δ>2/(2r+3), а в остальном следует использовать полную формулу [2], приведенную выше.

Максимальный размер сделки, который нельзя атаковать

Точно так же мы можем рассматривать максимально возможный размер сделки как функцию уровня комиссии, который не может быть атакован сэндвичем. Мы начинаем с уравнения [3] из статьи 2 выше, но делим обе части на x и на δ. Напомним, что Δ x u/x — это (нормализованный ликвидностью) размер сделки, и, следовательно, новый LHS уравнения Δ x u/xδ — это нормализованный размер сделки (также: проскальзывание), деленный на комиссии.

Ниже мы построили график относительной влажности приведенного выше уравнения

Максимальный нормализованный размер сделки относительно комиссий в зависимости от комиссий

(См. эту таблицу на Desmos)

Приведенный выше график можно интерпретировать следующим образом: каков максимальный нормализованный размер сделки в процентах от комиссий при данном уровне комиссий (в данном случае 0,2 = 20% комиссий)? Для малых значений это число равно единице, т.е. для малых комиссий максимальный размер нормализованной сделки равен уровню комиссии. Если комиссии больше, то возможный размер сделки увеличивается непропорционально. Например, при комиссии в 20% размер сделки может составлять 20% * 1,4 = 28% от размера пула, прежде чем он станет сэндвичем.

Однако следует отметить, что такое увеличение размеров сделок происходит только при довольно больших уровнях комиссий. Ниже приведено чуть более разумное представление на этом графике с уровнями комиссий до 5%, где улучшение линейно составляет около 5% на каждые 3% комиссий и, следовательно, ниже 10% на уровне комиссии 5%, т.е. не особенно значимо по сравнению с базовым значением в 100% (и определенно не при комиссиях < 1%).

Тот же график, но с уровнем комиссии до 5%

Заглавное изображение

Источник

Источник

Источник