这是我们系列文章的的第二部分。在上一篇中,我们了解了Uniswap及其核心机制,并开始构建交换合约。该合约可以接受用户的流动性、计算输出相应的代币数量并执行交换。本篇文章,我们将完成UniswapV1的实现。
这是我们系列文章的的第二部分。在上一篇中,我们了解了 Uniswap 及其核心机制,并开始构建交换合约。该合约可以接受用户的流动性、计算输出相应的代币数量并执行交换。
本篇文章,我们将完成 Uniswap V1 的实现。虽然它不会是 Uniswap V1 的完全复刻,但拥有所有的核心功能。我们直接开始吧。
在上一部分中,我们讨论了 addLiquidity 的实现,但是并不完整。这是有原因的,这篇文章我们将完善这部分的功能。
到目前为止,addLiquidity函数实现是这样的:
function addLiquidity(uint256 _tokenAmount) public payable {
IERC20 token = IERC20(tokenAddress);
token.transferFrom(msg.sender, address(this), _tokenAmount);
}
很显然,这个函数存在一些问题,这个函数允许随时添加任意数量的流动性。我们知道,汇率是按照供应量的比例计算的。
其中 $Px$ 和 $Py$ 是以太币和代币的价格; x 和 y 是以太币和代币的储备。无论是以太币还是Token代币,数量越多,价格越低。
我们还了解到,交换代币会以非线性方式改变储备,从而影响价格,并且套利者通过平衡价格以使其与大型中央交易所的价格相匹配来获利。
在我们的实现中是允许在任何时间点去显著的改变价格,换句话说,新的流动性添加进来后,和当前的储备率并不匹配,这确实是个问题,因为这样会导致价格操纵。我们希望去中心化交易所的价格与中心化交易所的价格尽可能的接近。我们希望我们的交易合约可以作为价格预言机。
因此,我们需要确保额外添加的流动性比例和池子中已经建立的比例相同。同时,当储备为空时,即当资金池尚未初始化的时候,我们希望允许任意比例的流动性。这是非常重要的时刻,因为这是最初确定价格的时候。
所以,addLiquidity 函数会有两个分支:
没有流动性的逻辑保持不变:
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);
}
这里的区别在于,我们并不会存入用户提供的所有代币,而只会存入根据当前准备金率计算的金额。为了得到这个金额,我们将现有代币和以太币的比率 (tokenReserve/ethReserve)乘以存入的以太币的数量,然后,如果用户存入的金额少于这个金额,则会抛出错误。
按照这种方式添加流动性,将有效的保护价格。
在上面的内容中,我们还没有讨论过这个概念,但是它是Uniswap设计的关键部分。
我们需要一种方法来奖励流动性提供者,如果他们没有受到激励,他们就不会提供流动性,因为没有人会无缘无故的将他们的代币放入到第三方的合约中。此外这种奖励不应该由开发程序的人支付。因为开发者必须获得投资或者发行代币来提供资金。
唯一好的解决办法是在每次代币互换中收取少量的费用,并将累积起来的费用分配给流动性提供者。这看起来相当公平:用户(交易者)为其他人提供的服务(流动性)付费。
为了保证奖励公平,我们需要根据流动性提供者的贡献(即他们提供的流动性的数量)按比例奖励他们。如果有人提供了50%的资金流动性,他们就应该获得50%的累积费用奖励。这是合理的。
这看起来比较复杂,然而,我们有一个比优雅的解决方案:LP代币
LP-tokens 是向流动性提供者发行的ERC20代币,用来换取流动性,可以把LP代币理解成股票:
1、流动性提供者可以获得相应的LP代币
2、流动性提供者获得的代币数量与他在池子中储备的流动性份额成正比
3、流动性提供者获得的费用根据持有的代币数量按照比例分配
4、LP代币可以兑换自己提供的回流动性,并且还有额外的累积费用奖励
上面是我们想要实现的能力,如何根据提供的流动性数量来计算发行的LP代币数量呢?这里需要满足一些前提条件。
1、每一份已经发行的权益必须始终正确,当有人在我之后存入或者移除流动性的时候,我的份额必须保持正确。
2、以太坊上的写入操作(例如存储新数据或更新合约中的现有数据)非常昂贵。因此,我们希望降低LP代币的维护成本(即我们不想运行定期重新计算和更新份额这样的定时任务)
想象一下,如果我们发行大量LP代币(比如10亿)并将其分配给所有流动性提供者。如果我们总是分配所有代币(第一个流动性提供者获得 10 亿,第二个流动性提供者获得其中的一部分)我们会被迫重新计算已经发行的股份和权益,这种操作是非常昂贵的。如果我们最初只分配一部分代币,那么我们将面临达到供应限制的风险,这最终迫使人们重新分配现有的份额。
一个比较好的解决方案是没有供应限制,并在添加新的流动性时候铸造新的LP代币,这种方式允许代币无限增长,并且如果我们使用正确的计算公式,当流动性增加或者减少时,所有已经发行的代币权益将保持正确(将按照比例缩放)。幸运的是,通货膨胀不会降低LP代币的价格,因为它们总是有一定数量的流动性支持,而流动性不依赖于代币的发行数量。
这个难题的最后一部分需...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!