Uniswap V2 Periphery 提供了用户友好的交互接口,封装了 Core 合约的底层操作,简化流动性管理和代币交换。
1. 概述
Periphery 合约是用户与 Uniswap 交互的主要入口,Core 合约负责核心逻辑,Periphery 负责:
- 处理 ETH ↔ WETH 转换
- 计算最优交易路径
- 提供滑点保护
- 支持 permit 签名授权
| 合约 |
功能 |
| UniswapV2Router02 |
路由合约,提供添加/移除流动性、代币交换等接口 |
| UniswapV2Library |
辅助库,计算价格、数量、交易对地址等 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ┌─────────────────────────────────────────────────────────┐ │ 用户 │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ UniswapV2Router02 │ │ (Periphery - 用户交互层) │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ UniswapV2Pair │ │ (Core - 核心逻辑层) │ └─────────────────────────────────────────────────────────┘
|
2. UniswapV2Router02 合约解析
UniswapV2Router02 是 V2 Router 的升级版,修复了 V1 的一些问题,是目前主要使用的路由合约。
2.1 添加流动性
addLiquidity
为 ERC-20/ERC-20 交易对添加流动性。
1 2 3 4 5 6 7 8 9 10
| function addLiquidity( address tokenA, address tokenB, uint amountADesired, // 期望存入的 tokenA 数量 uint amountBDesired, // 期望存入的 tokenB 数量 uint amountAMin, // 最少接受的 tokenA 数量(滑点保护) uint amountBMin, // 最少接受的 tokenB 数量(滑点保护) address to, // LP Token 接收地址 uint deadline // 交易截止时间 ) external returns (uint amountA, uint amountB, uint liquidity);
|
示例场景:
Alice 想为 ETH/USDC 池添加流动性,期望存入 1 ETH 和 2000 USDC:
1 2 3 4
| amountADesired = 1 ETH amountBDesired = 2000 USDC amountAMin = 0.99 ETH amountBMin = 1980 USDC
|
Router 会根据当前池子比例计算实际需要的数量,确保不超过期望值且不低于最小值。
addLiquidityETH
为 ERC-20/ETH 交易对添加流动性,自动处理 ETH → WETH 转换。
1 2 3 4 5 6 7 8
| function addLiquidityETH( address token, uint amountTokenDesired, uint amountTokenMin, uint amountETHMin, address to, uint deadline ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
|
2.2 移除流动性
removeLiquidity
销毁 LP Token,按比例取回两种代币。
1 2 3 4 5 6 7 8 9
| function removeLiquidity( address tokenA, address tokenB, uint liquidity, // 要销毁的 LP Token 数量 uint amountAMin, // 最少获得的 tokenA(滑点保护) uint amountBMin, // 最少获得的 tokenB(滑点保护) address to, uint deadline ) external returns (uint amountA, uint amountB);
|
removeLiquidityETH
移除流动性并自动将 WETH 转换为 ETH。
1 2 3 4 5 6 7 8
| function removeLiquidityETH( address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline ) external returns (uint amountToken, uint amountETH);
|
removeLiquidityWithPermit
通过 EIP-2612 permit 签名移除流动性,无需预先 approve。
1 2 3 4 5 6 7 8 9 10 11
| function removeLiquidityWithPermit( address tokenA, address tokenB, uint liquidity, uint amountAMin, uint amountBMin, address to, uint deadline, bool approveMax, // 是否授权最大值 uint8 v, bytes32 r, bytes32 s // 签名参数 ) external returns (uint amountA, uint amountB);
|
2.3 交换函数
swapExactTokensForTokens
指定输入数量,交换代币(Exact Input)。
1 2 3 4 5 6 7
| function swapExactTokensForTokens( uint amountIn, // 输入的代币数量 uint amountOutMin, // 最少获得的输出数量(滑点保护) address[] calldata path, // 交换路径 [tokenIn, ..., tokenOut] address to, uint deadline ) external returns (uint[] memory amounts);
|
示例:用 100 USDC 换 ETH
1 2 3
| amountIn = 100 USDC amountOutMin = 0.049 ETH path = [USDC, WETH]
|
多跳路径示例:USDC → DAI → ETH
1
| path = [USDC, DAI, WETH]
|
swapTokensForExactTokens
指定输出数量,交换代币(Exact Output)。
1 2 3 4 5 6 7
| function swapTokensForExactTokens( uint amountOut, // 期望获得的输出数量 uint amountInMax, // 最多支付的输入数量(滑点保护) address[] calldata path, address to, uint deadline ) external returns (uint[] memory amounts);
|
示例:想获得正好 1 ETH
1 2 3
| amountOut = 1 ETH amountInMax = 2100 USDC path = [USDC, WETH]
|
swapExactETHForTokens
用 ETH 交换代币。
1 2 3 4 5 6
| function swapExactETHForTokens( uint amountOutMin, address[] calldata path, // path[0] 必须是 WETH address to, uint deadline ) external payable returns (uint[] memory amounts);
|
swapTokensForExactETH
用代币交换指定数量的 ETH。
1 2 3 4 5 6 7
| function swapTokensForExactETH( uint amountOut, // 期望获得的 ETH 数量 uint amountInMax, address[] calldata path, // path[path.length-1] 必须是 WETH address to, uint deadline ) external returns (uint[] memory amounts);
|
3. UniswapV2Library 库解析
UniswapV2Library 提供纯函数计算,不涉及状态修改。
3.1 价格与数量计算
getAmountOut
根据输入数量计算输出数量(含 0.3% 手续费)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function getAmountOut( uint amountIn, uint reserveIn, uint reserveOut ) internal pure returns (uint amountOut) { require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT'); require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
// 扣除 0.3% 手续费 uint amountInWithFee = amountIn.mul(997); uint numerator = amountInWithFee.mul(reserveOut); uint denominator = reserveIn.mul(1000).add(amountInWithFee); amountOut = numerator / denominator; }
|
计算示例:
1 2 3 4 5 6 7
| 池子状态:reserve0 = 10 ETH, reserve1 = 20000 USDC 输入:1 ETH
amountInWithFee = 1 * 997 = 997 numerator = 997 * 20000 = 19,940,000 denominator = 10 * 1000 + 997 = 10,997 amountOut = 19,940,000 / 10,997 ≈ 1814 USDC
|
getAmountIn
根据期望输出计算所需输入。
1 2 3 4 5 6 7 8 9 10 11 12
| function getAmountIn( uint amountOut, uint reserveIn, uint reserveOut ) internal pure returns (uint amountIn) { require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT'); require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000); uint denominator = reserveOut.sub(amountOut).mul(997); amountIn = (numerator / denominator).add(1); }
|
getAmountsOut / getAmountsIn
沿路径计算每一跳的数量。
1 2 3 4 5 6 7 8 9 10 11 12 13
| // 计算路径上每个池的输出 function getAmountsOut( address factory, uint amountIn, address[] memory path ) internal view returns (uint[] memory amounts);
// 计算路径上每个池的输入 function getAmountsIn( address factory, uint amountOut, address[] memory path ) internal view returns (uint[] memory amounts);
|
3.2 辅助函数
pairFor
使用 CREATE2 计算交易对地址(无需链上查询)。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function pairFor( address factory, address tokenA, address tokenB ) internal pure returns (address pair) { (address token0, address token1) = sortTokens(tokenA, tokenB); pair = address(uint(keccak256(abi.encodePacked( hex'ff', factory, keccak256(abi.encodePacked(token0, token1)), hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash )))); }
|
getReserves
获取交易对的储备量。
1 2 3 4 5 6 7 8 9
| function getReserves( address factory, address tokenA, address tokenB ) internal view returns (uint reserveA, uint reserveB) { (address token0,) = sortTokens(tokenA, tokenB); (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves(); (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); }
|