本篇文章讲解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代币会被销毁。
