체인의정석

Uniswap V3 - SWAP 살펴보기 (Single Swap, Multihop Swap) 본문

블록체인/디파이

Uniswap V3 - SWAP 살펴보기 (Single Swap, Multihop Swap)

체인의정석 2023. 7. 14. 11:18
728x90
반응형

Uniswap V3관련 프로젝트에 들어가기 전에 어느정도 학습을 할 시간이 생겨서 유니스왑 v3를 따로 컨트렉트를 짜서 사용하는 방법에 대해서 한번 실습을 해보려고 한다.

https://docs.uniswap.org/contracts/v3/guides/local-environment

 

Set Up Your Local Environment | Uniswap

One of the most common questions we get asked is what development toolset to use to build on-chain integrations with Uniswap. There’s no right answer to this question but for this guide we’ll recommend a common one: Node.js , NPM and Hardhat.

docs.uniswap.org

위의 유니스왑 공식 다큐먼트가 설명이 잘 나와 있어 이걸 토대로 학습할 예정이다.

해당 다큐먼트는 유니스왑 코어에 대한 실습은 아니지만 유니스왑 V3의 함수들을 컨트렉트를 통해서 사용해 볼 수 있게 하였다. 

(어차피 프로젝트를 하더라도 컨트렉트 자체는 똑같이 포크를 할 것이며 , 나는 현재 백엔드 포지션이므로 각종 함수에 대한 내용을 이해하고 넘어가면 되는 상황이다. 그러므로 사실 이정도 학습만 하더라도 충분하다고 볼 수 있다.)

1. 환경설정하기

git clone https://github.com/Uniswap/uniswap-first-contract-example
cd uniswap-first-contract-example
npm install

다운받아보면 매우 간단한 코드가 들어있는데 노드 모듈로 이미 라이브러리와 인터페이스가 나와있다.

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

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

contract SimpleSwap {
   constructor() {
   }
}

이 중 SwapRouter의 인터페이스와 transferHelper라는 라이브러리를 가져와서 사용 중이다.

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

import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';

/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);

    struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);

    struct ExactOutputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}

인터페이스를 보면 exactInputSingle 이라는 함수는 ExaxtInputSingle 이라는 구조체를 받아서 스왑을 실행시켜준다.

유니스왑 V3에서는 V2와는 다르게 사용자들이 범위를 지정해서 들어가게 되기 때문에 여러 사용자들의 유동성들을 종합하여 계산을 하게 되고 유동성 공급자 마다 포지션이 제각각 다른 구조이다. 따라서 스왑할 때도 구조체로 여러 정보를 받아서 스왑을 하고 있음을 알 수 있다.

    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

먼저 이 구조체 부터 살펴보도록 하자

tokenIn, tokenOut 은 각각 input으로 넣을 토큰의 주소 output으로 넣을 토큰의 주소처럼 보인다.

fee의 경우 V3에서 차별화 된 부분인데 같은 유동성이라도 fee에 따라서 서로 다른 lp pool이 만들어지게 된다. 따라서 사실상 저 fee에 따라서 상호작용하는 풀의 CA가 바뀐다고 보면 된다.

recipient는 스왑을 진행할 때 수령자의 주소가 될 것이고

deadline은 해당 스왑이 유효한 시간, amountIn과 amountOutMinimum은 각 토큰에 대한 수량이 들어갈 것이다.

그리고 유니스왑 V3에서는 자료형을 더 효율적으로 관리하기 위해 루트 값을 씌워서 숫자형을 관리한다고 한다. 따라서 가냥 priceLimit이라고 보면 된다.

예제에서는 amountOutMinimum과 priceLimit 을 0으로 두지만 만약 슬리피지 같은걸 최소화 하고 싶으면 이 부분을 활성화 시키면 된다고 한다.

그리고 아래 비슷한 함수로 

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);

exapcInput 함수도 있는데 여기서는 path를 받는다. 만약 특정 경로를 거쳐야 하는 경우 위의 함수를 쓰는것 같다.

2.  Single Swaps (swapExactInputSingle, 

해당 부분은 찾아보니 유니스왑 공식 사이트에 자세한 설명이 있었다.

https://docs.uniswap.org/contracts/v3/guides/swaps/single-swaps

 

Single Swaps | Uniswap

Swaps are the most common interaction with the Uniswap protocol. The following example shows you how to implement a single-path swap contract that uses two functions that you create:

docs.uniswap.org

해석하기 귀찮으니 deepl 로 바로 번역

swapExactInputSingle 함수는 정해진 양의 토큰을 가능한 최대 양의 다른 토큰으로 교환하는 정확한 입력 스왑을 수행하기 위한 함수입니다. 이 함수는 ExactInputSingleParams 구조체와 ISwapRouter 인터페이스의 exactInputSingle 함수를 사용합니다.

swapExactOutputSingle 함수는 한 토큰의 최소 가능한 양을 다른 토큰의 정해진 양으로 교환하는 정확한 출력 스왑을 수행하기 위한 함수입니다. 이 함수는 ISwapRouter 인터페이스의 ExactOutputSingleParams 구조체와 exactOutputSingle 함수를 사용합니다.

그냥 입력스왑, 출력스왑의 차이 인것 같은데 일반적인 스왑은 입력스왑이라고 한다.

Single Swaps - swapExactInputSingle, swapExactOutputSingle

    /// @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);

일단 스왑을 하기 위해서는 해당 컨트렉트에 교환하려는 토큰을 받고 스왑을 실행시켜줄 라우터 주소에다가 approve를 시켜주어야 한다.

그리고 위에서 따로 해석해봤던 내용에 대한 정확한 설명도 나와있다.

tokenIn: 인바운드 토큰의 컨트랙트 주소

토큰아웃: 아웃바운드 토큰의 컨트랙트 주소

수수료: 스왑을 실행할 올바른 풀 컨트랙트를 결정하는 데 사용되는 풀의 수수료 계층입니다.

수신자: 아웃바운드 토큰의 목적지 주소

기한: 장기간 보류 중인 거래와 급격한 가격 변동으로부터 보호하기 위해 스왑이 실패하는 유닉스 시간입니다.

금액아웃최소값: 0으로 설정하고 있지만, 이는 프로덕션 환경에서 상당한 위험이 될 수 있습니다. 실제 배포의 경우, 이 값은 SDK 또는 온체인 가격 오라클을 사용하여 계산해야 하며, 이는 프론트 러닝 샌드위치 또는 다른 유형의 가격 조작으로 인해 거래 가격이 비정상적으로 나빠지는 것을 방지하는 데 도움이 됩니다.

sqrt가격제한X96: 이 매개변수를 0으로 설정하면 이 매개변수는 비활성화됩니다. 프로덕션에서 이 값은 스왑이 풀을 푸시할 가격의 한도를 설정하는 데 사용할 수 있으며, 가격 영향으로부터 보호하거나 다양한 가격 관련 메커니즘의 로직을 설정하는 데 도움이 될 수 있습니다.

대략 봐도 위와 일맥상통한 내용이다.

        // 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);
    }

자 그럼 해당 값들을 파라미터로 넣게 되면? 위와 같이 넣을 수 있다. 물론 해당 구조체를 만드는 행위는 굳이 컨트렉트가 아니더라도 프론트에서 구조체 형태로 넘기면 잘 작동이 될 것이다.

아무튼 구조체를 params라는 변수에 담아서 expactInputSingle 함수를 실행시키면 amountOut이 나오게 되는데 이게 실제로 유저가 받는 토큰의 양이라고 보면 된다.

Single Swaps - swapExactOutputSingle

번역기를 돌려보니

정확한 출력은 가능한 최소한의 입력 토큰을 고정된 양의 아웃바운드 토큰으로 교환합니다. 이는 덜 일반적인 스왑 스타일이지만 다양한 상황에서 유용합니다.

이 예시에서는 스왑을 예상하여 인바운드 자산을 전송하기 때문에 스왑이 실행된 후 인바운드 토큰의 일부가 남을 수 있으므로 스왑이 끝날 때 호출 주소에 다시 지불합니다.

이렇게 나오는데 

/// @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);
        }
    }

코드를 보니 이건 amountOut에게 기준이 맞추어져 있기 때문에 스왑을 실행하고 받는 결과 값이 amountIn이 나오게 된다.

근데 내가 원하는 output 양을 받고 남은 amount In이 내가 지불한 amount In maximum보다 작다면  차액만큼은 나한테 돌려주는 식으로 만들 수 있다.

한마디로 입력 스왑은 그냥 일반적으로 내가 입력한 값에 따라서 나오는 출력만큼을 받고 끝내는 케이스고

출력 스왑은 내가 받고자 하는 양에 맞추어서 입력값을 넉넉히 넣어주고 남은건 나한테 돌려주는 식으로 활용이 가능한 케이스라고 할 수 있다.

전체코드

// 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);
        }
    }
}

전체코드는 다음과 같다.

 

3.  Multihop Swap

싱글 스왑이 아닌 멀티 홉 스왑에 대해서도 살펴보도록 하겠다.

https://docs.uniswap.org/contracts/v3/guides/swaps/multihop-swaps

 

Multihop Swaps | Uniswap

Introduction

docs.uniswap.org

swapExactInputMultihop

중간에 다른 경로를 포함하여 스왑하는 것이 multihop 스왑이다.

해당 함수 역시 ExactInputParams 라는 이름의 구조체를 입력값으로 받고 있다.

여기서 차이점은 path , 즉 경로가 포함되어 있다는 것인데 경로에서 v2와의 차이점은 수수료 별로 lp 토큰의 주소가 다르기 때문에 토큰 주소와 함께 수수료도 같이 넣어주어야 경로를 찾아갈 수 있는 구조이다.

경로: 경로는 스왑 시퀀스에서 각 풀 컨트랙트 주소를 계산하는 데 필요한 변수인 (토큰 주소 - 수수료 - 토큰 주소)의 시퀀스입니다. 멀티홉 스왑 라우터 코드는 이러한 변수가 있는 올바른 풀을 자동으로 찾고 시퀀스의 각 풀에서 필요한 스왑을 실행합니다.

수신자: 아웃바운드 자산의 목적지 주소입니다.

기한: 트랜잭션이 되돌아가는 유닉스 시간으로, 긴 지연과 그로 인한 큰 가격 변동 가능성을 방지하기 위해 설정합니다.

금액인: 인바운드 자산의 금액입니다.

금액아웃 최소: 아웃바운드 자산의 최소 금액으로, 이보다 작으면 트랜잭션을 되돌릴 수 있습니다. 이 예제에서는 0으로 설정하지만, 프로덕션 환경에서는 SDK를 사용하여 예상 가격을 제시하거나 고급 조작 방지 시스템을 위해 온체인 가격 오라클을 사용해야 합니다.

 

    /// @notice swapExactInputMultihop swaps a fixed amount of DAI for a maximum possible amount of WETH9 through an intermediary pool.
    /// For this example, we will swap DAI to USDC, then USDC to WETH9 to achieve our desired output.
    /// @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 amount of DAI to be swapped.
    /// @return amountOut The amount of WETH9 received after the swap.
    function swapExactInputMultihop(uint256 amountIn) external returns (uint256 amountOut) {
        // Transfer `amountIn` 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);

        // Multiple pool swaps are encoded through bytes called a `path`. A path is a sequence of token addresses and poolFees that define the pools used in the swaps.
        // The format for pool encoding is (tokenIn, fee, tokenOut/tokenIn, fee, tokenOut) where tokenIn/tokenOut parameter is the shared token across the pools.
        // Since we are swapping DAI to USDC and then USDC to WETH9 the path encoding is (DAI, 0.3%, USDC, 0.3%, WETH9).
        ISwapRouter.ExactInputParams memory params =
            ISwapRouter.ExactInputParams({
                path: abi.encodePacked(DAI, poolFee, USDC, poolFee, WETH9),
                recipient: msg.sender,
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: 0
            });

        // Executes the swap.
        amountOut = swapRouter.exactInput(params);
    }

path를 보면 DAI - USDC - WETH9 을 거쳐서 스왑을 하는 것을 볼 수 있다.

swapExactOutputMultihop

싱글때의 output과 유사하지만 path의 순서를 반대로 입력해 준다는 점이 유의사항 인 것 같다.

경로: 경로는 역순으로 인코딩된 토큰 주소 수수료 토큰 주소의 시퀀스이며, 스왑 시퀀스에서 각 풀 컨트랙트 주소를 계산하는 데 필요한 변수입니다. 멀티홉 스왑 라우터 코드는 이러한 변수를 사용하여 올바른 풀을 자동으로 찾고, 시퀀스의 각 풀에서 필요한 스왑을 실행합니다.

수신자: 아웃바운드 자산의 목적지 주소입니다.

기한: 긴 지연과 이로 인한 큰 가격 변동 가능성을 방지하기 위해 트랜잭션이 되돌려지는 유닉스 시간입니다.

금액아웃: 원하는 WETH9 수량입니다.

금액인최대: 지정된 금액Out의 WETH9로 교환하고자 하는 최대 금액의 DAI입니다.

구조체는 위와 같이 구성되어 있고

    /// @notice swapExactOutputMultihop swaps a minimum possible amount of DAI for a fixed amount of WETH through an intermediary pool.
    /// For this example, we want to swap DAI for WETH9 through a USDC pool but we specify the desired amountOut of WETH9. Notice how the path encoding is slightly different in for exact output swaps.
    /// @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 desired amount of WETH9.
    /// @param amountInMaximum The maximum amount of DAI willing to be swapped for the specified amountOut of WETH9.
    /// @return amountIn The amountIn of DAI actually spent to receive the desired amountOut.
    function swapExactOutputMultihop(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
        // Transfer the specified `amountInMaximum` to this contract.
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum);
        // Approve the router to spend  `amountInMaximum`.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);

        // The parameter path is encoded as (tokenOut, fee, tokenIn/tokenOut, fee, tokenIn)
        // The tokenIn/tokenOut field is the shared token between the two pools used in the multiple pool swap. In this case USDC is the "shared" token.
        // For an exactOutput swap, the first swap that occurs is the swap which returns the eventual desired token.
        // In this case, our desired output token is WETH9 so that swap happens first, and is encoded in the path accordingly.
        ISwapRouter.ExactOutputParams memory params =
            ISwapRouter.ExactOutputParams({
                path: abi.encodePacked(WETH9, poolFee, USDC, poolFee, DAI),
                recipient: msg.sender,
                deadline: block.timestamp,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum
            });

        // Executes the swap, returning the amountIn actually spent.
        amountIn = swapRouter.exactOutput(params);

        // If the swap did not require the full amountInMaximum to achieve the exact amountOut then we refund msg.sender and approve the router to spend 0.
        if (amountIn < amountInMaximum) {
            TransferHelper.safeApprove(DAI, address(swapRouter), 0);
            TransferHelper.safeTransferFrom(DAI, address(this), msg.sender, amountInMaximum - amountIn);
        }
    }

코드를 보면 아까랑 경로가 반대인 것을 볼 수 있다. 이렇게 해야 처음에 보낸 토큰의 가격을 알 수 있고 토큰이 남게 된다면 체크를 해서 다시 돌려주게 된다.

728x90
반응형
Comments