본문 바로가기
개발공부일지/Block-Chain

DeFi - UniSwap 기본 흐름도

by Hynn1429 2023. 7. 28.
반응형

1. UniSwap Github

  • https://github.com/Uniswap
  • V2-core : LP pair, factory 등 핵심적인 기능의 컨트랙트
  • v2-periphery : 핵심기능을 편리하게 사용할 수 있도록 도와주는 컨트랙트
    • flashSwap
    • priceOracle

2. UniSwap 구조

 

  • Router : 유저와 인터렉션(=상호작용) 하는 컨트랙트
  • Pair : 유동성을 관리하는 LP Pair 컨트랙트
  • Factory : Pair 컨트랙트를 생성하는 컨트랙트
  • FeeTo : Pair에서 발생하는 토큰 스왑에 대한 수수료가 모이는 컨트랙트

⇒ 유저가 만약 토큰 스왑을 요청하고 싶다면 라우터 컨트랙트에 요청 → 라우터는 스왑 경로를 통해 해당하는 페어에 따라 토큰을 교환

⇒ 유저가 유동성 공급을 요청했을 때는 기존에 존재하는 유동성 풀이 아닌 새로운 유동성 풀을 생성하는 경우 라우터가 팩토리를 통해 엘피를 생성

3. V2 Core

1) UniSwapV2Pair.sol

  • Liquidity
    • 두 토큰 쌍에 대한 유동성 관리
    • LP토큰을 통해 유동성 풀의 지분 증명
  • Swap
    • 유동성에 따라 토큰 교환
    • 스왑 수수료 수취
  • FlashSwap
    • 유동성 풀에서 필요한 만큼 토큰을 먼저 가져온 뒤, 한 트랜잭션 내에서 다시 상환 가능
  • Price Oracle
    • 시간 가중 평균 가격(TWAP) 계산을 위한 수치 제공
    • 시간 가중 평균 가격(Time-Weighted Average Price)

2) UniSwapV2Factory.sol

  • createPair
    • 두 토큰 쌍에 대한 LP 컨트랙트 생성
    • low level call을 통한 컨트랙트 배포
  • allPair / getPair
    • 두 토큰 쌍을 통해 pair 컨트랙트 주소 조회
    • 전체 pair 컨트랙트 목록 관리
  • feeTo / feeToSetter
    • 유동성 공급자들이 swap 수수료를 받을 주소 설정

4. V2 Periphery

1) UniSwapV2Router01.sol

  • Utility : 토큰 교환 및 유동성 공급 등을 쉽게 할 수 있게 도와줌
  • add / remove Liquidity : 유동성 공급 및 해제
    • addLiquidity
    function addLiquidity(
      address tokenA, // 유동성을 공급하려는 Pair Token A
      address tokenB, // 유동성을 공급하려는 Pair Token B
      uint amountADesired, // 공급하고자 하는 A 토큰 수량
      uint amountBDesired, // 공급하고자 하는 B 토큰 수량
      uint amountAMin, // A 토큰에 대한 최소 공급 수량
      uint amountBMin, // B 토큰에 대한 최소 공급 수량
      address to, // 유동성 공급자의 주소(=LP토큰을 수령할 주소)
      uint deadline // 실행 기한, 트랜잭션 실행시 최소한 설정값 이전에 반드시 실행해야 함
    ) external override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
      (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
      address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
      TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
      TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
      liquidity = IUniswapV2Pair(pair).mint(to);
    }
    
    • removeLiquidity
    function removeLiquidity(
      address tokenA, // 유동성을 해제 하려는 Pair Token A
      address tokenB, // 유동성을 해제 하려는 Pair Token B
      uint liquidity, // 해제하려는 Pool에 대한 LP 토큰 수량
      uint amountAMin, // A 토큰 최소 수령 수량
      uint amountBMin, // B 토큰 최소 수령 수량
      address to,
      uint deadline
    ) public override ensure(deadline) returns (uint amountA, uint amountB) {
      address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
      IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
      (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');
    }
    
  • swapToken : 주어진 스왑 경로에 따라 토큰 스왑
    function swapTokensForExactTokens( // 특정 수량의 토큰을 얻기 위해 스왑할 때
        uint amountOut, // output을 고정 => 받을 토큰의 양 설정 가능
        uint amountInMax, // 최대 이만큼까지는 던질 수 있다
        address[] calldata path,
        address to, // 사용자의 주소
        uint deadline
    ) external override ensure(deadline) returns (uint[] memory amounts) {
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
        require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
        TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]);
        _swap(amounts, path, to);}
    
  • function swapExactTokensForTokens( // 일정 수량의 토큰을 다른 토큰으로 스왑할 때 uint amountIn, // input을 고정 => 던지는 토큰의 양 설정 가능 uint amountOutMin, // 최소한 이만큼은 받고 싶다 address[] calldata path, // 토큰 스왑 경로 address to, // 사용자의 주소 uint deadline ) external override ensure(deadline) returns (uint[] memory amounts) { amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'); TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to);} /* address[] calldata path, // 토큰 스왑 경로 => [ETH, USDT, USDC] 배열에 이렇게 존재한다면, `path[0] -> path[1] ETH에서 USDT로 바꾸겠다` 대충 뭐 이런 뜻? 경로를 지정해줌으로써 토큰 손실 없이 최적의 스왑이 가능해짐? <- 이건 코드 봐야 할 듯*/
  • getAmounts : 토큰 스왑 시 예상 수량 계산
    // 유동성 풀의 reserve를 알고 있을 때 스왑에 필요한 토큰의 수량 계산
    function getAmountIn(uint amountOut/*몇 개나 받을 거냐*/, uint reserveIn, uint reserveOut) public pure override returns (uint amountIn) {
     return UniswapV2Library.getAmountOut(amountOut, reserveIn, reserveOut);}
    
    // 토큰 수량과 스왑 경로를 알고 있을 때 각 경로별 토큰 수량 계산
    function getAmountsOut(uint amountIn, address[] memory path) public view override returns (uint[] memory amounts) {
      return UniswapV2Library.getAmountsOut(factory, amountIn, path);}
    function getAmountsIn(uint amountOut, address[] memory path) public view override returns (uint[] memory amounts) {
      return UniswapV2Library.getAmountsIn(factory, amountOut, path);}
    
  • // 유동성 풀의 reseve를 알고 있을 때 수령할 수 있는 토큰의 예상 수량 계산 function getAmountOut(uint amountIn/*몇 개나 던질 거냐*/, uint reserveIn, uint reserveOut) public pure override returns (uint amountOut) { return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);}

4. PancakeSwap의 MasterChef.sol

 

  • 거버넌스 토큰을 분배하기 위해 필요한 컨트랙트이다.
  • 구조
    • 유저들이 유동성 공급 후 받은 LP 토큰을 MC에 예치를 하게 되면 MC는 유저가 예치한 LP 토큰의 수량과 예치 기간 등을 종합적으로 계산하여 거버넌스 토큰인 CAKE 토큰을 유저에게 던져준다.
    • MC는 각각의 LP별로 거버넌스 토큰을 얼마나 분배해주는지, LP토큰에 대한 정보 등을 Pool Info라는 공간에 담는다.

5. 컨트랙트 만들기

1) 유동성 공급 및 해제 ⇒ Trader.sol

  • TRADER - 유동성 공급 메서드
function addLiquidityKlay(address token, uint amountDesired) payable external {
  // native token으로 pair된 애들이 들어오는 곳(A, B로 나누어져있지 않음)
  // 이 함수가 실행되는 시점에서는 이미 네이티브 토큰을 받은 상태! 그러므로 페어로 맺은 token만 amountDesired만큼 가져오면 된다
  IERC20(token).transferFrom(msg.sender, address(this), amountDesired);
  approveToken(token, ROUTER);
  IUniswapV2Router01(ROUTER).addLiquidityETH{value:msg.value}(token, amountDesired, 0, 0, msg.sender, block.timestamp+10);
}

function addLiquidity(address tokenA, address tokenB, uint amountA, uint amountB) external {
  // 토큰의 이동경로 : 유동성 공급자 -> 현재 이 컨트랙트의 CA(TRADER) -> 실제 풀이 생성되어 있는 곳으로 연결시킬 수 있는 컨트랙트의 CA(DEX-ROUTER) -> 실제 풀이 있는 CA(DEX-PAIR)
  // 유저에게서 유동성 토큰을 받기 위해서는 transferFrom 메서드를 사용해 함!
  IERC20(tokenA).transferFrom(msg.sender, address(this), amountA);
  IERC20(tokenB).transferFrom(msg.sender, address(this), amountB);

  // 현재 이 컨트랙트(TRADER)에서 ROUTER CA(DEX)로 금액을 옮겨야 하기 때문에 approve가 필요하다
  // IERC20(tokenA).approve(ROUTER, amountA), IERC20(tokenA).approve(ROUTER, amountB)를 사용해도 되고 approveToken 함수를 사용해도 된다!
  approveToken(tokenA, ROUTER);
  approveToken(tokenB, ROUTER);

  // 이 함수가 발동되면 실제로 공급이 되어야 하기 때문에 이어서 Router에서 addLiquidity를 실행시켜야 함?
  IUniswapV2Router01(ROUTER).addLiquidity(tokenA, tokenB, amountA, amountB, 0, 0, msg.sender, block.timestamp + 10 );
}
  • TRADER - 유동성 해제 메서드
function removeLiquidity(address tokenA, address tokenB, uint liquidity) external {
  // 유동성을 해제하고자 하는 풀에 있는 두 토큰의 주소를 받고, 
  // 그 Pool의 LP토큰 수량을 인자값으로 받아(liquidity) 두 토큰으로 구성되어 있는 풀에서 LP토큰 수량만큼 해제시킴으로써 돌려받게 됨

  // 가장 먼저 엘피 토큰을 가져와서 유동성을 해제시킨다 => transferFrom으로 엘피토큰을 가져옴
  // 엘피토큰의 주소는 받은 두 토큰의 주소라는 인자값을 통해 뽑아야 함 => lp토큰은 factory에서 관장함, 배포 되어 있는 애 가져오면 됨!(위에서 가져옴)
  address lp = IUniswapV2Factory(FACTORY).getPair(tokenA, tokenB);
  IERC20(lp).transferFrom(msg.sender, address(this), liquidity);

  // 가져온 엘피토큰을 라우터에 보내기 전에, 라우터가 사용할 수 있도록 approve
  approveToken(lp, ROUTER);
  // 라우터에서 remove
  IUniswapV2Router01(ROUTER).removeLiquidity(tokenA, tokenB, liquidity, 0, 0, msg.sender, block.timestamp + 10);
}

function removeLiquidityKlay(address token, uint liquidity) external {
  // 이미 유동성 풀에 두 토큰 하나가 자체토큰으로 고정되어 있기 때문에 페어된 다른 토큰의 address와 해지할 엘피토큰의 수량만 가져오면 됨
  address lp = IUniswapV2Factory(FACTORY).getPair(token, WKLAY);
  // 여기서 WKLAY는 클레이토큰(자체토큰)을 민팅한 컨트랙트의 CA인가?
  IERC20(lp).transferFrom(msg.sender, address(this), liquidity);

  approveToken(lp, ROUTER);
  IUniswapV2Router01(ROUTER).removeLiquidityETH(token, liquidity, 0, 0, msg.sender, block.timestamp + 10);
}

2) 토큰 교환 ⇒ Router.sol

    • 토큰 스왑 시 스왑되는 순서
    • 패스 설정(유동성)에 따라 받을 수 있는 토큰 수량의 차이가 발생하게 된다.Swap 요청을 Router에게 패스 → A-B 스왑 진행(A 10개를 주고 B 100개를 받아옴) → B-C 스왑 진행(B 100개를 주고 C 100개를 받음) → Router는 받은 C 100개를 user에게 건네줌
    • 최종적으로는 A 10 → C 100 으로 교환 완료됨!
      • ⇒ A토큰을 가지고 C토큰을 교환받으려고 할 때 설정한 패스는 A-B-C라고 가정했을 시Swap path
     
  • ⇒ 만약 A-C 다이렉트로 진행하게 된다면 A 10 → C 80으로 교환됨
  • TRADER - 던지는 토큰의 수량 지정 시 메서드(= 얼마를 팔고싶다, 보내는 값 설정)

function swapExactTokenToToken(uint amountIn, address[] calldata path) external {
  // 얼만큼 던질 건지, 어떤 순서대로 토큰을 바꿀 건지를 인자값으로 받는다
  // 먼저 유저로부터 토큰을 받아야 하기 때문에 transferFrom 사용해 inputToken을 유저의 계정으로부터 가져온다.
  // 여기서 inputToken은 던지는 토큰이기 때문에, path[]의 첫번째 index 값!
  address inputToken = path[0];
  IERC20(inputToken).transferFrom(msg.sender, address(this), amountIn);

  // router가 처리해줄 수 있도록 허락하기 위해 aprrove 해주기!
  approveToken(inputToken, ROUTER);
  // router에서 토큰을 교환하는 메서드를 실행!
  IUniswapV2Router01(ROUTER).swapExactTokensForTokens(amountIn, 0, path, msg.sender, block.timestamp + 10);
  // (얼마나 교환할 건지, 최소 얼만큼을 받을 건지, 어떤 토큰->어떤 토큰으로 바꿀 건지, 누구의 계좌로 바뀐 토큰이 들어갈 건지, 데드라인이 언제까진지)
}
  • TRADER - 받을 토큰의 수량 지정 시 메서드(= 얼마를 얻고싶다, 받는 값 설정)
function swapTokenToExactToken(uint amountOut, uint amountInMax, address[] calldata path) external {
  // 얼만큼 받고 싶은지, 최대 얼만큼 쓸 수 있는지, 어떤 순서대로 토큰을 바꿀 건지
  // amountInMax는 슬리피지를 반영한 값이 들어가는 건가?

  // 얼만큼의 토큰을 사용해야 하는지 계산
  uint[] memory amountsIn = IUniswapV2Router01(ROUTER).getAmountsIn(amountOut, path);
  uint amountIn = amountsIn[0]; // 근데 이 값이 amountInMax값을 넘으면 안되니까 require로 한번 걸러주기!
  require(amountIn <= amountInMax, "exceed amountInMax");

  address inputToken = path[0];

  // amountInMax만큼 우선 빼온 다음에 바꿔주고 나서 남은 금액 되돌려줘도 됨
  // IERC20(inputToken).transferFrom(msg.sender, address(this), amountInMax);
  // 혹은 amountInMax 범위 안에서 스왑에 필요한 토큰 수량만 사용할 수도 있음! => 이런 경우 먼저 연산을 해준다. getAmountsIn 사용
  IERC20(inputToken).transferFrom(msg.sender, address(this), amountIn);

  // approve + 라우터에서 교환
  approveToken(inputToken, ROUTER);
  IUniswapV2Router01(ROUTER).swapTokensForExactTokens(amountOut, amountIn, path, msg.sender, block.timestamp + 10);
}
    • 시간 가중 평균 가격(TWAP) 계산을 위한 수치 제공
    • 시간 가중 평균 가격(Time-Weighted Average Price)

2) UniSwapV2Factory.sol

  • createPair
    • 두 토큰 쌍에 대한 LP 컨트랙트 생성
    • low level call을 통한 컨트랙트 배포
  • allPair / getPair
    • 두 토큰 쌍을 통해 pair 컨트랙트 주소 조회
    • 전체 pair 컨트랙트 목록 관리
  • feeTo / feeToSetter
    • 유동성 공급자들이 swap 수수료를 받을 주소 설정

4. V2 Periphery

1) UniSwapV2Router01.sol

  • Utility : 토큰 교환 및 유동성 공급 등을 쉽게 할 수 있게 도와줌
  • add / remove Liquidity : 유동성 공급 및 해제
    • addLiquidity
    function addLiquidity(
      address tokenA, // 유동성을 공급하려는 Pair Token A
      address tokenB, // 유동성을 공급하려는 Pair Token B
      uint amountADesired, // 공급하고자 하는 A 토큰 수량
      uint amountBDesired, // 공급하고자 하는 B 토큰 수량
      uint amountAMin, // A 토큰에 대한 최소 공급 수량
      uint amountBMin, // B 토큰에 대한 최소 공급 수량
      address to, // 유동성 공급자의 주소(=LP토큰을 수령할 주소)
      uint deadline // 실행 기한, 트랜잭션 실행시 최소한 설정값 이전에 반드시 실행해야 함
    ) external override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
      (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
      address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
      TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
      TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
      liquidity = IUniswapV2Pair(pair).mint(to);
    }
    
    • removeLiquidity
    function removeLiquidity(
      address tokenA, // 유동성을 해제 하려는 Pair Token A
      address tokenB, // 유동성을 해제 하려는 Pair Token B
      uint liquidity, // 해제하려는 Pool에 대한 LP 토큰 수량
      uint amountAMin, // A 토큰 최소 수령 수량
      uint amountBMin, // B 토큰 최소 수령 수량
      address to,
      uint deadline
    ) public override ensure(deadline) returns (uint amountA, uint amountB) {
      address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
      IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
      (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');
    }
    
  • swapToken : 주어진 스왑 경로에 따라 토큰 스왑
    function swapTokensForExactTokens( // 특정 수량의 토큰을 얻기 위해 스왑할 때
        uint amountOut, // output을 고정 => 받을 토큰의 양 설정 가능
        uint amountInMax, // 최대 이만큼까지는 던질 수 있다
        address[] calldata path,
        address to, // 사용자의 주소
        uint deadline
    ) external override ensure(deadline) returns (uint[] memory amounts) {
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
        require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
        TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]);
        _swap(amounts, path, to);}
    
  • function swapExactTokensForTokens( // 일정 수량의 토큰을 다른 토큰으로 스왑할 때 uint amountIn, // input을 고정 => 던지는 토큰의 양 설정 가능 uint amountOutMin, // 최소한 이만큼은 받고 싶다 address[] calldata path, // 토큰 스왑 경로 address to, // 사용자의 주소 uint deadline ) external override ensure(deadline) returns (uint[] memory amounts) { amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'); TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to);} /* address[] calldata path, // 토큰 스왑 경로 => [ETH, USDT, USDC] 배열에 이렇게 존재한다면, `path[0] -> path[1] ETH에서 USDT로 바꾸겠다` 대충 뭐 이런 뜻? 경로를 지정해줌으로써 토큰 손실 없이 최적의 스왑이 가능해짐? <- 이건 코드 봐야 할 듯*/
  • getAmounts : 토큰 스왑 시 예상 수량 계산
    // 유동성 풀의 reserve를 알고 있을 때 스왑에 필요한 토큰의 수량 계산
    function getAmountIn(uint amountOut/*몇 개나 받을 거냐*/, uint reserveIn, uint reserveOut) public pure override returns (uint amountIn) {
     return UniswapV2Library.getAmountOut(amountOut, reserveIn, reserveOut);}
    
    // 토큰 수량과 스왑 경로를 알고 있을 때 각 경로별 토큰 수량 계산
    function getAmountsOut(uint amountIn, address[] memory path) public view override returns (uint[] memory amounts) {
      return UniswapV2Library.getAmountsOut(factory, amountIn, path);}
    function getAmountsIn(uint amountOut, address[] memory path) public view override returns (uint[] memory amounts) {
      return UniswapV2Library.getAmountsIn(factory, amountOut, path);}
    
  • // 유동성 풀의 reseve를 알고 있을 때 수령할 수 있는 토큰의 예상 수량 계산 function getAmountOut(uint amountIn/*몇 개나 던질 거냐*/, uint reserveIn, uint reserveOut) public pure override returns (uint amountOut) { return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);}

4. PancakeSwap의 MasterChef.sol

  • 거버넌스 토큰을 분배하기 위해 필요한 컨트랙트이다.
  • 구조
    • 유저들이 유동성 공급 후 받은 LP 토큰을 MC에 예치를 하게 되면 MC는 유저가 예치한 LP 토큰의 수량과 예치 기간 등을 종합적으로 계산하여 거버넌스 토큰인 CAKE 토큰을 유저에게 던져준다.
    • MC는 각각의 LP별로 거버넌스 토큰을 얼마나 분배해주는지, LP토큰에 대한 정보 등을 Pool Info라는 공간에 담는다.

5. 컨트랙트 만들기

1) 유동성 공급 및 해제 ⇒ Trader.sol

  • TRADER - 유동성 공급 메서드
function addLiquidityKlay(address token, uint amountDesired) payable external {
  // native token으로 pair된 애들이 들어오는 곳(A, B로 나누어져있지 않음)
  // 이 함수가 실행되는 시점에서는 이미 네이티브 토큰을 받은 상태! 그러므로 페어로 맺은 token만 amountDesired만큼 가져오면 된다
  IERC20(token).transferFrom(msg.sender, address(this), amountDesired);
  approveToken(token, ROUTER);
  IUniswapV2Router01(ROUTER).addLiquidityETH{value:msg.value}(token, amountDesired, 0, 0, msg.sender, block.timestamp+10);
}

function addLiquidity(address tokenA, address tokenB, uint amountA, uint amountB) external {
  // 토큰의 이동경로 : 유동성 공급자 -> 현재 이 컨트랙트의 CA(TRADER) -> 실제 풀이 생성되어 있는 곳으로 연결시킬 수 있는 컨트랙트의 CA(DEX-ROUTER) -> 실제 풀이 있는 CA(DEX-PAIR)
  // 유저에게서 유동성 토큰을 받기 위해서는 transferFrom 메서드를 사용해 함!
  IERC20(tokenA).transferFrom(msg.sender, address(this), amountA);
  IERC20(tokenB).transferFrom(msg.sender, address(this), amountB);

  // 현재 이 컨트랙트(TRADER)에서 ROUTER CA(DEX)로 금액을 옮겨야 하기 때문에 approve가 필요하다
  // IERC20(tokenA).approve(ROUTER, amountA), IERC20(tokenA).approve(ROUTER, amountB)를 사용해도 되고 approveToken 함수를 사용해도 된다!
  approveToken(tokenA, ROUTER);
  approveToken(tokenB, ROUTER);

  // 이 함수가 발동되면 실제로 공급이 되어야 하기 때문에 이어서 Router에서 addLiquidity를 실행시켜야 함?
  IUniswapV2Router01(ROUTER).addLiquidity(tokenA, tokenB, amountA, amountB, 0, 0, msg.sender, block.timestamp + 10 );
}
  • TRADER - 유동성 해제 메서드
function removeLiquidity(address tokenA, address tokenB, uint liquidity) external {
  // 유동성을 해제하고자 하는 풀에 있는 두 토큰의 주소를 받고, 
  // 그 Pool의 LP토큰 수량을 인자값으로 받아(liquidity) 두 토큰으로 구성되어 있는 풀에서 LP토큰 수량만큼 해제시킴으로써 돌려받게 됨

  // 가장 먼저 엘피 토큰을 가져와서 유동성을 해제시킨다 => transferFrom으로 엘피토큰을 가져옴
  // 엘피토큰의 주소는 받은 두 토큰의 주소라는 인자값을 통해 뽑아야 함 => lp토큰은 factory에서 관장함, 배포 되어 있는 애 가져오면 됨!(위에서 가져옴)
  address lp = IUniswapV2Factory(FACTORY).getPair(tokenA, tokenB);
  IERC20(lp).transferFrom(msg.sender, address(this), liquidity);

  // 가져온 엘피토큰을 라우터에 보내기 전에, 라우터가 사용할 수 있도록 approve
  approveToken(lp, ROUTER);
  // 라우터에서 remove
  IUniswapV2Router01(ROUTER).removeLiquidity(tokenA, tokenB, liquidity, 0, 0, msg.sender, block.timestamp + 10);
}

function removeLiquidityKlay(address token, uint liquidity) external {
  // 이미 유동성 풀에 두 토큰 하나가 자체토큰으로 고정되어 있기 때문에 페어된 다른 토큰의 address와 해지할 엘피토큰의 수량만 가져오면 됨
  address lp = IUniswapV2Factory(FACTORY).getPair(token, WKLAY);
  // 여기서 WKLAY는 클레이토큰(자체토큰)을 민팅한 컨트랙트의 CA인가?
  IERC20(lp).transferFrom(msg.sender, address(this), liquidity);

  approveToken(lp, ROUTER);
  IUniswapV2Router01(ROUTER).removeLiquidityETH(token, liquidity, 0, 0, msg.sender, block.timestamp + 10);
}
반응형

댓글