Как стать автором
Обновить

Uniswap v3 Single Swaps (перевод гайда)

Время на прочтение8 мин
Количество просмотров4.1K
Автор оригинала: https://docs.uniswap.org/protocol/guides/swaps/single-swaps

Документация содержит хорошие подробные туториалы,ниже представлен перевод

Single Swaps

Проведение операций обмена(свопы) — самое распростроненное использование Uniswap. В данном примере рассмотрим, как имплементировать single-path swap в смарт-контрактах. Контракт будет использовать две функции,которые мы поэтапно создадим.

  • swapExactInputSingle(...)

  • swapExactOutputSingle(...)

swapExactInputSingle — обменивает фиксированное количество одного токена на максимально возможное количество другого токена.( fix one → max another )

Эта функция задействует структуру ExactInputSingleParams и функцию exactInputSingle(...)из интерфейса ISwapRouter

swapExactOutputSingle — обменивает минимально возможное количество одного токена на фиксированное количество другого токена. ( Min one → fix another )

Эта функция задействует структуру ExactOutputSingleParams и функцию exactOutputSingle(...) из интерфейса ISwapRouter.

Для упрощения примера,адреса контрактов токенов (тут DAI и WETH9) захардкодили. Очевидно,что контракт можно модифицировать так, чтобы изменять пулы и токены для каждой транзакции.

Set Up the Contract

abicoder v2, позволяет кодировать и декодировать произвольные вложенные массивы и структуры в calldata.

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

Импортируем необходимые пакетики (через npm или yarn)

import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';

Cсоздаем контракт SwapExamples, и объявляем immutable public переменную swapRouter типа ISwapRouter. Это позволит нам использовать функции в интерфейсе ISwapRouter.

contract SwapExamples {
    // For the scope of these swap examples,
    // we will detail the design considerations when using `exactInput`, `exactInputSingle`, `exactOutput`, and  `exactOutputSingle`.
    // It should be noted that for the sake of these examples we pass in the swap router as a constructor argument instead of inheriting it.
    // More advanced example contracts will detail how to inherit the swap router safely.
    // This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.

    ISwapRouter public immutable swapRouter;

Для упрощения примера хардкодим адреса контрактов токенов и уровни комиссий пула.

    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

    // For this example, we will set the pool fee to 0.3%.
    uint24 public constant poolFee = 3000;

    constructor(ISwapRouter _swapRouter) {
        swapRouter = _swapRouter;
    }

Exact Input Swaps

Вызывающий(условный пользователь) должен утвердить контракт на снятие токенов со счета вызывающего адреса для выполнения свопа. "Утвердить" означает вызвать на нем функцию approvе.Помните, поскольку наш контракт это сам по себе контракт, а не расширение вызывающей стороны(нас) то, мы также должны одобрить(утвердить) контракт Uniswap protocol routerдля использования токенов, которыми будет владеть наш контракт после того, как они будут сняты с вызывающего адреса (нас).

Немного сложновато,но при вдумчивом прочтении все становится на свои места.

В заключение, переводим amount of DAI с адреса вызывающего на адрес нашего контракта.Значение amount должно быть передано во второй approve.

В коде все перечисленное выглядит проще:

/// @notice swapExactInputSingle swaps a fixed amount of DAI for a maximum possible amount of WETH
/// using the DAI/WETH9 0.3% pool by calling `exactInputSingle` in the swap router.
/// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.
/// @param amountIn The exact amount of DAI that will be swapped for WETH9.
/// @return amountOut The amount of WETH9 received.
    function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {
        // msg.sender must approve this contract

        // Transfer the specified amount of DAI to this contract.
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);

        // Approve the router to spend DAI.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);

Swap Input Parameters

Что бы выполнить функцию свопа нам необходимо снабдить структуру ExactInputSingleParamsнеобходимой для свопа информацией.

  • tokenIn адрес контракта входящего токена

  • tokenOutадрес контракта исходящего токена

  • feeУровень комиссии пула, используется для определения правильности контракта пула, в котором выполняется своп.

  • recipient адрес назначения исходящего токена(или адресс получателя)

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

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

  • sqrtPriceLimitX96Мы устанавливаем в 0 — это делает этот парамент неактивным.В продакшене, это значение можно использовать для установки предела цены, по которой своп будет проходить в пуле.

Call the function

// Naively set amountOutMinimum to 0. In production, use an oracle or other data source to choose a safer value for amountOutMinimum.
// We also set the sqrtPriceLimitx96 to be 0 to ensure we swap our exact input amount.
        ISwapRouter.ExactInputSingleParams memory params =
            ISwapRouter.ExactInputSingleParams({
                tokenIn: DAI,
                tokenOut: WETH9,
                fee: poolFee,
                recipient: msg.sender,
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

        // The call to `exactInputSingle` executes the swap.
        amountOut = swapRouter.exactInputSingle(params);
    }

Exact Output Swaps

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

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

/// @notice swapExactOutputSingle swaps a minimum possible amount of DAI for a fixed amount of WETH.
/// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,
/// the calling address will need to approve for a slightly higher amount, anticipating some variance.
/// @param amountOut The exact amount of WETH9 to receive from the swap.
/// @param amountInMaximum The amount of DAI we are willing to spend to receive the specified amount of WETH9.
/// @return amountIn The amount of DAI actually spent in the swap.
function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
// Transfer the specified amount of DAI to this contract.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);

        // Approve the router to spend the specified `amountInMaximum` of DAI.
        // In production, you should choose the maximum amount to spend based on oracles or other data sources to achieve a better swap.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);

        ISwapRouter.ExactOutputSingleParams memory params =
            ISwapRouter.ExactOutputSingleParams({
                tokenIn: DAI,
                tokenOut: WETH9,
                fee: poolFee,
                recipient: msg.sender,
                deadline: block.timestamp,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum,
                sqrtPriceLimitX96: 0
            });

        // Executes the swap returning the amountIn needed to spend to receive the desired amountOut.
        amountIn = swapRouter.exactOutputSingle(params);

        // For exact output swaps, the amountInMaximum may not have all been spent.
        // If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.
        if (amountIn < amountInMaximum) {
            TransferHelper.safeApprove(DAI, address(swapRouter), 0);
            TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);
        }
    }

Полный код примера

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';

contract SwapExamples {
    // For the scope of these swap examples,
    // we will detail the design considerations when using
    // `exactInput`, `exactInputSingle`, `exactOutput`, and  `exactOutputSingle`.

    // It should be noted that for the sake of these examples, we purposefully pass in the swap router instead of inherit the swap router for simplicity.
    // More advanced example contracts will detail how to inherit the swap router safely.

    ISwapRouter public immutable swapRouter;

    // This example swaps DAI/WETH9 for single path swaps and DAI/USDC/WETH9 for multi path swaps.

    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

    // For this example, we will set the pool fee to 0.3%.
    uint24 public constant poolFee = 3000;

    constructor(ISwapRouter _swapRouter) {
        swapRouter = _swapRouter;
    }

    /// @notice swapExactInputSingle swaps a fixed amount of DAI for a maximum possible amount of WETH9
    /// using the DAI/WETH9 0.3% pool by calling `exactInputSingle` in the swap router.
    /// @dev The calling address must approve this contract to spend at least `amountIn` worth of its DAI for this function to succeed.
    /// @param amountIn The exact amount of DAI that will be swapped for WETH9.
    /// @return amountOut The amount of WETH9 received.
    function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {
        // msg.sender must approve this contract

        // Transfer the specified amount of DAI to this contract.
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);

        // Approve the router to spend DAI.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);

        // Naively set amountOutMinimum to 0. In production, use an oracle or other data source to choose a safer value for amountOutMinimum.
        // We also set the sqrtPriceLimitx96 to be 0 to ensure we swap our exact input amount.
        ISwapRouter.ExactInputSingleParams memory params =
            ISwapRouter.ExactInputSingleParams({
                tokenIn: DAI,
                tokenOut: WETH9,
                fee: poolFee,
                recipient: msg.sender,
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

        // The call to `exactInputSingle` executes the swap.
        amountOut = swapRouter.exactInputSingle(params);
    }

    /// @notice swapExactOutputSingle swaps a minimum possible amount of DAI for a fixed amount of WETH.
    /// @dev The calling address must approve this contract to spend its DAI for this function to succeed. As the amount of input DAI is variable,
    /// the calling address will need to approve for a slightly higher amount, anticipating some variance.
    /// @param amountOut The exact amount of WETH9 to receive from the swap.
    /// @param amountInMaximum The amount of DAI we are willing to spend to receive the specified amount of WETH9.
    /// @return amountIn The amount of DAI actually spent in the swap.
    function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
        // Transfer the specified amount of DAI to this contract.
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);

        // Approve the router to spend the specifed `amountInMaximum` of DAI.
        // In production, you should choose the maximum amount to spend based on oracles or other data sources to acheive a better swap.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);

        ISwapRouter.ExactOutputSingleParams memory params =
            ISwapRouter.ExactOutputSingleParams({
                tokenIn: DAI,
                tokenOut: WETH9,
                fee: poolFee,
                recipient: msg.sender,
                deadline: block.timestamp,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum,
                sqrtPriceLimitX96: 0
            });

        // Executes the swap returning the amountIn needed to spend to receive the desired amountOut.
        amountIn = swapRouter.exactOutputSingle(params);

        // For exact output swaps, the amountInMaximum may not have all been spent.
        // If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.
        if (amountIn < amountInMaximum) {
            TransferHelper.safeApprove(DAI, address(swapRouter), 0);
            TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);
        }
    }
}

Примечание:

https://github.com/Uniswap/v3-core

https://github.com/Uniswap/v3-periphery

Контракты, импортируемые пакетными менеджерами, содержат старые версии солидити.Если вы пишете свои контракты с solidity ^0.8.0,то нужно импортировать ветку 0.8 в офицальном гитхабе Uniswap (по ссылочкам выше,заходим в ветки и выбираем 0.8.0).Это обязательно!!!!!! В противном случае вы словите конфликт с заиморченым контрактом ERC721 openzeppelin.

команда для импорта ветки под Linux выглядит так :

npm install "https://github.com/Uniswap/v3-core.git#0.8" --save

После этого в package.json должны появиться такие строчки:

"dependencies": {
    "@uniswap/v3-core": "github:Uniswap/v3-core#0.8",
    "@uniswap/v3-periphery": "github:Uniswap/v3-periphery#0.8",
    "git": "^0.1.5"
  }

Теги:
Хабы:
Всего голосов 3: ↑3 и ↓0+3
Комментарии0

Публикации

Истории

Работа

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань