文章详细介绍了Uniswap V2中的流动性提供者(LP)代币的燃烧和铸造过程,包括相关函数的工作原理、安全检查和费用机制。
Uniswap V2 的生命周期是某人第一次铸造 LP 代币(提供流动性,即将代币添加到池中),然后第二个存款者铸造流动性,进行交换,然后最终流动性提供者燃烧他们的 LP 代币以赎回池代币。
事实证明,反向研究这些功能更容易——燃烧、铸造流动性,然后铸造初始流动性。
所以我们先从燃烧开始。
在燃烧流动性代币之前,池中需要有流动性,因此让我们做这个假设。我们假设系统中有两个代币:token0
和 token1
。
我们在下面标注了燃烧函数,我们将解释那些不太明显的部分。
在第 140 行(紫色框),流动性通过池合约拥有的 LP 代币数量来衡量。假设燃烧者在调用燃烧之前发送了 LP 代币,但建议作为一次交易的一部分发送。(如果作为两次交易发送,其他人可以燃烧你的 LP 代币并移除你的流动性!)用户发送到合约的数量将被燃烧。一般来说,我们可以假设合约中 LP 代币的余额为零,因为如果 LP 代币只是静静地待在关联合约中,其他人会燃烧它们,并免费索取部分 token0
和 token1
。将代币作为交易的一部分发送的机制在 Uniswap V2 Swap 文章中介绍。
第 142 行和 154 行的红色框表示费用,我们暂时跳过这些,因为 Uniswap 对流动性提供者不适用。
第 144 行到 145 行的橙色框是 LP 提供者将获得的数量的计算。如果流动性代币的总供应量为 1,000,且他们燃烧 100 LP 代币,那么他们将获得池中持有的 token0
和 token1
的 10%。“流动性 / totalSupply” 是他们燃烧的 LP 代币总供应量的份额。
第 147 行到 149 行的蓝色框是 LP 代币实际上被燃烧的地方,并将 token0
和 token1
发送给流动性提供者。
第 150-151 行的黄色框更新余额变量,以便第 153 行(绿色框)调用的 _update
可以更新 _reserve
变量。除了更新 TWAP,_update
函数只是更新 _reserve
变量。
假设池中有相等数量的 token0
和 token1
。这意味着燃烧者在燃烧 LP 代币时期望获得两个代币的相等数量。但是,token0
到 token1
的池比例可能在燃烧交易签名和确认之间发生变化。如果燃烧者的后续逻辑依赖于接收到特定数量的 token0
或 token1
(例如偿还一个 闪电贷),那么如果收到的 token0
或 token1
略少,可能导致该逻辑中断。燃烧合约必须准备好接收少于预期的 token0
或 token1
,并在需要时撤回交易。
这是铸造流动性的函数。大部分功能与燃烧相似,因此我们不会重复那些显而易见的部分。
如果池为空,即流动性代币的总供应量为零,则尚未提供流动性。这在第 119 行(黄色框)检查。在这一部分,我们关注已经提供流动性的情况(第 123 行的黄色框)。用户获得的流动性,随后在第 126 行铸造给他们(绿色框),是两个值中的较小者。
该代码行所测量的比例为 amount0 / _reserve0
——按 LP 代币的 totalSupply
缩放。
假设池中有 10 个 token0
和 10 个 token1
。如果用户提供 10 个 token0
和 0 个 token1
,他们将获得 (10/10, 0/10) 中的最小值,并获得零流动性代币!另一个例子:如果他们增加 LP 代币的供应(记住,这个比例按当前流动性的 _totalSupply
缩放)。
用户将获得的两个比例(amount0 / _reserve0
或 amount1 / _reserve1
)中较差的一个,这激励他们在不改变 token0
和 token1
比例的情况下增加 token0
和 token1
的供应。
为什么要这样执行?假设池中当前有 100 个 token0
和 1 个 token1
,而 LP 代币的供应为 1。假设这两个代币的总价值为每个 100 美元,因此池的总价值为 200 美元。
如果我们取这两个比例中的 最大值,某人可以再提供一个额外的 token1
(成本 100 美元),并将池的价值提高到 300 美元。他们将池的价值增加了 50%。然而,在最大值计算下,他们将获得铸造的 1 个 LP 代币,这意味着他们拥有 LP 代币的 50% 供应权,因为总流通供应量现在为 2 个 LP 代币。现在他们通过仅存入 100 美元的价值而控制着 300 美元池的 50%(值 150 美元)。这显然是从其他 LP 提供者那里偷走了价值。
用户可能会尝试遵循代币比例,但如果其他交易在他们之前执行并改变 token0
到 token1
的余额,那么他们获得的流动性代币将少于预期。
Uniswap 不要求精确金额,因为否则事务可能会回退。其他交易如果先执行,会在铸造者发送交易和该交易被包括在区块之间改变要求。
就像燃烧的情况一样,LP 代币的 totalSupply
在此时可能发生变化,因此必须实施一定的滑点保护。
像任何 LP 池一样,Uniswap V2 需要防御“通货膨胀攻击”。我们在关于 ERC4626 的文章中描述了这个问题及其防御,因此此处不再重复。Uniswap V2 的防御措施是首先燃烧 MINIMUM_LIQUIDITY
代币,以确保没有人拥有 LP 代币的全部供应,并且可以轻易操纵价格。如果你对这一攻击向量不熟悉,请再次参阅其他文章。
更有趣的问题是为什么 Uniswap V2 取提供的代币的乘积的平方根来计算铸造的 LP 份额的数量。
具体而言,liquidity = sqrt(amount0*amount)
,在减去 MINIMUM_LIQUIDITY
后。
似乎我们可以给第一个 LP 铸造一个任意数量的代币,他们拥有 100% 的股份(减去烧掉的),那么按 0.01 还是 100 进行缩放有什么不同呢?
这里是 白皮书 的合理解释:
Uniswap v2 最初按所提供金额的几何平均值铸造股份,流动性 = sqrt(xy)。该公式确保任何时候流动性池股份的价值基本上与最初存入的流动性比例无关……上述公式确保流动性池股份的价值不会低于该池中储备的几何平均值。
这是什么意思呢?
获取直觉的最佳方法之一是插入值并观察会发生什么,所以让我们这样做。
假设我们不使用平方根函数来衡量流动性,且池中最初有 10 个 token0
和 10 个 token1
。后来,池中有 20 个 token0
和 20 个 token1
。
直观地说,这流动性是翻倍还是四倍?因为如果我们不取平方根,流动性一开始是 100(10×10),最后为 400(20×20)。可以说流动性并没有翻倍。一开始你能获得的 token0
最大值(渐近值)是 100,但在流动性增长之后,那个代币的流动性“深度”翻倍,而不是四倍。
但如果未来的流动性提供者在铸造或燃烧时没有采用平方根来计算流动性,这又有什么影响呢?我们看到新流动性提供者被“强迫”按当前比率提供资产,燃烧者只能按当前比率赎回——与平方根无关。
答案在于,如果 Uniswap 选择这样做,Uniswap 会如何从 LP 中收取费用。
回到我们之前的例子中,池从 100 个 token0
和 100 个 token1
增长到每种产品 200 个,流动性提供者的利润为 100%,因此他们应该按该金额支付比率的费用。如果我们测量池的大小从 100 到 400,则他们必须为四倍的利润支付费用。
Uniswap 选择在流动性移除时收取费用,因为在交换过程中收取协议费用会增加非常常见操作的汽油成本。Uniswap V2 实际上从未开启过协议费用,因此这个讨论在理论上有些。
最初发布于 2023 年 10 月 30 日
- 原文链接: rareskills.io/post/unisw...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!