区块链2.0的杀手应用 — UniswapV2

区块链安全 2年前 (2022) admin
537 0 0


一、简介


UniswapV2是一个建立在以太坊上的主流去中心化加密交易平台(DEX),实现了自动化做市商(AMM)。
AMM要实现s自动完成与买卖方的交易,需要满足以下几个特性:
1.AMM要持有资产,由于要双向报价,所以需要同时持有两种资产。
2.AMM资产池应该能实现充值与提现。
3.AMM应该根据市场情况自动调整价格。
4.AMM要能通过交易赚取利润。


二、 原理


UniswapV2部署在以太坊上,其本身是一个支持ERC-20标准的token合约,其定义在合约文件UniswapV2ERC20.sol中。
UniswapV2可以完成任意两种ERC-20token的交换,其实现在uniswapV2Pair合约中,该合约主要提供以下3种功能:
1.流动性追踪,追踪交易池中的代币余额,并提供流动性代币。
2.自动做市,通过算法计算出市场价格。
3.去中心化预言机,供外部使用。

2.1流动性
什么是流动性?在uniswap中可以被认为是在一个交易对合约中的两种代币的总和,如果用户同时向合约质押两种代币,则被称为增加流动性。由所有流动性汇成的池子为流动性池。而向流动性池中添加流动性的的人被称为流动性提供者(LP)。而流动性池会根据流动性提供者(LP)对池子的贡献,会给LP一些凭证,这个凭证使用uniswap中的ERC-20token合约实现,LP提供流动性时,token合约给LP铸相应的币,减少流动性的时候会销毁相应的token,这个凭证被称为LPs。而每个LP对池子的贡献对于池子的比重被称为流动性池份额

2.2功能
2.2.1 质押 
首先工厂合约会初始化创建一个交易对(pair)合约,在这个合约的池子中,两种代币的初始值都是0,需要LP质押一定数量的两种代币来启动池子。 在池子中的两种代币的相对价值是由两种代币的数量比例决定的
区块链2.0的杀手应用 — UniswapV2
意思是两种代币的总价值应该一直保持相同,而交易会改变两种代币的数量,所以价格也在一直变化,其公式为: 其中两种代币的数量分别为x, y, 而k是一个常数。 第一个LP在质押的两个代币的数量分别为x0,y0,, 那么获得的LPSs_0,满足公式:
区块链2.0的杀手应用 — UniswapV2
而在交易过程中,合约会收取一定比例的手续费,而手续费也会放回到流动性池中,相当于流动性的总价值变多,而LPs的总量没有变化,所以相当于单个LPs的价值升高。升高的价值就是LP获取的收益。 而如果LPs的单价过高,高到1wei(以太坊中的最小单位)个LPs的价值也非常高,这时候再往池子总添加流动性的成本就会非常高,不利于维持交易的流动性。为了防止这种情况,uniswap设置了一个最小流动性,该值为1000wei,创建流动性池的时候,都要向0地址锁定这个值的LPS,如果认为要把LPs价格提的很高,那么他就需要再0地址锁定的时候付出更多的成本。 
接下来在有LP继续添加流动性的时候,新铸造的LPs为:
区块链2.0的杀手应用 — UniswapV2
x_n,y_n为原先池子中两种代币的数量,x_a,y_a为新添加的两种代币的数量, 合约会按照两种代币新添加值与原值比例小的那个,再与原LPs的数量相乘,作为新铸造的LPs。为了利益最大化,LP通常会按照现有池子的比例提供两种代币,即:
区块链2.0的杀手应用 — UniswapV2
LPs由LP持有,并且其他性质与ERC-20一样,可以进行自由交易。 

2.2.2自动做市 
首先我们已经知道流动池中的两种代币满足恒定乘积公式:
x × y=k 
那么如果想用x换y,则满足:
(x+Δx)×(y-Δy)=k 
换出来的y为: 
区块链2.0的杀手应用 — UniswapV2
如果交易量较小的话,交易价格就近似于两种代币在池子中的比值。
交易过程: 
1. 交易之前,Pair合约中两种token的数量 x1,y1满足: 
区块链2.0的杀手应用 — UniswapV2
2. 用户发送token x到pair合约。 
3. 调用合约中的swap接口,计算获取到的y token, 并发送这些token给用户。 
4. 完成交易之后,pair合约中两种代币的数量x2,y2应满足:
区块链2.0的杀手应用 — UniswapV2

2.2.3  价格预言 
UniswapV2中通过测量和记录每一个块的第一次交易之前的价格来进行,从而避免操纵价格预言机所产生的攻击,计算出的叫个为上一个区块中的相对价格的平均累计值:
区块链2.0的杀手应用 — UniswapV2
2.2.4   闪电贷 
UniswapV2允许用户在同一原子交易中完成借贷于还款。用户可以不用向pair合约做任何抵押就可以借走池子中的所有a代币,只要在交易结束后还上足够数量的b代币即可,则回退整个交易。
交易过程:
1. 交易之前pair合约中两种token的数量应满足:
x × y=k 
2. 用户调用合约中的swap函数,指定要借贷的数量与回调函数的相关参数。
3. pair合约向用户发送相应的x代币。 
4. 合约调用用户指定的回调函数。
5. 检查回调函数函数完成之后,池子中的两种代币数量应满足:
区块链2.0的杀手应用 — UniswapV2
若不满足则回退整个交易。



三、源码解析


3.1   UniswapV2ERC20.sol

该合约为一个ERC-20合约,定义了LPtoken代币。
pragma solidity =0.5.16;

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

contract UniswapV2ERC20 is IUniswapV2ERC20 {
    using SafeMath for uint;
  
  // token名称
    string public constant name = 'Uniswap V2';
    // token的标志
    string public constant symbol = 'UNI-V2';
    // 精度
    uint8 public constant decimals = 18;
    // token的总供应量
    uint  public totalSupply;
    
    // 每个地址的余额
    mapping(address => uint) public balanceOf;
    
    // 每个地址对每个地址的授权数量
    mapping(address => mapping(address => uint)) public allowance;
  
  // EIP-712中规定的DOMAIN_SEPARATOR
    bytes32 public DOMAIN_SEPARATOR;
    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    // EIP-712中规定的TYPEHASH
    bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
    // 地址与其nonce值,用于避免重放
    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 {
      // 当前链的id
        uint chainId;
        assembly {
          // 通过内联汇编获取chainid
            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)
            )
        );
    }
  
  // 铸币 向指定地址铸造一定数量的代币
    function _mint(address to, uint value) internal {
      // 增加相应的总供应量
        totalSupply = totalSupply.add(value);
        // 对应地址增加余额
        balanceOf[to] = balanceOf[to].add(value);
        // 触发事件,0地址向目标地址发送相应的代币
        emit Transfer(address(0), to, value);
    }
  
  // 销毁 销毁某个地址的一定数量的代币
    function _burn(address from, uint value) internal {
      // 减小余额
        balanceOf[from] = balanceOf[from].sub(value);
        // 减小相应的总供应量
        totalSupply = totalSupply.sub(value);
        // 触发事件 目标地址向0地址发送相应数量的代币
        emit Transfer(from, address(0), value);
    }
  
  // 授权
    function _approve(address owner, address spender, uint value) private {
      // 修改相应的allowance
        allowance[owner][spender] = value;
        emit Approval(owner, spender, value);
    }
  
  // 转账
    function _transfer(address from, address to, uint value) private {
        balanceOf[from] = balanceOf[from].sub(value);
        balanceOf[to] = balanceOf[to].add(value);
        emit Transfer(from, to, value);
    }

    function approve(address spender, uint value) external returns (bool) {
        _approve(msg.sender, spender, value);
        return true;
    }
  
  // 调用者向某地址转账
    function transfer(address to, uint value) external returns (bool) {
        _transfer(msg.sender, to, value);
        return true;
    }
  
  // 授权转账
    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;
    }
  
  // 授权
    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(
                'x19x01',
                DOMAIN_SEPARATOR,
                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
            )
        );
        // 验证签名并获取签名信息的地址
        address recoveredAddress = ecrecover(digest, v, r, s);
        // 确保地址不是0地址且地址是owner地址
        require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
        // 授权
        _approve(owner, spender, value);
    }
}

3.2   UniswapV2Pair.sol

该合约时unswap的核心合约,该合约定义了两种代币组成的交易对,代币存在这个合约中并组成交易池。
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;
    // UQ112x112库,将224分为两个部分,一个112最为整数部分,另一个112作为浮点数
    using UQ112x112 for uint224;
 
  // 最小流动性定义,
    uint public constant MINIMUM_LIQUIDITY = 10**3;
    // transfer的函数签名
    bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
  
  // factory合约的地址
    address public factory;
    
    // 交易对中的两个token地址
    address public token0;
    address public token1;
  
  //表示两个token的储备量,用uint112存储
    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;
    // 最近一次流动性事件之后的k值
    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;
    }
  
  // transfer函数
    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);
  
  // 因为该合约由factory部署,那么msg.sender就是factory的地址
    constructor() public {
        factory = msg.sender;
    }
  
  // 因为该合约是使用create2的方式部署,所以构造函数不能有任何参数,就需要initalize函数初始化两个token地址
    // 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
        // 间隔时间大于0 储备量大于0
        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();
        // 开启平台收取手续费
        feeOn = feeTo != address(0);
        uint _kLast = kLast; // gas savings
        if (feeOn) {
            if (_kLast != 0) {
              // 计算现在k的平方根
                uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
                // 计算之前的k的平方根
                uint rootKLast = Math.sqrt(_kLast);
                // 通常情况下,k值应该为增加的
                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);
                }
            }
        } else if (_kLast != 0) {
            kLast = 0;
        }
    }
  
  // 给流动性提供者铸币
  // 1.先获取两token的储备量和本合约中两种代币的余额
  // 2.通过余额和储备量相减获取增加量
  // 3.如果是初始化,则流动性为两增加量的算术平方根减去最小流动性,如果新加流动性,则按照两种代币数量增加小的为准增加流动性
  // 4.将Lp mint给提供流动性的地址
  // 5.更新两种代币的储备量
    // this low-level function should be called from a contract which performs important safety checks
    function mint(address to) external lock returns (uint liquidity) {
      // 获取两个token的储备量
        (uint112 _reserve0, uint112 _reserve1,) = geReserves(); // gas savings
        
        // 这个合约中两个token的余额
        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; // gas savings, must be defined here since totalSupply can update in _mintFee
        
        // 如果总供应量为0,即初始化的时候
        if (_totalSupply == 0) {
          // 流动性为两数的算数平方根减去最小流动性
            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
            // 向0地址锁定最小流动性
           _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
        } else {
          // 如果流动性不为0,则按照两种代币的增加比例添加新的流动性
            liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
        }
        // 确认流动性大于0
        require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
        // 向指定地址铸造相应的流动性
        _mint(to, liquidity);
    
    // 更新储备量
        _update(balance0, balance1, _reserve0, _reserve1);
        // 如果开启收费,则k值为两储备量相乘
        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
        emit Mint(msg.sender, amount0, amount1);
    }
  
  // 撤回流动性
  // 1.获取合约中两种代币的储备量与余额
  // 2.获取路由合约发送到本合约的Lptoken
  // 3.按照Lp在总供应量中的比例取走相应数量的代币
  // 4.销毁本合约中所有的流动性
  // 5.更新余额和供应量
    // 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));
        
        // 流动性为用户由路由合约发送到本pair合约的要销毁的金额
        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 与 amount1为用户可以取走的两种代币的数量
        // 按照用户在总供应量中的比例取走相应的代币,其中已经含有相应的手续费奖励
        amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
        amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
        // 确保取出的的代币数量都大于0
        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);
    }
  
  // 交易token,需要输入两种token的量,token要发送到的地址,data用于闪电贷的回调
    // this low-level function should be called from a contract which performs important safety checks
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
      // 确保至少一个数量大于0
        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;
        // 发送地址不能为这两个token合约
        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
        
        // 如果data有长度,则调用to的接口进行闪电贷
        if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
        
        // 获取合约中两种代币的余额
        balance0 = IERC20(_token0).balanceOf(address(this));
        balance1 = IERC20(_token1).balanceOf(address(this));
        }
        
    // amountIn = balance - (_reserve - amountOut)
        // 根据取出的储备量、原有的储备量及最新的余额,反求出输入的金额
        uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
        uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
        // 确保输入的金额至少有一个大于0
        require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
        { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
        
        // 调整后的余额 = (1 - 0.3%)* 原余额
        uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
        uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
        
        // 新k值应该大于旧的k值,增加值为手续费
        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
    function sync() external lock {
        _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
    }
}
手续费:
每一笔交易都有0.3%的手续费,可以理解为用户对换出去的另一种代币数量少于用k值计算出的值,这时pair合约中的另一种代币的减少量小于使用k算出的理论值,意味着现在另一种代币的余额比预期的更大了,所以k也变大了。手续费计算公式:
区块链2.0的杀手应用 — UniswapV2
其中,为LPtoken的总供应量,为两个时期的值。

3.3   UniswapV2Factory.sol

该合约用于部署pair合约:
pragma solidity =0.5.16;

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

contract UniswapV2Factory is IUniswapV2Factory {
    // 设置0.05%的手续费给开发者,默认不开启
    address public feeTo;
    // 设置feeTo地址的地址,相当于管理员
    address public feeToSetter;
  
  // 每两个erc20代币之间的交易对合约地址
    mapping(address => mapping(address => address)) public getPair;
    // 所有的交易对
    address[] public allPairs;

    event PairCreated(address indexed token0, address indexed token1, address pair, uint);
  
  // 0.05%手续费管理员
    constructor(address _feeToSetter) public {
        feeToSetter = _feeToSetter;
    }
  
  // 获取交易对数量
    function allPairsLength() external view returns (uint) {
        return allPairs.length;
    }
  
  // 创建交易对
    function createPair(address tokenA, address tokenB) external returns (address pair) {
        // 两个token的合约地址不能相同
        require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
        // 两个token按照地址从小到大排序
        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
        // 两个地址不能为0地址
        require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
        // 检查交易对是否存在
        require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
        // 获取模板合约UniswapV2Pair的合约字节码
        bytes memory bytecode = type(UniswapV2Pair).creationCode;
        // 生成salt用于create2创建合约,salt值固定
        bytes32 salt = keccak256(abi.encodePacked(token0, token1));
        // pair为交易对地址
        assembly {
            pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        // 调用pair合约中的initialize方法
        IUniswapV2Pair(pair).initialize(token0, token1);
        // 将交易对合约的地址放入getPair表中
        getPair[token0][token1] = pair;
        getPair[token1][token0] = pair; // populate mapping in the reverse direction
        // pair合约地址push进allPairs
        allPairs.push(pair);
        emit PairCreated(token0, token1, pair, allPairs.length);
    }
  
  // 设置收取0.05%手续费的地址
    function setFeeTo(address _feeTo) external {
        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
        feeTo = _feeTo;
    }
  
  // 更改owner
    function setFeeToSetter(address _feeToSetter) external {
        require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
        feeToSetter = _feeToSetter;
    }
}

3.4   UniswapV2Router02.sol

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;
  
  // factory合约地址
    address public immutable override factory;
    // WETH合约地址
    address public immutable override WETH;
  
  // 确保时间在范围之内
    modifier ensure(uint deadline) {
        require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
        _;
    }

    constructor(address _factory, address _WETH) public {
        factory = _factory;
        WETH = _WETH;
    }
  
  // 只接收从WTEH传来的以太币
    receive() external payable {
        assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
    }

    // **** ADD LIQUIDITY ****
    // 增加流动性 ,用户需要提供一个范围
    function _addLiquidity(
        address tokenA,     // tokenA地址
        address tokenB,     // tokenB地址
        uint amountADesired,// 期望添加A的数量
        uint amountBDesired,// 期望添加B的数量
        uint amountAMin,    // 添加A的最小数量
        uint amountBMin     // 添加B的最小数量
    ) internal virtual returns (uint amountA, uint amountB) { // 实际添加的AB的数量
        // create the pair if it doesn't exist yet
        // 如果交易对不存在则创建一个
        if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
            IUniswapV2Factory(factory).createPair(tokenA, tokenB);
        }
        // 获取factory合约中两种代币的储备量
        (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
        if (reserveA == 0 && reserveB == 0) {
          // 对于两种代币都为空的情况,那么则会按照期望添加的量进行添加
            (amountA, amountB) = (amountADesired, amountBDesired);
        } else {
          // fatory合约中有这两种代币的储量,则按照规则添加
            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,   // Lp发送地址
        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);
        // 交易对合约向to地址发送Lp
        liquidity = IUniswapV2Pair(pair).mint(to);
    }
    
    // 添加eth与另外一种token的流动性
    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) {
        (amountToken, amountETH) = _addLiquidity(
            token,
            WETH,
            amountTokenDesired,
            msg.value,
            amountTokenMin,
            amountETHMin
        );
        address pair = UniswapV2Library.pairFor(factory, token, WETH);
        TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
        IWETH(WETH).deposit{value: amountETH}();
        assert(IWETH(WETH).transfer(pair, amountETH));
        liquidity = IUniswapV2Pair(pair).mint(to);
        // refund dust eth, if any
        if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
    }
  
  // 移除流动性
    // **** REMOVE LIQUIDITY ****
    function removeLiquidity(
        address tokenA,     // tokenA地址
        address tokenB,     // tokenB地址
        uint liquidity,     // 销毁Lp数量
        uint amountAMin,
        uint amountBMin,
        address to,         // 取回的ABtoken的发送地址
        uint deadline       // 过期时间
    ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
      // 获取交易对
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        // 向交易对发送Lptoken,合约并不会检验
        IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
        // 交易对合约完成销毁并发送两种代币到to地址
        (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
        // 获取返回的amount0是哪个token
        (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');
    }
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
        (amountToken, amountETH) = removeLiquidity(
            token,
            WETH,
            liquidity,
            amountTokenMin,
            amountETHMin,
            address(this),
            deadline
        );
        TransferHelper.safeTransfer(token, to, amountToken);
        IWETH(WETH).withdraw(amountETH);
        TransferHelper.safeTransferETH(to, amountETH);
    }
    
    // 通过签名授权减少流动性
    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);
    }
    
    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) ****
    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 {
      // 将path依次向后遍历,每次取两个地址
        for (uint i; i < path.length - 1; i++) {
          // 前一个地址作为被卖代币,to为目标买入代币
            (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');
        // 向pair合约转钱
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        // 执行对换
        _swap(amounts, path, to);
    }
    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);
    }
    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);
    }
    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]);
    }
    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]);
    }
    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));
        }
    }
    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'
        );
    }
    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'
        );
    }
    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);
    }
}

原文始发于微信公众号(山石网科安全技术研究院):区块链2.0的杀手应用 — UniswapV2

版权声明:admin 发表于 2022年11月9日 上午11:05。
转载请注明:区块链2.0的杀手应用 — UniswapV2 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...