Uniswap V2 路由器详解 — `addLiquidity`逐行解析

  • zealynx
  • 发布于 2023-10-17 16:23
  • 阅读 7

文章深入分析了Uniswap V2 Router智能合约中的addLiquidity函数,逐行解析其实现细节,包括参数、执行流程、LP代币铸造机制。同时,文章还探讨了流动性提供者(LP)的收益及其面临的潜在风险,如无常损失。

在今天的文章中,我们将深入探讨 Uniswap V2 Router 智能合约的核心,逐行分析 addLiquidity 函数。

但不仅如此——在对代码进行深入分析之后,我们还将指出协议的一些潜在风险及其对用户的影响。

作为 Uniswap 生态系统的一部分,V2 Router 在促进流动性提供方面发挥着关键作用。我们今天关注的 addLiquidity 函数,正是让用户能够向流动性池供应资产的功能——这是 Uniswap 交易运作必不可少的操作。如需更广泛地了解 Uniswap V2 的完整架构、安全隐患和分叉风险,请参阅我们关于 Uniswap V2 安全 的配套文章。


视频演示

如果你更喜欢视频格式,可以在 YouTube 上观看此内容:

www.youtube.com

www.youtube.com 被阻止

此内容被阻止。请联系网站所有者解决此问题。

ERR_BLOCKED_BY_CSP

此内容被阻止。请联系网站所有者解决此问题。


addLiquidity 函数

让我们深入代码,剖析 UniswapV2Router02.sol 合约中的 addLiquidity 函数。

参数

  • tokenA, tokenB (address): 作为流动性提供的 ERC-20 代币的地址。
  • amountADesired, amountBDesired (uint): 流动性提供者希望添加到池中的 tokenAtokenB 的期望数量。
  • amountAMin, amountBMin (uint): 流动性提供者愿意添加的 tokenAtokenB 的最小数量。这些值用于防止交易发起和执行之间的价格滑点。
  • to (address): 流动性代币将发送到的地址。通常,这是流动性提供者的地址。
  • deadline (uint): 一个时间戳,在此之后交易将回滚,确保交易及时执行。

修饰符

  • ensure(deadline): 合约中声明的一个自定义修饰符,它检查当前的 block.timestamp 与提供的 deadline,如果 deadline 已过,则回滚。

返回值

  • amountA, amountB (uint): 存入流动性池的 tokenAtokenB 的实际数量。

  • liquidity (uint): 铸造并发送到 to 地址的 LP token 数量。

    • *

函数执行:逐步解析

步骤 1 — 调用内部 _addLiquidity

外部 addLiquidity 函数的第一行调用了一个内部 _addLiquidity 函数。让我们看看里面。

步骤 2 — 检查交易对是否存在

_addLiquidity() 内部,合约检查这两个代币的交易对是否已经存在。如果不存在,它会创建一个新的:

1if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
2    IUniswapV2Factory(factory).createPair(tokenA, tokenB);
3}

步骤 3 — 获取当前储备量

接下来,合约从 UniswapV2Pair 合约中获取现有储备量(reserveAreserveB):

1IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves()

这些储备量代表池中每种代币的当前余额,对于计算最佳存款数量至关重要。

步骤 4 — 计算最佳数量

通过 UniswapV2Library 获取储备量后,合约会运行一系列检查来确定实际存入多少代币。

如果交易对是新创建的(储备量为零),amountAamountB 就是作为参数传递的期望数量:

1(amountA, amountB) = (amountADesired, amountBDesired);

如果交易对已经存在,合约会根据当前比例计算最佳 amountB

1amountBOptimal = amountADesired.mul(reserveB) / reserveA;

如果 amountBOptimal 小于或等于 amountBDesired,则返回:

1(uint amountA, uint amountB) = (amountADesired, amountBOptimal);

否则,它会以相同的方式计算 amountAOptimal 并返回:

1(uint amountA, uint amountB) = (amountAOptimal, amountBDesired);

这个逻辑确保了代币以正确的比例存入,以匹配池的当前价格,从而防止不必要的滑点。


如何计算 LP token 流动性

现在我们知道了 amountAamountB 是如何确定的。让我们看看 liquidity 返回值是如何计算的。

使用 CREATE2 部署交易对

合约使用 UniswapV2Library 中的 pairFor 函数,通过 CREATE2 操作码确定性地计算交易对合约的地址——无需进行任何外部调用:

1pair = address(uint(keccak256(abi.encodePacked(
2    hex'ff',
3    factory,
4    keccak256(abi.encodePacked(token0, token1)),
5    hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
6))));

这个 pairFor 函数返回已部署(或将部署)的交易对合约的地址。

铸造 LP token

当你添加流动性时,交易对合约会铸造 LP token。当你移除流动性时,LP token 会被销毁。

首先,我们获取交易对地址:

1address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);

然后,我们铸造 ERC-20 LP token 并计算返回的流动性:

1liquidity = IUniswapV2Pair(pair).mint(to);

mint 函数内部,交易对合约将余额存储为标准 ERC-20 状态:

1uint balance0 = IERC20(token0).balanceOf(address(this));
2uint balance1 = IERC20(token1).balanceOf(address(this));

你收到的 LP token 代表你在池中的比例份额。这些是完全可转让的 ERC-20 token,可以稍后兑换成基础资产。


实际影响

现在我们理解了机制,接下来探讨添加流动性如何影响更广泛的 Uniswap 生态系统,流动性提供者获得哪些收益,以及他们面临哪些风险。

对流动性池的影响

流动性增加:addLiquidity 函数是为流动性池提供资金的门户。调用时,它会通过添加指定数量的两种代币来更新储备量,增加可用的总流动性。这对于实现交易和减少滑点至关重要。

Price Stability

价格稳定性:资金充足的池子可以吸收更大的交易而不会产生显著的价格影响。通过允许用户持续添加流动性,该函数有助于更稳定的汇率。

Token Balance

代币对可用性:该函数还通过使用户能够创建全新的池子或向现有池子添加流动性,从而促进 Uniswap 上不同代币对的可用性。

流动性提供者的收益

  • LP token:流动性提供者收到代表其在池中份额的 LP token。这些 token 可以随时兑换成基础资产。
  • 交易费用:LPs 赚取其池中每笔交易 0.3% 交易费的一部分,与其份额成比例。
  • 套利机会:LPs 可能会从 Uniswap 和其他交易所之间价格差异产生的套利机会中受益。

潜在风险

  • 无常损失:这是最显著的风险。当池中代币的价格比率与它们存入时相比发生变化时,就会发生无常损失。如果一种代币的价格相对于另一种代币显著上涨,LPs 在提款时可能会遭受损失——他们最终持有更多贬值的代币,而更少增值的代币。有关 AMM 特定风险的深入分析,请参阅我们的 AMM 安全基础 系列。

  • 智能合约风险:智能合约代码总是存在 bug 的风险。尽管 Uniswap 的合约已经过广泛审计,但风险永远无法完全消除。这就是为什么 全面的智能合约审计 对于任何 DeFi 协议都至关重要。

  • 监管风险:围绕 DeFi 和流动性提供的监管环境仍在不断发展,这可能给参与者带来合规风险。

  • 价格波动:高价格波动直接影响 LPs 在提款时收到的资产数量,加剧了无常损失的影响。

    • *

结论

我们已经详细阐述了 Uniswap V2 上添加流动性的内部工作原理——从最初的参数验证,到基于储备量的数量计算,再到 CREATE2 交易对地址派生和 LP token 铸造。理解这些内部机制对于任何在 DeFi 协议上构建、集成或审计的人来说都至关重要。

如果你对 Uniswap 各版本更广泛的架构感兴趣,请查看我们的完整系列:V1V2V3V4


联系我们

在 Zealynx,我们专注于审计 DeFi 协议——从 AMM 路由器和流动性池逻辑到复杂的代币集成。无论你是在构建新的 DEX、分叉 Uniswap,还是需要对你的流动性管理合约进行安全审查,我们的团队都随时准备提供帮助——联系我们

想要通过更多此类深入分析保持领先吗?订阅我们的时事通讯,确保你不会错过未来的洞察。


常见问题:Uniswap V2 Router — addLiquidity

  1. addLiquidity 函数实际做了什么?

Uniswap V2 Router 中的 addLiquidity 函数允许用户将两种 ERC-20 代币存入流动性池。它根据池的当前储备量计算最佳数量,转移代币,并铸造代表提供者在池中份额的 LP token。

  1. amountMin 参数的目的是什么?

amountAMinamountBMin 参数用于防止价格滑点。在交易提交和执行之间,池的价格比率可能会发生变化。这些最小值确保如果实际存款数量低于提供者可接受的阈值,交易将回滚。

  1. 为什么 Uniswap 使用 CREATE2 来确定交易对地址?

CREATE2 允许合约确定性地计算交易对合约的地址,而无需进行任何外部调用。这意味着任何合约或用户都可以仅凭工厂地址和两个代币地址在链下计算交易对的地址,这比每次交互都调用工厂的 getPair 函数更省 Gas。

  1. 什么是无常损失?何时发生?

当池中两种代币的价格比率相对于存入流动性时发生变化时,就会发生无常损失。分歧越大,损失越大。之所以称之为“无常”,是因为只有在提取流动性时,损失才会实现——如果价格回到原始比率,损失就会消失。然而,在实践中,价格很少能完全回到原点,这使得它成为流动性提供者实际的成本。

  1. 如果调用 addLiquidity 时交易对尚不存在,会发生什么?

如果交易对不存在,内部 _addLiquidity 函数会调用 IUniswapV2Factory(factory).createPair(tokenA, tokenB) 来部署一个新的交易对合约。由于尚无储备量,因此直接使用期望的数量,无需进行任何比例调整。

  1. LP token 与常规 ERC-20 token 有何不同?

LP token 是标准的 ERC-20 token——它们可以被转移、交易,并用于其他 DeFi 协议。它们的特殊之处在于它们代表了对流动性池中基础资产的比例索取权。当你销毁(赎回)LP token 时,你将收回你在池中两种代币的份额,以及任何累积的交易费用。


词汇表

术语 定义
流动性池 一个持有两种代币储备量的智能合约,通过恒定乘积公式实现自动化交易。
LP Token 代表流动性提供者在池储备中按比例拥有份额的 ERC-20 代币。
无常损失 当池中代币的价格比率偏离存款时的比率时,LP 经历的无常损失。
CREATE2 一个 EVM 操作码,根据部署者、盐值和初始化代码哈希将合约部署到确定性地址。
滑点 交易的预期价格与实际执行价格之间的差异。

查看完整词汇表 →

  • 原文链接: zealynx.io/blogs/uniswap...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
zealynx
zealynx
江湖只有他的大名,没有他的介绍。