UniswapV2 walkthrough

Chiu Lv4

1. Swap结算价格计算

假设一个pair有100 TokenA , 100 TokenB
即假设1 TokenA = 1 TokenB
X,Y代表TokenA,TokenB的数量
任意时刻应满足:

取到大于是因为存在手续费让k上涨

image.png

如果用25 TokenA换25 TokenB => (125 TokenA , 75 TokenB) , K’ = 9375

image.png
K’ < K , 不满足公式

实际求解(不算fee):




image.png

通用公式:

2. 为什么不适用EIP-1167最小代理来克隆pair

A:如果使用最小代理部署,每笔交易delegate call额外消耗2600 Gas , 长期来看部署节省的成本被高频交易抵消

3. Swap 手续费

每次swap流程,用户输入token的0.3%作为手续费fee扣掉,直接进入池子,此时K上涨。

4. FeeOn 协议费

若开启了FeeOn(大多数协议都不开),则在burnormint的时候增发k涨幅1/6LP Tokenfeeto地址(一般是项目方)
注:

  1. feeswap手续费,和协议费不是一个东西,协议费通过mintFee()函数计算
  2. mintFee()是增发,不是扣手续费fee1/6
  3. swap不进行mintFee,节省gas,简化主流程

5. 协议费mintFee()公式


也就是:

其中:

6. burn流程的协议费计算

虽然burn的时候K下降,直觉来讲不存在协议费,但swap不会结算协议费,且不更新KLast
如果burn之前存在swap操作,burn内部的_mintFee()能计算出当前K>KLast,进而结算协议费。形式同样是增发share给feeto地址

7. Core部分代码注释

UniswapV2Factory.sol

  • getPair

    1
    2
    // 根据两个token地址来查询pair地址
    mapping(address => mapping(address => address)) public getPair;
  • setFeeTo()

    设置协议费接收地址

  • setFeeToSetter()

    转让协议费设置权限

  • CreatePair()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function createPair(address tokenA, address tokenB) external returns (address pair) {
    require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
    (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
    require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
    require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient

    //获取 UniswapV2Pair 合约的创建字节码
    bytes memory bytecode = type(UniswapV2Pair).creationCode;
    bytes32 salt = keccak256(abi.encodePacked(token0, token1));

    //计算出pair地址,不需要链上查询
    //0:没有提供额外ETH
    //add(bytecode, 32):跳过 bytes 类型前的 32 字节长度描述,指向真实字节码内容
    //mload(bytecode):字节码长度
    assembly {
    pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
    }

    //初始化pair合约,绑定这俩token
    IUniswapV2Pair(pair).initialize(token0, token1);

    //保证(A,B),(B,A)是同一个pair
    getPair[token0][token1] = pair;
    getPair[token1][token0] = pair; // populate mapping in the reverse direction

    allPairs.push(pair);
    emit PairCreated(token0, token1, pair, allPairs.length);
    }

UniswapV2Pair.sol

  • 变量说明

    名称 表示什么 单位
    reserve0/1 pair 合约里登记的 token0/1 储备量 token0/1 数量
    balance0/1 实际 pair 地址持有的 token0/1 数量(链上余额) token0/1 数量
    amount0/1 某次操作中使用的 token0/1 数量 token0/1 数量
    totalSupply 当前发行的所有 LP token 数量 LP token 数量
    liquidity 当前 mint/burn 操作涉及的 LP token 份额 LP token 数量
  • _update()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 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

    //priceXCumulativeLast供预言机使用
    //加权平均时间价格TWAP = ∆price0CumulativeLast / ∆timeElapsed
    //price0CumulativeLast → 累积的「token1 per token0」价格 * 时间
    //price0CumulativeLast = price0 * timeElapsed = _reserve1 / _reserve0 * timeElapsed
    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);
    }
  • _mintFee()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //如果sqrt(k)增长了,说明池子增长了,协议就要分走「净增长」的1/6
    // 每次swap交易收取协议费太耗费gas, 所以只在每次添加和移除流动性的时候才进行协议费计算
    // mint的时候k上涨,手续费是凭空增发 涨幅1/6 的LP token给协议,不扣用户的钱
    // burn的时候如果因为之前存在swap手续费导致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) {
    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);
    }
    }
    //如果没开启协议费,不再记录历史增长
    } else if (_kLast != 0) {
    kLast = 0;
    }
    }
  • mint()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    // mint是铸造LP token给用户,不是给池子转钱
    // this low-level function should be called from a contract which performs important safety checks
    function mint(address to) external lock returns (uint liquidity) {
    //在调用mint之前,addLiquidity已经完成了转账
    //所以当前的balance是reserve + LP token
    (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
    uint balance0 = IERC20(token0).balanceOf(address(this));
    uint balance1 = IERC20(token1).balanceOf(address(this));

    //计算应该给用户多少 LP token
    uint amount0 = balance0.sub(_reserve0);
    uint amount1 = balance1.sub(_reserve1);

    //是否开启协议费(protocol fee)
    //如果开启了协议费,则会凭空铸造(增发)1/6 LP token给feeTo地址,并不是直接扣用户的钱
    bool feeOn = _mintFee(_reserve0, _reserve1);
    //totalSupply会在_mintFee中增加
    //Q: 为什么会增加? A:把 LP Token 想象成“基金单位”,而池子里的资金是基金资产。
    // 当作为基金管理方(治理)抽取管理费时,不是从现有资产里拿走现金,而是多印一些基金份额归自己。

    //这里必须在下一次调用_mintFee之前把旧的_totalSupply缓存下来,供后续计算使用
    uint _totalSupply = totalSupply; // gas savings
    if (_totalSupply == 0) {
    //如果第一次创建池子,sqrt(x * y) 作为初始流动性基准
    liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
    //锁住 MINIMUM_LIQUIDITY(通常是 1000 wei),mint 给 0 地址,防止除零/攻击。
    _mint(address(0), MINIMUM_LIQUIDITY);
    /*
    按比例算出用户应得的 LP token:
    看这次新增 token 相对于之前池子 token 的比例。
    两边取较小值(避免滑点破坏池子平衡)。
    */
    liquidity = Math.min(
    amount0.mul(_totalSupply) / _reserve0,
    amount1.mul(_totalSupply) / _reserve1
    );
    }
    require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');

    //给用户mint计算好的LP token
    _mint(to, liquidity);

    //更新储备值(把旧 reserve 更新为新余额)
    _update(balance0, balance1, _reserve0, _reserve1);

    //如果协议费开启,更新 kLast,供后续 mintFee 计算用
    if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
    emit Mint(msg.sender, amount0, amount1);
    }
  • burn()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    function burn(address to) external lock returns (uint amount0, uint amount1) {
    (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings

    //把token0和token1的地址存到局部变量中,减少多次SLOAD,节省gas
    address _token0 = token0; // gas savings
    address _token1 = token1; // gas savings

    //获取pair实际持有的token数量
    uint balance0 = IERC20(_token0).balanceOf(address(this));
    uint balance1 = IERC20(_token1).balanceOf(address(this));

    //获取pair实际持有的LP token数量。这是removeLiquidity的调用者transfer进来准备销毁的
    uint liquidity = balanceOf[address(this)];

    //如果协议开了fee,就先分给协议方对应的LP token
    bool feeOn = _mintFee(_reserve0, _reserve1);
    uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee

    //计算amount使用balance,保证按照实际比例退钱
    // amount = ( 你这次销毁的LP token / 总LP token )* 池子余额
    // 除法向下取整,先乘再除是为了减少精度损失
    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');

    //pair销毁收到的LP token
    _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);
    }
  • swap()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
    //至少输出一种token
    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;

    //每个局部变量都会分配一个stack slot,但编译器限制函数内局部变量最多只能用16个slot
    //此处用block来限制_token作用域,避免stack too deep错误
    { // 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

    //data.length不为零,说明可能有flash swap , 去执行用户的回调函数
    if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
    //balance是用户还钱之后池子的余额
    balance0 = IERC20(_token0).balanceOf(address(this));
    balance1 = IERC20(_token1).balanceOf(address(this));
    }

    //amount0In = balance0 - (_reserve0 - amount0Out)
    //_reserve0 - amount0Out = balance0 - amount0In
    uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
    uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
    //如果amount0In与amount1In都为0,则说明没有用户还够钱,revert
    require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');

    //限制作用域
    { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
    uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
    uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
    //swap 后池子(扣除手续费)的乘积至少和 swap 前一样。否则存在套利,会revert
    //换句话说,swap后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);
    }
  • skim()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 同步balance到reserve,把多余的token转给用户
    // 捞出误打的token,避免被锁死在池子
    // 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));
    }
  • sync()

    1
    2
    3
    4
    5
    // 绕过uniswap逻辑直接打币进pair的话,reserve没更新,导致k=reserve0 * reserve1不准确,sync强制同步储备
    // force reserves to match balances
    function sync() external lock {
    _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
    }

8. Periphery部分代码注释

Router01

  • addLiquidity()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function addLiquidity(
    address tokenA,
    address tokenB,
    uint amountADesired,
    uint amountBDesired,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
    ) external override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
    // validate that the pair exists, if not it will be created
    (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);

    // send tokens to pair
    // 这里的pairFor是根据tokenA,tokenB的地址用create2计算出来的,和UniswapV2Factory中的getPair逻辑一致
    address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);

    //将tokenA和tokenB从msg.sender转移到pair合约中
    TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
    TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);

    //调用pair合约的mint函数,铸造Liquidity Provider token(此处的liquidity)给to地址(流动性提供者)
    liquidity = IUniswapV2Pair(pair).mint(to);
    }
  • _addLiquidity()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    function _addLiquidity(
    address tokenA,
    address tokenB,
    uint amountADesired,
    uint amountBDesired,
    uint amountAMin,
    uint amountBMin
    ) private 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,但如果最佳b数量大于期望存入的b数量(b给的不够),就反过来定b求a
    (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
    if (reserveA == 0 && reserveB == 0) {
    (amountA, amountB) = (amountADesired, amountBDesired);
    } else {
    uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
    //如果b给的够
    if (amountBOptimal <= amountBDesired) {
    require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
    (amountA, amountB) = (amountADesired, amountBOptimal);
    //如果b不够,定b求a,fallback逻辑
    } else {
    uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
    //防御性判断,既然b不够,那么a肯定够(在user提供的a,b之间取小于提供值的最佳值,而且成比例,所以b不够a肯定够)
    assert(amountAOptimal <= amountADesired);
    require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
    (amountA, amountB) = (amountAOptimal, amountBDesired);
    }
    }
    }
  • addLiquidityETH()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    function addLiquidityETH(
    address token,
    uint amountTokenDesired,
    uint amountTokenMin,
    uint amountETHMin,
    address to,
    uint deadline
    ) external payable override 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);
    //将token从msg.sender转移到pair合约中
    TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
    //将ETH转入WETH合约,转换成WETH
    IWETH(WETH).deposit{value: amountETH}();
    //将WETH转移到pair合约中
    assert(IWETH(WETH).transfer(pair, amountETH));
    liquidity = IUniswapV2Pair(pair).mint(to);
    //退还多余的ETH给msg.sender
    if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH); // refund dust eth, if any
    }
  • removeLiquidity()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function removeLiquidity(
    address tokenA,
    address tokenB,
    uint liquidity,
    uint amountAMin,
    uint amountBMin,
    address to,
    uint deadline
    ) public override ensure(deadline) returns (uint amountA, uint amountB) {
    address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);

    // send LP token to pair
    IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity);

    // burn LP token,transfer token0,1 to (to)
    (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);

    //pair内部用token0,token1的顺序来存储流动性,所以需要根据tokenA,tokenB的地址来排序
    //根据tokenA,tokenB的地址排序,保证token0是地址小的那个
    (address token0, ) = UniswapV2Library.sortTokens(tokenA, tokenB);
    (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);

    //防御性判断,确保取出的token数量不小于最小值
    require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
    require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
    }
  • _swap()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function _swap(uint[] memory amounts, address[] memory path, address _to) private {
    for (uint i; i < path.length - 1; i++) {
    (address input, address output) = (path[i], path[i + 1]);
    //确定当前 pair 的 token0 和 token1,用于决定金额输出时应该放在哪个参数里
    (address token0, ) = UniswapV2Library.sortTokens(input, output);
    uint amountOut = amounts[i + 1];
    //swap(amount0Out, amount1Out, to, data) 调用规范
    (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
    //如果不是最后一个pair,则to是下一个pair的地址,否则是用户指定的地址
    address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
    //执行实际的 swap,最后一个参数data,用于闪电贷,这里设为空
    IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
    amount0Out,
    amount1Out,
    to,
    new bytes(0)
    );
    }
    }
  • swapExactTokensForTokens()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function swapExactTokensForTokens(
    uint amountIn,
    uint amountOutMin,
    address[] calldata path,
    address to,
    uint deadline
    ) external override ensure(deadline) returns (uint[] memory amounts) {
    //amount是每个path交易之后的数量,0.3%手续费在getAmountsOut()中扣除
    amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
    require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
    //把token转入第一跳的pair
    TransferHelper.safeTransferFrom(
    path[0],
    msg.sender,
    UniswapV2Library.pairFor(factory, path[0], path[1]),
    amounts[0]
    );
    _swap(amounts, path, to);
    }
  • swapTokensForExactTokens()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //定amountOut求amountIn
    function swapTokensForExactTokens(
    uint amountOut,
    uint amountInMax,
    address[] calldata path,
    address to,
    uint deadline
    ) external 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);
    }

Router02

  • _swapSupportingFeeOnTransferTokens()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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);
    //实时读取 pair 中的余额减去原储备,得到真正进入的 input 数量,解决实际到账小于发送数的问题
    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));
    }
    }
  • removeLiquidityETHWithPermitSupportingFeeOnTransferTokens()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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);
    //是否最大授权(true -> uint(-1))
    uint value = approveMax ? uint(-1) : liquidity;
    //v,r,s为owner对参数签名结果
    IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
    amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
    token, liquidity, amountTokenMin, amountETHMin, to, deadline
    );
    }
  • removeLiquidityETHSupportingFeeOnTransferTokens()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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
    );
    //直接调用balanceOf获取token真实数量。
    //因为整个 Token 都已经转给了 Router(即使被扣了税),就按实际余额转出,不检查amountTokenMin
    TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
    IWETH(WETH).withdraw(amountETH);
    TransferHelper.safeTransferETH(to, amountETH);
    }

Library

  • pairFor()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 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);
    pair = address(uint(keccak256(abi.encodePacked(
    hex'ff',
    factory,
    keccak256(abi.encodePacked(token0, token1)),
    hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
    ))));
    }
  • quote()

    1
    2
    3
    4
    5
    6
    7
    //池子里放入 amountA 数量的 tokenA,理论上能换出多少数量的 tokenB(不包含 swap 手续费,也不考虑滑点,只是裸的价格比例)
    // given some amount of an asset and pair reserves, returns an equivalent amount of the other asset
    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;
    }
  • getAmountOut()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
    //reserveIn 是交换进池子的token在池子中储备量,reserveOut 是交换出的
    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');
    //收0.3%的手续费
    uint amountInWithFee = amountIn.mul(997);
    uint numerator = amountInWithFee.mul(reserveOut);
    uint denominator = reserveIn.mul(1000).add(amountInWithFee);
    /* note :
    恒定乘积公式:
    reserveIn * reserveOut = newReserveIn * newReserveOut = old_k


    此时newReserveIn = reserveIn + amountInWithFee
    newReserveOut = reserveOut - amountOut

    带入后得到:
    reserveIn * reserveOut = (reserveIn + amountInWithFee) * (reserveOut - amountOut)
    reserveIn * reserveOut = (reserveIn + amountInWithFee) * reserveOut - (reserveIn + amountInWithFee) * amountOut
    (reserveIn + amountInWithFee) * amountOut = (reserveIn + amountInWithFee) * reserveOut - reserveIn * reserveOut
    (reserveIn + amountInWithFee) * amountOut = amountInWithFee * reserveOut
    得到代码里的:amountOut = (amountInWithFee * reserveOut) / (reserveIn + amountInWithFee)

    之后手续费会加入池子,k会增大
    swap,addLiquidity增大k
    removeLiquidity减小k
    */

    amountOut = numerator / denominator;
    }
  • getAmountIn()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // given an output amount of an asset and pair reserves, returns a required input amount of the other asset
    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);
    //加 1 是为了解决整除向下取整的问题(精度保守估计,多给一点)
    amountIn = (numerator / denominator).add(1);
    }
  • Title: UniswapV2 walkthrough
  • Author: Chiu
  • Created at : 2025-06-02 10:39:00
  • Updated at : 2025-07-15 13:02:07
  • Link: https://github.com/Idealist17/github.io/2025/06/02/UniswapV2/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments