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