상세 컨텐츠

본문 제목

UniswapV2-periphery - UniswapV2Library.sol 코드 분석

Blockchain/DeFi

by Yongari 2023. 3. 17. 23:25

본문

 

UniswapV2Library.sol 개요 

UniswapV2Library.sol은 UniswapV2Router02.sol에서 많이 사용하는 함수를 가지고 있는 라이브러리 솔리디티 파일이다.
각 함수별로 어떤 용도로 쓰이는 지 핵심만 나열해보면 다음과 같다. 여기에 있는 함수를 이해해야 UniswapV2Router02.sol도 이해할 수 있다.

 

다음은 핵심함수들에 대한 설명이다.

 

 

 

 

UniswapV2Library.sol 핵심 함수 

 

1. sortTokens 함수 

function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1)
//두 개의 토큰 주소를 입력받아 정렬된 토큰 주소를 반환하는 함수이다.

 

 

2. pairFor 함수 

function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair)
//팩토리 주소와 두 개의 토큰 주소를 입력받아 해당 페어의 주소를 계산하는 함수이다.

 

3. getReserves 함수

function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB)

//팩토리 주소와 두 개의 토큰 주소를 입력받아 해당 페어의 예약을 가져오는 함수이다.



4. quote 함수

function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB)
//어떤 자산의 금액과 페어의 예약을 입력받아 다른 자산의 등가 금액을 반환하는 함수이다.



5. getAmountOut 함수

function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut)

//어떤 자산의 입력 금액과 해당 페어의 예약을 입력받아 다른 자산의 최대 출력 금액을 반환하는 함수이다.

 

 

6. getAmountIn 함수

function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn)

//getAmountIn 함수는 reserveIn과 reserveOut이라는 두 자산의 보유량과 amountOut이라는 출력 자산의 양이 주어졌을 때, 입력 자산의 양을 계산해주는 함수입니다.

 

7. getAmountsOut 함수

function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts

//factory와 입력 토큰량, 계산할 토큰 배열인 path를 인자로 받는다.

 

8. getAmountsIn 함수

function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts

//factory와 출력 토큰량, 계산할 토큰 배열인 path를 인자로 받는다.

 

 


하단에 코드분석을 통해서는 코드에 대한 디테일한 사항들을 알 수 있으며 핵심 함수 설명을 통해서는 전체적인 그림을 그려볼 수 있다. 코드가 길지 않아서 금방 파악할 수 있다. 

UniswapV2Library.sol 코드 분석

pragma solidity >=0.5.0;

import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';

import "./SafeMath.sol";

library UniswapV2Library {
    using SafeMath for uint;

    // returns sorted token addresses, used to handle return values from pairs sorted in this order
    function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
        //두 개의 토큰 주소를 입력받아 정렬된 토큰 주소를 반환하는 함수이다.
        //토큰A와 토큰B가 같지 않아야 하며 같으면 UniswapV2Library: IDENTICAL_ADDRESSES과 같은 에러가 발생한다. 
        require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');

        //두 개의 토큰 주소가 서로 다른지 확인하고, 작은 주소를 token0, 큰 주소를 token1에 할당한다.
        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        
        //token0이 0x0 주소인지 확인하고, 그렇다면 예외를 발생시킨다.
        require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
    }

    // calculates the CREATE2 address for a pair without making any external calls
    //팩토리 주소와 두 개의 토큰 주소를 입력받아 해당 페어의 주소를 계산하는 함수이다.
    function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {

        //sortTokens 함수를 사용하여 토큰 주소를 정렬하고, keccak256 해시 함수를 사용하여 CREATE2 주소를 계산한다.
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(uint(keccak256(abi.encodePacked(
                hex'ff',
                factory,
                keccak256(abi.encodePacked(token0, token1)),
                hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
            ))));
    }

    // fetches and sorts the reserves for a pair
    //팩토리 주소와 두 개의 토큰 주소를 입력받아 해당 페어의 예약을 가져오는 함수이다.
    function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {

        //sortTokens 함수를 사용하여 토큰 주소를 정렬하고, 
        (address token0,) = sortTokens(tokenA, tokenB);
       
        //IUniswapV2Pair 인터페이스의 getReserves 함수를 호출하여 예약을 가져온다.
        (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
       
        //tokenA가 token0이면 reserveA에 reserve0을, tokenB가 token1이면 reserveB에 reserve1을 할당한다.
        //아니면 반대로 동작한다. 
        (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
    }

    // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
    //어떤 자산의 금액과 페어의 예약을 입력받아 다른 자산의 등가 금액을 반환하는 함수이다.
    function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {

        //amountA가 0보다 큰지 확인하고, reserveA와 reserveB가 모두 0보다 큰지 확인한다.
        require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
        require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        
        // amountA는 reserveB와 곱한 후 reserveA로 나누어 amountB를 계산한다.
        amountB = amountA.mul(reserveB) / reserveA;
    }

    // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
    //어떤 자산의 입력 금액과 해당 페어의 예약을 입력받아 다른 자산의 최대 출력 금액을 반환하는 함수이다.
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {

        //amountIn이 0보다 큰지 확인하고, reserveIn과 reserveOut이 모두 0보다 큰지 확인한다.
        require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');

        //amountIn에 997을 곱한 값을 계산하고, amountInWithFee에 할당한다.
        uint amountInWithFee = amountIn.mul(997);

        //numerator는 amountInWithFee와 reserveOut과 곱한 값을 할당한다. 
        uint numerator = amountInWithFee.mul(reserveOut);

        // denominator는 reserveIn에 1000을 곱하고 mountInWithFee를 더한다.
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);

        //amountOut은 numerator를 denominator로 나눈다. 
        amountOut = numerator / denominator;
    }

    // given an output amount of an asset and pair reserves, returns a required input amount of the other asset
    //getAmountIn 함수는 reserveIn과 reserveOut이라는 두 자산의 보유량과 amountOut이라는 출력 자산의 양이 주어졌을 때, 입력 자산의 양을 계산해주는 함수입니다.
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {

        //amountOut과 reserveIn, reserveOut은 0보다 커야합니다. 
        require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        
        //numerator는 reserveIn과 출력토큰량인 amountOut을 곱하고 이후 1000을 곱합니다. 
        uint numerator = reserveIn.mul(amountOut).mul(1000);
        
        //denominator는 reserveOut에서 amountOut을 빼고 997을 곱합니다. 
        uint denominator = reserveOut.sub(amountOut).mul(997);
        
        // amountIn 입력토큰 량은 (numerator / denominator)를 나눈 값에 1을 더한다. 
        amountIn = (numerator / denominator).add(1);
    }

    // performs chained getAmountOut calculations on any number of pairs
    //factory와 입력토큰량, 계산할 토큰 배열인 path를 인자로 받는다. 
    function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {

        //배열길이는 2이상이어야한다. 토큰1, 토큰2 
        require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');

        //출력 토큰량은 path 길이만큼 uint 값을 할당한다. 
        amounts = new uint[](path.length);

        //배열의 첫번째 값은 amountIn이다. 입력토큰량이다. 
        amounts[0] = amountIn;
        //path의 길이만큼 반복문 실행 
        for (uint i; i < path.length - 1; i++) {

            //(uint reserveIn, uint reserveOut)은 getReserves를 호출해서 값을 각각 값을 가져온다. 
            (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
            //amounts의 마지막 값은 getAmountOut를 호출한 값이다. 
            amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
        }
    }

    // performs chained getAmountIn calculations on any number of pairs
    //factory와 출력 토큰량, 계산할 토큰 배열인 path를 인자로 받는다. 
    function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {

        //배열의 길이는 2이상이어야 한다. 
        require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');

        //출력토큰량은 path의 길이만큼 할당한다. 
        amounts = new uint[](path.length);

        //amounts의 마지막 값은 amountOut이다. 
        amounts[amounts.length - 1] = amountOut;

        //path 길이만큼 반복문 실행 
        for (uint i = path.length - 1; i > 0; i--) {

            //getReserves를 호출한 값에서 reserveIn과 reserveOut에 변수를 할당한다. 
            (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);

            //amounts의 마지막 값은 getAmountIn을 호출한 값으로 할당한다. 
            amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
        }
    }
}

관련글 더보기