Uniswap V2分析

前言作为AMM的开创者,Uniswap在defi领域有着举足轻重的地位,稳居top1,并且除了稳定币兑换池,Uniswap的交易量可以说是遥遥领先于其它DEX。

前言

作为AMM的开创者,Uniswap在defi领域有着举足轻重的地位,稳居top1,并且除了稳定币兑换池,Uniswap的交易量可以说是遥遥领先于其它DEX。所以让我们来看看它具体是如何实现的。之所以分析V2,是应为V1是用vyper写的。

core源码地址 periphery源码地址

架构

Uniswap V2主要分为core与periphery两个模块。

Uniswap-v2-periphery--------Uniswap-v2-core
                        | 
UniswapV2Migrator.sol   |   UniswapV2ERC20.sol
                        |
UniswapV2Router01.sol   |   UniswapV2Factory.sol
                        |
UniswapV2Router01.sol   |   UniswapV2Pair.sol
                        | 

我们先介绍几个主要合约的功能:

uniswap-v2-core UniswapV2Factory:工厂合约,用于创建Pair合约

UniswapV2Pair:负责核心逻辑,如swap/mint/burn,价格预言机等功能,其本身是一个ERC20合约,继承UniswapV2ERC20(Factory只允许创建唯一的交易对)

UniswapV2ERC20:这是一个扩展的ERC20实现,用于实现LPToken

uniswap-v2-periphery

UniswapV2Router02:最新版的路由合约,相比UniswapV2Router01增加了对FeeOnTransfer代币的支持;实现Uniswap v2最常用的接口,比如添加/移除流动性,使用代币A交换代币B,使用ETH交换代币等,用来提升用户的体验。

UniswapV1Router01:旧版本Router实现,与Router02类似,但不支持FeeOnTransferTokens,目前已不使用

UniswapV2Migrator:用于迁移流动性

core

UniswapV2Factory

在工厂合约中最重要的是createPair方法

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'); // single check is sufficient
    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);
}

具体实现是先判断tokenA与tokenB是否相同,然后排序(防止生成A-B与B-A这样的相同的pair),再用create2生成合约,最后记录合约地址。

UniswapV2ERC20

标准的ERC20实现,唯一不同的是实现了EIP-2612以支持转账的离线授权

function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
    require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
    bytes32 digest = keccak256(
        abi.encodePacked(
            '\x19\x01',
            DOMAIN_SEPARATOR,
            keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
        )
    );
    address recoveredAddress = ecrecover(digest, v, r, s);
    require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
    _approve(owner, spender, value);
}

UniswapV2Pair

Pair合约主要实现了四个方法:mint,burn,swap,skim。

mint

用于添加流动性。

  function mint(address to) external lock returns (uint liquidity) {
        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
        uint balance0 = IERC20(token0).balanceOf(address(this));
        uint balance1 = IERC20(token1).balanceOf(address(this));
        uint amount0 = balance0.sub(_reserve0);
        uint amount1 = balance1.sub(_reserve1);

        bool feeOn = _mintFee(_reserve0, _reserve1);
        uint _totalSupply = totalSupply; 
        if (_totalSupply == 0) {
            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
           _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
        } else {
            liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
        }
        require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
        _mint(to, liquidity);

        _update(balance0, balance1, _reserve0, _reserve1);
        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
        emit Mint(msg.sender, amount0, amount1);
    }

首先通过getReserves()获取两种代币的缓存余额(防止攻击者操控价格预言机,计算协议手续费),然后计算用户转入的token数amount0与amount1,再计算是否需要收取协议手续费,然后计算流动性(如果是第一次添加,则要锁定MINIMUM_LIQUIDITY的LP)。最后铸造LpToken,更新储备。

burn

用于移除流动性

    function burn(address to) external lock returns (uint amount0, uint amount1) {
        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
        address _token0 = token0;                                // gas savings
        address _token1 = token1;                                // gas savings
        uint balance0 = IERC20(_token0).balanceOf(address(this));
        uint balance1 = IERC20(_token1).balanceOf(address(this));
        uint liquidity = balanceOf[address(this)];

        bool feeOn = _mintFee(_reserve0, _reserve1);
        uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
        amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
        amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
        require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
        _burn(address(this), liquidity);
        _safeTransfer(_token0, to, amount0);
        _safeTransfer(_token1, to, amount1);
        balance0 = IERC20(_token0).balanceOf(address(this));
        balance1 = IERC20(_token1).balanceOf(address(this));

        _update(balance0, balance1, _reserve0, _reserve1);
        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
        emit Burn(msg.sender, amount0, amount1, to);
    }

基本与mint函数类似,就不赘述了。

swap

用于两种代币的交换

    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
        require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
        require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');

        uint balance0;
        uint balance1;
        { // scope for _token{0,1}, avoids stack too deep errors
        address _token0 = token0;
        address _token1 = token1;
        require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
        if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
        if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
        if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
        balance0 = IERC20(_token0).balanceOf(address(this));
        balance1 = IERC20(_token1).balanceOf(address(this));
        }
        uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
        uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
        require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
        { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
        uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
        uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
        require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
        }

        _update(balance0, balance1, _reserve0, _reserve1);
        emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
    }

首先经历一系列检查后,swap主要通过amountOut的大小来判断用户需要的代币,这样是为了兼容闪电贷功能。

由于在swap方法最后会检查余额(扣掉手续费后)符合k恒等式约束,因此合约可以先将用户希望获得的代币转出,;如果使用闪电贷,即用户之前并没有向合约转入用于交易的代币,则需要在自定义的uniswapV2Call方法中将借出的代币归还。

skim

用于清理合约中多余的Token。无原因是多余的代币都会导致池子里的储备量和实际余额不一致,从而影响价格和流动性。

 function skim(address to) external lock {
        address _token0 = token0; // gas savings
        address _token1 = token1; // gas savings
        _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
        _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
    }
点赞 3
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
不可思议之人
不可思议之人
0x46b7...98ee
江湖只有他的大名,没有他的介绍。