Uniswap V1(三):流动性提供机制

本篇文章讲解Uniswap V1的流动性提供机制。

1.存入流动性

上篇文章中,我们使用这个函数来给DEX提供流动性:
function addLiquidity(uint256 _tokenAmount) public payable {
        IERC20 token = IERC20(tokenAddress);
        token.transferFrom(msg.sender, address(this), _tokenAmount);
    }
但这个函数是有很大问题的,它允许随时添加任意数量的流动性。我们知道,汇率是按照储蓄金比例计算的,如果允许随意添加任意数量,也就是能够随意改变汇率,进行价格操控。
 
所以我们必须确保以已在池中建立的相同比例添加额外的流动性。
 
不过,如果还没有资金池,也就是首次添加流动性的时候,可以按照任意比例添加,因为这个时候是最初设定价格的时候。
 
if (getReserve() == 0) {
    IERC20 token = IERC20(tokenAddress);
    token.transferFrom(msg.sender, address(this), _tokenAmount);
} else {
    uint256 ethReserve = address(this).balance - msg.value;
    uint256 tokenReserve = getReserve();
    uint256 tokenAmount = (msg.value * tokenReserve) / ethReserve;
    require(_tokenAmount >= tokenAmount, "insufficient token amount");
    IERC20 token = IERC20(tokenAddress);
    token.transferFrom(msg.sender, address(this), tokenAmount);
}
 
按照比例添加流动性,使得存入流动性后,不会改变价格。

2.LP代币

流动性问题解决了,如何解决奖励流动性提供者的问题呢?毕竟人家把钱存入资金池,自然是为了有回报。
 
因为是DEX,奖励肯定不是由某个机构来承担,而是从DEX本身来产生,所以解决方案是交易者每次交换代币的时候,智能合约收取少量手续费,并分配给流动性提供者,这很公平,用户(交易者)为他人提供的服务(流动性)付费。
 
为了使奖励公平,我们需要根据流动性提供者的贡献(即他们提供的流动性数量)按比例奖励流动性提供者。如果有人提供了50%的池流动性,他们应该获得50%的累积费用……。这也非常合理,对吧。
 
但是,要实现这一点可不容易,需要维护流动性提供者的账户,每次结算要进行分配,如果有新的流动性提供者进来,或者已有的流动性提供者退出,所有已分配的一切,都要重新计算。
 
不过,如果用代币,这个问题就会变得很简单。LP代币是发行给流动性提供者以换取其流动性的ERC20代币。
  • 当你提供流动性,你会获得LP代币;
  • 你获得的LP代币数量和你给池子提供流动性的份额是成正比的;
  • 奖励费用按照你持有代币的数量按比例分配;
  • LP代币可以兑换回其对应的流动性代币;
 
LP代币是没有供应限制的,每次增加新的流动性时铸造新的代币,每次减少流动性的时候销毁对应的代币;所以尽管LP代币没有供应限制,却并不会因为通货膨胀而贬值,因为它们总是有一定数量的流动性支持,而这些流动性并不依赖于已发行代币的数量。
 
PS:有没有发现,在区块链,涉及到奖励,回报等等,代币都是最好的方式,没有之一,比如挖矿(维持公链运转),比如提供流动性(维持DEX运转),这也是去中心化应用离不开代币的原因,因为它必须是靠奖励驱动的。
 
那么如何计算LP代币数量?
 
$$\text{amountMinted} = \text{totalAmount} \times \frac{\text{ethDeposited}}{\text{ethReserve}}$$
 
这个公式里,ethDeposited是存入的ETH数量,ethReserve是ETH储备量,totalAmount是LP发行总量,amountMinted是本次提供流动性需要发行的LP代币数量。
 
所以,在初次初始化流动性的时候,LP发行数量等于ETH数量,后来,随着流动性的增加,他们是非线性的关系。
 
if (getReserve() == 0) {
        ...
        uint256 liquidity = address(this).balance;
        _mint(msg.sender, liquidity);
        return liquidity;
} else {
        ...
        uint256 liquidity = (totalSupply() * msg.value) / ethReserve;
        _mint(msg.sender, liquidity);
        return liquidity;
}
 

3.流动性奖励

 
当交易者将ETH发送到DEX时,我们可以直接从该代币中减去手续费,收取的手续费存入基金,只有当流动性提供者取出流动性时,就可以从该基金中提取与其份额成比例的金额。
 
因为有了这个手续费,X*Y=k的恒定等式其实并不恒定,不过那一点小的变化,并不会对储蓄金汇率造成非常大的影响。
 
当流动性提供者去除自己提供的流动性时,会进行结算,获得LP代币等比例数量的ETH和代币,以及与其LP代币份额成比例的基金份额。
 
所以交易的函数需要改变,减去手续费用,这里为了方便看到差异,收取1%的费用,事实上Uniswap收取的是0.3%的费用。
 
function getAmount(
  uint256 inputAmount,
  uint256 inputReserve,
  uint256 outputReserve
) private pure returns (uint256) {
  require(inputReserve > 0 && outputReserve > 0, "invalid reserves");
  uint256 inputAmountWithFee = inputAmount * 99;
  uint256 numerator = inputAmountWithFee * outputReserve;
  uint256 denominator = (inputReserve * 100) + inputAmountWithFee;
  return numerator / denominator;
}
 

4.取出流动性

当流动性提供者想要取出自己的流动性代币的时候,取出的也是一对代币,不过取出的数量,需要根据他账户的LP代币数量,来计算他能取出多少流动性代币。
 
$$\text{removedAmount} = \text{reserve} \times \frac{\text{amountLP}}{\text{totalAmountLP}}$$
 
能取出的数量 = 总存储的数量 * 账户LP数量占总LP发行量的比例
 
function removeLiquidity(uint256 _amount) public returns (uint256, uint256) {
  require(_amount > 0, "invalid amount");
  uint256 ethAmount = (address(this).balance * _amount) / totalSupply();
  uint256 tokenAmount = (getReserve() * _amount) / totalSupply();
  _burn(msg.sender, _amount);
 payable(msg.sender).transfer(ethAmount);
 IERC20(tokenAddress).transfer(msg.sender, tokenAmount);
  return (ethAmount, tokenAmount);
}
 
每当移除一定的流动性时,对应的LP代币会被销毁。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部