説明
この記事はUniswap v2のソースコードを読み込んで思ったことをつらつらと書いたこのツィートを体裁よくまとめたものです。
各種リンクは時間によって切れたりずれたりすると思います。ご了承ください。
ソース
Uniswap V2(取引まわり)はこの2つのリポジトリで管理されている
uniswap-v2-core
uniswap-v2-periphery
興味があればどうぞ。
呟きまとめ
プールの作成
UniswapV2Factory.createPairを実行することにより、いわゆるプールが出来上がる。注①
プールと言われているが、コード内部的にはpairと呼称されている。
このFactoryは世界に一つだけで、プールが出来上がるのはこのメソッドのみ。
(誰かが勝手にFactoryデプロイしたら知らん)
同一ペアのプールが複数できないように、ちゃんとチェックされている。注②
プールを作成するところは、アセンブリで記述されている。注③
アセンブリで記述するとガスが少なくてすむことがあるので、おそらくそれが理由であると思う。
可読性と要相談だが、アセンブリを撤廃したVyperはある意味潔い。
function createPair(address tokenA, address tokenB) external returns (address pair) { //**注①**
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); //**注②**
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {//**注③**
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
IUniswapV2Pair(pair).initialize(token0, token1);
getPair[token0][token1] = pair;
getPair[token1][token0] = pair; // populate mapping in the reverse direction
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}
流動性の追加
UniswapV2Router02のaddLiquidityがUniswapV2Factory.createPairを実行してプールが作成され、流動性が追加されるという流れ。注④
ERC20ペアの場合はaddLiquidity、ether-erc20の場合はaddLiquidityETHを実行すればいい。
ちなみにUniswapV2Router01はバグがあるので、今後は使ってくれるなとのこと参考記事
流動性を提供したsenderにはUNI-V2トークンが付与される。
付与される量は提供した流動性の量に比例する。注⑤
誰にどれだけUNI-V2トークンが付与されたかは、pairコントラクトのTransferイベントでfromが0になっている物を確認するか、Mintイベントのamountから逆算すればわかる。
ちなみに流動性提供時、必要なトークンをpairに対しTransferFromしているので、事前にapproveしておかないとエラーになる。注意。
// **** 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)) {
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,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) 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);
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = IUniswapV2Pair(pair).mint(to); //**注⑤**
}
流動性の解除
UniswapV2Router02.removeLiquidityから始まるメソッドを実行する。
大雑把な流れとしては流動性プロバイダが保持しているUNI-V2トークンをpairに返却してburnしている。//注⑥
こちらも内部でtransferFromしているので、事前のapprovalは必須。
処理が成功すれば流動性提供時に提供したトークンはtoに指定したアドレスに戻る。
// **** 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');
}
トレード
UniswapV2Pairのswap関数を実行してトークンの交換を行う。DEXなので、当然ガス代はかかってくる。
それにしても、openzeppelinを使わず、独自実装でERC20を作ってるのが、こだわりなのか、なんなのかって 感じはする。
よくできてるけど。
// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
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);
}