상세 컨텐츠

본문 제목

UniswapV2 periphery - Uniswapv2Router.sol 코드 분석

Blockchain/DeFi

by Yongari 2023. 3. 17. 15:43

본문

 

 

 

UniswapV2Router.sol의 핵심함수

 

 

swapExactTokensForTokens: 사용자가 정확한 양의 입력 토큰을 정확한 양의 출력 토큰으로 스왑할 수 있으며, 수수료 수취인을 선택적으로 지정할 수 있습니다.

swapTokensForExactTokens: 사용자가 거래를 이행하는 데 필요한 만큼의 입력 토큰으로 정확한 양의 출력 토큰을 교환할 수 있으며, 수수료 수취인을 선택적으로 지정할 수 있습니다.

swapExactETHForTokens: 사용자가 수수료 수취인(선택 사항)을 지정하여 정확한 양의 이더리움을 지정된 양의 출력 토큰으로 교환할 수 있습니다.

swapTokensForExactETH: 사용자가 수수료 수취인(옵션)과 함께 입력 토큰을 정확한 양의 이더리움으로 교환할 수 있도록 허용합니다.

swapExactTokensForETH: 사용자가 수수료 수취인(옵션)과 함께 정확한 양의 입력 토큰을 지정된 양의 이더리움으로 교환할 수 있습니다.

swapETHForExactTokens: 사용자가 수수료 수취인(옵션)을 지정하여 지정된 금액의 이더리움을 지정된 금액의 출력 토큰으로 스왑할 수 있도록 허용합니다.

addLiquidity: 사용자가 동일한 가치의 두 토큰을 예치하고 그 대가로 LP 토큰을 받아 유니스왑 풀에 유동성을 공급할 수 있습니다.

removeLiquidity: 사용자가 유니스왑 풀에서 유동성을 제거하고 풀에 예치된 두 토큰의 동등한 가치를 유동성 풀의 지분에 비례하여 돌려받을 수 있습니다.

removeLiquidityWithPermit: 제거유동성과 유사하지만, 사용자가 라우터가 유동성 토큰을 사용하도록 승인하기 위해 EIP-712 허가서에 서명해야 합니다.

addLiquidityETH: 추가 유동성과 유사하지만, 사용자가 유동성을 제공하기 위해 지정된 ERC20 토큰과 함께 이더를 예치할 수 있습니다.

removeLiquidityETH: removeLiquidity와 유사하지만, 사용자가 유니스왑 풀에서 지정된 ERC20 토큰과 함께 이더를 인출할 수 있습니다.

removeLiquidityETHWithPermit: 제거유동성과 유사하지만, 사용자가 유니스왑 풀에서 지정된 ERC20 토큰과 함께 이더를 출금할 수 있도록 허용합니다.

 

 

pragma solidity =0.6.6;

import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import './interfaces/IUniswapV2Router02.sol';
import './libraries/UniswapV2Library.sol';
import './libraries/SafeMath.sol';
import './interfaces/IERC20.sol';
import './interfaces/IWETH.sol';

contract UniswapV2Router02 is IUniswapV2Router02 {
    using SafeMath for uint; //UniswapV2Router02 컨트랙트를 정의합니다. IUniswapV2Router02 인터페이스를 구현하며, uint 타입에 대한 SafeMath 라이브러리를 사용합니다.

    //immutable타입으로 factory와 WETH 변수로 선언 
    address public immutable override factory;
    address public immutable override WETH;

    //주어진 deadline이 현재 블록 타임스탬프 이상인지 확인하는 역할을 합니다.
    //modifier ensure는 데드라인이 현재 블록 타임스탬프보다 크거나 같은지 확인합니다. 그렇지 않으면 "UniswapV2Router: EXPIRED" 오류가 발생합니다.
    modifier ensure(uint deadline) {
        require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
        _;
    }

    //constructor는 스마트 컨트랙트가 빌드될 때 한번 실행된다. 생성자 constructor는 factory와 WETH 주소를 받아와서 저장합니다.
    constructor(address _factory, address _WETH) public {
        factory = _factory;
        WETH = _WETH;
    }

    //receive() 함수는 fallback 함수로, ETH를 WETH 컨트랙트를 통해서만 받을 수 있도록 지정합니다.
    receive() external payable {
        assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
    }

    // **** ADD LIQUIDITY ****
    //_addLiquidity() 함수는 tokenA와 tokenB 토큰의 유동성을 추가하는 데 필요한 최적의 amountA와 amountB를 계산하는 함수입니다. 함수 내부에서는 다음과 같은 일을 합니다:
    function _addLiquidity(
        address tokenA, //tokenA: 추가하려는 유동성을 가진 첫 번째 토큰의 주소
        address tokenB,  //tokenB: 추가하려는 유동성을 가진 두 번째 토큰의 주소
        uint amountADesired, //amountADesired: 첫 번째 토큰을 얼마나 추가하려고 하는지를 나타내는 양
        uint amountBDesired, //amountBDesired: 두 번째 토큰을 얼마나 추가하려고 하는지를 나타내는 양
        uint amountAMin, //amountAMin: 첫 번째 토큰의 최소한의 양
        uint amountBMin  //amountBMin: 두 번째 토큰의 최소한의 양
    ) internal virtual returns (uint amountA, uint amountB) {
        // create the pair if it doesn't exist yet
        //먼저 해당 토큰 쌍의 풀이 존재하지 않는 경우(createPair 함수를 호출해서), 토큰 쌍을 생성합니다.
        if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
           
            IUniswapV2Factory(factory).createPair(tokenA, tokenB);
        }
        
        //reserveA, reserveB 변수에 UniswapV2Library.getReserves(factory, tokenA, tokenB); 함수를 호출해서 reserve에서 가져옴 
        (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);

        //reseveA와 reserveB가 둘 다 0이면 amountA=amountADesired 이고 amountB=amountBDesired
        //그 다음, 해당 토큰 쌍의 현재 reserveA와 reserveB를 가져와서 두 값이 모두 0이면, amountADesired와 amountBDesired를 그대로 사용합니다. 
        if (reserveA == 0 && reserveB == 0) {
            
            (amountA, amountB) = (amountADesired, amountBDesired);

        } else {
            //그렇지 않으면, UniswapV2Library의 quote 함수를 호출해서 optimal한 양을 계산합니다.
            uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
            //만약 amountBOptimal이 amountBDesired 이하인 경우, amountBOptimal이 amountBMin 이상인지 확인한 후 amountA와 amountB에 amountADesired와 amountBOptimal을 할당합니다. 
            if (amountBOptimal <= amountBDesired) {
                require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
                (amountA, amountB) = (amountADesired, amountBOptimal);

            //
            } else {
                uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);

                assert(amountAOptimal <= amountADesired);
                // amountAOptimal을 계산하고 amountAOptimal이 amountAMin 이상인지 확인한 후 amountA와 amountB에 amountAOptimal과 amountBDesired를 할당합니다.
                require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
                //따라서 이 함수는 적절한 양을 계산하고, 그 양을 반환합니다. 이 함수가 반환하는 값은 추가된 토큰의 양을 나타내는 amountA와 amountB입니다.
                (amountA, amountB) = (amountAOptimal, amountBDesired);
            }
        }
    }
    function addLiquidity(
        address tokenA, //유동성을 추가하려는 tokenA의 주소
        address tokenB, //유동성을 추가하려는 tokenB의 주소
        uint amountADesired, //tokenA의 추가하려는 양
        uint amountBDesired, //tokenB의 추가하려는 양
        uint amountAMin, //최소한으로 받아들일 수 있는 tokenA의 양
        uint amountBMin, //최소한으로 받아들일 수 있는 tokenB의 양
        address to, //유동성 토큰을 발행하고 싶은 주소
        uint deadline //함수가 실행되기 전까지의 시간 제한
    ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
        //는 먼저 내부 함수 _addLiquidity를 호출하여 풀에 추가할 각 토큰의 실제 금액을 계산합니다.
        (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);

        // 그런 다음 UniswapV2Library.pairFor 함수를 사용하여 두 토큰에 해당하는 Uniswap V2 쌍의 주소를 가져옵니다.
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        
        //그런 다음 이 함수는 TransferHelper.safeTransferFrom 함수를 사용하여 각 토큰의 계산된 금액을 호출자로부터 해당 쌍으로 전송합니다. 
        //토큰이 페어에 들어오면 이 함수는 페어 컨트랙트의 발행 함수를 호출하여 지정된 수신자에게 LP 토큰을 발행합니다. 
        TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
        TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
        
        //마지막으로 이 함수는 풀에 추가된 각 토큰의 실제 금액과 발행된 LP 토큰의 양을 반환합니다.
        liquidity = IUniswapV2Pair(pair).mint(to);
    }


    //이 기능은 사용자가 유니스왑 V2 풀에 유동성을 추가할 수 있도록 하는 유니스왑 V2 라우터02 스마트 콘트랙트에서 제공하는 또 다른 방법입니다.
    // 그러나 이 기능을 사용하면 두 개의 ERC20 토큰을 제공하는 대신 이더와 단일 ERC20 토큰을 사용하여 유동성을 추가할 수 있습니다.
    //이 함수는 ERC20 토큰의 주소, 풀에 추가할 ERC20 토큰의 원하는 수량, 풀에 추가해야 하는 ERC20 토큰과 이더리움의 최소 수량, 
    // 유동성 추가 시 발행되는 LP 토큰의 수신자 주소, 거래가 실행되어야 하는 기한 등 여러 매개변수를 입력받습니다.
    function addLiquidityETH(
        address token, // ERC20 토큰의 주소,
        uint amountTokenDesired, //, 풀에 추가할 ERC20 토큰의 원하는 수량
        uint amountTokenMin, //풀에 추가해야 하는 ERC20 토큰 최소 수량  
        uint amountETHMin, //풀에 추가해야 하는 이더리움의 최소 수량
        address to, //유동성 추가 시 발행되는 LP 토큰의 수신자 주소,
        uint deadline  //거래가 실행되어야 하는 기한
        //이 함수는 유료이므로 호출자는 트랜잭션의 일부로 해당 함수에 이더를 보내야 합니다. 전송된 이더리움의 양은 풀에 유동성을 추가하는 데 사용됩니다.
    ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
        //이 함수는 ERC20 토큰, WETH 주소(유니스왑 V2에서 사용하는 래핑된 이더리움 토큰), 원하는 ERC20 토큰 수량, 호출자가 전송한 ETH 수량으로 내부 함수 _addLiquidity를 호출합니다.
        (amountToken, amountETH) = _addLiquidity(
            token,
            WETH,
            amountTokenDesired,
            msg.value,
            amountTokenMin,
            amountETHMin
        );
        //이 함수는 UniswapV2Library.pairFor 함수를 사용해 ERC20 토큰과 이더리움에 해당하는 유니스왑 V2 쌍의 주소를 가져옵니다.
        address pair = UniswapV2Library.pairFor(factory, token, WETH);

        //이 함수는 TransferHelper.safeTransferFrom 함수를 사용하여 호출자로부터 원하는 양의 ERC20 토큰을 쌍으로 전송합니다.
        TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
        
        //그런 다음 이 함수는 WETH 컨트랙트의 입금 함수를 호출하여 수신한 이더를 랩드 이더(WETH)로 변환합니다.
        IWETH(WETH).deposit{value: amountETH}();

        //그런 다음 이 함수는 어설트 함수를 사용하여 IWETH 인터페이스의 전송 함수를 사용하여 랩드 이더가 페어 컨트랙트로 전송되는지 확인합니다.
        assert(IWETH(WETH).transfer(pair, amountETH));

        //그런 다음 이 함수는 페어 컨트랙트의 발행 함수를 호출하여 지정된 수신자에게 LP 토큰을 발행합니다.
        liquidity = IUniswapV2Pair(pair).mint(to);

        // refund dust eth, if any
        //유동성을 추가한 후 초과 이더리움이 남는 경우, 이 함수는 TransferHelper.safeTransferETH 함수를 사용하여 초과분을 호출자에게 다시 전송합니다. 
        //이는 소량의 이더가 컨트랙트에 먼지로 잠기는 것을 방지하기 위한 것입니다.
        if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);

        //전반적으로 이 기능을 통해 사용자는 이더와 단일 ERC20 토큰을 사용해 유니스왑 V2 풀에 유동성을 공급할 수 있으며, 이는 거래에 사용하거나 기초 자산으로 상환할 수 있습니다.
    }

    // **** REMOVE LIQUIDITY ****
    //function removeLiquidity(): 유동성을 제거하여 토큰들을 분리합니다.
    function removeLiquidity(
        address tokenA, //tokenA: 첫 번째 토큰의 주소
        address tokenB, //tokenB: 두 번째 토큰의 주소
        uint liquidity, //유동성: 제거할 유동성의 양
        uint amountAMin, //amountAMin: 받을 토큰 A의 최소 금액
        uint amountBMin, //amountBMin: 받을 토큰 B의 최소 금액
        address to, //받는 사람: 토큰을 보낼 주소
        uint deadline //기한: 트랜잭션이 포함되어야 하는 타임스탬프

    ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {

        //이 함수는 먼저 유니스왑V2 라이브러리에서 pairFor() 함수를 사용하여 페어 컨트랙트의 주소를 가져옵니다. 
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);

        //그런 다음 transferFrom() 함수를 사용하여 발신자의 유동성 토큰을 페어 콘트랙트로 전송합니다.
        IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
       
        //그런 다음 페어 컨트랙트의 burn() 함수를 호출하여 유동성을 제거하고 받은 토큰 A와 토큰 B의 양을 가져옵니다. 그런 다음 이 함수는 토큰을 정렬하고 받은 토큰의 양을 반환합니다.
        (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
       
        
        (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
        (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
        require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
        require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
    }

    
    //removeLiquidityETH(): 이 함수는 유니스왑 풀에서 한 쌍의 이더와 다른 토큰의 유동성을 제거하는 데 사용됩니다. 이 함수는 다음 매개변수를 받습니다:
    function removeLiquidityETH(
        address token, //토큰: 유동성을 제거할 토큰의 주소입니다.
        uint liquidity, //유동성: 제거할 유동성의 양
        uint amountTokenMin, //amountTokenMin: 받을 토큰의 최소 금액
        uint amountETHMin, //amountETHMin: 받을 이더리움의 최소 금액
        address to, //to: 토큰을 보낼 주소
        uint deadline //deadline: 트랜잭션이 포함되어야 하는 타임스탬프
    ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
        //이 함수는 먼저 removeLiquidity() 함수를 호출하여 수령한 토큰과 이더리움의 금액을 가져옵니다. 
        //
        (amountToken, amountETH) = removeLiquidity(
            token,
            WETH,
            liquidity,
            amountTokenMin,
            amountETHMin,
            address(this),
            deadline
        );
        
        //그런 다음 safeTransfer() 함수를 사용하여 토큰을 전송하고 
        TransferHelper.safeTransfer(token, to, amountToken);
        
        //withdraw() 함수를 사용하여 ETH를 WETH로 변환하고 
        IWETH(WETH).withdraw(amountETH);
        
        //ETH를 수신자 주소로 전송합니다.
        TransferHelper.safeTransferETH(to, amountETH);
    }

    //이는 온체인 트랜잭션이 아닌 서명으로 승인할 수 있는 ERC-20 표준의 특징인 허가(permit)를 통해 유동성을 제거할 수 있는 UniswapV2Router02 콘트랙트의 함수다. 
    function removeLiquidityWithPermit(
        address tokenA, //pair에 있는 토큰A 주소 
        address tokenB, , //pair에 있는 토큰B 주소
        uint liquidity,  //제거할 유동성 수량 
        uint amountAMin,  //토큰A의 최소 허용 수량, 
        uint amountBMin,  // 토큰B의 최소 허용 수량, 
        address to, //수신자 주소 
        uint deadline, //마감일 
        bool approveMax, //최대 유동성 수량 승인 여부를 나타내는 bool 값
        uint8 v, bytes32 r, bytes32 s //허가서명(v, r, s)를 입력으로 받는다. 
    ) external virtual override returns (uint amountA, uint amountB) {
        //이 함수는 먼저 페어 주소를 가져온 다음 
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        
        //삼항 연산자 value가 true면 uint(-1) 값 아니면 liquidity를 할당
        //최대 유동성 수량 승인 여부를 나타내는 bool 값
        uint value = approveMax ? uint(-1) : liquidity;

        //페어 컨트랙트에서 허가 함수를 호출하여 허가 서명을 확인하고 발신자를 대신해 유동성을 사용할 수 있도록 UniswapV2Router02 컨트랙트를 승인합니다.
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);

        //마지막으로, 이 함수는 동일한 입력 매개변수를 사용하여 removeLiquidity 함수를 호출하고 수신한 토큰A와 토큰B의 금액을 반환합니다.
        (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
    }

    //이는 온체인 트랜잭션이 아닌 서명으로 승인할 수 있는 ERC-20 표준의 특징인 허가(permit)를 통해 유동성을 제거할 수 있는 UniswapV2Router02 콘트랙트의 함수다.
    // 위의 함수와 달리 ERC-20 토큰과 WETH 토큰을 받는다. 수신한 ERC-20 토큰과 ETH의 금액을 반환합니다. 
    function removeLiquidityETHWithPermit(
        address token, //pair에 있는 토큰 주소 
        uint liquidity, //제거할 유동성 
        uint amountTokenMin, //토큰의 최소 허용 수량, 
        uint amountETHMin, //ETH의 최소 허용 수량, 
        address to, //수신인 
        uint deadline, //마감일 
        bool approveMax, //최대 유동성 수량 승인 여부를 나타내는 bool 값
        uint8 v, bytes32 r, bytes32 s //허가서명(v, r, s)를 입력으로 받는다. 
    ) external virtual override returns (uint amountToken, uint amountETH) {
         //이 함수는 먼저 페어 주소를 가져온 다음 
        address pair = UniswapV2Library.pairFor(factory, token, WETH);

        //삼항 연산자 value가 true면 uint(-1) 값 아니면 liquidity를 할당
        //최대 유동성 수량 승인 여부를 나타내는 bool 값
        uint value = approveMax ? uint(-1) : liquidity;
        
         //페어 컨트랙트에서 허가 함수를 호출하여 허가 서명을 확인하고 발신자를 대신해 유동성을 사용할 수 있도록 UniswapV2Router02 컨트랙트를 승인합니다.
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);

        //페어 컨트랙트에서 permit을 호출한 후 내부적으로 removeLiquidityETH 함수를 호출합니다.
        (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
    }



    // **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token, //주소 토큰,: 유동성 풀의 일부인 ERC20 토큰의 주소입니다.
        uint liquidity, //uint 유동성,: 풀에서 제거할 유동성(LP) 토큰의 양입니다.
        uint amountTokenMin, //사용자가 받을 것으로 예상되는 최소 토큰 수량입니다.
        uint amountETHMin, //사용자가 받을 것으로 예상되는 최소 이더리움 수량입니다.
        address to, // 유동성이 제거된 후 토큰과 이더리움을 받을 주소입니다.
        uint deadline //트랜잭션이 실행될 기한이며, 이 기한이 지나면 트랜잭션이 처리되지 않습니다.
    ) public virtual override ensure(deadline) returns (uint amountETH) { 
        // public virtual override 컨트랙트 외부에서 함수에 접근할 수 있고 자식 컨트랙트에 의해 재정의될 수 있음을 지정하는 접근 수정자입니다.
        // ensure(deadline) : 트랜잭션 실행 기한이 지나지 않았는지 확인하는 수정자입니다.
        // returns (uint amountETH) {: 이 줄은 사용자에게 반환되는 이더리움의 양인 함수의 반환값을 선언합니다.
        (, amountETH) = removeLiquidity(  
            // (, amountETH) = removeLiquidity(: 이 줄은 여러 매개 변수를 사용하여 다른 함수 removeLiquidity를 호출하며, 
            // 이 함수는 사용자가 받게 될 토큰 금액과 사용자가 받게 될 ETH 금액이라는 두 가지 값을 반환합니다.
            token,  //유동성 풀의 일부인 ERC20 토큰의 주소입니다.
            WETH,   //유동성 풀의 일부인 랩드 이더(WETH) 토큰의 주소입니다
            liquidity, //풀에서 제거할 유동성(LP) 토큰의 양입니다.
            amountTokenMin,  // 사용자가 받을 것으로 예상되는 토큰의 최소 금액입니다.
            amountETHMin,  //사용자가 받을 것으로 예상되는 최소 이더리움 수량입니다
            address(this), //유동성이 제거된 후 LP 토큰을 받을 주소입니다.
            deadline //트랜잭션이 실행될 기한입니다.
        );
        //TransferHelper.safeTransfer(토큰, to, IERC20(토큰).balanceOf(주소(this)));: 이 줄은 (유동성이 제거된 후) 남은 ERC20 토큰을 사용자 주소로 전송합니다.
        TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this))); 
        //IWETH(WETH).withdraw(amountETH);: 이 줄은 WETH 토큰을 ETH로 변환합니다
        IWETH(WETH).withdraw(amountETH);
        //TransferHelper.safeTransferETH(to, amountETH);: 이 줄은 변환된 이더를 사용자의 주소로 전송합니다.
        TransferHelper.safeTransferETH(to, amountETH);

        //전반적으로 이 함수는 유니스왑 유동성 풀에서 유동성을 제거하고, WETH 토큰을 ETH로 변환한 다음, 나머지 ERC20 토큰과 변환된 ETH를 사용자 주소로 전송합니다.
    }


    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token, //주소 토큰,: 유동성을 제거할 ERC-20 토큰의 주소입니다.
        uint liquidity, //제거할 유동성의 양입니다.
        uint amountTokenMin, //유동성을 제거한 대가로 받을 최소 토큰 수량입니다.
        uint amountETHMin, //제거되는 유동성의 대가로 받을 최소 이더리움 수량입니다.
        address to,  //토큰과 이더리움을 받을 주소입니다.
        uint deadline, //트랜잭션이 실행되어야 하는 기한입니다.
        bool approveMax, //최대 유동성을 승인할지 여부를 결정하는 bool 플래그입니다.
        uint8 v, bytes32 r, bytes32 s // 허가 서명의 v,r,s 구성 요소입니다.

    // removeLiquidityETHWithPermitSupportingFeeOnTransferTokens 함수의 구현이므로 외부, 가상, 오버라이드로 표시되어 있습니다. 이 함수는 유동성 대가로 받은 이더리움의 양을 반환합니다.
    ) external virtual override returns (uint amountETH) {
        
        
        //주소 쌍 = UniswapV2Library.pairFor(factory, token, WETH);: 
        //이 줄은 공장 주소, 토큰 주소, WETH 주소와 함께 유니스왑V2라이브러리 컨트랙트의 pairFor 함수를 호출하여 유동성 풀의 주소를 결정합니다
        address pair = UniswapV2Library.pairFor(factory, token, WETH); 

        // uint 값 = 승인 최대 ? uint(-1) : 유동성;: 이 줄은 허가에 대해 승인할 값을 설정합니다. 
        //approveMax가 참이면 값을 가능한 최대값(uint(-1))으로 설정하고, 그렇지 않으면 값을 유동성으로 설정합니다.
        uint value = approveMax ? uint(-1) : liquidity;

        //IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);: 
        //이 줄은 유동성 풀 콘트랙트의 허가 함수를 호출하여 라우터가 지정된 양의 유동성을 사용할 수 있도록 승인합니다.
        // 허가 함수는 발신자(msg.sender), 수신자(address(this)), 승인할 값(유동성 또는 uint(-1)), 기한, 허가 서명(v, r, s) 등 여러 인수를 받습니다.
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);

        //amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(토큰, 유동성, 금액토큰민, 금액ETH민, to, 마감일);:
        // 이 줄은 풀에서 유동성을 제거하고 받은 이더리움의 양을 반환합니다. 지정된 인수를 사용하여 라우터 컨트랙트의 
        //removeLiquidityETHSupportingFeeOnTransferTokens 함수를 호출하여 유동성을 제거하고 토큰과 ETH를 수신자 주소로 전송합니다. 반환된 값은 amountETH에 할당됩니다.
        amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
            token, liquidity, amountTokenMin, amountETHMin, to, deadline
        );
    }


    // **** SWAP ****
    // requires the initial amount to have already been sent to the first pair
    //_스왑에 대한 함수 정의입니다. 이 함수는 스왑할 금액 배열, 스왑할 토큰의 주소가 포함된 경로 배열, 스왑한 토큰을 보낼 주소인 _to의 세 가지 인수를 받습니다.
    // 이 함수는 내부적이고 가상이므로 컨트랙트 내에서만 접근할 수 있으며 하위 컨트랙트에 의해 재정의될 수 있습니다.
    function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
        //amount은 입력 토큰부터 시작하여 거래에서 스왑할 각 토큰의 금액을 지정하는 금액 배열입니다.
        //path는 입력 토큰에서 출력 토큰까지 거래의 경로를 나타내는 토큰 주소 배열입니다.
        //to는 트랜잭션의 출력이 전송될 주소입니다.
        for (uint i; i < path.length - 1; i++) {
            //이 함수는 path 배열을 반복하며 path를 따라 연속된 각 토큰 쌍에 대해 스왑을 실행합니다.
            //각 토큰 쌍에 대해 함수는 token0 및 token1 주소와 출력 토큰 amountOut의 양을 결정합니다.

            //(주소 입력, 주소 출력) = (경로[i], 경로[i + 1]);: 이 줄은 디스트럭처링을 사용하여 경로[i]를 입력에, 경로[i + 1]을 출력에 할당합니다.
            (address input, address output) = (path[i], path[i + 1]);
            
            //(주소 토큰0,) = UniswapV2Library.sortTokens(입력, 출력);: 
            //이 줄은 UniswapV2Library의 sortTokens 함수를 사용하여 입력 토큰과 출력 토큰을 정렬하고 정렬된 순서에서 가장 먼저 오는 토큰의 주소를 반환합니다. 
            //이 주소는 token0에 할당됩니다.
            (address token0,) = UniswapV2Library.sortTokens(input, output);
            
            //uint amountOut = amounts[i + 1];: 이 줄은 금액 배열의 다음 값을 amountOut에 할당합니다.
            uint amountOut = amounts[i + 1];
            

            //(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));: 이 줄은 입력 토큰이 token0과 같은지 확인합니다. 
            //같으면 amount0Out에 0을 할당하고 amount1Out에 amountOut을 할당합니다. 그렇지 않으면, amount0Out에 amountOut이 할당되고 amount1Out에 0이 할당됩니다.
            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
            
            //address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;: 이 줄은 i가 경로의 길이에서 2를 뺀 값보다 작은지 확인합니다.
            // 이보다 작으면 출력 토큰과 경로의 다음 토큰을 포함하는 쌍의 주소가 에 할당됩니다. 그렇지 않으면 _to가 to에 할당됩니다.
            address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
            
            // IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(amount0Out, amount1Out, to, new bytes(0));: 
            // 이 줄은 입력 토큰과 출력 토큰이 포함된 유니스왑 쌍에서 스왑 함수를 호출합니다.
            // amount0Out과 amount1Out 변수는 스왑할 각 토큰의 양을 결정하고, to는 스왑된 토큰을 전송할 주소이며, new bytes(0) 매개 변수는 필요한 경우 추가 데이터에 사용됩니다.
            IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
                amount0Out, amount1Out, to, new bytes(0)
            );
        }
    }


    //function swapExactTokensForTokens(): 입력한 토큰을 목적 토큰으로 교환합니다.
    //함수 정의는 함수 이름 swapExactTokensForTokens와 입력 인수 amountIn, amountOutMin, path, to, deadline으로 시작됩니다. 이 함수는 금액이라는 부호 없는 정수의 배열을 반환합니다.
    function swapExactTokensForTokens(

        uint amountIn, //입력금액 
        uint amountOutMin, //사용자가 지정한 최소 출력량 
        address[] calldata path, //주소 배열 path 
        address to, //수신받을사람의 주소 
        uint deadline //거래기한 
    ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
        
        //이 줄은 유니스왑V2 라이브러리 콘트랙트에서 getAmountsOut 함수를 호출하여 주어진 입력 금액(amountIn)과 토큰의 경로(path)에 대한 예상 출력 금액(amountsOut)을 계산합니다.
        amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);

        //이 줄은 예상 출력량(amountsOut)이 사용자가 지정한 최소 출력량(amountOutMin)보다 크거나 같은지 확인합니다. 예상 출력량이 최소 출력량보다 작으면 함수는 오류 메시지를 던집니다.
        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
        
        //이 줄은 함수 호출자(msg.sender)로부터 입력 토큰(경로[0])을 TransferHelper 컨트랙트의 safeTransferFrom 함수를 사용하여 
        // 해당 UniswapV2 페어 컨트랙트(UniswapV2Library.pairFor(factory, 경로[0], 경로[1]))로 전송합니다.
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        
        //이 줄은 내부 _swap 함수를 호출하여 스왑 작업을 실행합니다. 스왑 함수는 주어진 토큰 경로(패스)에서 입력 토큰과 출력 토큰 쌍 간에 일련의 토큰 스왑을 실행하는 역할을 담당합니다.
        _swap(amounts, path, to);
    }


    // swapTokensForExactTokens()는 입력 토큰을 지정된 양의 출력 토큰으로 교환할 수 있게 해줍니다. 
    function swapTokensForExactTokens(
        uint amountOut, // amountOut(원하는 출력 토큰의 양)
        uint amountInMax, // amountInMax(사용할 최대 입력 토큰의 양),
        address[] calldata path, //path(스왑을 위해 취할 경로를 나타내는 토큰 주소 배열)
        address to, //to(출력 토큰의 수신자 주소), 
        uint deadline //deadline(트랜잭션이 더 이상 유효하지 않은 유닉스 타임스탬프)의 입력 파라미터가 포함되어 있습니다.
    ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
        //ensure() 수정자는 기한이 지나지 않았는지 확인합니다.

        //원하는 출력 토큰을 받는 데 필요한 입력 토큰의 양을 결정하기 위해 UniswapV2Library의 getAmountsIn() 함수가 호출됩니다.
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);

        //require() 문은 최대 입력 금액이 초과되지 않았는지 확인합니다.
        require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');

        //TransferHelper.safeTransferFrom()이 호출되어 호출자로부터 입력 토큰을 지정된 유니스왑V2 페어 컨트랙트로 전송합니다.
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        
        //_swap() 함수는 금액, 경로, to 매개변수와 함께 호출됩니다.
        _swap(amounts, path, to);
    }



    //function swapExactETHForTokens(): 지정된 양의 출력 토큰으로 이더를 교환할 수 있습니다. 
    //함수 정의에는 amountOutMin(받을 최소 출력 토큰 수량), path(스왑 경로를 나타내는 토큰 주소 배열), to(출력 토큰의 수신자 주소), 
    //deadline(트랜잭션이 더 이상 유효하지 않은 유닉스 타임스탬프)의 입력 파라미터가 포함되어 있습니다.
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        payable
        ensure(deadline) //ensure() 수정자는 기한이 지나지 않았는지 확인합니다.
        returns (uint[] memory amounts) 
    {
        //require() 문은 경로 배열의 첫 번째 토큰이 WETH 토큰인지 확인합니다.
        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');

        //지정된 이더리움 수량에 대해 수신할 출력 토큰의 양을 결정하기 위해 UniswapV2Library의 getAmountsOut() 함수가 호출됩니다.
        amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);

        //require() 문은 최소 출력 금액이 충족되는지 확인합니다.
        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');

        //IWETH 컨트랙트에서 deposit() 함수를 호출하여 WETH 토큰을 입금하고,
        IWETH(WETH).deposit{value: amounts[0]}();

        // 지정된 금액의 WETH를 지정된 UniswapV2 페어 컨트랙트로 이체합니다.
        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));

        //스왑() 함수는 금액, 경로, to 매개변수와 함께 호출됩니다.
        _swap(amounts, path, to);
    }



    //function swapTokensForExactETH(): 목적 이더리움을 입력한 토큰으로 교환합니다.
    /*
    uint amountOut : 교환하려는 이더리움의 양 (출력값)
    uint amountInMax : 입력으로 허용되는 최대 ERC20 토큰의 양
    address[] calldata path : 교환하려는 ERC20 토큰의 경로. 경로의 첫 번째 토큰은 입력으로 주어지고, 마지막 토큰은 이더리움입니다.
    address to : 이더리움을 받을 주소
    uint deadline : 거래 마감 시간
     */
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        ensure(deadline)
        returns (uint[] memory amounts)
    {
        
        //path[path.length - 1] == WETH 값이 맞는지 확인합니다. 
        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
        //amounts 변수에 UniswapV2Library.getAmountsIn 호출하여 교환전에 교환 비율을 계산한다. 
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);

        //계산된 입력값이 amounts[0]에 저장되어 있습니다.
        //이 입력값이 입력으로 주어진 최대 ERC20 토큰의 양 amountInMax를 초과하지 않는지 확인한 후,
        require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
        // 계산된 입력값만큼의 ERC20 토큰을 사용자의 계정에서 해당 ERC20 토큰의 UniswapV2 풀에 전송합니다
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        //_swap() 함수를 호출하여 실제로 ERC20 토큰을 이더리움으로 교환하고,
        _swap(amounts, path, address(this));
        ///WETH에서는 amounts[amounts.length - 1]만큼 인출합니다. 
        IWETH(WETH).withdraw(amounts[amounts.length - 1]);
        //이더리움을 amounts[amounts.length - 1] 양만큼 사용자가 지정한 주소 to로 전송합니다.
        TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
    }



    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        ensure(deadline)
        returns (uint[] memory amounts)
    {   
        //입력된 경로(path)의 마지막 토큰이 이더리움(WETH)인지 확인합니다. 그렇지 않으면 함수를 중지하고 'UniswapV2Router: INVALID_PATH' 에러 메시지를 반환합니다
        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');

        //UniswapV2Library에서 getAmountsOut 함수를 호출하여 입력된 토큰(amountIn)의 교환에 필요한 출력(amounts)을 계산합니다. 이때, factory는 UniswapV2 팩토리 컨트랙트 주소입니다.
        amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);

        //amountOutMin보다 교환된 출력 토큰의 양이 큰지 확인합니다. 만약 그렇지 않다면 함수를 중지하고 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT' 에러 메시지를 반환합니다.
        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');

        //TransferHelper를 사용하여 교환에 필요한 첫 번째 페어에 토큰을 보냅니다. 
        //이때 msg.sender는 토큰을 전송한 계정 주소이며, pairFor 함수는 UniswapV2 팩토리 컨트랙트에서 토큰의 페어 주소를 가져옵니다.
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );

        //내부 _swap 함수를 호출하여 토큰 교환을 수행합니다. 이때, _swap 함수는 amounts와 path를 전달하고, 마지막으로 교환을 위해 사용할 컨트랙트 주소를 지정합니다.
        _swap(amounts, path, address(this));

        //이더리움을 WETH 토큰으로 다시 변환합니다. 이때, IWETH는 WETH 토큰의 인터페이스이며, withdraw 함수를 호출하여 이더리움을 WETH 토큰으로 다시 변환합니다.
        IWETH(WETH).withdraw(amounts[amounts.length - 1]);

        //TransferHelper를 사용하여 지정된 수신자(to)에게 교환된 이더리움을 안전하게 전송합니다. 이때, amounts 배열의 마지막 항목에는 교환된 이더리움의 양이 저장되어 있습니다.
        TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
    }


    //swapETHForExactTokens 함수는 ETH를 다른 토큰으로 교환하는 함수입니다.
    //amountOut은 교환할 토큰의 수량입니다.
    //path는 교환 경로입니다. 이 경로는 WETH를 경유해야 하며, 다른 토큰과 직접적으로 교환이 가능한 경로여야 합니다.
    //to는 교환 결과를 받을 주소입니다. 
    //deadline는 거래의 유효 기간을 의미합니다. 이 기간 이전에 거래가 완료되어야 합니다. 
   
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        payable
        ensure(deadline)
        returns (uint[] memory amounts)
    {
         //require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');는 path 배열의 첫 번째 토큰이 WETH인지 확인합니다.
        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
        //amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);는 amountOut을 교환하기 위해 필요한 WETH의 수량과 path 상의 각 토큰 수량을 계산합니다.
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);

        //require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');는 msg.value가 교환에 필요한 최소 WETH 수량보다 큰지 확인합니다.
        require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');

        //IWETH(WETH).deposit{value: amounts[0]}();는 WETH를 입금합니다.
        IWETH(WETH).deposit{value: amounts[0]}();

        //assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));는 WETH를 path상의 다음 토큰으로 전송합니다.
        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));

        //_swap(amounts, path, to);는 _swap 함수를 호출하여 토큰을 교환합니다.
        _swap(amounts, path, to);
        // refund dust eth, if any
        //if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);는 교환 후 남은 WETH를 msg.sender에게 반환합니다.
        if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
    }


    // **** SWAP (supporting fee-on-transfer tokens) ****
    // requires the initial amount to have already been sent to the first pair
    //_swapSupportingFeeOnTransferTokens 함수는 수수료를 부과하는 토큰에 대한 교환이 가능한 함수입니다.
    //address[] memory path: 통화 교환 경로. 예를 들어, DAI를 WETH로 교환하는 경우 [DAI, WETH] 배열입니다.
    //address _to: 교환 결과를 수신할 주소.
    function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {

        //이 함수는 반복문을 통해 교환을 수행합니다. i 변수는 경로 배열을 반복하는 인덱스 역할을 합니다. 각 반복에서 다음 작업을 수행합니다.
        for (uint i; i < path.length - 1; i++) {

            //(address input, address output) = (path[i], path[i + 1]): 현재 입력과 출력 토큰을 가져옵니다.
            (address input, address output) = (path[i], path[i + 1]);
            
            //(address token0,) = UniswapV2Library.sortTokens(input, output): 현재 페어에서 토큰 0과 토큰 1을 가져옵니다.
            (address token0,) = UniswapV2Library.sortTokens(input, output);

            //IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)): 현재 입력과 출력 토큰으로 Uniswap V2 페어를 가져옵니다.
            IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
            uint amountInput;
            uint amountOutput;
            { // scope to avoid stack too deep errors
            //(uint reserve0, uint reserve1,) = pair.getReserves(): 페어의 예약량을 가져옵니다.
            (uint reserve0, uint reserve1,) = pair.getReserves();

            //(uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0): 입력 토큰과 출력 토큰의 예약량을 가져옵니다.
            (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);

            //amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput): 입력 토큰의 양을 계산합니다.
            amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);

            //amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput): 출력 토큰의 예상 수량을 계산합니다.
            amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
            }

            //(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0)): 출력 토큰의 양을 설정합니다.
            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));

            //address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) :
            // _to: 다음 입력 토큰과 출력 토큰이 모두 경로 배열에 남아 있는 경우, 다음 교환 페어의 주소를 계산합니다. 그렇지 않으면 _to 변수의 값을 사용합니다.
            address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;

            //pair.swap(amount0Out, amount1Out, to, new bytes(0)): 교환을 수행합니다. 교환된 출력 토큰은 다음 교환에서 입력 토큰으로 사용됩니다.
            pair.swap(amount0Out, amount1Out, to, new bytes(0));
        }
    }

    //이 함수는 주어진 토큰을 다른 토큰으로 교환하는 기능을 구현하고, 이 때 fee-on-transfer 토큰을 지원한다.
    // 입력받은 amountIn 만큼의 path[0] 토큰을 교환하여 최소한 amountOutMin 만큼의 path[path.length - 1] 토큰을 to 주소로 보내는 함수를 정의한다. 
    //이 때, fee-on-transfer 토큰을 지원하기 위해 _swapSupportingFeeOnTransferTokens 함수를 호출한다.
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external virtual override ensure(deadline) {

        //TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn): msg.sender 가 path[0] 토큰을 amountIn 만큼 보내는 함수이다. 
        //이를 위해 TransferHelper.safeTransferFrom 함수를 호출하고, path[0] 와 path[1] 에 대응하는 페어 주소를 전달한다.
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
        );

        //path 배열에서 마지막 토큰의 계정 잔액을 to 주소의 잔액으로 설정합니다.
        uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);

        //_swapSupportingFeeOnTransferTokens(path, to): _swapSupportingFeeOnTransferTokens 함수를 호출하여 교환을 수행한다.
        _swapSupportingFeeOnTransferTokens(path, to);

        //require(IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'): 교환 이후 to 주소의 path[path.length - 1] 토큰 
        // balanceBefore 보다 적으면 INSUFFICIENT_OUTPUT_AMOUNT 에러를 발생시킨다.
        require(
            IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
            'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
        );
    }


    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin, //uint amountOutMin: 구매하려는 토큰의 최소 양입니다.
        address[] calldata path, //address[] calldata path: 거래 경로를 나타내는 배열입니다. 이 경우에는 ETH와 구매하려는 토큰이 포함됩니다.
        address to, //address to: 구매한 토큰을 보낼 대상 주소입니다.
        uint deadline //: 거래의 유효 기간을 나타내는 타임스탬프입니다.
    )
        external
        virtual
        override
        payable
        ensure(deadline)
    {
        //require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');: 거래 경로의 첫번째 토큰이 WETH인지 확인합니다.
        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');

        //uint amountIn = msg.value;: 입력 값으로 전송된 ETH의 양을 저장합니다.
        uint amountIn = msg.value;

        //IWETH(WETH).deposit{value: amountIn}();: ETH를 WETH로 변환합니다.
        IWETH(WETH).deposit{value: amountIn}();

        //assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));: WETH를 UniswapV2의 해당 페어 주소로 이전합니다.
        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));

        //uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);: 거래 이전에 대상 주소에서 구매하려는 토큰의 잔액을 가져옵니다.
        uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);

        //_swapSupportingFeeOnTransferTokens(path, to);: 거래를 수행하고, 수수료를 지원하는 토큰을 처리하는 내부 함수를 호출합니다.
        _swapSupportingFeeOnTransferTokens(path, to);

        //require(IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');: 
        //구매한 토큰의 양이 최소 구매 양 이상인지 확인합니다. 만약 구매한 토큰의 양이 적으면 함수는 실패합니다.
        require(
            IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
            'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
        );
    }



    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn, //amountIn: 함수가 처리할 토큰 양을 나타냅니다.
        uint amountOutMin, //amountOutMin: 교환 후 토큰의 최소 허용 양입니다.
        address[] calldata path, //path: 교환을 위한 토큰 경로입니다. path[0]은 input token이며 path[path.length - 1]은 output token, 즉 WETH입니다.
        address to, //to: 교환 결과를 받을 주소입니다.
        uint deadline //deadline: 교환에 대한 기한을 나타냅니다.
    )
        external
        virtual
        override
        ensure(deadline)
    {
        //path[path.length - 1] == WETH인지 확인합니다.
        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');

        //TransferHelper.safeTransferFrom() 함수를 사용하여 사용자가 보낸 토큰을 UniswapV2Library.pairFor() 함수를 사용하여 교환을 수행할 페어에 안전하게 전송합니다.
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
        );

        //_swapSupportingFeeOnTransferTokens() 함수를 호출하여 교환을 수행합니다. 이 함수는 path에서 지정된 토큰 경로를 따라 교환을 수행하며, 해당 교환에 대한 수수료를 적용합니다.
        _swapSupportingFeeOnTransferTokens(path, address(this));
        
        //교환 결과로 얻은 WETH 양을 계산합니다.
        uint amountOut = IERC20(WETH).balanceOf(address(this));
        
        //amountOut이 amountOutMin보다 큰지 확인합니다.
        require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
        
        //IWETH(WETH).withdraw() 함수를 사용하여 WETH를 이더리움으로 전환하고, 
        IWETH(WETH).withdraw(amountOut);
        
        //TransferHelper.safeTransferETH() 함수를 사용하여 이더리움을 to 주소로 안전하게 전송합니다.
        TransferHelper.safeTransferETH(to, amountOut);
    }



    // **** LIBRARY FUNCTIONS ****
    //quote(): 입력 토큰 수량과 두 개의 리저브(자산의 잔액)를 사용하여 목적 토큰 수량을 계산합니다.
    function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
        return UniswapV2Library.quote(amountA, reserveA, reserveB);
    }


    //getAmountOut(): 입력 토큰 수량과 두 개의 리저브(자산의 잔액)를 사용하여 목적 토큰 수량을 계산합니다.
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
        public
        pure
        virtual
        override
        returns (uint amountOut)
    {
        return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
    }


    //getAmountIn(): 목적 토큰 수량과 두 개의 리저브(자산의 잔액)를 사용하여 필요한 입력 토큰 수량을 계산합니다.
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
        public
        pure
        virtual
        override
        returns (uint amountIn)
    {
        return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
    }



    //getAmountsOut(): 입력 토큰 수량과 경로 배열을 사용하여 목적 토큰 수량을 계산합니다. 
    //이 함수는 경로에 따라 연속적인 거래를 수행하는 경우 토큰 교환에 필요한 최종 결과 수량을 계산하는 데 사용됩니다.
    function getAmountsOut(uint amountIn, address[] memory path)
        public
        view
        virtual
        override
        returns (uint[] memory amounts)
    {
        return UniswapV2Library.getAmountsOut(factory, amountIn, path);
    }



    //getAmountsIn(): 목적 토큰 수량과 경로 배열을 사용하여 필요한 입력 토큰 수량을 계산합니다. 
    //이 함수는 경로에 따라 연속적인 거래를 수행하는 경우 필요한 최소 입력 수량을 계산하는 데 사용됩니다.
    function getAmountsIn(uint amountOut, address[] memory path)
        public
        view
        virtual
        override
        returns (uint[] memory amounts)
    {
        return UniswapV2Library.getAmountsIn(factory, amountOut, path);
    }
}

관련글 더보기