DeFi - Token to Token, Token To Coin, Coin to Token(ERC20)

프동프동·2023년 1월 19일
0

솔리디티(Solidity)

목록 보기
8/20

DeFi - Swap

Pactory Contract

변수

pool : ERC20 토큰 컨트랙트의 주소에 Liquidity 컨트랙트의 주소를 저장하함

mapping(address => address) public pool;

함수

cratePool() : 하나의 풀 인스턴스를 생성한다

function createPool(address _erc20Token) public returns (address) {
  // pool 하나 생성
  Liquidity liquidity = new Liquidity(_erc20Token);
  // 토큰의 주소로 => 해당 Pool을 저장한다.
  pool[_erc20Token] = address(liquidity);
  
  return address(liquidity);
}

getPool() : ERC20 토큰 주소를 통해 생성된 Pool을 확인할 수 있다.

function getPool(address _tokenAddress) public view returns (address){
// 주소로 풀을 확인할 수 있다.
return pool[_tokenAddress];
}

Liquidity Contract

변수

IFactory factory : factory 컨트랙트의 인터페이스를 사용하기 위함

IFactory factory;

IERC20 token : ERC20 컨트랙트의 인터페이스를 사용하기 위함

생성자

constructor (address _token) ERC20("lpToken","LP"){
  factory = IFactory(msg.sender);
  token = IERC20(_token);
}
  • Pool을 만들고자 하는 토큰의 주소를 초기값으로 받아 token인스턴스를 생성한다.
  • LP 토큰을 만든다.

함수

addLiquidity() : 토큰과 코인을 받아 유동성을 공급한다.

function addLiquidity(uint256 _maxToken) public payable{
  //  해당 풀이 가지고 있는 전체 공급량
  uint256 totalLiquidity = totalSupply();

  // 유동성이 존재할 때
  if (totalLiquidity > 0){
    // 현재 풀에 존재하는 코인의 개수를 구한다.
    uint256 ethAmount = address(this).balance - msg.value;
    // 현재 풀에 존재하는 토큰의 개수를 구한다.
    uint256 tokenAmount = token.balanceOf(address(this));
    // 실제 유동성 공급하려는 토큰의 개수를 몇개를 집어넣을지 비율을 구해야한다.
    // 200 x (500/1000)
    uint256 inputTokenAmount = msg.value * tokenAmount / ethAmount;
    // 사용자가 입력한 토큰보다 적은 양을 liquidity 컨트랙트가 가져올 수 있도록 조건을 설정한다.
    require(_maxToken >= inputTokenAmount);
    // 발행될 LP 토큰의 개수를 구한다.
    // 유동성 공급자가 보내는 코인의 개수가 현재 풀에서 얼마나 차지하는지 비율을 구해 LP토큰의 발행량을 구한다.
    uint256 liquidityToken = totalLiquidity * msg.value / ethAmount;
    _mint(msg.sender,liquidityToken);
    token.transferFrom(msg.sender, address(this), inputTokenAmount);
  }else{
    // 유동성이 없을 때(초기)
    // 입력받은 토큰의 개수
    uint tokenAmount = _maxToken;
    // 가지고 있는 코인의 개수
    uint initLiquidity = address(this).balance;
    // 가지고 있던 코의 개수 만큼 토큰을 발행한다.(Uniswap v1에서 단순하게 하기 위해 공급된 코인 개수와 맞춰서 LP토큰을 발행 함)
    _mint(msg.sender, initLiquidity);
    // 유동성 공급자가 가지고 있는 토큰을 Liquidity 컨트랙트에게 보낸다(공급한다)
    token.transferFrom(msg.sender, address(this), tokenAmount);
  }
}

removeLiquidity() : 유동성을 제거하고 수수료를 받는다.

function removeLiquidity(uint256 _lpTokenAmount) public {
  // 1. 전체 WEMIX의 개수를 가져온다.
  uint256 totalLiquidity = totalSupply();
  // 2. 유동성 공급자가 받아갈 WEMEI의 개수
  uint256 wemixAmount = _lpTokenAmount * address(this).balance / totalLiquidity;
  // 3. 유동성 공급자가 받아갈 토큰의 개수
  uint256 tokenAmount = _lpTokenAmount * token.balanceOf(address(this)) / totalLiquidity;
  // 4. LP토큰을 소각한다
  _burn(msg.sender, _lpTokenAmount);
  // 5. 유동성 공급자에게 WEMIX 코인을 보내준다.
  payable(msg.sender).transfer(wemixAmount);
  // 6. 유동성 공급자에게 토큰을 보내준다.
  token.transfer(msg.sender, tokenAmount);
}

getBalance() : 풀이 가지고 있는 코인의 개수를 확인한다.

function getBalance() public view returns(uint256){
  return address(this).balance;
}

cointToToken() : 코인을 토큰으로 스왑한다.

  • swapCoinToToken(), coinToTokenTransfer() : 다른 입력을 처리하기 위함
function swapCoinToToken(uint256 _minToken) public payable{
  coinToToken( _minToken, msg.sender);
}
  • minToken : 사용자가 받으려는 최소의 토큰의 개수를 입력 받기 위함
function coinToToken(uint256 _minToken, address _recipient) public payable{
  // 사용자게에 받은 코인의 양 
  uint256 inputAmount = msg.value;
  // Liquidity가 가지고 있는 코인의 양
  uint256 x = address(this).balance - inputAmount;
  // Liquidity가 가지고 있는 토큰의 양

  uint256 y = token.balanceOf(address(this));
  // 10 코인, x 30코인 y 50토큰
  uint256 outputAmount = getSwapRatio(msg.value, x, y); // 12.5?
  require(outputAmount >= _minToken, "Insufficient output Amount");
  // 사용자가 입력한 최소의 슬리피지값 이상은 되야 교환가능하다.
  require(outputAmount >= _minToken , "lack of amount");
  // 사용자에게 보낸다.
  require(token.transfer(_recipient, outputAmount));
}
  • CPMM을 적용해서 스왑한다.
function coinToTokenTransfer(uint256 _minTokens, address _recipient)
    public
    payable
{
    coinToToken(_minTokens, _recipient);
}

swapTokenToCoin() : 토큰을 코인으로 교환하는 함

  // _tokenAmount : 몇개의 토큰을 바꿀껀지
  // _minCoin : 슬리피지가 입력된 값(사용자가 최소 이정도는 받아야겠다라고 설정)
  function swapTokenToCoin(uint256 _tokenAmount, uint256 _minCoin) public payable{
    // 스왑 시 받을 코인 계산(CPMM)
    uint256 outputAmount = getSwapRatio(_tokenAmount, token.balanceOf(address(this)), address(this).balance);
    // 입력받은 슬리피지보다 많이 받아야한다.
    require(outputAmount >= _minCoin , "lack of amount");
    // 사용자가 토큰을 Liquidity(컨트랙트)에게 보내게한다.
    IERC20(token).transferFrom(msg.sender, address(this), _tokenAmount);
    // 코인을 보낸다. 함수를 호출한 사용자에게 CPMM 슬리피지가 적용된 양 만큼
    // payable(msg.sender) : https://ethereum.stackexchange.com/questions/113243/payablemsg-sender
    payable(msg.sender).transfer(outputAmount);
  }

getSwapRatio() : CPMM을 적용해 비율을 계산한다

// CPMM
// inputAmount : 사용자에게 받은 값,  1
// x : liquidity의 input,  500 + 1 = 501
// y : liquidity output, 1000
//// 10 코인, x 30코 y 50토큰 =
// 50 * 10 = 500
// 30 + 10 = 40 
function getSwapRatio(uint256 inputAmount, uint256 x, uint256 y) public pure returns (uint256){
  uint256 numerator = y * inputAmount; // 1000 x 1
  uint256 denominator = x + inputAmount; // 501 + 1
  return numerator/denominator;
}

getSwapRatioFee() : 수수료를 적용한 CPMM

// 스왑하는 사람에게 수수료를 제하고 준다.
// 수수료는 1%
// lp들이 유동성을 제거할 때 코인/ ERC20으로 수수료를 더 받아갈 수 있다.
  function getSwapRatioFee(uint256 inputAmount, uint256 x, uint256 y) public pure returns (uint256){
  uint256 fee = inputAmount * 99; // 1%를 적용시키기 위함
  uint256 numerator = y * fee;
  uint256 denominator = x + 100 * fee; 
  return numerator/denominator;
}

tokenToTokenSwap() : 토큰과 토큰을 스왑한다.

function tokenToTokenSwap(
    uint256 _tokenAmount, 
    uint256 _minToken, 
    uint256 _minCoin, 
    address _tokenAddress //상대토큰
) public {
    // 1. 토큰의 주소로 factory 컨트랙트에서 해당 토큰이랑 이루어진 Pool 주소를 가져온다.
    address tokenLiquidity = factory.getPool(_tokenAddress);

    // Token -> Coin
    // 2. 사용자가 받게 될 코인 양 계산
    uint256 coinOutputAmount = getSwapRatio(
        _tokenAmount,
        token.balanceOf(address(this)),
        address(this).balance
    );
    require(
        _minCoin <= coinOutputAmount,
        "Insufficient coin output amount"
    );
     // 3. 사용자에의 토큰을 가져온다.
    require(
        token.transferFrom(msg.sender, address(this), _tokenAmount),
        "fail transfer"
    );

    // Coin To Token
    // 4. factory 컨트랙트에서 찾은 상대 토큰의 풀에 코인을 보내고 토큰을 받는다
    ILiquidity(tokenLiquidity).swapCoinToTokenTransfer{
        value: coinOutputAmount
    }(_minToken, msg.sender);
}
profile
좋은 개발자가 되고싶은

0개의 댓글