目录概述核心架构核心机制关键合约解析安全特性价格预言机个人见解与学习概述UniswapV2是一个去中心化的自动做市商(AMM)协议,允许用户在以太坊上交换ERC20代币,而无需传统的订单簿。它通过恒定乘积公式x*y=k来维持流动性池的平衡。核心特点无
Uniswap V2 是一个去中心化的自动做市商(AMM)协议,允许用户在以太坊上交换 ERC20 代币,而无需传统的订单簿。它通过恒定乘积公式 x * y = k 来维持流动性池的平衡。
Uniswap V2 由两个主要部分组成:
用户 -> Router -> Factory -> Pair
-> Library (计算)
Uniswap V2 使用恒定乘积公式来维持流动性池的平衡:
reserve0 * reserve1 = k (常数)
变量说明:
reserve0: 交易对中第一种代币(token0)的储备量(单位:代币的最小单位,如 wei)reserve1: 交易对中第二种代币(token1)的储备量(单位:代币的最小单位,如 wei)k: 恒定乘积常数,表示流动性池的总价值(在扣除手续费前保持不变)工作原理:
k 值保持不变(扣除手续费后)示例: 假设池中有 1000 TokenA 和 1000 TokenB,k = 1,000,000
当用户首次添加流动性时:
liquidity = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY
变量说明:
liquidity: 应铸造给用户的 LP token 数量(单位:LP token 的最小单位)amount0: 用户添加的第一种代币(token0)的数量(单位:代币的最小单位)amount1: 用户添加的第二种代币(token1)的数量(单位:代币的最小单位)MINIMUM_LIQUIDITY: 最小流动性常量,固定为 1000 wei,用于防止首次添加时被全部提取后续添加时:
liquidity = min(amount0 * totalSupply / reserve0, amount1 * totalSupply / reserve1)
变量说明:
liquidity: 应铸造给用户的 LP token 数量(单位:LP token 的最小单位)amount0: 用户添加的第一种代币(token0)的数量(单位:代币的最小单位)amount1: 用户添加的第二种代币(token1)的数量(单位:代币的最小单位)totalSupply: 当前 LP token 的总供应量(单位:LP token 的最小单位)reserve0: 交易对中第一种代币(token0)的当前储备量(单位:代币的最小单位)reserve1: 交易对中第二种代币(token1)的当前储备量(单位:代币的最小单位)min(): 取两个值中的较小值,确保按比例添加流动性关键点:
MINIMUM_LIQUIDITY(1000 wei)的 LP tokenamount0 = liquidity * balance0 / totalSupply
amount1 = liquidity * balance1 / totalSupply
变量说明:
amount0: 用户将获得的第一种代币(token0)的数量(单位:代币的最小单位)amount1: 用户将获得的第二种代币(token1)的数量(单位:代币的最小单位)liquidity: 用户要销毁的 LP token 数量(单位:LP token 的最小单位)balance0: 交易对合约中第一种代币(token0)的当前余额(单位:代币的最小单位)balance1: 交易对合约中第二种代币(token1)的当前余额(单位:代币的最小单位)totalSupply: 当前 LP token 的总供应量(单位:LP token 的最小单位)工作原理:
输出数量计算(考虑 0.3% 手续费):
amountInWithFee = amountIn * 997
numerator = amountInWithFee * reserveOut
denominator = reserveIn * 1000 + amountInWithFee
amountOut = numerator / denominator
变量说明:
amountIn: 用户输入的代币数量(单位:代币的最小单位)amountInWithFee: 扣除手续费后的输入数量,997 = 1000 - 3(0.3% 手续费 = 3/1000)reserveOut: 输出代币的当前储备量(单位:代币的最小单位)reserveIn: 输入代币的当前储备量(单位:代币的最小单位)numerator: 分子,用于计算输出数量denominator: 分母,用于计算输出数量amountOut: 用户将获得的输出代币数量(单位:代币的最小单位)验证(确保 k 值不减少):
(balance0 * 1000 - amount0In * 3) * (balance1 * 1000 - amount1In * 3) >= reserve0 * reserve1 * 1000^2
变量说明:
balance0: 交换后第一种代币(token0)的余额(单位:代币的最小单位)balance1: 交换后第二种代币(token1)的余额(单位:代币的最小单位)amount0In: 第一种代币的输入数量(如果用户输入的是 token0,否则为 0)(单位:代币的最小单位)amount1In: 第二种代币的输入数量(如果用户输入的是 token1,否则为 0)(单位:代币的最小单位)reserve0: 交换前第一种代币(token0)的储备量(单位:代币的最小单位)reserve1: 交换前第二种代币(token1)的储备量(单位:代币的最小单位)1000: 用于处理 0.3% 手续费(3/1000),乘以 1000 避免小数运算3: 手续费部分(0.3% = 3/1000)工作原理:
如果启用了协议费用(通过 Factory 的 feeTo 设置):
liquidity = totalSupply * (sqrt(k) - sqrt(kLast)) / (5 * sqrt(k) + sqrt(kLast))变量说明:
liquidity: 应铸造给协议费用接收地址的 LP token 数量(单位:LP token 的最小单位)totalSupply: 当前 LP token 的总供应量(单位:LP token 的最小单位)k: 当前恒定乘积值,k = reserve0 * reserve1(单位:代币数量的平方)kLast: 上次更新储备量时的恒定乘积值(单位:代币数量的平方)sqrt(): 平方根函数,用于计算流动性增长5: 协议费用比例系数,表示协议收取总手续费的 1/6(即 1/(1+5) = 1/6)工作原理:
k 值增加时(由于手续费累积),计算应铸造给协议的 LP token职责:创建和管理交易对
核心函数:
createPair
keccak256(abi.encodePacked(token0, token1)) 作为 saltgetPair
token0 => token1 => pairAddress设计亮点:
职责:实现 AMM 的核心逻辑
核心函数:
mint(添加流动性)
步骤:
1. 获取当前储备量和余额
2. 计算新增的代币数量
3. 处理协议费用(如果启用)
4. 计算应铸造的 LP token 数量
5. 如果是首次,锁定最小流动性
6. 铸造 LP token
7. 更新储备量
burn(移除流动性)
步骤:
1. 获取当前储备量
2. 计算应返回的代币数量(按比例)
3. 销毁 LP token
4. 转移代币给用户
5. 更新储备量
swap(交换)
步骤:
1. 验证输出数量
2. 先转移输出代币(乐观转移)
3. 如果提供回调,执行回调(闪电贷)
4. 获取新余额,计算输入数量
5. 验证恒定乘积公式(扣除手续费后)
6. 更新储备量
_update(更新储备量)
设计亮点:
uint112 存储储备量,三个变量打包在一个存储槽中(节省 gas)职责:提供用户友好的接口
核心功能:
添加流动性
addLiquidity: ERC20/ERC20 交易对addLiquidityETH: ERC20/ETH 交易对(通过 WETH)移除流动性
removeLiquidity: 移除 ERC20/ERC20 流动性removeLiquidityETH: 移除 ERC20/ETH 流动性交换
swapExactTokensForTokens: 精确输入,交换代币swapTokensForExactTokens: 精确输出,交换代币税费代币支持
设计亮点:
pairFor 函数计算交易对地址(需要正确的 init_code_hash)amountMin 参数)init_code_hash 是 UniswapV2Pair 合约创建字节码的 keccak256 哈希值,用于在 pairFor 函数中计算交易对的 CREATE2 地址。
为什么重要:
init_code_hash 不正确,pairFor 计算的地址会错误init_code_hash 也会不同如何计算:
bytes memory bytecode = type(UniswapV2Pair).creationCode;
bytes32 initCodeHash = keccak256(bytecode);
如何更新:
在 UniswapV2Library.sol 的 pairFor 函数中更新硬编码的哈希值。
lock 修饰符防止重入攻击uint112,防止溢出amountMin 参数deadline 参数Uniswap V2 内置了价格预言机功能,通过累积价格(Cumulative Price)实现。
每个区块首次交换时,更新累积价格:
price0CumulativeLast += (reserve1 / reserve0) * timeElapsed
price1CumulativeLast += (reserve0 / reserve1) * timeElapsed
变量说明:
price0CumulativeLast: token0 相对于 token1 的累积价格(单位:价格 * 秒数,无单位)price1CumulativeLast: token1 相对于 token0 的累积价格(单位:价格 * 秒数,无单位)reserve0: 第一种代币(token0)的储备量(单位:代币的最小单位)reserve1: 第二种代币(token1)的储备量(单位:代币的最小单位)timeElapsed: 自上次更新以来经过的时间(单位:秒)+=: 累加操作,将当前价格乘以时间间隔累加到累积价格中工作原理:
TWAP = (priceCumulativeEnd - priceCumulativeStart) / timeElapsed
变量说明:
TWAP: 时间加权平均价格(Time-Weighted Average Price)(单位:价格,如 token1/token0 或 token0/token1)priceCumulativeEnd: 结束时间点的累积价格(单位:价格 * 秒数,无单位)priceCumulativeStart: 开始时间点的累积价格(单位:价格 * 秒数,无单位)timeElapsed: 计算时间段的总时长(单位:秒)工作原理:
优势:
使用场景:
Uniswap V2 的设计非常优雅,主要体现在:
代码中有很多 gas 优化技巧:
uint112 和 uint32 打包在一个存储槽中通过深入分析 Uniswap V2 的代码,我学到了:
虽然 Uniswap V2 已经很优秀,但仍有改进空间:
Uniswap V2 是一个设计精良的 AMM 协议,通过简单的数学公式实现了复杂的去中心化交易功能。其代码简洁、安全、高效,是学习 DeFi 和 Solidity 开发的绝佳案例。
通过深入分析其源代码,我们不仅理解了 AMM 的工作原理,还学到了很多 Solidity 编程和 DeFi 协议设计的最佳实践。这些知识对于理解更复杂的 DeFi 协议(如 Uniswap V3)和开发自己的 DeFi 应用都非常有价值。
本文档基于对 Uniswap V2 源代码的深入分析,旨在帮助开发者更好地理解 AMM 协议的工作原理。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!