移除流动性,实际上是造市商进行 “资金” 赎回的一种方式。 造市商按照一定比例往 “资金池” 注入一定量的 token 对,以使其他人可以在这个池子中完成 token 兑换;赎回时,自然也是按照一定“参股”比例,收回自己原本注入的 token 对。
UniswapV2-创建 UniswapV2Pair UniswapV2-添加流动性 UniswapV2-移除流动性 UniswapV2-兑换(swap) UniswapV2-公式分析
一、概述 如下代码块所示,在移除移动性时,需要如下步骤(下述 pair 代表持有 token对 的合约):
确定当前要在哪个 pair 中移除流动性;
将用户的 lp token 转移到 pair 地址;
pair 将收到的 lp token 销毁,并按照 pair 中 lp token 的总量,以及 tokenA 和 tokenB 的数量,将兑换出来的 tokenA 和 tokenB 发送到指定接收地址;
检查兑换出来的 tokenA 和 tokenB 的数量,是否满足预期设定,如不满足,则整个移除流动性的交易失败。
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 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); IUniswapV 2Pair(pair).transferFrom (msg.sender , pair, liquidity); (uint amount0, uint amount1) = IUniswapV 2Pair(pair).burn (to); (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' ); }
二、将持有的 lp token 转移给 pair 通过 transferFrom 方法,将 lp token 发给 pair,后续将在 pair合约 内统一完成 lp token 销毁以及 token对 的赎回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 _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); }
三、在 pair 中销毁 lp token 及 token 赎回 先删除用户的 lp token,并根据其 “占股” 情况,返回用户的 token对。 可以注意到,仅在 lp token 添加和销毁的时候,会涉及到协议费的相关逻辑。用户在 “取现” 或者 “参股” 的时候才会导致分红的变化,这个时候,平台就来 “薅” LP 羊毛了(目前收费开关还没打开)。协议费的计算过程,在 添加流动性 时已经介绍,这里不再赘述。
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 function burn (address to ) external lock returns (uint amount0, uint amount1) { (uint112 _reserve0, uint112 _reserve1,) = getReserves (); address _token0 = token0; address _token1 = token1; uint balance0 = IERC20 (_token0).balanceOf (address (this )); uint balance1 = IERC20 (_token1).balanceOf (address (this )); uint liquidity = balanceOf[address (this )]; bool feeOn = _mintFee (_reserve0, _reserve1); uint _totalSupply = totalSupply; amount0 = liquidity.mul (balance0) / _totalSupply; amount1 = liquidity.mul (balance1) / _totalSupply; 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); emit Burn (msg.sender , amount0, amount1, to); }
四、pair 数据更新 这里主要涉及到 pair 中,token 存量的更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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; if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0 ) { price0CumulativeLast += uint (UQ112 x112.encode (_reserve1).uqdiv (_reserve0)) * timeElapsed; price1CumulativeLast += uint (UQ112 x112.encode (_reserve0).uqdiv (_reserve1)) * timeElapsed; } reserve0 = uint112 (balance0); reserve1 = uint112 (balance1); blockTimestampLast = blockTimestamp; emit Sync (reserve0, reserve1); }