{"content":{"title":"Uniswap V2分析","body":"## 前言\r\n作为AMM的开创者，Uniswap在defi领域有着举足轻重的地位，[稳居top1](https://tokenterminal.com/terminal)，并且除了稳定币兑换池，Uniswap的交易量可以说是遥遥领先于其它DEX。所以让我们来看看它具体是如何实现的。之所以分析V2，是因为V1是用vyper写的。\r\n\r\n[core源码地址](https://github.com/Uniswap/v2-core)\r\n[periphery源码地址](https://github.com/Uniswap/v2-periphery)\r\n\r\n\r\n## 架构\r\nUniswap V2主要分为core与periphery两个模块。\r\n```\r\nUniswap-v2-periphery--------Uniswap-v2-core\r\n                        | \r\nUniswapV2Migrator.sol   |   UniswapV2ERC20.sol\r\n                        |\r\nUniswapV2Router01.sol   |   UniswapV2Factory.sol\r\n                        |\r\nUniswapV2Router01.sol   |   UniswapV2Pair.sol\r\n                        | \r\n```\r\n我们先介绍几个主要合约的功能：\r\n\r\nuniswap-v2-core\r\nUniswapV2Factory：工厂合约，用于创建Pair合约\r\n\r\nUniswapV2Pair：负责核心逻辑，如swap/mint/burn，价格预言机等功能，其本身是一个ERC20合约，继承UniswapV2ERC20(Factory只允许创建唯一的交易对)\r\n\r\nUniswapV2ERC20：这是一个扩展的ERC20实现，用于实现LPToken\r\n\r\nuniswap-v2-periphery\r\n\r\nUniswapV2Router02：最新版的路由合约，相比UniswapV2Router01增加了对FeeOnTransfer代币的支持；实现Uniswap v2最常用的接口，比如添加/移除流动性，使用代币A交换代币B，使用ETH交换代币等，用来提升用户的体验。\r\n\r\nUniswapV1Router01：旧版本Router实现，与Router02类似，但不支持FeeOnTransferTokens，目前已不使用\r\n\r\nUniswapV2Migrator：用于迁移流动性\r\n\r\n## core\r\n\r\n### UniswapV2Factory\r\n在工厂合约中最重要的是createPair方法\r\n```solidity\r\nfunction createPair(address tokenA, address tokenB) external returns (address pair) {\r\n    require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');\r\n    (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);\r\n    require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');\r\n    require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient\r\n    bytes memory bytecode = type(UniswapV2Pair).creationCode;\r\n    bytes32 salt = keccak256(abi.encodePacked(token0, token1));\r\n    assembly {\r\n        pair := create2(0, add(bytecode, 32), mload(bytecode), salt)\r\n    }\r\n    IUniswapV2Pair(pair).initialize(token0, token1);\r\n    getPair[token0][token1] = pair;\r\n    getPair[token1][token0] = pair; // populate mapping in the reverse direction\r\n    allPairs.push(pair);\r\n    emit PairCreated(token0, token1, pair, allPairs.length);\r\n}\r\n```\r\n具体实现是先判断tokenA与tokenB是否相同，然后排序(防止生成A-B与B-A这样的相同的pair)，再用create2生成合约，最后记录合约地址。\r\n\r\n### UniswapV2ERC20\r\n标准的ERC20实现，唯一不同的是实现了EIP-2612以支持转账的离线授权\r\n```solidity\r\nfunction permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {\r\n    require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');\r\n    bytes32 digest = keccak256(\r\n        abi.encodePacked(\r\n            '\\x19\\x01',\r\n            DOMAIN_SEPARATOR,\r\n            keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))\r\n        )\r\n    );\r\n    address recoveredAddress = ecrecover(digest, v, r, s);\r\n    require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');\r\n    _approve(owner, spender, value);\r\n}\r\n```\r\n\r\n### UniswapV2Pair\r\nPair合约主要实现了四个方法：mint，burn，swap，skim。\r\n\r\n#### mint\r\n用于添加流动性。\r\n```solidity\r\n  function mint(address to) external lock returns (uint liquidity) {\r\n        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings\r\n        uint balance0 = IERC20(token0).balanceOf(address(this));\r\n        uint balance1 = IERC20(token1).balanceOf(address(this));\r\n        uint amount0 = balance0.sub(_reserve0);\r\n        uint amount1 = balance1.sub(_reserve1);\r\n\r\n        bool feeOn = _mintFee(_reserve0, _reserve1);\r\n        uint _totalSupply = totalSupply; \r\n        if (_totalSupply == 0) {\r\n            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);\r\n           _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens\r\n        } else {\r\n            liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);\r\n        }\r\n        require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');\r\n        _mint(to, liquidity);\r\n\r\n        _update(balance0, balance1, _reserve0, _reserve1);\r\n        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date\r\n        emit Mint(msg.sender, amount0, amount1);\r\n    }\r\n```\r\n首先通过getReserves()获取两种代币的缓存余额(防止攻击者操控价格预言机，计算协议手续费)，然后计算用户转入的token数amount0与amount1，再计算是否需要收取协议手续费，然后计算流动性(如果是第一次添加，则要锁定MINIMUM_LIQUIDITY的LP)。最后铸造LpToken，更新储备。\r\n\r\n#### burn\r\n用于移除流动性\r\n```solidity\r\n    function burn(address to) external lock returns (uint amount0, uint amount1) {\r\n        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings\r\n        address _token0 = token0;                                // gas savings\r\n        address _token1 = token1;                                // gas savings\r\n        uint balance0 = IERC20(_token0).balanceOf(address(this));\r\n        uint balance1 = IERC20(_token1).balanceOf(address(this));\r\n        uint liquidity = balanceOf[address(this)];\r\n\r\n        bool feeOn = _mintFee(_reserve0, _reserve1);\r\n        uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee\r\n        amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution\r\n        amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution\r\n        require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');\r\n        _burn(address(this), liquidity);\r\n        _safeTransfer(_token0, to, amount0);\r\n        _safeTransfer(_token1, to, amount1);\r\n        balance0 = IERC20(_token0).balanceOf(address(this));\r\n        balance1 = IERC20(_token1).balanceOf(address(this));\r\n\r\n        _update(balance0, balance1, _reserve0, _reserve1);\r\n        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date\r\n        emit Burn(msg.sender, amount0, amount1, to);\r\n    }\r\n```\r\n基本与mint函数类似，就不赘述了。\r\n#### swap\r\n用于两种代币的交换\r\n```solidity\r\n    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {\r\n        require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');\r\n        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings\r\n        require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');\r\n\r\n        uint balance0;\r\n        uint balance1;\r\n        { // scope for _token{0,1}, avoids stack too deep errors\r\n        address _token0 = token0;\r\n        address _token1 = token1;\r\n        require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');\r\n        if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens\r\n        if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens\r\n        if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);\r\n        balance0 = IERC20(_token0).balanceOf(address(this));\r\n        balance1 = IERC20(_token1).balanceOf(address(this));\r\n        }\r\n        uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;\r\n        uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;\r\n        require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');\r\n        { // scope for reserve{0,1}Adjusted, avoids stack too deep errors\r\n        uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));\r\n        uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));\r\n        require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');\r\n        }\r\n\r\n        _update(balance0, balance1, _reserve0, _reserve1);\r\n        emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);\r\n    }\r\n```\r\n首先经历一系列检查后，swap主要通过amountOut的大小来判断用户需要的代币，这样是为了兼容闪电贷功能。\r\n\r\n由于在swap方法最后会检查余额（扣掉手续费后）符合k恒等式约束，因此合约可以先将用户希望获得的代币转出，；如果使用闪电贷，即用户之前并没有向合约转入用于交易的代币，则需要在自定义的uniswapV2Call方法中将借出的代币归还。\r\n\r\n#### skim\r\n用于清理合约中多余的Token。无原因是多余的代币都会导致池子里的储备量和实际余额不一致，从而影响价格和流动性。\r\n```solidity\r\n function skim(address to) external lock {\r\n        address _token0 = token0; // gas savings\r\n        address _token1 = token1; // gas savings\r\n        _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));\r\n        _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));\r\n    }\r\n```"},"author":{"user":"https://learnblockchain.cn/people/13107","address":"0x46b724cFB1dC45e01C93a524191F98E8A89d98ee"},"history":"bafkreidiet6eeve4bh3swdze5ypeafeahk7i6rh73pvdjccjeq3ndigpsm","timestamp":1715094865,"version":1}