uniswap v2 代码解读

  • BY_DLIFE
  • 更新于 2024-04-21 18:14
  • 阅读 212

前言:UniswapV2代码结构Uniswap智能合约代码由两个github项目组成。一个是core,一个是periphery。https://github.com/Uniswap/uniswap-v2-core.githttps://github.com/Uniswap/uniswap-

前言:

Uniswap V2代码结构

Uniswap智能合约代码由两个github项目组成。一个是core,一个是periphery。

https://github.com/Uniswap/uniswap-v2-core.git

https://github.com/Uniswap/uniswap-v2-periphery.git

core偏核心逻辑,单个swap的逻辑。periphery偏外围服务,一个个swap的基础上构建服务。单个swap,两种代币形成的交易对,俗称“池子”。每个交易对有一些基本属性:reserve0/reserve1以及total supply。reserve0/reserve1是交易对的两种代币的储存量。total supply是当前流动性代币的总量。每个交易对都对应一个流动性代币(LPT - liquidity provider token)。简单的说,LPT记录了所有流动性提供者的贡献。所有流动性代币的总和就是total supply。Uniswap协议的思想是reserve0*reserve1的乘积不变。

Periphery逻辑

核心逻辑实现在UniswapV2Router02.sol中。称为Router,因为Periphery实现了“路由”,支持各个swap之间的连接。基本上实现了三个功能:1/ add liquidity(增加流动性)2/remove liqudity (抽取流动性) 3/ swap(交换)。

uniswap v2非常重要,必须要非常熟悉该协议!!!

以下便是对uniswap v2 代码的解读。

v2-core

1. UniswapV2ERC20

uniswap v2的代币实际上是 ERC20代币。实现ERC20标准方法。

代码解读如下:

pragma solidity =0.5.16;

import './interfaces/IUniswapV2ERC20.sol';
import './libraries/SafeMath.sol';

contract UniswapV2ERC20 is IUniswapV2ERC20 {
    using SafeMath for uint; // 将 SafeMath 库合约用于 uint 类型

    string public constant name = 'Uniswap V2'; // 代币的名字
    string public constant symbol = 'UNI-V2'; // 代币符号
    uint8 public constant decimals = 18;
    uint  public totalSupply; // 发行量

    // 存储某地址的代币余额,address => uint的映射
    mapping(address => uint) public balanceOf; 

    // 存储某一地址对另一地址的代币授权量,授权之后可以允许被授权人使用授权人的代币进行转账 `transferFrom`
    mapping(address => mapping(address => uint)) public allowance; 

    // 
    bytes32 public DOMAIN_SEPARATOR;

    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

    // 
    mapping(address => uint) public nonces;

    // 两个在授权和转账时会被触发的事件
    event Approval(address indexed owner, address indexed spender, uint value);
    event Transfer(address indexed from, address indexed to, uint value);

    constructor() public {
        uint chainId;
        assembly {
            // chainid指令用于获取当前区块链的链ID, 它唯一地标识了当前区块链的网络
            chainId := chainid
        }
         // 初始化 DOMAIN_SEPARATOR 变量
        DOMAIN_SEPARATOR = keccak256(
            abi.encode(
                keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
                keccak256(bytes(name)),
                keccak256(bytes('1')),
                chainId,
                address(this)
            )
        );
    }

    /**
        1. 铸币操作,因为有库函数的引用,可以直接调用 add,sub等运算
        2. totalSupply 发行量时对于整个系统来说,而balance是对于某个账户来说
     */
    function _mint(address to, uint value) internal {
        totalSupply = totalSupply.add(value); // 发行量累加 value
        balanceOf[to] = balanceOf[to].add(value); // to账户的 余额累加 value
        emit Transfer(address(0), to, value); // 触发交易事件
    }

    /**
        1. 销币操作
     */
    function _burn(address from, uint value) internal {
        balanceOf[from] = balanceOf[from].sub(value); // from 账户的 余额累减 value
        totalSupply = totalSupply.sub(value); // 发行量累减 value
        emit Transfer(from, address(0), value); // 触发交易事件
    }

    /**
        1. 授权操作
        2. owner一般是调用者,spender则是授权者,value是授权代币量
     */
    function _approve(address owner, address spender, uint value) private {
        // 记录owner 对spender 的授权量为 value
        allowance[owner][spender] = value;
        emit Approval(owner, spender, value); // 触发授权事件
    }

    /**
        1. 转账操作
        2. 内置函数_transfer(), from账户向to账户转移 value的代币
        3. 而在外部函数中,transfer中的from为合约调用者
     */
    function _transfer(address from, address to, uint value) private {
        // from 账户余额减少value
        balanceOf[from] = balanceOf[from].sub(value);
        // to 账户余额增加 value
        balanceOf[to] = balanceOf[to].add(value);
        emit Transfer(from, to, value);
    }

    /**
        1. 外部授权操作
        2. 外部授权规定了授权者是调用者,予以:'我'给spender授权
     */
    function approve(address spender, uint value) external returns (bool) {
        _approve(msg.sender, spender, value);
        return true;
    }

    /**
        1. 外部转账操作
        2. 规定了转账者是调用者,予以:'我'给to转账value
     */
    function transfer(address to, uint value) external returns (bool) {
        _transfer(msg.sender, to, value);
        return true;
    }

    /**
        1. 外部转账操作,msg.sender是代理人
        2. 在 pragma=0.5.16的版本中,uint(-1)= 115792089237316195423570985008687907853269984665640564039457584007913129639935
        3. 要调用该函数,事先要让 from执行approve函数,给msg.sender授权
        4. 最后调用_transfer()函数,执行from 向 to 转账 value的操作
     */
    function transferFrom(address from, address to, uint value) external returns (bool) {
        if (allowance[from][msg.sender] != uint(-1)) {
            allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
        }
        _transfer(from, to, value);
        return true;
    }

    /**
        1. 许可操作
        2. permit函数的作用是将代币授权给指定的目标地址,
            使得目标地址可以代表代币持有人进行交易,而无需进行传统的授权交易。
            这种新型的授权方法可以提高代币交易的效率和安全性,
            同时也可以减少交易的成本和时间。
        3. owner授权者,spender被授权者,value代币数目,deadline:授权的截止时间,必须在此时间之前完成授权
           nonce:随机数,用于避免授权被重复使用,v、r、s:用于验证授权的签名参数。
     */
    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);
    }
}

2. UniswapV2Factory

工厂合约,用于创建Pair合约(以及设置协议手续费接收地址)

代码解读如下:

pragma solidity =0.5.16;

import './interfaces/IUniswapV2Factory.sol';
import './UniswapV2Pair.sol';

contract UniswapV2Factory is IUniswapV2Factory {

    address public feeTo; // 手续费接收地址
    address public feeToSetter; // 手续费接收地址的设置者

    /**
        如果将 getPair设置为public,则编译的时候会在该合约中默认生成set 和get函数
        解读:通过两个地址获取到交易对地址
     */
    mapping(address => mapping(address => address)) public getPair;

    // 数组,存储所有交易对
    address[] public allPairs;

    // 交易对创建事件
    event PairCreated(address indexed token0, address indexed token1, address pair, uint);

    // 初始化,手续费接收地址的设置者
    constructor(address _feeToSetter) public {
        feeToSetter = _feeToSetter;
    }

    // 获取交易对的对数
    function allPairsLength() external view returns (uint) {
        return allPairs.length;
    }

    /**
        首先将token0 token1按照顺序排序,确保token0字面地址小于token1。
        接着使用assembly + create2创建合约。
        assembly可以在Solidity中使用Yul语言直接操作EVM,是较底层的操作方法。
        《Uniswap v2 白皮书》中讲到,create2主要用于创建确定性的交易对合约地址,
        目的是根据两个代币地址直接计算pair地址,而无需调用链上合约查询。
     */ 
    function createPair(address tokenA, address tokenB) external returns (address pair) {
        // 两种代币地址不能相同
        require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
        // 先将token0 token1按照顺序排序,确保token0字面地址小于token1
        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        // 确保token0不等于 address(0),则两个地址都不为 address(0)
        require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
        // 确保这两种代币地址的交易对为address(0),即这两个代币尚未创建交易对
        require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
        // 获取 `UniswapV2Pair`的字节码
        bytes memory bytecode = type(UniswapV2Pair).creationCode;
        // 生成盐salt,salt由这两个地址紧打包再hash获得,是唯一的
        bytes32 salt = keccak256(abi.encodePacked(token0, token1));
        // 通过creat2计算交易对地址
        assembly {
            pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }      
        // 将新生成的交易对中的两种代币设置为 token0, token1
        IUniswapV2Pair(pair).initialize(token0, token1);
        // 记录token0 和 token1生成的交易对
        getPair[token0][token1] = pair;
        // 反向填充映射
        getPair[token1][token0] = pair; // populate mapping in the reverse direction
        // 保存该交易对pair
        allPairs.push(pair);
        // 创建成功,触发交易对生成事件
        emit PairCreated(token0, token1, pair, allPairs.length);
    }

    // 设置手续费接收地址
    function setFeeTo(address _feeTo) external {
        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
        feeTo = _feeTo;
    }

    // 修改手续费接收地址的设置者
    function setFeeToSetter(address _feeToSetter) external {
        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
        feeToSetter = _feeToSetter;
    }
}

3. UniswapV2Pair

Pair(交易对)合约,定义和交易有关的几个最基础方法,如swap/mint/burn,价格预言机等功能,其本身是一个ERC20合约,继承UniswapV2ERC20

Pair合约主要实现了三个方法:mint(添加流动性)、burn(移除流动性)、swap(兑换)。

代码解读如下:

pragma solidity =0.5.16;

import './interfaces/IUniswapV2Pair.sol';
import './UniswapV2ERC20.sol';
import './libraries/Math.sol';
import './libraries/UQ112x112.sol';
import './interfaces/IERC20.sol';
import './interfaces/IUniswapV2Factory.sol';
import './interfaces/IUniswapV2Callee.sol';

contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
    // 引用库函数
    using SafeMath  for uint;
    using UQ112x112 for uint224;

    // 最低额度的流动性
    uint public constant MINIMUM_LIQUIDITY = 10**3;
    // tansfer函数的选择器 
    bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));

    // 工厂?token0和token1交易对中的两种代币
    address public factory;
    address public token0;
    address public token1;

    // token0和token1交易对中的两种代币的存储量
    uint112 private reserve0;           // uses single storage slot, accessible via getReserves
    uint112 private reserve1;           // uses single storage slot, accessible via getReserves
    // 上次更新的时间
    uint32  private blockTimestampLast; // uses single storage slot, accessible via getReserves

    // 累加交易价格
    uint public price0CumulativeLast;
    uint public price1CumulativeLast;
    uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event

    // 锁标志
    uint private unlocked = 1;

    // 修饰器为了防止异步
    modifier lock() {
        require(unlocked == 1, 'UniswapV2: LOCKED');
        unlocked = 0;
        _;
        unlocked = 1;
    }

    // 读取交易对中两种代币的余额,以及上一次交易对更新的时间
    function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
        _reserve0 = reserve0;
        _reserve1 = reserve1;
        _blockTimestampLast = blockTimestampLast;
    }

    /**
        1. 通过call的方式,调用token中的 transfer 函数---兼容性更强
        2. 判断调用是否成功,success是否为true,data是否为空
     */
    function _safeTransfer(address token, address to, uint value) private {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
    }

    event Mint(address indexed sender, uint amount0, uint amount1);
    event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
    event Swap(
        address indexed sender,
        uint amount0In,
        uint amount1In,
        uint amount0Out,
        uint amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    // 初始化 工厂地址为调用者
    constructor() public {
        factory = msg.sender;
    }

    // called once by the factory at time of deployment
    // 初始化交易对中的两种代币地址
    function initialize(address _token0, address _token1) external {
        require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
        token0 = _token0;
        token1 = _token1;
    }

    // update reserves and, on the first call per block, price accumulators
    function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
        require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
        // 记录当前更新时间
        uint32 blockTimestamp = uint32(block.timestamp % 2**32);
        // 记录过去了多久
        uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
        // 计算出当前的交易价格
        if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
            // * never overflows, and + overflow is desired
            price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
            price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
        }
        // 将这两种代币的存储量设置为代币的余额
        reserve0 = uint112(balance0);
        reserve1 = uint112(balance1);
        // 更新当前操作时间
        blockTimestampLast = blockTimestamp;
        // 触发同步事件
        emit Sync(reserve0, reserve1);
    }

    // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
    // 计算铸币手续费
    function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
        address feeTo = IUniswapV2Factory(factory).feeTo(); // 获取当前factory铸币手续费的接收地址
        feeOn = feeTo != address(0); // 检查该factory是否设置了手续费接收地址
        uint _kLast = kLast; // gas savings
        if (feeOn) { // 如果该factory有手续费接收地址
            if (_kLast != 0) {
                uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
                uint rootKLast = Math.sqrt(_kLast);
                if (rootK > rootKLast) {
                    uint numerator = totalSupply.mul(rootK.sub(rootKLast));
                    uint denominator = rootK.mul(5).add(rootKLast);
                    uint liquidity = numerator / denominator;
                    if (liquidity > 0) _mint(feeTo, liquidity); // 给feeTo地址铸币liquidity
                }
            }
        } else if (_kLast != 0) {
            kLast = 0;
        }
    }

    // this low-level function should be called from a contract which performs important safety checks
    // 铸币操作,添加流动性
    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));
        // 计算当前合约中两个代币的净增量,并赋值给amount0和amount1变量
        // 净增量等于余额减去储备量
        uint amount0 = balance0.sub(_reserve0);
        uint amount1 = balance1.sub(_reserve1);
        // 调用_mintFee函数,计算是否需要收取协议手续费,并返回一个布尔值,赋值给feeOn变量
        bool feeOn = _mintFee(_reserve0, _reserve1); // 理解为支付完铸币费用
        // 记录发行量,难道这里也讲究异步的现象?
        uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee

        // 如果是首次提供该交易对的流动性,则根据根号xy生成流动性代币,并销毁其中的MINIMUM_LIQUIDITY(即1000wei
        if (_totalSupply == 0) {
            // 计算流动性代币的数量,等于两个代币净增量乘积的平方根减去最小流动性常量,并赋值给liquidity变量
            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
            // 调用_mint函数,向零地址铸造最小流动性常量数量的流动性代币(永久锁定)
           _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
        } else {
            // 计算流动性代币的数量,等于两个代币净增量与储备量比例乘积与总供应量乘积的较小值,并赋值给liquidity变量
            liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
        }
        require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
        _mint(to, liquidity); // 为to铸币,liquidity

        _update(balance0, balance1, _reserve0, _reserve1); // 调用_update函数,更新当前合约中两个代币的储备量为最新的余额
        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
        emit Mint(msg.sender, amount0, amount1);
    }

    // this low-level function should be called from a contract which performs important safety checks
    // 销币操作,移除流动性
    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)];

        // 参考白皮书,为了节省交易手续费,Uniswap v2只在mint/burn流动性时收取累计的协议手续费。
        bool feeOn = _mintFee(_reserve0, _reserve1);
        uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
        // 采用mint中计算 liquidity的方法倒推amount
        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');
        // 销毁本合约的 流动性 liquidity
        _burn(address(this), liquidity);
        // 调用_token0(_token1)中的 transfer函数

        // 通过各自token中实现的transfer将token转移回to
        _safeTransfer(_token0, to, amount0);
        _safeTransfer(_token1, to, amount1);
        //更新合约自身的token0、1余额
        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);
    }

    // this low-level function should be called from a contract which performs important safety checks

   /**
        实现两种代币的交换(交易)功能-----闪电贷功能
            uint amount0Out:要转换的第一种代币的数量。
            uint amount1Out:要转换的第二种代币的数量。
            address to:接收转换后代币的目标地址。
            bytes data:可选的额外数据,用于向目标地址提供更多信息。
   */  
   // 接收者 to 必须要实现 `uniswapV2Call`函数,通过此函数输入交换的代币
    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
        // 计算调整后的余额,因为在solidity中没有浮点数 0.3%,这样是为了模拟出 0.3%
        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);
    }

    // force balances to match reserves
    /**
        用于移除资金池中多余代币的函数。它的作用是将代币池中多余的代币转移到指定的目标地址,以便于在资金池中保持正确的代币比例
     */
    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));
    }

    // force reserves to match balances
    // 同步,迫使代币余额与代币储量相匹配,调用_update函数
    function sync() external lock {
        _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
    }
}

分析 mint函数,举例

假设有一个流动性池,其中有 10 个 ETH 和 5000 个 USDC,总供应量为 70.71 个流动性代币(这里面包括过去交易留下的Fees)。现在,有人想向这个池中添加 1 个 ETH 和 500 个 USDC,以获得更多的流动性代币。那么,他们将获得多少流动性代币呢?

首先,我们需要计算添加后的两种代币的储备量和余额。储备量等于原来的储备量加上添加的数量,余额等于储备量加上协议手续费(假设为 0.3%)。因此,我们有:

  • 储备量:reserve0 = 10 + 1 = 11 ETH,reserve1 = 5000 + 500 = 5500 USDC
  • 余额:balance0 = reserve0 (1 + 0.003) = 11.033 ETH,balance1 = reserve1 (1 + 0.003) = 5516.5 USDC

接下来,我们需要计算添加后的总供应量。如果是第一次添加流动性,则使用公式 sqrt(x y) — MINIMUM_LIQUIDITY,否则使用公式 min(x totalSupply / reserve0, y * totalSupply / reserve1)。因为这不是第一次添加流动性,所以我们使用后者。因此,我们有:

  • 总供应量:totalSupply = min(balance0 70.71 / reserve0, balance1 70.71 / reserve1) = min(71.41, 71.41) = 71.41

最后,我们需要计算添加者获得的流动性代币的数量。这个数量等于添加后的总供应量减去添加前的总供应量。因此,我们有:

  • 流动性代币:liquidity = totalSupply — 70.71 = 71.41–70.71 = 0.7

也就是说,添加者将获得大约 0.7 个流动性代币。

分析burn,举例

假设有一个池子,它允许交易 ETH 和 DAI,它的地址是 0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11。这个池子有以下的状态:

  • 流动性代币的总供应量是 3,000,000
  • 池子里有 10,000 个 ETH 和 4,000,000 个 DAI。
  • 协议费用是 0.05%
  • 你拥有 30,000 个流动性代币,也就是池子的 1%

现在,你想要退出这个池子,把你的流动性代币销毁,并拿回你的 ETH 和 DAI。你可以调用这个函数,把 to 参数设为你自己的地址。这样,函数会做以下的事情:

  • 它会从池子里获取 ETH 和 DAI 的储备量,分别是 _reserve0 = 10,000_reserve1 = 4,000,000
  • 它会获取 ETH 和 DAI 的地址,分别是 _token0 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2_token1 = 0x6B175474E89094C44Da98b954EedeAC495271d0F
  • 它会获取合约自身的 ETH 和 DAI 的余额,分别是 balance0 = 10,000balance1 = 4,000,000。注意,这里假设没有其他人在同一区块内与池子交互,否则余额可能会有变化。
  • 它会获取合约自身的流动性代币余额,也就是 liquidity = 30,000
  • 它会调用 _mintFee 函数,来分发协议费用给流动性提供者。假设在你加入池子后,没有发生过任何交易,那么协议费用就是零,所以 _mintFee 函数不会改变任何东西,并返回 feeOn = false
  • 它会计算你能够拿回的 ETH 和 DAI 的数量,分别是 amount0 = liquidity * balance0 / totalSupply = 30,000 * 10,000 / 3,000,000 = 100amount1 = liquidity * balance1 / totalSupply = 30,000 * 4,000,000 / 3,000,000 = 40,000。这保证了按比例分配。

    知识点:

abi.decode(data, (bool))是Solidity中的一种函数调用,用于将字节数组(byte array)解码为布尔值(bool)类型。

具体来说,abi.decode函数接受两个参数:字节数组和数据类型。在这里,字节数组是要解码的数据,数据类型是要解码成的目标类型,即布尔值。

函数调用abi.decode(data, (bool))将字节数组解码为一个布尔值。这个布尔值的值取决于字节数组中的数据。如果字节数组中的数据为0,则解码后的布尔值为false,否则为true。

知识点:

UQ112x112.encode(_reserve1).uqdiv(_reserve0)

UQ112x112.encode(_reserve1).uqdiv(_reserve0)是Uniswap V2中的一个计算交易价格的操作,使用了UQ112x112固定点数算法。

具体来说,_reserve0_reserve1是Uniswap V2交易对中两种资产的余额(reserve),UQ112x112.encode()函数将余额编码为UQ112x112固定点数格式,然后使用了.uqdiv()函数对两种资产的余额进行了除法操作,计算出当前的交易价格。

UQ112x112固定点数算法是一种用于在以太坊合约中进行精确数学计算的算法。它将浮点数转换为整数,并使用固定的小数位数进行计算。在Uniswap V2中,UQ112x112固定点数算法被广泛应用于计算交易价格和资金池分配等。

在这个操作中,UQ112x112.encode(_reserve1)_reserve1编码为UQ112x112格式,然后.uqdiv(_reserve0)将编码后的_reserve1除以_reserve0。最终的结果是一个UQ112x112格式的数,表示当前的交易价格。

v2-periphery

1. UniswapV2Router02

pragma solidity =0.6.6;

import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';

import './interfaces/IUniswapV2Router02.sol';
import './libraries/UniswapV2Library.sol';
import './libraries/SafeMath.sol';
import './interfaces/IERC20.sol';
import './interfaces/IWETH.sol';

contract UniswapV2Router02 is IUniswapV2Router02 {
    using SafeMath for uint;

    address public immutable override factory;
    address public immutable override WETH;

    // 修饰器确保 操作在截止日期之前
    modifier ensure(uint deadline) {
        require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
        _;
    }

    // 初始化factory 和 WETH地址
    constructor(address _factory, address _WETH) public {
        factory = _factory;
        WETH = _WETH;
    }

    // 接收ETH
    receive() external payable {
        assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
    }

    // **** ADD LIQUIDITY ****
    /**  _addLiquidity可以帮助计算最佳汇率。如果是首次添加流动性,则会先创建交易对合约;
        否则根据当前池子余额计算应该注入的最佳代币数量。 
    */
    function _addLiquidity(
        address tokenA, // 代币A
        address tokenB, // 代币B
        uint amountADesired, // 希望存入的代币A数量
        uint amountBDesired, // 希望存入的代币B数量
        uint amountAMin, // 最少存入的代币A数量
        uint amountBMin // 最少存入的代币B数量
    ) 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);
        }
        // 获取交易对中代币A B的存储量
        (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
        // 如果储备量都为 0,那两个预期支付额就是成交量
        if (reserveA == 0 && reserveB == 0) {
            (amountA, amountB) = (amountADesired, amountBDesired);
        } else {
            // 采用控制变量法算出最佳的汇率

            /**
                如果计算得出的结果值 amountBOptimal 不比 amountBDesired 大,且不会小于 amountBMin,
                就可将 amountADesired 和该 amountBOptimal 作为结果值返回。
                如果 amountBOptimal 大于 amountBDesired,则根据 amountBDesired 计算得出需要支付多少 tokenA,
                得到 amountAOptimal,只要 amountAOptimal 不大于 amountADesired 且不会小于 amountAMin,
                就可将 amountAOptimal 和 amountBDesired 作为结果值返回。
             */

            // 调用quote函数,换算amountADesired 对应的B代币为多少
            uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);

            // 最佳的B代币数量少于希望存入的代币B数量
            if (amountBOptimal <= amountBDesired) {
                // 这是定 amountADesired,求B的最佳数量
                // 而且最佳B代币数量要求 >= amountBMin
                require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
                // 返回希望存入的A,和最佳的B数量
                (amountA, amountB) = (amountADesired, amountBOptimal);
            } else {
                // 定amountBDesired,求A的最佳数量
                // 调用quote函数,换算amountBDesired 对应的A代币为多少
                uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
                assert(amountAOptimal <= amountADesired);
                require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
                // 返回希望存入的B,和最佳的A数量
                (amountA, amountB) = (amountAOptimal, amountBDesired);
            }
        }
    }
    function addLiquidity(
        address tokenA, // 代币A
        address tokenB, // 代币B
        uint amountADesired, // 希望存入的代币A数量
        uint amountBDesired, // 希望存入的代币B数量
        uint amountAMin, // 用户可接受的最小成交代币A数量
        uint amountBMin, // 用户可接受的最小成交代币B数量
        address to, // 流动性代币接收地址
        uint deadline // 该笔交易的有效时间,如果超过该时间还没得到交易处理就直接失效不进行交易了
    ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
        // 调用_addLiquidity函数,返回 代币A和代币B 的最佳汇率
        (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
        // 获取交易对地址
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        // 调用tokenA的`transferFrom`函数,实现msg.sender向交易对pair转入amountA代币
        TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
        TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
        // 获取存入代币后uniswapv2中的流动性
        liquidity = IUniswapV2Pair(pair).mint(to);
    }

    /** 添加流动性 */
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
        // 调用_addLiquidity函数,返回 TOken和ETH的最佳汇率
        (amountToken, amountETH) = _addLiquidity(
            token,
            WETH,
            amountTokenDesired,
            msg.value,
            amountTokenMin,
            amountETHMin
        );
        // 获取交易对地址
        address pair = UniswapV2Library.pairFor(factory, token, WETH);
        // 调用token的`transferFrom`函数,实现msg.sender向交易对pair转入amountToken代币
        TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
        // 先把amountETH数量的ETH存入本合约
        IWETH(WETH).deposit{value: amountETH}();
        // 再将amount数量的WETH转入交易对中
        assert(IWETH(WETH).transfer(pair, amountETH));
        // 获取存入代币后uniswapv2中的流动性
        liquidity = IUniswapV2Pair(pair).mint(to);
        // refund dust eth, if any
        // 如果还有剩余的ETH,将退还
        if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
    }

    // **** 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);
        // 将流动性代币从用户划转到 pair 合约
        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);
        // 如果低于用户设定的最低预期(amountAMin/amountBMin),则回滚交易
        require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
        require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
    }

    function removeLiquidityETH(
        address token, //待移除流动性的代币地址
        uint liquidity, // 要移除的流动性数量
        uint amountTokenMin, // 用户愿意接受的最小代币数量,如果实际返回的代币数量小于该值,则函数会抛出异常
        uint amountETHMin, // 用户愿意接受的最小ETH数量,如果实际返回的ETH数量小于该值,则函数会抛出异常
        address to, // 代币和ETH将被发送到的目标地址
        uint deadline // 操作的截止时间,必须在该时间之前完成操作,否则操作将被视为无效
    ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
        // 调用 removeLiquidity,得到退还的 Token 和 ETH
        // removeLiquidity中执行了 `burn`但是是将token和ETH转移到了addrss(this),说明还可以执行 safeTransfer 和 withdraw
        (amountToken, amountETH) = removeLiquidity(
            token,
            WETH,
            liquidity,
            amountTokenMin,
            amountETHMin,
            address(this),
            deadline
        );
        // 向to转账(token)
        TransferHelper.safeTransfer(token, to, amountToken);
        // 将weth取出来,暂时存在addrss(this)
        IWETH(WETH).withdraw(amountETH);
        // 将address(this)中的WETH转到指定地址to
        TransferHelper.safeTransferETH(to, amountETH);
    }

    // 其实就是在调用实际的 removeLiquidity 之前先用 permit 方式完成授权操作
    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external virtual override returns (uint amountA, uint amountB) {
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        uint value = approveMax ? uint(-1) : liquidity;
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
    }

    // 在调用实际的 removeLiquidity 之前先用 permit 方式完成授权操作
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external virtual override returns (uint amountToken, uint amountETH) {
        address pair = UniswapV2Library.pairFor(factory, token, WETH);
        uint value = approveMax ? uint(-1) : liquidity;
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
    }

    // **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****
    /** 
        返回值没有 amountToken;
        调用 removeLiquidity 后也没有 amountToken 值返回
        进行 safeTransfer 时传值直接读取当前地址的 token 余额。
     */
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) public virtual override ensure(deadline) returns (uint amountETH) {
        (, amountETH) = removeLiquidity(
            token,
            WETH,
            liquidity,
            amountTokenMin,
            amountETHMin,
            address(this),
            deadline
        );
        TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
        IWETH(WETH).withdraw(amountETH);
        TransferHelper.safeTransferETH(to, amountETH);
    }

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external virtual override returns (uint amountETH) {
        address pair = UniswapV2Library.pairFor(factory, token, WETH);
        uint value = approveMax ? uint(-1) : liquidity;
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
            token, liquidity, amountTokenMin, amountETHMin, to, deadline
        );
    }

    // **** 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 {
        /** 历整个兑换路径,并对路径中每两个配对的 token 调用 pair 合约的兑换函数,实现底层的兑换处理 */
        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];
            // 如下语句是在判断要兑换的是哪种 货币,比如 A 或者 B 
            (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)
            );
        }
    }

    /** 用 ERC20 兑换 ERC20,但支付的数量是指定的,而兑换回的数量则是未确定的 */
    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');
        // 将支付的代币转到 pair 合约
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        _swap(amounts, path, to);
    }
    /** 用 ERC20 兑换 ERC20,与上一个函数不同,指定的是兑换回的数量 */
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
        require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        _swap(amounts, path, to);
    }

    /** 指定 ETH 数量兑换 ERC20 */
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        payable
        ensure(deadline)
        returns (uint[] memory amounts)
    {
        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
        amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
        IWETH(WETH).deposit{value: amounts[0]}();
        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
        _swap(amounts, path, to);
    }
    /** 用 ERC20 兑换成指定数量的 ETH */
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        ensure(deadline)
        returns (uint[] memory amounts)
    {
        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
        require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        _swap(amounts, path, address(this));
        IWETH(WETH).withdraw(amounts[amounts.length - 1]);
        TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
    }
    /** 用指定数量的 ERC20 兑换 ETH */
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        ensure(deadline)
        returns (uint[] memory amounts)
    {
        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
        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, address(this));
        IWETH(WETH).withdraw(amounts[amounts.length - 1]);
        TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
    }
    /** 用 ETH 兑换指定数量的 ERC20 */
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        payable
        ensure(deadline)
        returns (uint[] memory amounts)
    {
        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
        amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
        require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
        IWETH(WETH).deposit{value: amounts[0]}();
        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
        _swap(amounts, path, to);
        // refund dust eth, if any
        if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
    }

    // **** SWAP (supporting fee-on-transfer tokens) ****
    // requires the initial amount to have already been sent to the first pair

    function _swapSupportingFeeOnTransferTokens(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);
            IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
            uint amountInput;
            uint amountOutput;
            { // scope to avoid stack too deep errors
            (uint reserve0, uint reserve1,) = pair.getReserves();
            (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
            amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
            amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
            }
            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
            address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
            pair.swap(amount0Out, amount1Out, to, new bytes(0));
        }
    }

    /** 指定数量的 ERC20 兑换 ERC20,支持转账时扣费 */
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external virtual override ensure(deadline) {
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
        );
        uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
        _swapSupportingFeeOnTransferTokens(path, to);
        require(
            IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
            'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
        );
    }
    /** 指定数量的 ETH 兑换 ERC20,支持转账时扣费 */
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    )
        external
        virtual
        override
        payable
        ensure(deadline)
    {
        require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
        uint amountIn = msg.value;
        IWETH(WETH).deposit{value: amountIn}();
        assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
        uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
        _swapSupportingFeeOnTransferTokens(path, to);
        require(
            IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
            'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
        );
    }
     /** 指定数量的 ETH 兑换 ERC20,支持转账时扣费 */
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    )
        external
        virtual
        override
        ensure(deadline)
    {
        require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
        );
        _swapSupportingFeeOnTransferTokens(path, address(this));
        uint amountOut = IERC20(WETH).balanceOf(address(this));
        require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
        IWETH(WETH).withdraw(amountOut);
        TransferHelper.safeTransferETH(to, amountOut);
    }

    // **** LIBRARY FUNCTIONS ****
    function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
        return UniswapV2Library.quote(amountA, reserveA, reserveB);
    }

    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
        public
        pure
        virtual
        override
        returns (uint amountOut)
    {
        return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
    }

    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
        public
        pure
        virtual
        override
        returns (uint amountIn)
    {
        return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
    }

    function getAmountsOut(uint amountIn, address[] memory path)
        public
        view
        virtual
        override
        returns (uint[] memory amounts)
    {
        return UniswapV2Library.getAmountsOut(factory, amountIn, path);
    }

    function getAmountsIn(uint amountOut, address[] memory path)
        public
        view
        virtual
        override
        returns (uint[] memory amounts)
    {
        return UniswapV2Library.getAmountsIn(factory, amountOut, path);
    }
}

解读 addLiquidity中的amountAMin 和 amountBMin

该值一般是由前端根据预期值和滑点值计算得出的。比如,预期值 amountADesired 为 1000,设置的滑点为 0.5%,那就可以计算得出可接受的最小值 amountAMin 为 1000 * (1 - 0.5%) = 995。

addLiquidityETH

addLiquidityETH 则支付的其中一个 token 则是 ETH,而不是 ERC20 代币。来看看其代码实现:

image.png

可看到,入参不再是两个 token 地址,而只有一个 token 地址,因为另一个是以太坊主币 ETH。预期支付的 ETH 金额也是直接从 msg.value 读取的,所以入参里也不需要 ETH 的 Desired 参数。但是会定义 amountETHMin 表示愿意接受成交的 ETH 最小额。

实现逻辑上,请注意,调用 _addLiquidity 时传入的第二个参数是 WETH。其实,addLiquidityETH 实际上也是将 ETH 转为 WETH 进行处理的。可以看到代码中还有这么一行:

IWETH(WETH).deposit{value: amountETH}();

这就是将用户转入的 ETH 转成了 WETH。

而最后一行代码则会判断,如果一开始支付的 msg.value 大于实际需要支付的金额,多余的部分将返还给用户

Library

1. UniswapV2Library

image.png

代码解读如下:

pragma solidity >=0.5.0;

import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';

import "./SafeMath.sol";

library UniswapV2Library {
    using SafeMath for uint;

    // returns sorted token addresses, used to handle return values from pairs sorted in this order
    // 给 tokenA和tokenB排序,按字面值排序
    function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
        require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
    }

    // calculates the CREATE2 address for a pair without making any external calls
    // 输入工厂地址和两个代币地址,计算这两个代币的交易对地址
    function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
        // 先对这两个代币地址进行排序
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        // 采用create2的方式计算地址
        pair = address(uint(keccak256(abi.encodePacked(
                hex'ff',
                factory,
                keccak256(abi.encodePacked(token0, token1)),
                // UniswapV2Pair 合约的 creationCode 的哈希值
                hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
            ))));
    }

    // fetches and sorts the reserves for a pair
    function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
        // 先让 tokenA 和 tokenB 从小到大排列
        (address token0,) = sortTokens(tokenA, tokenB);

        // 根据 `pairFor(factory, tokenA, tokenB)`算出一个新的地址
        // .getReserves() 获取 该新地址的 reserve0,reserve1
        (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();

        // 如果交换过顺序就交换输出,简单来说及时为了对应输入的形参
        (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
    }

    // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
    /**
        数量为amountA的代币A,按照合约中两种代币余额比例,换算成另一个代币B。此时不考虑手续费,因为仅是计价单位的换算
        【根据给定的两个 token 的储备量和其中一个 token 数量,计算得到另一个 token 等值的数值】
     */
    function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
        require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
        require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        amountB = amountA.mul(reserveB) / reserveA;
    }

    // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
    /**
        该方法计算:输入一定数量(amountIn)代币A,根据池子中代币余额,能得到多少数量(amountOut)代币B。
        amountIn指输入的代币A,reserveIn 指代币A的存储量,reserveOut指代币B的存储量

        【根据给定的两个 token 的储备量和输入的 token 数量,计算得到输出的 token 数量,该计算会扣减掉 0.3% 的手续费】
     */
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
        require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        uint amountInWithFee = amountIn.mul(997);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
    }

    // given an output amount of an asset and pair reserves, returns a required input amount of the other asset
    /**
        该方法计算当希望获得一定数量(amountOut)的代币B时,应该输入多少数量(amoutnIn)的代币A。
        amountOut指要得到的代币B,reserveIn 指代币A的存储量,reserveOut指代币B的存储量
        【根据给定的两个 token 的储备量和输出的 token 数量,计算得到输入的 token 数量,该计算会扣减掉 0.3% 的手续费】
     */
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
        require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        uint numerator = reserveIn.mul(amountOut).mul(1000);
        uint denominator = reserveOut.sub(amountOut).mul(997);
        amountIn = (numerator / denominator).add(1);
    }

    // performs chained getAmountOut calculations on any number of pairs
    /**
        该方法用于计算在使用多个交易对时,输入一定数量(amountIn)的第一种代币,
        最终能收到多少数量的最后一种代币(amounts)。amounts数组中的第一个元素表示amountIn,
        最后一个元素表示该目标代币对应的数量。该方法实际上是循环调用getAmountIn方法。
        【根据兑换路径和输入数量,计算得到兑换路径中每个交易对的输出数量】
     */
    function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
        require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
        // 创建一个和path等长的uint数组
        amounts = new uint[](path.length);
        amounts[0] = amountIn;
        for (uint i; i < path.length - 1; i++) {
            (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
            amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
        }
    }

    // performs chained getAmountIn calculations on any number of pairs
    /**
        与getAmountsOut相对,getAmountsIn用于计算当希望收到一定数量(amountOut)的目标代币,
        应该分别输入多少数量的中间代币。计算方法也是循环调用getAmountIn。
     */
    function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
        require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
        amounts = new uint[](path.length);
        amounts[amounts.length - 1] = amountOut;
        for (uint i = path.length - 1; i > 0; i--) {
            (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
            amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
        }
    }
}

getAmountOut 的实现:

image.png

根据 AMM 的原理,恒定乘积公式「x * y = K」,兑换前后 K 值不变。因此,在不考虑交易手续费的情况下,以下公式会成立:

reserveIn * reserveOut = (reserveIn + amountIn) * (reserveOut - amountOut)

将公式右边的表达式展开,并推导下,就变成了:

reserveIn * reserveOut = reserveIn * reserveOut + amountIn * reserveOut - (reserveIn + amountIn) * amountOut
->
amountIn * reserveOut = (reserveIn + amountIn) * amountOut
->
amountOut = amountIn * reserveOut / (reserveIn + amountIn)

而实际上交易时,还需要扣减千分之三的交易手续费,所以实际上:

amountIn = amountIn * 997 / 1000

代入上面的公式后,最终结果就变成了:

amountOut = (amountIn * 997 / 1000) * reserverOut / (reserveIn + amountIn * 997 / 1000)
->
amountOut = amountIn * 997 * reserveOut / 1000 * (reserveIn + amountIn * 997 / 1000)
->
amountOut = amountIn * 997 * reserveOut / (reserveIn * 1000 + amountIn * 997)

这即是最后代码实现中的计算公式了。

解读getAmountsOut

根据兑换路径和输入数量,计算得到兑换路径中每个交易对的输出数量。

举例:

假如一个交易地址数组 path[A,B,C,D] ,其中

pair(A,B)中的tokenA=6,tokenB=9; pair(B,C)中的tokenA=10,tokenB=8; pair(C,D)中的tokenA=19,tokenB=84

则 amounts[0] = IN_tokenA,

pair(A,B) : amount[1] = getAmountOut(IN_tokenA, reserveA, reserveB),

pair(B,C): amount[2] = getAmountOut(IN_tokenA, reserveA, reserveB),

pair(C,D): amount[3] = getAmountOut(IN_tokenA, reserveA, reserveB).

该函数会计算 path 中每一个中间资产和最终资产的数量,比如 path 为 [A,B,C],则会先将 A 兑换成 B,再将 B 兑换成 C。返回值则是一个数组,第一个元素是 A 的数量,即 amountIn,而第二个元素则是兑换到的代币 B 的数量,最后一个元素则是最终要兑换得到的代币 C 的数量。

从代码中还可看到,每一次兑换其实都调用了 getAmountOut 函数,这也意味着每一次中间兑换都会扣减千分之三的交易手续费。那如果兑换两次,实际支付假设为 1000,那最终实际兑换得到的价值只剩下:

1000 * (1 - 0.003) * (1 - 0.003) = 994.009

即实际支付的交易手续费将近千分之六了。兑换路径越长,实际扣减的交易手续费会更多,所以兑换路径一般不宜过长。

getAmountsIn同理

最后,码字不易,点个赞呗~🤪

参考链接

uniswap v2白皮书 链接1

uniswap v2代码 链接2

uiniswap v2代码core代码解读 链接3

登链社区

点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
BY_DLIFE
BY_DLIFE
0x994d...4240
立志成为一名智能合约安全审计师。文章都是我的个人理解,如果有不对的地方欢迎在评论区指出来。