UniSwap이 무엇인지, 그리고 Core코드에 관해서는 아래의 링크에 정리하였으니, 참고하면 될 것 같다.
: Periphery 파일의 구조는 다음과 같다.
: Periphery에는 Core 프로젝트에 있는 컨트랙트들을 보다 쉽게 사용할 수 있는 도우미(helper) 컨트랙트들이 포함되어 있다.
: Core 프로젝트의 함수들을 사용자가 직접 호출할 경우 사용자의 실수로 인해 보안적 위험에 노출되기 쉽기 때문에 일반적으로는 Periphery에 정의된 컨트랙트를 통해 거래소 기능을 사용하는 것이 권장됩니다.
: Periphery 안의 UniswapV2Router02 컨트랙트가 현재 유니스왑 V2 웹인터페이에서 사용되는 컨트랙트입니다.
: 여기서 크게 눈여겨봐야할 부분은 Library, Router이다.
: library를 사용하는 Router Contract는 안전하게 풀에 유동성을 swap,add,remove하고 이에 필요한 안전 체크를 진행한다.
: 컨트랙트로 유니스왑과 상호작용하고 싶을 경우, router를 통하도록 작성한다.
https://www.steemcoinpan.com/hive-101145/@donekim/defi-wrapped-ether-weth
: ETH는 ERC-20토큰이 아니기 때문에, 탈중앙화 플랫폼에서 ETH를 편리하게 사용하고자 하는 토큰이 WETH이다.
: ERC-20 ERC-20 pair pool간에 유동성을 부여한다.
// **** ADD LIQUIDITY ****
function _addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal virtual returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
// address(0) 은 생성되지 않은 경우, 즉 pair가 없을 때 를 의미한다.
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
} else {
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
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);
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
(amountA, amountB) = (amountAOptimal, amountBDesired);
}
}
}
function addLiquidity(
address tokenA, // 풀토큰
address tokenB, // 풀토큰
uint amountADesired, // amountBDesired/amountADesired 일때 더할 토큰 A의 유동성양
uint amountBDesired,// amountADesired/amountBDesired 일때 더할 토큰 B의 유동성양
uint amountAMin,
uint amountBMin,
// 유동성 풀에 넣을 최소의 토큰의 양
address to, // 받을 사람
uint deadline // 트랜잭션이 취소되기까지의 timestamp 시간
) external virtual 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); // pair의 주소를 계산하는 함수
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = IUniswapV2Pair(pair).mint(to);// 유동성 생성
}
: WETH가 관련된 유동성 추가
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
(amountToken, amountETH) = _addLiquidity(
token,
WETH,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = UniswapV2Library.pairFor(factory, token, WETH);
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
IWETH(WETH).deposit{value: amountETH}();
assert(IWETH(WETH).transfer(pair, amountETH));
liquidity = IUniswapV2Pair(pair).mint(to);
// refund dust eth, if any
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}
// **** REMOVE LIQUIDITY ****
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual 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');
}
: 여기서 주목할 부분들은 다음과 같다.
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
: 이 부분의 코드는 앞서 우리가 넣었던 유동성 토큰을, 다시 꺼내고자 할때, 그 절대적인 양이 같지 않기 때문에 비율로 따져서 계산해야 한다. 그 계산된 값을 받아오는 것이다.
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
: docs에는 잘 설명이 되어있지 않은데, require의 경우에는 기존의 if문과는 달리 에러가 발생했을시 앞서 진행한 모든 과정들을 백지화 하는 특징을 지닌다. 즉, 이 코드에서는 '내가 지정한 최소 인출량보다 토큰이 작을 경우에, 지금까지 진행해온 모든 과정을 백지화 한다'라는 의미가 한 문장에 압축되어있는 것이라고 보면 된다.
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
(amountToken, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, amountToken);
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual 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);
}
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}
: periphery에서 편리하게 데이터를 가져오고 가격을 구성하기 위한 기능들을 제공한다.
addLiquidity 함수에서
amountBDesired/amountADesired 일때 더할 토큰 A의 유동성양 이라고 하셨는데
amountBDesired/amountADesired 일때 라는게 어떤 의미인지 이해가 되지 않습니다