Ethereum
March 12, 2023

Создаем MEV бота на Solidity

Оригинал — https://www.blocknative.com/blog/mev-and-creating-a-basic-arbitrage-bot-on-ethereum-mainnet

Канал — https://t.me/jetix37eth

MEV, или Максимальная Извлекаемая Ценность— это максимальное значение, которое может быть извлечено из блока. Хотя традиционно это была возможность, в которой могли участвовать только майнеры — именно поэтому ее иногда называли Miner Extractable Value, — демократизация мемпула предоставила трейдерам больше возможностей извлекать ценность, влияя на то, что попадает в блок.

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

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

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

Арбитраж в MEV

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

Допустим, Вы начали с актива A и продали его за актив B на бирже, где актив B дешевле. Затем вы бы взяли актив и продали его за актив А на другой бирже, получив взамен больше актива А, чем когда вы начинали.

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

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

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

Как только вы обнаружите транзакцию, которая может вызвать возможность арбитража, когда она попадет в цепочку, вы можете отправить две свои транзакции, которые завершат возможность арбитража, непосредственно в мемпуд или в Flashbots в виде пакета. Если все сделано правильно, это означает, что транзакция мемпула, которая создает возможность + все ваши две транзакции будут добыты в одном блоке.

В результате, в тот момент, когда создается возможность арбитража, она также завершается следующими двумя транзакциями — вашими двумя транзакциями.


Давайте разберем это на совершенно нереалистичном, но, надеюсь, проливающем свет примере.

Предположим, вы отслеживаете мемпул и обнаруживаете транзакцию, которая повлияет на пул ликвидности в Uniswap V2 для токенов A и B. Поскольку вы знаете, что пулы ликвидности регулируются уравнением x * y = k, тогда вы знаете, когда видите, что кто-то отправляет обмен 2 токена A на как можно большее количество токена B, вы можете рассчитать (основываясь на резервах этих токенов в пуле ликвидности, которые в данном случае равны 10 и 10), что он получат взамен 1,7 токена B.

Ключевым моментом является то, что вы знаете, что эта транзакция повлияет на цену между токенами A и B на Uniswap V2, но не на другой DEX, такой как Sushiswap… по крайней мере, до тех пор, пока кто-нибудь не воспользуется возможностью арбитража.

В тот момент, когда эта транзакция будет добыта, токен A будет дешевле на Uniswap, чем на Sushiswap, поэтому тот, кто может продать токен A на Sushiswap за токен B, а затем продать эти токены B на Uniswap за токен A, должен в конечном итоге получить больше токена A, чем они начали. И это именно то, что иллюстрирует этот пример.

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

Поиск возможностей для арбитража

Проводя некоторое исследование различных возможностей в MEV, я наткнулся на готовый смарт-контракт в этом потрясающем репозитории github, который расскажет вам, как оптимально рассчитать правильную сумму, необходимую для арбитража между двумя Uniswap V2 дексами — например, Uniswap V2 и Sushiswap.

Ключевой момент этого смарт-контракта заключается в том, что вы можете предоставить ему два пула — один пул Uniswap и один пул Sushiswap и сообщить, есть ли какая-либо прибыль от арбитража между двумя пулами. Однако расчет выполняется путем анализа резервов этих двух пулов в их текущем состоянии (т.е. последнего состояния блока).

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

Теперь моей целью в этом проекте было написать базовый скрипт, который модифицирует этот смарт-контракт для успешного обнаружения возможностей арбитража с использованием расчета изменения чистого баланса платформы Blocknative Simulation Platform.

Пишем базовый скрипт

Этот пост не предназначен для того, чтобы узнать о Solidity. Существует множество отличных постов и руководств, но для того, чтобы по-настоящему конкурировать за возможности MEV (по крайней мере, в мире EVM), вам нужно знать Solidity.

Тем не менее, я использовал свои ограниченные знания о Solidity, чтобы обновить смарт-контракт в соответствии с моими потребностями. Чтобы рассчитать прибыль от арбитражной возможности в реальном времени, мне нужно было бы каким-то образом снабдить мою функцию getProfit корректировкой пула ликвидности. Эта корректировка происходит, когда я вижу транзакцию обмена в мемпуле, которая повлияет на пул ликвидности Uniswap или Sushiswap.

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

Заметьте, что getProfit полагается на getOrderedReserves для расчета резервов каждого пула и правильного направления сделок. Поэтому я добавил некоторую логику в getOrderedReserves, которая принимала бы данные мемпула при обмене токенами в Uniswap или Sushiswap, а затем корректировала резервы токенов этой пары, чтобы при вызове getProfit он включал данные мемпула, а не только последнее состояние цепочки.

Теперь, когда я настроил смарт-контракт так, чтобы он мог включать данные мемпула в реальном времени, я могу завершить свой скрипт, используя mempool API от Blocknative и flashbots private relay.

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

Лучший способ гарантировать, что он попадет в начало блока (или, по крайней мере, близко к нему), - это использовать частную ретрансляцию flash-ботов.

Для этого проекта я просмотрел ожидающие события моделирования на маршрутизаторах Uniswap V2 и Sushiswap, используя Javascript SDK от Blocknative. Ожидающие загрузки моделирования включают изменения чистого баланса по всем задействованным адресам (например, пул ликвидности), что позволяет мне сделать две вещи:

  • Проверить, существует ли эта пара на другой бирже
  • Использовать изменения баланса в качестве входных данных в качестве "корректировок" в моей функции getProfit в моем смарт-контракте

Когда я получаю эти события, я перебираю список netBalanceChange в нашей полезной нагрузке Simulation Platform и пропускаю, когда адрес равен самому фактическому маршрутизатору (потому что я ищу только адреса пула ликвидности) или если netBalanceChange[index].address не содержит 2 элементов (если у него есть только 1 элемент, то это не может быть пул, потому что пул будет иметь 2 или более резервов токенов с изменениями чистого баланса).

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

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

Аналогично, если у другого DEX действительно есть пул ликвидности с той же парой токенов, то я могу осуществлять арбитраж между ними. Используя мой скрипт, это означает, что я обнаружил отложенный обмен либо на Uniswap, либо на Sushiswap в мемпуле, и я обнаружил тот же пул ликвидности на другом DEX. Теперь у меня есть вся информация, необходимая для предоставления моего смарт-контракта, чтобы увидеть, есть ли возможность получения прибыли.

Результат, который я получаю от своего смарт-контракта, не включает газ, поэтому я произвожу наивный расчет, чтобы увидеть, какова будет общая стоимость газа (включая мои дополнительные чаевые майнерам на ретрансляции flashbots). Я знал, что две мои транзакции обойдутся примерно в 240 тысяч газа, поэтому я захардкодил это для расчета чистой прибыли.

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

Если чистый расчет больше 0, я успешно нашел возможность арбитража, где я получу чистый 5% и отдам майнеру 95%!

Создаем flashbots-пакет для арбитража

Затем мне нужно было создать свой пакет flashbots для отправки на ретранслятор. Поскольку обнаруженная транзакция мемпула может быть транзакцией типа 0 или типа 2, у меня должны быть сценарии для обоих.

Я использовал свой модифицированный смарт-контракт для построения своей транзакции, которая включала бы либо две внутренние транзакции, либо два обмена между двумя DEX.

Создав транзакцию, я затем создал пакет flashbots и смоделировал его, чтобы гарантировать, что транзакция не завершится неудачей по какой-либо причине. Я также создал окончательный расчет чистой прибыли, чтобы убедиться, что я все еще получаю прибыль. Если моя транзакция прошла симуляцию, и я все еще получал прибыль, то я отправлял ее на ретрансляцию flashbots для включения.