Третья статья в нашей серии, посвященная режиму Fusion, посвящена компоненту разрешения свопов внутри цепочки.

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

Давайте продолжим с того места, где остановились. Мы находимся на этапе процесса разрешения свопа, когда бэкенд резолвера «решил» выполнить заказ Fusion, полученный от сервиса 1inch relayer, в определенном блоке, с определенным количеством обмененного актива, который будет возвращен пользователю. Теперь мы рассмотрим часть процесса разрешения свопа в цепочке. Но сначала мы опишем участников этого процесса.

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

Очень важное замечание: в некоторых случаях (не всегда) резолвер выполняет несколько заполнений в одном пакете, до 32. Обычно в рабочем контракте резолвера имеется несколько балансов токенов, и бэкэнд может взять несколько заказов из «потока», который предоставляет ретранслятор, и выполнить последовательность заполнений.

Давайте рассмотрим следующий сценарий.

Решатель выбрал 3 потенциально прибыльных заказа от ретранслятора для выполнения:

  • 100 DAI для минимум 0,6 WETH

  • 0,6 WETH для не менее 100 DAI

  • 0,01 WBTC для не менее 36 UNI

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

Теперь бэкэнд предоставляет информацию о выбранных заказах в контракт работника. Что происходит дальше?

NB: эта диаграмма является продолжением диаграммы из статьи, посвященной оффчейн-компоненте разрешения свопов. Но для простоты понимания мы начнем с шага 1.

Шаг 1

Рабочий контракт (или кошелек) вызывает метод settleOrders() расчетного контракта 1inch, доставляя информацию о заказе в облегченном сжатом формате байтов; аргументы calldata и tokensAndAmounts используются для хранения этой информации.

Здесь вы можете заметить несколько интересных деталей:

  • rateBump исходит от котировщика и фактически определяет возврат. Это процентная разница между текущей суммой аукциона и минимальной суммой аукциона. Например, если значение rateBump равно 4_000_000, а auctionEndAmount (мин. возврат) равно 500, то текущая сумма, принимаемая на аукционе, равна 700.

  • totalFee — это комиссия, которую все резолверы должны заплатить в фонд 1inch Foundation (Важно: в настоящее время эта функция не используется — резолверы не платят никаких комиссий в фонд 1inch Foundation).

  • limitOrderProtocol — это экземпляр протокола, который объявлен в расчетном контракте через соответствующий интерфейс Solidity. Он используется для вызова этого контракта из расчетного контракта.

Шаг 2

Контракт на расчет вызывает метод fillOrderTo() контракта лимитного ордера, предоставляя данные одного из 3 ордеров из списка — скажем, 100 DAI за не менее 0,6 WETH:

  • Взаимодействие — содержит информацию о том, что в массиве есть еще 2 заказа, которые необходимо будет выполнить впоследствии.

  • Создание или получение сумм — буквально, сколько заплатить или сколько получить. Только одна из этих сумм может быть ненулевой. Если вы устанавливаете makingAmount, вы указываете, сколько вы хотели бы получить как решатель. В качестве альтернативы, устанавливая takingAmount, вы определяете, сколько вы хотели бы продать как решатель. Одно будет рассчитано на основе другого.

  • Цель — адрес рабочего контракта, который будет получать исходные токены.

Шаги 3-4

Контракт лимитного ордера вызывает исходный контракт токена для перевода суммы свопа от пользователя к рабочему контракту распознавателя, используя интерфейс ERC20 Solidity.

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

Шаги 5-6

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

Если пакет не содержит больше заказов (сценарий 1 ниже), он завершает взаимодействие и вызывает рабочий контракт решателя, «приказывая» ему разрешить заказы. Это будет возможно, поскольку рабочий контракт импортирует наш интерфейс, называемый IResolver. В Solidity кросс-контрактные взаимодействия обычно выполняются таким образом. Мы рассмотрим, что делает рабочий на шагах 16–17 ниже.

Если в пакете остаются другие заказы, расчетный контракт снова вызывает контракт лимитного заказа. Два контракта продолжат обмениваться данными до тех пор, пока все заказы не будут урегулированы и все средства не будут собраны со счетов пользователей (100 DAI, 0,6 WETH и 0,01 WBTC в нашем примере).

Шаг 7

Мы почти закончили. Контракт worker-resolver получил все средства от пользователей через 1inch settlement и limit order contract. Теперь пришло время фактически разрешить ордера!

Вот свойства заказов:

  • Заказ 1: 100 DAI минимум на 0,6 WETH

  • Заказ 2: 0,6 WETH на сумму не менее 100 DAI

  • Заказ 3: 0,01 WBTC за не менее 36 UNI

Похоже, мы можем буквально сопоставить заказ 1 и заказ 2 без каких-либо дополнительных действий. Поэтому мы просто безопасно отправим 0,6 WETH, собранных с пользователя, который разместил заказ 2, пользователю, который разместил заказ 1, и наоборот. Таким образом, заказы 2 из 3 были решены с использованием собственных средств пользователей.

Последний оставшийся заказ — это своп 0,01 WBTC на 36 UNI. Рабочий контракт не имеет ни одного UNI на своем балансе. Таким образом, рабочий контракт вызывает контракт маршрутизатора агрегации 1inch так же, как это делает любой пользователь в устаревшем свопе. Бэкэнд резолвера может вызвать наш API агрегации, чтобы получить оптимальный маршрут и передать его рабочему для выполнения. Маршрутизатор выполняет этот своп и доставляет 36 UNI резолверу.

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

Шаги 8-11

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

Нет, не могут из-за способа реализации архитектуры. Помните, шаг 5 этого потока — это обратный вызов метода fillOrderTo(), вызываемого расчетным контрактом для лимитного ордера. Таким образом, функция все еще выполняется!

Поскольку вызывающей стороной был расчетный контракт, в этой конфигурации он считается принимающим ордер. Следовательно, этот экземпляр должен получить ответ от решателя и переданные токены. Затем он предоставляет одобрение контракту лимитного ордера, который, в свою очередь, вызывает transferFrom() для контракта целевого токена, и сумма свопа наконец попадает в кошелек пользователя. Готово!

Реальный пример Etherscan

В качестве последнего упражнения давайте рассмотрим реальный случай обмена Fusion: 1INCH на DAI, разрешенный Seawise resolver, и его переводы токенов ERC20. Для лучшего опыта вам нужно сопоставить изображение ниже с диаграммой выше, сопоставив шаги.

На шаге 4 контракт лимитного ордера вызывает transferFrom() для контракта токена 1INCH, чтобы перевести исходный токен с адреса создателя (0x90…9044) на адрес распознавателя.

Шаги 5 и 6 не отражены в этом списке, поскольку они не подразумевают никаких передач токенов ERC20.

На шаге 7 Seawise в качестве решателя обменивает 1INCH на DAI в пуле Uniswap в качестве источника ликвидности.

На шаге 8 Seawise переводит DAI в расчетный контракт на 1 дюйм.

Шаги 9 и 10 здесь также пропускаются, поскольку это внутренние ответы обратного вызова между распознавателем и контрактами 1inch.

Наконец, на шаге 11 расчетный контракт 1inch передает целевые токены создателю.

Не стесняйтесь изучить эту транзакцию на Etherscan:

https://etherscan.io/tx/0x55e621337837f4f69f0c398ad5e9072a24811bbfd8cb2b208d621b940c9689b5