Uniswap V2 核心接口文档
源码基于 Uniswap V2 官方仓库 v2-core / v2-periphery,Solidity 0.5.16。
文档结构:先架构总览,再逐合约展开,含完整函数签名、Events、关键参数说明及安全注意事项。
目录
- 架构总览
- Core:UniswapV2Factory
- Core:UniswapV2Pair
- [Core:闪电兑换接口 IUniswapV2Callee](#Core:闪电兑换接口 IUniswapV2Callee)
- Periphery:UniswapV2Library
- Periphery:UniswapV2Router02
- 重要常量速查
- 安全注意事项
1. 架构总览
┌─────────────────────────────────────────────────────────────┐
│ 架构分层 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Periphery │ │ Interface │ DApp / Frontend │
│ │ UniswapV2Router02 │ IUniswapV2Router02 │ │
│ └──────┬───────┘ └──────────────┘ │
│ │ │
│ ┌──────▼───────────────────────────────────────┐ │
│ │ Core │ │
│ │ UniswapV2Factory ← 创建/管理 Pair │ │
│ │ UniswapV2Pair ← 核心 AMM 逻辑 │ │
│ │ UniswapV2ERC20 ← LP Token (ERC20) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ UniswapV2Library (Periphery) │ │
│ │ pairFor / getAmountOut / getAmountIn 等 │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
| 层级 | 合约 | 职责 |
|---|---|---|
| Core | UniswapV2Factory |
创建交易对、管理 Pair 注册表 |
| Core | UniswapV2Pair |
AMM 核心逻辑:mint / burn / swap / 预言机 |
| Core | UniswapV2ERC20 |
LP Token(ERC20 + EIP-2612 permit) |
| Periphery | UniswapV2Router02 |
用户入口:添加流动性 / 移除流动性 / 交易 |
| Periphery | UniswapV2Library |
纯函数计算库:价格 / 金额 / pair 地址 |
主线网常用地址
| 合约 | 地址 |
|---|---|
| Factory | 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f |
| Router02 | 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D |
2. Core:UniswapV2Factory
源码:
Uniswap/v2-core/contracts/UniswapV2Factory.sol角色:交易对工厂 ,通过
create2部署 Pair 合约并维护注册表。
2.1 状态变量
solidity
address public feeTo; // 协议手续费接收地址(非0则开启,抽取 1/6 的流动性增长)
address public feeToSetter; // 有权修改 feeTo 的地址(部署时构造函数传入)
mapping(address => mapping(address => address)) public getPair;
// getPair[tokenA][tokenB] = Pair 合约地址,按 token 地址排序索引
address[] public allPairs; // 所有创建的 Pair 地址数组
2.2 核心函数
createPair
solidity
function createPair(address tokenA, address tokenB) external returns (address pair)
创建新的交易对 Pair 合约。
| 参数 | 类型 | 说明 |
|---|---|---|
tokenA |
address | 代币 A 地址 |
tokenB |
address | 代币 B 地址 |
约束条件:
tokenA != tokenBtokenA != address(0)且tokenB != address(0)getPair[token0][token1] == address(0)(已存在则 revert)
实现细节:
- 内部自动按地址排序确定
token0 / token1 - 使用
CREATE2+keccak256(token0, token1)作为 salt,确保 Pair 地址可预测 - Pair 内调用
initialize(token0, token1)完成初始化 - 触发事件
PairCreated(token0, token1, pair, allPairs.length)
返回: 新部署的 Pair 合约地址
getPair
solidity
function getPair(address tokenA, address tokenB) external view returns (address pair)
查询两个代币对应的 Pair 地址(按地址排序后查找)。
allPairs / allPairsLength
solidity
function allPairs(uint index) external view returns (address pair)
function allPairsLength() external view returns (uint)
枚举所有已创建的 Pair。
setFeeTo
solidity
function setFeeTo(address _feeTo) external
设置协议手续费接收地址。
权限: 仅 feeToSetter 可调用。
当
feeTo != address(0)时,Pair 的_mintFee()会将 sqrt(k) 增长量的 1/6 铸造为协议手续费,等效抽取 LP 总手续费的 0.05%(总手续费的 1/6 = 0.05%)。
setFeeToSetter
solidity
function setFeeToSetter(address _feeToSetter) external
转移 feeToSetter 权限。
权限: 仅当前 feeToSetter 可调用。
2.3 Events
solidity
event PairCreated(
address indexed token0, // 按地址排序后的较小者
address indexed token1, // 按地址排序后的较大者
address pair, // Pair 合约地址
uint // allPairs.length,即该交易对的序号(从 1 开始)
);
3. Core:UniswapV2Pair
源码:
Uniswap/v2-core/contracts/UniswapV2Pair.sol角色:AMM 流动性池核心,存储两种代币的储备,执行 mint / burn / swap / 预言机更新。
3.1 状态变量
solidity
address public factory; // 创建此 Pair 的 Factory 地址
address public token0; // 按地址排序较小的代币
address public token1; // 按地址排序较大的代币
uint112 private reserve0; // token0 的储备量
uint112 private reserve1; // token1 的储备量
uint32 private blockTimestampLast; // 上次更新价格的区块时间戳
uint public price0CumulativeLast; // token0 的累计价格(用于 TWAP 预言机)
uint public price1CumulativeLast; // token1 的累计价格
uint public kLast; // 上次手续费计算时的 reserve0 * reserve1
3.2 核心常量
solidity
uint public constant MINIMUM_LIQUIDITY = 10**3;
// 首次添加流动性时永久锁入 address(0) 的 LP Token 数量,防止稀释攻击
3.3 只读函数(Read-Only)
getReserves
solidity
function getReserves()
public
view
returns (
uint112 _reserve0,
uint112 _reserve1,
uint32 _blockTimestampLast
)
返回当前储备量及上次价格更新的区块时间戳。
注意 :两个 token 的
reserve0 / reserve1与token0 / token1一一对应,需结合token0()确定各代币的储备。
mint / burn / swap / skim / sync 权限说明
这些函数均为 external,但通过 lock 修饰符(unlocked == 1 检查)防止重入。
3.4 流动性操作
mint
solidity
function mint(address to) external lock returns (uint liquidity)
添加流动性 。调用者需先已将两种代币转入 Pair 合约(ERC20 transfer),mint 根据转入量的增量铸造 LP Token。
| 参数 | 类型 | 说明 |
|---|---|---|
to |
address | LP Token 接收地址 |
计算逻辑:
// 首次添加(totalSupply == 0)
liquidity = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY
// 后续添加
liquidity = min(
amount0 * totalSupply / reserve0,
amount1 * totalSupply / reserve1
)
事件:
solidity
event Mint(address indexed sender, uint amount0, uint amount1);
// sender = msg.sender,amount0/amount1 = 本次实际注入量
burn
solidity
function burn(address to) external lock returns (uint amount0, uint amount1)
移除流动性 。调用者需先将 LP Token 转入 Pair 合约(ERC20 transfer),burn 按比例销毁 LP Token 并转回两种代币。
| 参数 | 类型 | 说明 |
|---|---|---|
to |
address | 收回代币的接收地址 |
计算逻辑:
amount0 = liquidity * balance0 / totalSupply
amount1 = liquidity * balance1 / totalSupply
事件:
solidity
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
// sender = msg.sender,to = 代币接收方
3.5 交易操作
swap
solidity
function swap(
uint amount0Out,
uint amount1Out,
address to,
bytes calldata data
) external lock
代币交换(低-level,需外部做安全检查)。
| 参数 | 类型 | 说明 |
|---|---|---|
amount0Out |
uint | 要输出的 token0 数量(可为 0) |
amount1Out |
uint | 要输出的 token1 数量(可为 0) |
to |
address | 接收代币的地址(不能是 token0/token1 本身) |
data |
bytes | 非空则回调 IUniswapV2Callee(to).uniswapV2Call(...)(闪电兑换) |
约束条件:
solidity
require(amount0Out > 0 || amount1Out > 0, 'INSUFFICIENT_OUTPUT_AMOUNT');
require(amount0Out < reserve0 && amount1Out < reserve1, 'INSUFFICIENT_LIQUIDITY');
require(to != token0 && to != token1, 'INVALID_TO');
K 值校验(防止手续费提取攻击):
solidity
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),
'UniswapV2: K'
);
每笔交易扣 0.3% 手续费 → 实际余额增量比理论少 0.3% → 乘以 997/1000 修正,K 值校验保证交易后
k' >= k * 1000²(即 0.3% 手续费被合理扣留)。
事件:
solidity
event Swap(
address indexed sender,
uint amount0In, // token0 输入量(正向交易)
uint amount1In, // token1 输入量
uint amount0Out, // token0 输出量
uint amount1Out, // token1 输出量
address indexed to
);
3.6 辅助操作
skim
solidity
function skim(address to) external lock
将池合约中超出 reserve 的代币余额(通常是意外转入的代币)转出给 to。
sync
solidity
function sync() external lock
将池内实际 ERC20 余额强制同步为 reserve,用于恢复因外部转账导致的储备不一致。
3.7 预言机相关
price0CumulativeLast / price1CumulativeLast
solidity
uint public price0CumulativeLast; // token1/token0 的累计价格 × 时间
uint public price1CumulativeLast; // token0/token1 的累计价格 × 时间
更新时机: _update() 在每个区块(首次调用时)累加:
solidity
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
用途: 计算 TWAP(时间加权平均价格):
solidity
// 时间段 [t1, t2] 内的平均价格
price_avg = (price0CumulativeLast[t2] - price0CumulativeLast[t1]) / (t2 - t1)
4. Core:IUniswapV2Callee
源码:
Uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol用途:**闪电兑换(Flash Swap)**回调接口。
接口签名
solidity
pragma solidity >=0.5.0;
interface IUniswapV2Callee {
function uniswapV2Call(
address sender, // 调用者(即调用 swap 的 msg.sender)
uint amount0, // 输出的 token0 数量
uint amount1, // 输出的 token1 数量
bytes calldata data // swap 调用时传入的 data
) external;
}
闪电兑换工作流程
1. 任意合约调用 Pair.swap(amount0Out, amount1Out, to, data)
↓
2. Pair 先将代币转给 to(乐观转账)
↓
3. Pair 调用 IUniswapV2Callee(to).uniswapV2Call(...)
↓
4. to 收到回调:在 uniswapV2Call 中执行任意逻辑
↓
5. to 必须确保 swap 结束后 balance >= 扣除手续费后的 reserve
(即:to 必须"还上"从 Pair 借出的代币 + 手续费,或用另一种代币偿还)
↓
6. Pair 执行 K 值校验,确认 balance 满足要求
注意:闪电兑换可以用一种代币借出,偿还时用另一种代币(不等额),这是套利的基础。
5. Periphery:UniswapV2Library
源码:
Uniswap/v2-periphery/contracts/libraries/UniswapV2Library.sol性质:纯函数库(library),无状态,仅用于计算。
5.1 sortTokens
solidity
function sortTokens(address tokenA, address tokenB)
internal pure returns (address token0, address token1)
按地址排序两个代币,确保与 Pair 的 token0 / token1 顺序一致。
约束: tokenA != tokenB,tokenA != address(0)
5.2 pairFor
solidity
function pairFor(
address factory,
address tokenA,
address tokenB
) internal pure returns (address pair)
通过 factory 地址 + 两个代币地址,用 CREATE2 反推 Pair 地址,无需做外部调用。
内部用到的 init code hash(主网):
0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f
⚠️ 不同链/不同部署的 init code hash 可能不同,需用实际部署的 Pair bytecode 重新计算。
5.3 getReserves
solidity
function getReserves(
address factory,
address tokenA,
address tokenB
) internal view returns (uint reserveA, uint reserveB)
获取指定两个代币的当前储备量,自动处理 token0/token1 的排序映射。
5.4 quote
solidity
function quote(
uint amountA,
uint reserveA,
uint reserveB
) internal pure returns (uint amountB)
已知输入量 amountA,估算可获得的另一种代币数量(不考虑手续费,等效 1:1 定价)。
solidity
amountB = amountA * reserveB / reserveA
5.5 getAmountOut
solidity
function getAmountOut(
uint amountIn,
uint reserveIn,
uint reserveOut
) internal pure returns (uint amountOut)
已知输入量,计算最大输出量(含 0.3% 手续费)。
solidity
// 扣除 0.3% 手续费:amountIn * 997 / 1000
uint amountInWithFee = amountIn * 997;
// 恒定乘积公式
amountOut = amountInWithFee * reserveOut / (reserveIn * 1000 + amountInWithFee)
5.6 getAmountIn
solidity
function getAmountIn(
uint amountOut,
uint reserveIn,
uint reserveOut
) internal pure returns (uint amountIn)
已知目标输出量,计算最小输入量(含 0.3% 手续费)。
solidity
uint numerator = reserveIn * amountOut * 1000;
uint denominator = (reserveOut - amountOut) * 997;
amountIn = numerator / denominator + 1; // +1 防止四舍五入导致实际不足
5.7 getAmountsOut
solidity
function getAmountsOut(
address factory,
uint amountIn,
address[] memory path
) internal view returns (uint[] memory amounts)
多跳路径的输出量计算,从 path0 → path1 → ... → pathlast。
| 参数 | 说明 |
|---|---|
factory |
工厂地址 |
amountIn |
输入总量 |
path |
交易路径,如 [WETH, USDC, DAI] |
返回: amounts[i] = pathi 的输出量(amounts[0] == amountIn)。
5.8 getAmountsIn
solidity
function getAmountsIn(
address factory,
uint amountOut,
address[] memory path
) internal view returns (uint[] memory amounts)
多跳路径的反向输入量计算(已知最终目标量,推算初始输入量)。
6. Periphery:UniswapV2Router02
源码:
Uniswap/v2-periphery/contracts/UniswapV2Router02.sol角色:用户入口合约,封装 Core 的低-level 操作,提供友好的添加/移除流动性、交易接口。
⚠️ Router02 仅用于与 Core 交互,内部不持有用户资产(无回调陷阱风险)。
6.1 构造函数与状态
solidity
address public immutable override factory; // Factory 地址
address public immutable override WETH; // WETH 地址(如 Mainnet: 0xC02aa...)
constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}
receive() external payable {
// 仅接受来自 WETH 合约的 ETH 退还(removeLiquidityETH 退款路径)
assert(msg.sender == WETH);
}
6.2 内部辅助
_addLiquidity(internal)
solidity
function _addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin
) internal virtual returns (uint amountA, uint amountB)
计算最优的两种代币注入量(维持池内当前价格比例):
- 若池不存在 → 创建 Pair,按 desired 全量注入
- 若池存在 → 用
quote计算维持当前比例的最优量
6.3 添加流动性
addLiquidity
solidity
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external virtual override ensure(deadline)
returns (uint amountA, uint amountB, uint liquidity)
添加 ERC20-ERC20 流动性。
| 参数 | 说明 |
|---|---|
tokenA / tokenB |
两种代币地址 |
amountADesired / amountBDesired |
希望注入的数量上限 |
amountAMin / amountBMin |
最小注入量(防止滑点过高) |
to |
LP Token 接收地址 |
deadline |
交易截止时间戳(防止重放) |
addLiquidityETH
solidity
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external virtual override payable ensure(deadline)
returns (uint amountToken, uint amountETH, uint liquidity)
添加 ETH-ERC20 流动性(Router 将 ETH 包裹为 WETH 后注入)。
| 参数 | 说明 |
|---|---|
token |
ERC20 代币地址 |
amountTokenDesired |
希望注入的 Token 数量 |
msg.value |
随调用一起发送的 ETH 数量上限 |
amountTokenMin / amountETHMin |
最小注入量 |
6.4 移除流动性
removeLiquidity
solidity
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity, // 要销毁的 LP Token 数量
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual override ensure(deadline)
returns (uint amountA, uint amountB)
移除 ERC20-ERC20 流动性,将代币发送至 to。
removeLiquidityETH
solidity
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline)
returns (uint amountToken, uint amountETH)
移除 ETH-ERC20 流动性,ETH 自动从 WETH 解除包裹后发送给 to。
带 Permit 的变体
solidity
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 virtual override returns (uint amountA, uint amountB)
function removeLiquidityETHWithPermit(
address token, uint liquidity,
uint amountTokenMin, uint amountETHMin, address to, uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountToken, uint amountETH)
允许离线签名(EIP-2612),无需预先 approve,直接用 permit 授权。
支持 Fee-on-Transfer 代币的移除
solidity
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountETH)
适用于转出时扣除手续费的代币(余额变化不是线性的),通过转出后查余额而非固定计算量来确认。
6.5 代币交换
swapExactTokensForTokens(ERC20 → ERC20,固定输入量)
solidity
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin, // 最小输出量(防滑点)
address[] calldata path, // 交易路径
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts)
固定输入量 :amountIn 全部由 path[0] 换出,amountOutMin 控制最低输出。
swapTokensForExactTokens(ERC20 → ERC20,固定输出量)
solidity
function swapTokensForExactTokens(
uint amountOut, // 期望输出的目标数量
uint amountInMax, // 最大愿意支付的输入量
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts)
固定输出量 :精确控制换出的目标数量,超出 amountInMax 则 revert。
swapExactETHForTokens(ETH → ERC20)
solidity
function swapExactETHForTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override payable ensure(deadline) returns (uint[] memory amounts)
// path[0] 必须是 WETH
swapETHForExactTokens(ETH → ERC20,固定输出)
solidity
function swapETHForExactTokens(
uint amountOut,
address[] calldata path,
address to,
uint deadline
) external virtual override payable ensure(deadline) returns (uint[] memory amounts)
// path[0] 必须是 WETH
swapExactTokensForETH(ERC20 → ETH)
solidity
function swapExactTokensForETH(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts)
// path[last] 必须是 WETH
swapTokensForExactETH(ERC20 → ETH,固定输出)
solidity
function swapTokensForExactETH(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts)
// path[last] 必须是 WETH
支持 Fee-on-Transfer 代币的交换
solidity
// ERC20 → ERC20(输入方代币含 fee-on-transfer)
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn, uint amountOutMin,
address[] calldata path, address to, uint deadline
) external virtual override ensure(deadline)
// ETH → ERC20(输入方含 fee-on-transfer)
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin, address[] calldata path, address to, uint deadline
) external virtual override payable ensure(deadline)
// ERC20 → ETH(输出方含 fee-on-transfer)
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn, uint amountOutMin,
address[] calldata path, address to, uint deadline
) external virtual override ensure(deadline)
关键区别 :因为无法事前计算实际输出量,改用交易后查余额差值来验证:
solidity
uint balanceBefore = IERC20(path[last]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[last]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
6.6 计算函数(Library 代理)
solidity
function quote(
uint amountA,
uint reserveA,
uint reserveB
) public pure virtual override returns (uint amountB)
function getAmountOut(
uint amountIn,
uint reserveIn,
uint reserveOut
) public pure virtual override returns (uint amountOut)
function getAmountIn(
uint amountOut,
uint reserveIn,
uint reserveOut
) public pure virtual override returns (uint amountIn)
function getAmountsOut(
uint amountIn,
address[] memory path
) public view virtual override returns (uint[] memory amounts)
function getAmountsIn(
uint amountOut,
address[] memory path
) public view virtual override returns (uint[] memory amounts)
这些函数直接代理 UniswapV2Library 的同名方法,前端/合约可调用以预计算交易价格。
7. 重要常量速查
| 常量 | 值 | 所在合约 | 说明 |
|---|---|---|---|
MINIMUM_LIQUIDITY |
10**3 |
Pair | 首次添加流动性时永久锁入 address(0) 的 LP Token |
手续费率 |
0.3% |
Pair (swap) | 每笔交易扣 0.3% → 全归 LP |
协议手续费 |
1/6 × 手续费增长 |
Pair (_mintFee) | 当 feeTo != 0 时触发,约占 LP 总手续费的 0.05% |
手续费精度因子 |
1000 |
Library | 计算时使用 997/1000(= 1 - 0.003) |
K 校验精度 |
1000² |
Pair (swap) | 交易后需满足 balance0Adj × balance1Adj >= reserve0 × reserve1 × 1000² |
Pair init code hash(主网) |
0x96e8ac42... |
Library | pairFor 反推地址用,不同链需重新计算 |
8. 安全注意事项
8.1 时间戳攻击(deadline)
所有交易必须传 deadline 参数,Router 用 ensure 修饰符检查:
solidity
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
→ 防止交易在签署后被矿工延迟打包导致不利价格执行。
8.2 K 值校验(防止手续费提取攻击)
Pair 的 swap 中强制校验:
balance0Adj × balance1Adj ≥ reserve0 × reserve1 × 1000²
→ 确保用户支付的 0.3% 手续费被合理扣留,防止通过异常余额绕过的攻击。
8.3 重入保护
Pair 的 mint / burn / swap / skim / sync 均使用 lock 修饰符:
solidity
modifier lock() {
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
_;
unlocked = 1;
}
→ 单合约重入被阻止,但跨合约调用(如 multicall 场景)仍需额外注意。
8.4 闪电兑换安全(IUniswapV2Callee)
实现 uniswapV2Call 时必须:
- 在回调结束时(或通过其他路径)偿还从 Pair 借出的代币
- 偿还量必须 ≥
amount0Out + amount0Out × 3/1000(或 token1 等效) - 建议使用 等量偿还(即借 A 还 A),避免汇率风险
8.5 approve + transferFrom 的两步骤陷阱
- Router 的
addLiquidity系列函数不调用 approve ,而是要求用户预先 approve Router 可以支配的代币额度 - 对于支持
permit的代币(EIP-2612),应优先使用 permit 签名代替 approve,避免 approve 授权导致的潜在风险
8.6 Pair 地址的跨链差异
UniswapV2Library.pairFor 中硬编码的 init code hash 仅适用于以太坊主网:
solidity
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f'
部署到其他 EVM 链时需用对应链的 Pair bytecode 重新计算。
8.7 滑点与 amountOutMin / amountInMax
- 输出保护 :
swapExact*系列必须设amountOutMin,防止价格剧烈波动下的糟糕成交 - 输入保护 :
swap*ForExact*系列必须设amountInMax,防止过度消耗预算 - 建议值:普通交易 0.5% 以内,高波动资产 ≤ 1%
8.8 skim / sync 的使用场景
sync:用于当外部直接向 Pair 合约转账(如错误转账)后,强制将 ERC20 余额同步为 reserveskim:用于提取超出 reserve 的意外余额,可用于紧急救援
源码仓库:v2-core / v2-periphery