1.概述UniswapV2配对合约是去中心化交易所UniswapV2的核心组件,实现了自动做市商(AMM)功能。该合约管理两个ERC20代币之间的流动性池,允许用户无需许可地添加/移除流动性并进行代币交换。1.1核心特性恒定乘积做市商:x*y=k模型0.3%交
Uniswap V2 配对合约是去中心化交易所 Uniswap V2 的核心组件,实现了自动做市商(AMM)功能。该合约管理两个 ERC20 代币之间的流动性池,允许用户无需许可地添加/移除流动性并进行代币交换。
| 参数 | 值 | 说明 |
|---|---|---|
| MINIMUM_LIQUIDITY | 10³ | 首次流动性时永久锁定的最小流动性 |
| 交易手续费 | 0.3% | 每笔交易收取的费用 |
| 协议费用 | 1/6 | 流动性增长量的 1/6 作为协议费 |
contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20
// 打包存储(1个存储槽,256位)
uint112 private reserve0; // 代币0的储备量
uint112 private reserve1; // 代币1的储备量
uint32 private blockTimestampLast; // 最后更新时间戳
// 单独存储
uint public price0CumulativeLast; // 代币0的累积价格
uint public price1CumulativeLast; // 代币1的累积价格
uint public kLast; // 最近流动性事件后的 reserve0 * reserve1
// 数学库
using SafeMath for uint; // 安全数学运算
using UQ112x112 for uint224; // 定点数运算
// 接口
IERC20, IUniswapV2Factory, IUniswapV2Callee
reserve0 * reserve1 = constant product (k)
price0 = reserve1 / reserve0(reserve0 ± Δ0) * (reserve1 ± Δ1) ≥ k使用累积价格实现 TWAP:
// 累积价格计算公式
price0CumulativeLast += (reserve1 / reserve0) * timeElapsed
price1CumulativeLast += (reserve0 / reserve1) * timeElapsed
外部合约可通过两个时间点的累积价格差计算平均价格。
交易手续费:
(balance0 * 1000 - amount0In * 3) * (balance1 * 1000 - amount1In * 3) ≥ reserve0 * reserve1 * 1000²协议费用:
feeTo地址控制liquidity = totalSupply * (√k - √kLast) / (5 * √k + √kLast)constructor()constructor() public {
factory = msg.sender;
}
initializeinitialize(address _token0, address _token1)function initialize(address _token0, address _token1) external
getReserves()function getReserves() public view returns (uint112, uint112, uint32)
返回当前储备量和最后更新时间戳,用于外部合约获取池子状态。
mint(address to)- 添加流动性算法:
计算用户存入的代币数量
如果是首次添加:
liquidity = √(amount0 * amount1) - MINIMUM_LIQUIDITYMINIMUM_LIQUIDITY到地址 0如果是后续添加:
liquidity = min(amount0 * totalSupply / reserve0, amount1 * totalSupply / reserve1)更新储备和累积价格
首次流动性锁定原因:防止通过极小金额的首次添加操纵价格,然后通过捐赠攻击获取所有流动性。
burn(address to)- 移除流动性算法:
计算应返还的代币数量:
amount0 = liquidity * balance0 / totalSupplyamount1 = liquidity * balance1 / totalSupply销毁 LP 代币
返还两种代币给用户
更新储备
注意:使用实际余额而非储备计算,确保流动性提供者获得应得的手续费。
swap(uint amount0Out, uint amount1Out, address to, bytes data)核心交换流程:
输入验证:
乐观转账:
恒定乘积检查:
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2));
闪电交换支持:
data.length > 0,调用 IUniswapV2Callee(to).uniswapV2Call()skim(address to)提取合约中超过储备的代币余额,用于恢复意外转入的代币。
sync()强制将储备更新为当前余额,通常在 skim后调用。
_update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1)更新储备和累积价格的核心函数:
Sync事件_mintFee(uint112 _reserve0, uint112 _reserve1)铸造协议费用:
feeTo地址是否设置feeTo地址kLastmodifier lock() {
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
_;
unlocked = 1;
}
简单的互斥锁,防止重入攻击。
// 防止无效交换
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
function _safeTransfer(address token, address to, uint value) private {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
}
同时支持标准和非标准 ERC20 代币。
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
{}控制栈深度// Gas 节约:从内存读取而非存储
(uint112 _reserve0, uint112 _reserve1,) = getReserves();
address _token0 = token0;
address _token1 = token1;
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
// 仅当条件满足时执行计算
}
用于处理 112.112 定点数,提供高精度的价格计算:
// 编码为 224 位定点数
uint224 q112 = UQ112x112.encode(uint112(value));
// 定点数除法
uint224 result = UQ112x112.uqdiv(numerator, denominator);
提供整数平方根函数,用于协议费用计算:
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
// 1. 用户将代币转入配对合约
token0.transfer(pair, amount0);
token1.transfer(pair, amount1);
// 2. 调用 mint
pair.mint(userAddress);
// 直接调用
pair.swap(amount0Out, amount1Out, to, "");
// 通过路由器调用(推荐)
router.swapExactTokensForTokens(amountIn, amountOutMin, path, to, deadline);
// 1. 执行交换
pair.swap(amount0Out, amount1Out, address(this), data);
// 2. 在 uniswapV2Call 中执行操作
function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external {
// 使用借入的代币执行操作
// 必须在本交易内偿还
uint fee = ((amount0 + amount1) * 3) / 997 + 1;
token.transfer(pair, amount0 + amount1 + fee);
}
<!--EndFragment-->
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!