LoginSignup
16
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-12-03

説明

この記事は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);
    }
16
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
3