Недавно я писал учебник по разработке децентрализованной биржи https://github.com/WTFAcademy/WTF-Dapp, основываясь на коде Uniswap V3, я узнал много полезных вещей. Ранее я разрабатывал простые NFT контракты, но это моя первая попытка разработать Defi контракт, уверен, что эти маленькие советы будут очень полезны тем, кто хочет изучить разработку контрактов.
Разработчики контрактов могут сразу перейти по адресу https://github.com/WTFAcademy/WTF-Dapp и внести свой вклад в код, чтобы помочь Web3 развиваться~
Теперь давайте посмотрим на эти маленькие советы, некоторые из которых можно назвать хитростями.
Адреса контрактов, развернутых контрактом, можно предсказать
Обычно адреса контрактов, которые мы получаем при развертывании, выглядят случайными, потому что они связаны с « nonce », поэтому адреса контрактов трудно предсказать. Но в Uniswap у нас есть такая необходимость: необходимо вывести адрес контракта, основываясь на торговой паре и соответствующей информации. Это очень полезно в многих случаях, например, для проверки разрешений на торговлю или получения адреса пула и т.д.
В Uniswap контракт создается с помощью кода « pool = address(new Uniswap V3 Pool{salt: keccak 256(abi.encode(token 0, token 1, fee))}()); ». Добавление « salt » позволяет использовать CREATE 2 (https://github.com/AmazingAng/WTF-Solidity/blob/main/25_Create2/readme.md), что позволяет предсказать адрес созданного контракта. Логика генерации адреса — « Новый адрес = hash("0x FF", адрес создателя, salt, initcode) ».
Эту часть вы можете посмотреть в главе курса WTF-DApp по адресу https://github.com/WTFAcademy/WTF-Dapp/blob/main/P103_Factory/readme.md, чтобы узнать больше.
Умело используйте функции обратного вызова
В Solidity контракты могут вызывать друг друга. Одним из сценариев является ситуация, когда A вызывает метод B, а B в вызываемом методе вызывает A, что также может быть полезно в определенных ситуациях.
В Uniswap, когда вы вызываете метод « swap » контракта « Uniswap V3 Pool » для торговли, он вызывает обратный вызов « swapCallback », который передает вычисленный необходимый для торговли « Token ». Вызывающая сторона должна передать токены, необходимые для торговли, в « Uniswap V3 Pool » в обратном вызове, а не разбирать метод « swap » на две части, чтобы вызыватель мог его вызвать, что обеспечивает безопасность метода « swap » и гарантирует, что вся логика выполняется полностью, без необходимости в сложной записи переменных для обеспечения безопасности.
Фрагмент кода выглядит следующим образом:
Вы можете изучить часть курса, касающуюся торговли, чтобы узнать больше на https://github.com/WTFAcademy/WTF-Dapp/blob/main/P106_PoolSwap/readme.md.
Используйте исключения для передачи информации, используйте try catch для оценки торговли
При изучении кода Uniswap мы заметили, что в его контракте https://github.com/Uniswap/v3-periphery/blob/main/contracts/lens/Quoter.sol метод « swap » « Uniswap V3 Pool » выполняется с использованием конструкции « try catch »:
Почему так? Потому что нам нужно смоделировать метод « swap », чтобы оценить необходимые токены для торговли, но поскольку при оценке фактический обмен токенами не происходит, возникает ошибка. В Uniswap это обрабатывается путем выбрасывания специальной ошибки в функции обратного вызова торговли, а затем захвата этой ошибки, чтобы извлечь необходимые данные из сообщения об ошибке.
Выглядит довольно хакерски, но и очень удобно. Таким образом, нам не нужно модифицировать метод swap для оценки торговли, логика становится проще. В нашем курсе мы также обратились к этой логике и реализовали контракт по адресу https://github.com/WTFAcademy/WTF-Dapp/blob/main/demo-contract/contracts/wtfswap/SwapRouter.sol.
Используйте большие числа для решения проблем с точностью
В коде Uniswap есть много логики вычислений, например, как вычислить токены для обмена на основе текущей цены и ликвидности, и в этом процессе мы должны избегать потери точности при делении. В Uniswap в процессе вычислений часто используется операция « << FixedPoint 96.RESOLUTION », которая представляет собой сдвиг влево на 96 бит, что эквивалентно умножению на « 2 ^ 96 ». После сдвига мы выполняем деление, что позволяет гарантировать точность без выхода за пределы при нормальных сделках (обычно используется « uint 256 » для расчетов, что достаточно).
Код如下 (вычисление количества токенов, необходимых для биржи, на основе цены и ликвидности):
Как видно, сначала в Uniswap цена представлена как квадратный корень, умноженный на « 2 ^ 96 » (соответствует « sqrtRatioAX 96 » и « sqrtRatioBX 96 » в приведенном выше коде), затем ликвидность « liquidity » сдвигается влево, чтобы вычислить « numerator 1 ». В последующих вычислениях « 2 ^ 96 » будет сокращен, чтобы получить окончательный результат.
Конечно, теоретически всегда будет потеря точности, но такая ситуация — это минимальная потеря единицы, что приемлемо.
Больше информации вы можете узнать на https://github.com/WTFAcademy/WTF-Dapp/blob/main/P106_PoolSwap/readme.md.
Используйте способ Share для расчета дохода
В Uniswap нам нужно фиксировать доходы от комиссий ликвидных поставщиков (LP). Очевидно, что мы не можем фиксировать комиссии для каждого LP при каждой сделке, так как это потребует много газа. Как это обработать?
В Uniswap мы можем увидеть, что в « Position » определена следующая структура:
Среди них есть « feeGrowthInside0LastX128 и feeGrowthInside1LastX128 », которые фиксируют, сколько комиссии каждый ликвидный участник должен получить при последнем извлечении комиссии для каждой позиции (Position).
Проще говоря, мне нужно просто зафиксировать общую комиссию и сколько комиссии должен получить каждый ликвидный участник, так что когда LP извлекает комиссию, он может рассчитать, сколько он может получить на основе своей ликвидности. Это похоже на то, как если бы вы владели акциями какой-то компании, вам нужно узнать историческую доходность на акцию и доходность при последнем извлечении, чтобы получить свои дивиденды.
Ранее мы в статье (Изящный дизайн контракта: как stETH автоматически распределяет доходы по дням? Позвольте вашему ETH участвовать в ставках для получения стабильного процента) также упоминали метод вычисления дохода stETH, это аналогично.
Не вся информация должна быть получена из цепочки
Хранение на блокчейне относительно дорого, поэтому не вся информация должна быть на блокчейне или получаться из него. Например, многие интерфейсы, используемые на фронтенде Uniswap, являются традиционными интерфейсами Web2.
Списки пулов для торговли, информация о пулах и т.д. могут храниться в обычных базах данных, некоторые могут потребовать периодической синхронизации с блокчейном, но нам не нужно постоянно вызывать цепочку или RPC интерфейсы, предоставляемые узловыми службами, для получения соответствующих данных.
Конечно, сейчас многие поставщики блокчейна PRC предлагают некоторые продвинутые интерфейсы, вы можете быстрее и дешевле получать некоторые данные, это аналогично. Например, ZAN предоставляет интерфейс для получения всех NFT у конкретного пользователя, и эта информация явно может быть улучшена за счет кэширования для повышения производительности и эффективности, вы можете перейти по адресу https://zan.top/service/advance-api для получения более подробной информации.
Конечно, ключевые сделки обязательно происходят на блокчейне.
Научитесь разрабатывать контракты по частям, также научитесь использовать уже существующие стандартные контракты, такие как ERC 721
Проект может включать несколько фактически развернутых контрактов, даже если фактическое развертывание включает только один контракт, мы можем разделить контракт на несколько контрактов с помощью наследования для удобства обслуживания.
Например, в Uniswap, контракт https://github.com/Uniswap/v3-periphery/blob/main/contracts/NonfungiblePositionManager.sol наследует много контрактов, код выглядит следующим образом:
Кроме того, когда вы смотрите реализацию контракта « ERC 721 Permit », вы заметите, что он напрямую использует контракт « @openzeppelin/contracts/token/ERC 721/ERC 721.sol », что, с одной стороны, удобно для управления позициями с помощью NFT, а с другой стороны, позволяет использовать существующие стандартные контракты для повышения эффективности разработки контрактов.
В нашем курсе вы можете изучить https://github.com/WTFAcademy/WTF-Dapp/blob/main/P 108 _PositionManager /readme.md и попробовать разработать простой ERC 721 контракт для управления позициями.
Итог
Сколько бы статей вы ни прочитали, ничто не сравнится с тем, чтобы самостоятельно заняться разработкой. В процессе попытки реализовать упрощенную версию децентрализованной биржи вы сможете глубже понять реализацию кода Uniswap и узнать больше о знаниях, которые можно будет применить в реальных проектах.
Курс WTF-DApp является совместным проектом сообщества разработчиков ZAN и сообщества разработчиков WTF Academy. Если вы также интересуетесь Web3 и разработкой проектов Defi, вы можете ознакомиться с нашим практическим курсом https://github.com/WTFAcademy/WTF-Dapp, чтобы шаг за шагом создать упрощенную версию биржи, что, безусловно, будет вам полезно~
Эта статья написана Fisher из команды ZAN (X аккаунт @zan_team) и Yudao (X аккаунт @yudao 1024).