Uniswapのソースを見た時のつらつらとした感想

# 説明
この記事は[Uniswap v2のソースコードを読み込んで思ったことをつらつらと書いたこのツィート](https://twitter.com/horobi_gengar/status/1311830678586105857?s=20)を体裁よくまとめたものです。
各種リンクは時間によって切れたりずれたりすると思います。ご了承ください。

# ソース
Uniswap V2(取引まわり)はこの2つのリポジトリで管理されている
[uniswap-v2-core](https://github.com/Uniswap/uniswap-v2-core)
[uniswap-v2-periphery](https://github.com/Uniswap/uniswap-v2-periphery)
興味があればどうぞ。

# 呟きまとめ
## プールの作成
UniswapV2Factory.createPairを実行することにより、いわゆるプールが出来上がる。**注①**
プールと言われているが、コード内部的にはpairと呼称されている。

このFactoryは世界に一つだけで、プールが出来上がるのはこのメソッドのみ。
(誰かが勝手にFactoryデプロイしたら知らん)
同一ペアのプールが複数できないように、ちゃんとチェックされている。**注②**

プールを作成するところは、アセンブリで記述されている。**注③**
アセンブリで記述するとガスが少なくてすむことがあるので、おそらくそれが理由であると思う。
可読性と要相談だが、アセンブリを撤廃したVyperはある意味潔い。

[ソース](https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2Factory.sol#L23)
[インスタンス](https://etherscan.io/address/0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f)

```solidity
    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](https://etherscan.io/address/0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D)がUniswapV2Factory.createPairを実行してプールが作成され、流動性が追加されるという流れ。**注④**

ERC20ペアの場合はaddLiquidity、ether-erc20の場合は[addLiquidityETH](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router02.sol#L77)を実行すればいい。
ちなみにUniswapV2Router01はバグがあるので、今後は使ってくれるなとのこと[参考記事](https://uniswap.org/docs/v2/smart-contracts/router01/)

流動性を提供したsenderには[UNI-V2トークンが付与される](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router02.sol#L75)。
付与される量は提供した流動性の量に比例する。**注⑤**

誰にどれだけUNI-V2トークンが付与されたかは、[pairコントラクトのTransferイベント](https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/interfaces/IUniswapV2ERC20.sol#L4)でfromが0になっている物を確認するか、[Mintイベント](https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2Pair.sol#L130)のamountから逆算すればわかる。

ちなみに流動性提供時、必要なトークンを[pairに対しTransferFromしている](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router02.sol#L73)ので、事前にapproveしておかないとエラーになる。注意。

[ソース](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router02.sol)
[インスタンス](https://etherscan.io/address/0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D)

```solidity
    // **** 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](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router02.sol#L103)から始まるメソッドを実行する。
大雑把な流れとしては流動性プロバイダが保持しているUNI-V2トークンを[pairに返却してburn](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router02.sol#L114)している。//**注⑥**
こちらも内部でtransferFromしているので、事前のapprovalは必須。
処理が成功すれば流動性提供時に提供したトークンは[toに指定したアドレスに戻る](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/UniswapV2Router02.sol#L137)。

```solidity
    // **** 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関数](https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2Pair.sol#L159)を実行してトークンの交換を行う。DEXなので、当然ガス代はかかってくる。
それにしても、openzeppelinを使わず、独自実装でERC20を作ってるのが、こだわりなのか、なんなのかって　感じはする。
よくできてるけど。

```solidity
    // **** 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);
    }
```