Uniswap V2 核心接口文档源码基于 Uniswap V2 官方仓库 v2-core / v2-peripherySolidity 0.5.16。文档结构先架构总览再逐合约展开含完整函数签名、Events、关键参数说明及安全注意事项。目录架构总览CoreUniswapV2FactoryCoreUniswapV2PairCore闪电兑换接口 IUniswapV2CalleePeripheryUniswapV2LibraryPeripheryUniswapV2Router02重要常量速查安全注意事项1. 架构总览┌─────────────────────────────────────────────────────────────┐ │ 架构分层 │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Periphery │ │ Interface │ DApp / Frontend │ │ │ UniswapV2Router02 │ IUniswapV2Router02 │ │ │ └──────┬───────┘ └──────────────┘ │ │ │ │ │ ┌──────▼───────────────────────────────────────┐ │ │ │ Core │ │ │ │ UniswapV2Factory ← 创建/管理 Pair │ │ │ │ UniswapV2Pair ← 核心 AMM 逻辑 │ │ │ │ UniswapV2ERC20 ← LP Token (ERC20) │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ UniswapV2Library (Periphery) │ │ │ │ pairFor / getAmountOut / getAmountIn 等 │ │ │ └─────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘层级合约职责CoreUniswapV2Factory创建交易对、管理 Pair 注册表CoreUniswapV2PairAMM 核心逻辑mint / burn / swap / 预言机CoreUniswapV2ERC20LP TokenERC20 EIP-2612 permitPeripheryUniswapV2Router02用户入口添加流动性 / 移除流动性 / 交易PeripheryUniswapV2Library纯函数计算库价格 / 金额 / pair 地址主线网常用地址合约地址Factory0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6fRouter020x7a250d5630B4cF539739dF2C5dAcb4c659F2488D2. CoreUniswapV2Factory源码Uniswap/v2-core/contracts/UniswapV2Factory.sol角色交易对工厂通过create2部署 Pair 合约并维护注册表。2.1 状态变量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 核心函数createPairfunction createPair(address tokenA, address tokenB) external returns (address pair)创建新的交易对 Pair 合约。参数类型说明tokenAaddress代币 A 地址tokenBaddress代币 B 地址约束条件tokenA ! tokenBtokenA ! address(0)且tokenB ! address(0)getPair[token0][token1] address(0)已存在则 revert实现细节内部自动按地址排序确定token0 / token1使用CREATE2keccak256(token0, token1)作为 salt确保 Pair 地址可预测Pair 内调用initialize(token0, token1)完成初始化触发事件PairCreated(token0, token1, pair, allPairs.length)返回新部署的 Pair 合约地址getPairfunction getPair(address tokenA, address tokenB) external view returns (address pair)查询两个代币对应的 Pair 地址按地址排序后查找。allPairs/allPairsLengthfunction allPairs(uint index) external view returns (address pair) function allPairsLength() external view returns (uint)枚举所有已创建的 Pair。setFeeTofunction setFeeTo(address _feeTo) external设置协议手续费接收地址。权限仅feeToSetter可调用。当feeTo ! address(0)时Pair 的_mintFee()会将 sqrt(k) 增长量的 1/6 铸造为协议手续费等效抽取 LP 总手续费的0.05%总手续费的 1/6 0.05%。setFeeToSetterfunction setFeeToSetter(address _feeToSetter) external转移feeToSetter权限。权限仅当前feeToSetter可调用。2.3 Eventsevent PairCreated( address indexed token0, // 按地址排序后的较小者 address indexed token1, // 按地址排序后的较大者 address pair, // Pair 合约地址 uint // allPairs.length即该交易对的序号从 1 开始 );3. CoreUniswapV2Pair源码Uniswap/v2-core/contracts/UniswapV2Pair.sol角色AMM 流动性池核心存储两种代币的储备执行 mint / burn / swap / 预言机更新。3.1 状态变量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 * reserve13.2 核心常量uint public constant MINIMUM_LIQUIDITY 10**3; // 首次添加流动性时永久锁入 address(0) 的 LP Token 数量防止稀释攻击3.3 只读函数Read-OnlygetReservesfunction 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 流动性操作mintfunction mint(address to) external lock returns (uint liquidity)添加流动性。调用者需先已将两种代币转入 Pair 合约ERC20transfermint根据转入量的增量铸造 LP Token。参数类型说明toaddressLP Token 接收地址计算逻辑// 首次添加totalSupply 0 liquidity sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY // 后续添加 liquidity min( amount0 * totalSupply / reserve0, amount1 * totalSupply / reserve1 )事件event Mint(address indexed sender, uint amount0, uint amount1); // sender msg.senderamount0/amount1 本次实际注入量burnfunction burn(address to) external lock returns (uint amount0, uint amount1)移除流动性。调用者需先将 LP Token 转入 Pair 合约ERC20transferburn按比例销毁 LP Token 并转回两种代币。参数类型说明toaddress收回代币的接收地址计算逻辑amount0 liquidity * balance0 / totalSupply amount1 liquidity * balance1 / totalSupply事件event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); // sender msg.senderto 代币接收方3.5 交易操作swapfunction swap( uint amount0Out, uint amount1Out, address to, bytes calldata data ) external lock代币交换低-level需外部做安全检查。参数类型说明amount0Outuint要输出的 token0 数量可为 0amount1Outuint要输出的 token1 数量可为 0toaddress接收代币的地址不能是 token0/token1 本身databytes非空则回调IUniswapV2Callee(to).uniswapV2Call(...)闪电兑换约束条件require(amount0Out 0 || amount1Out 0, INSUFFICIENT_OUTPUT_AMOUNT); require(amount0Out reserve0 amount1Out reserve1, INSUFFICIENT_LIQUIDITY); require(to ! token0 to ! token1, INVALID_TO);K 值校验防止手续费提取攻击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% 手续费被合理扣留。事件event Swap( address indexed sender, uint amount0In, // token0 输入量正向交易 uint amount1In, // token1 输入量 uint amount0Out, // token0 输出量 uint amount1Out, // token1 输出量 address indexed to );3.6 辅助操作skimfunction skim(address to) external lock将池合约中超出reserve的代币余额通常是意外转入的代币转出给to。syncfunction sync() external lock将池内实际 ERC20 余额强制同步为reserve用于恢复因外部转账导致的储备不一致。3.7 预言机相关price0CumulativeLast/price1CumulativeLastuint public price0CumulativeLast; // token1/token0 的累计价格 × 时间 uint public price1CumulativeLast; // token0/token1 的累计价格 × 时间更新时机_update()在每个区块首次调用时累加price0CumulativeLast uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed; price1CumulativeLast uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;用途计算 TWAP时间加权平均价格// 时间段 [t1, t2] 内的平均价格 price_avg (price0CumulativeLast[t2] - price0CumulativeLast[t1]) / (t2 - t1)4. CoreIUniswapV2Callee源码Uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol用途**闪电兑换Flash Swap**回调接口。接口签名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. PeripheryUniswapV2Library源码Uniswap/v2-periphery/contracts/libraries/UniswapV2Library.sol性质纯函数库library无状态仅用于计算。5.1sortTokensfunction sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1)按地址排序两个代币确保与 Pair 的token0 / token1顺序一致。约束tokenA ! tokenBtokenA ! address(0)5.2pairForfunction 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.3getReservesfunction getReserves( address factory, address tokenA, address tokenB ) internal view returns (uint reserveA, uint reserveB)获取指定两个代币的当前储备量自动处理 token0/token1 的排序映射。5.4quotefunction quote( uint amountA, uint reserveA, uint reserveB ) internal pure returns (uint amountB)已知输入量amountA估算可获得的另一种代币数量不考虑手续费等效 1:1 定价。amountB amountA * reserveB / reserveA5.5getAmountOutfunction getAmountOut( uint amountIn, uint reserveIn, uint reserveOut ) internal pure returns (uint amountOut)已知输入量计算最大输出量含 0.3% 手续费。// 扣除 0.3% 手续费amountIn * 997 / 1000 uint amountInWithFee amountIn * 997; // 恒定乘积公式 amountOut amountInWithFee * reserveOut / (reserveIn * 1000 amountInWithFee)5.6getAmountInfunction getAmountIn( uint amountOut, uint reserveIn, uint reserveOut ) internal pure returns (uint amountIn)已知目标输出量计算最小输入量含 0.3% 手续费。uint numerator reserveIn * amountOut * 1000; uint denominator (reserveOut - amountOut) * 997; amountIn numerator / denominator 1; // 1 防止四舍五入导致实际不足5.7getAmountsOutfunction getAmountsOut( address factory, uint amountIn, address[] memory path ) internal view returns (uint[] memory amounts)多跳路径的输出量计算从 path[0] → path[1] → … → path[last]。参数说明factory工厂地址amountIn输入总量path交易路径如[WETH, USDC, DAI]返回amounts[i] path[i] 的输出量amounts[0] amountIn。5.8getAmountsInfunction getAmountsIn( address factory, uint amountOut, address[] memory path ) internal view returns (uint[] memory amounts)多跳路径的反向输入量计算已知最终目标量推算初始输入量。6. PeripheryUniswapV2Router02源码Uniswap/v2-periphery/contracts/UniswapV2Router02.sol角色用户入口合约封装 Core 的低-level 操作提供友好的添加/移除流动性、交易接口。⚠️ Router02 仅用于与 Core 交互内部不持有用户资产无回调陷阱风险。6.1 构造函数与状态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 内部辅助_addLiquidityinternalfunction _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 添加流动性addLiquidityfunction 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最小注入量防止滑点过高toLP Token 接收地址deadline交易截止时间戳防止重放addLiquidityETHfunction 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 后注入。参数说明tokenERC20 代币地址amountTokenDesired希望注入的 Token 数量msg.value随调用一起发送的 ETH 数量上限amountTokenMin / amountETHMin最小注入量6.4 移除流动性removeLiquidityfunction 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。removeLiquidityETHfunction 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 的变体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 代币的移除function removeLiquidityETHSupportingFeeOnTransferTokens( address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline ) public virtual override ensure(deadline) returns (uint amountETH)适用于转出时扣除手续费的代币余额变化不是线性的通过转出后查余额而非固定计算量来确认。6.5 代币交换swapExactTokensForTokensERC20 → ERC20固定输入量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控制最低输出。swapTokensForExactTokensERC20 → ERC20固定输出量function swapTokensForExactTokens( uint amountOut, // 期望输出的目标数量 uint amountInMax, // 最大愿意支付的输入量 address[] calldata path, address to, uint deadline ) external virtual override ensure(deadline) returns (uint[] memory amounts)固定输出量精确控制换出的目标数量超出amountInMax则 revert。swapExactETHForTokensETH → ERC20function swapExactETHForTokens( uint amountOutMin, address[] calldata path, address to, uint deadline ) external virtual override payable ensure(deadline) returns (uint[] memory amounts) // path[0] 必须是 WETHswapETHForExactTokensETH → ERC20固定输出function swapETHForExactTokens( uint amountOut, address[] calldata path, address to, uint deadline ) external virtual override payable ensure(deadline) returns (uint[] memory amounts) // path[0] 必须是 WETHswapExactTokensForETHERC20 → ETHfunction swapExactTokensForETH( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external virtual override ensure(deadline) returns (uint[] memory amounts) // path[last] 必须是 WETHswapTokensForExactETHERC20 → ETH固定输出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 代币的交换// 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)关键区别因为无法事前计算实际输出量改用交易后查余额差值来验证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 代理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_LIQUIDITY10**3Pair首次添加流动性时永久锁入address(0)的 LP Token手续费率0.3%Pair (swap)每笔交易扣 0.3% → 全归 LP协议手续费1/6 × 手续费增长Pair (_mintFee)当feeTo ! 0时触发约占 LP 总手续费的0.05%手续费精度因子1000Library计算时使用 997/1000 1 - 0.003K 校验精度1000²Pair (swap)交易后需满足balance0Adj × balance1Adj reserve0 × reserve1 × 1000²Pair init code hash主网0x96e8ac42...LibrarypairFor 反推地址用不同链需重新计算8. 安全注意事项8.1 时间戳攻击deadline所有交易必须传deadline参数Router 用ensure修饰符检查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修饰符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.5approvetransferFrom的两步骤陷阱Router 的addLiquidity系列函数不调用 approve而是要求用户预先 approveRouter 可以支配的代币额度对于支持permit的代币EIP-2612应优先使用 permit 签名代替 approve避免 approve 授权导致的潜在风险8.6 Pair 地址的跨链差异UniswapV2Library.pairFor中硬编码的init code hash仅适用于以太坊主网hex96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f部署到其他 EVM 链时需用对应链的 Pair bytecode 重新计算。8.7 滑点与amountOutMin/amountInMax输出保护swapExact*系列必须设amountOutMin防止价格剧烈波动下的糟糕成交输入保护swap*ForExact*系列必须设amountInMax防止过度消耗预算建议值普通交易 0.5% 以内高波动资产 ≤ 1%8.8skim/sync的使用场景sync用于当外部直接向 Pair 合约转账如错误转账后强制将 ERC20 余额同步为 reserveskim用于提取超出 reserve 的意外余额可用于紧急救援源码仓库v2-core / v2-periphery
Uniswap V2 Core 和 Periphery 合约核心接口文档
Uniswap V2 核心接口文档源码基于 Uniswap V2 官方仓库 v2-core / v2-peripherySolidity 0.5.16。文档结构先架构总览再逐合约展开含完整函数签名、Events、关键参数说明及安全注意事项。目录架构总览CoreUniswapV2FactoryCoreUniswapV2PairCore闪电兑换接口 IUniswapV2CalleePeripheryUniswapV2LibraryPeripheryUniswapV2Router02重要常量速查安全注意事项1. 架构总览┌─────────────────────────────────────────────────────────────┐ │ 架构分层 │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ Periphery │ │ Interface │ DApp / Frontend │ │ │ UniswapV2Router02 │ IUniswapV2Router02 │ │ │ └──────┬───────┘ └──────────────┘ │ │ │ │ │ ┌──────▼───────────────────────────────────────┐ │ │ │ Core │ │ │ │ UniswapV2Factory ← 创建/管理 Pair │ │ │ │ UniswapV2Pair ← 核心 AMM 逻辑 │ │ │ │ UniswapV2ERC20 ← LP Token (ERC20) │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ UniswapV2Library (Periphery) │ │ │ │ pairFor / getAmountOut / getAmountIn 等 │ │ │ └─────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘层级合约职责CoreUniswapV2Factory创建交易对、管理 Pair 注册表CoreUniswapV2PairAMM 核心逻辑mint / burn / swap / 预言机CoreUniswapV2ERC20LP TokenERC20 EIP-2612 permitPeripheryUniswapV2Router02用户入口添加流动性 / 移除流动性 / 交易PeripheryUniswapV2Library纯函数计算库价格 / 金额 / pair 地址主线网常用地址合约地址Factory0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6fRouter020x7a250d5630B4cF539739dF2C5dAcb4c659F2488D2. CoreUniswapV2Factory源码Uniswap/v2-core/contracts/UniswapV2Factory.sol角色交易对工厂通过create2部署 Pair 合约并维护注册表。2.1 状态变量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 核心函数createPairfunction createPair(address tokenA, address tokenB) external returns (address pair)创建新的交易对 Pair 合约。参数类型说明tokenAaddress代币 A 地址tokenBaddress代币 B 地址约束条件tokenA ! tokenBtokenA ! address(0)且tokenB ! address(0)getPair[token0][token1] address(0)已存在则 revert实现细节内部自动按地址排序确定token0 / token1使用CREATE2keccak256(token0, token1)作为 salt确保 Pair 地址可预测Pair 内调用initialize(token0, token1)完成初始化触发事件PairCreated(token0, token1, pair, allPairs.length)返回新部署的 Pair 合约地址getPairfunction getPair(address tokenA, address tokenB) external view returns (address pair)查询两个代币对应的 Pair 地址按地址排序后查找。allPairs/allPairsLengthfunction allPairs(uint index) external view returns (address pair) function allPairsLength() external view returns (uint)枚举所有已创建的 Pair。setFeeTofunction setFeeTo(address _feeTo) external设置协议手续费接收地址。权限仅feeToSetter可调用。当feeTo ! address(0)时Pair 的_mintFee()会将 sqrt(k) 增长量的 1/6 铸造为协议手续费等效抽取 LP 总手续费的0.05%总手续费的 1/6 0.05%。setFeeToSetterfunction setFeeToSetter(address _feeToSetter) external转移feeToSetter权限。权限仅当前feeToSetter可调用。2.3 Eventsevent PairCreated( address indexed token0, // 按地址排序后的较小者 address indexed token1, // 按地址排序后的较大者 address pair, // Pair 合约地址 uint // allPairs.length即该交易对的序号从 1 开始 );3. CoreUniswapV2Pair源码Uniswap/v2-core/contracts/UniswapV2Pair.sol角色AMM 流动性池核心存储两种代币的储备执行 mint / burn / swap / 预言机更新。3.1 状态变量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 * reserve13.2 核心常量uint public constant MINIMUM_LIQUIDITY 10**3; // 首次添加流动性时永久锁入 address(0) 的 LP Token 数量防止稀释攻击3.3 只读函数Read-OnlygetReservesfunction 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 流动性操作mintfunction mint(address to) external lock returns (uint liquidity)添加流动性。调用者需先已将两种代币转入 Pair 合约ERC20transfermint根据转入量的增量铸造 LP Token。参数类型说明toaddressLP Token 接收地址计算逻辑// 首次添加totalSupply 0 liquidity sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY // 后续添加 liquidity min( amount0 * totalSupply / reserve0, amount1 * totalSupply / reserve1 )事件event Mint(address indexed sender, uint amount0, uint amount1); // sender msg.senderamount0/amount1 本次实际注入量burnfunction burn(address to) external lock returns (uint amount0, uint amount1)移除流动性。调用者需先将 LP Token 转入 Pair 合约ERC20transferburn按比例销毁 LP Token 并转回两种代币。参数类型说明toaddress收回代币的接收地址计算逻辑amount0 liquidity * balance0 / totalSupply amount1 liquidity * balance1 / totalSupply事件event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); // sender msg.senderto 代币接收方3.5 交易操作swapfunction swap( uint amount0Out, uint amount1Out, address to, bytes calldata data ) external lock代币交换低-level需外部做安全检查。参数类型说明amount0Outuint要输出的 token0 数量可为 0amount1Outuint要输出的 token1 数量可为 0toaddress接收代币的地址不能是 token0/token1 本身databytes非空则回调IUniswapV2Callee(to).uniswapV2Call(...)闪电兑换约束条件require(amount0Out 0 || amount1Out 0, INSUFFICIENT_OUTPUT_AMOUNT); require(amount0Out reserve0 amount1Out reserve1, INSUFFICIENT_LIQUIDITY); require(to ! token0 to ! token1, INVALID_TO);K 值校验防止手续费提取攻击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% 手续费被合理扣留。事件event Swap( address indexed sender, uint amount0In, // token0 输入量正向交易 uint amount1In, // token1 输入量 uint amount0Out, // token0 输出量 uint amount1Out, // token1 输出量 address indexed to );3.6 辅助操作skimfunction skim(address to) external lock将池合约中超出reserve的代币余额通常是意外转入的代币转出给to。syncfunction sync() external lock将池内实际 ERC20 余额强制同步为reserve用于恢复因外部转账导致的储备不一致。3.7 预言机相关price0CumulativeLast/price1CumulativeLastuint public price0CumulativeLast; // token1/token0 的累计价格 × 时间 uint public price1CumulativeLast; // token0/token1 的累计价格 × 时间更新时机_update()在每个区块首次调用时累加price0CumulativeLast uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed; price1CumulativeLast uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;用途计算 TWAP时间加权平均价格// 时间段 [t1, t2] 内的平均价格 price_avg (price0CumulativeLast[t2] - price0CumulativeLast[t1]) / (t2 - t1)4. CoreIUniswapV2Callee源码Uniswap/v2-core/contracts/interfaces/IUniswapV2Callee.sol用途**闪电兑换Flash Swap**回调接口。接口签名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. PeripheryUniswapV2Library源码Uniswap/v2-periphery/contracts/libraries/UniswapV2Library.sol性质纯函数库library无状态仅用于计算。5.1sortTokensfunction sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1)按地址排序两个代币确保与 Pair 的token0 / token1顺序一致。约束tokenA ! tokenBtokenA ! address(0)5.2pairForfunction 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.3getReservesfunction getReserves( address factory, address tokenA, address tokenB ) internal view returns (uint reserveA, uint reserveB)获取指定两个代币的当前储备量自动处理 token0/token1 的排序映射。5.4quotefunction quote( uint amountA, uint reserveA, uint reserveB ) internal pure returns (uint amountB)已知输入量amountA估算可获得的另一种代币数量不考虑手续费等效 1:1 定价。amountB amountA * reserveB / reserveA5.5getAmountOutfunction getAmountOut( uint amountIn, uint reserveIn, uint reserveOut ) internal pure returns (uint amountOut)已知输入量计算最大输出量含 0.3% 手续费。// 扣除 0.3% 手续费amountIn * 997 / 1000 uint amountInWithFee amountIn * 997; // 恒定乘积公式 amountOut amountInWithFee * reserveOut / (reserveIn * 1000 amountInWithFee)5.6getAmountInfunction getAmountIn( uint amountOut, uint reserveIn, uint reserveOut ) internal pure returns (uint amountIn)已知目标输出量计算最小输入量含 0.3% 手续费。uint numerator reserveIn * amountOut * 1000; uint denominator (reserveOut - amountOut) * 997; amountIn numerator / denominator 1; // 1 防止四舍五入导致实际不足5.7getAmountsOutfunction getAmountsOut( address factory, uint amountIn, address[] memory path ) internal view returns (uint[] memory amounts)多跳路径的输出量计算从 path[0] → path[1] → … → path[last]。参数说明factory工厂地址amountIn输入总量path交易路径如[WETH, USDC, DAI]返回amounts[i] path[i] 的输出量amounts[0] amountIn。5.8getAmountsInfunction getAmountsIn( address factory, uint amountOut, address[] memory path ) internal view returns (uint[] memory amounts)多跳路径的反向输入量计算已知最终目标量推算初始输入量。6. PeripheryUniswapV2Router02源码Uniswap/v2-periphery/contracts/UniswapV2Router02.sol角色用户入口合约封装 Core 的低-level 操作提供友好的添加/移除流动性、交易接口。⚠️ Router02 仅用于与 Core 交互内部不持有用户资产无回调陷阱风险。6.1 构造函数与状态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 内部辅助_addLiquidityinternalfunction _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 添加流动性addLiquidityfunction 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最小注入量防止滑点过高toLP Token 接收地址deadline交易截止时间戳防止重放addLiquidityETHfunction 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 后注入。参数说明tokenERC20 代币地址amountTokenDesired希望注入的 Token 数量msg.value随调用一起发送的 ETH 数量上限amountTokenMin / amountETHMin最小注入量6.4 移除流动性removeLiquidityfunction 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。removeLiquidityETHfunction 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 的变体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 代币的移除function removeLiquidityETHSupportingFeeOnTransferTokens( address token, uint liquidity, uint amountTokenMin, uint amountETHMin, address to, uint deadline ) public virtual override ensure(deadline) returns (uint amountETH)适用于转出时扣除手续费的代币余额变化不是线性的通过转出后查余额而非固定计算量来确认。6.5 代币交换swapExactTokensForTokensERC20 → ERC20固定输入量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控制最低输出。swapTokensForExactTokensERC20 → ERC20固定输出量function swapTokensForExactTokens( uint amountOut, // 期望输出的目标数量 uint amountInMax, // 最大愿意支付的输入量 address[] calldata path, address to, uint deadline ) external virtual override ensure(deadline) returns (uint[] memory amounts)固定输出量精确控制换出的目标数量超出amountInMax则 revert。swapExactETHForTokensETH → ERC20function swapExactETHForTokens( uint amountOutMin, address[] calldata path, address to, uint deadline ) external virtual override payable ensure(deadline) returns (uint[] memory amounts) // path[0] 必须是 WETHswapETHForExactTokensETH → ERC20固定输出function swapETHForExactTokens( uint amountOut, address[] calldata path, address to, uint deadline ) external virtual override payable ensure(deadline) returns (uint[] memory amounts) // path[0] 必须是 WETHswapExactTokensForETHERC20 → ETHfunction swapExactTokensForETH( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external virtual override ensure(deadline) returns (uint[] memory amounts) // path[last] 必须是 WETHswapTokensForExactETHERC20 → ETH固定输出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 代币的交换// 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)关键区别因为无法事前计算实际输出量改用交易后查余额差值来验证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 代理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_LIQUIDITY10**3Pair首次添加流动性时永久锁入address(0)的 LP Token手续费率0.3%Pair (swap)每笔交易扣 0.3% → 全归 LP协议手续费1/6 × 手续费增长Pair (_mintFee)当feeTo ! 0时触发约占 LP 总手续费的0.05%手续费精度因子1000Library计算时使用 997/1000 1 - 0.003K 校验精度1000²Pair (swap)交易后需满足balance0Adj × balance1Adj reserve0 × reserve1 × 1000²Pair init code hash主网0x96e8ac42...LibrarypairFor 反推地址用不同链需重新计算8. 安全注意事项8.1 时间戳攻击deadline所有交易必须传deadline参数Router 用ensure修饰符检查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修饰符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.5approvetransferFrom的两步骤陷阱Router 的addLiquidity系列函数不调用 approve而是要求用户预先 approveRouter 可以支配的代币额度对于支持permit的代币EIP-2612应优先使用 permit 签名代替 approve避免 approve 授权导致的潜在风险8.6 Pair 地址的跨链差异UniswapV2Library.pairFor中硬编码的init code hash仅适用于以太坊主网hex96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f部署到其他 EVM 链时需用对应链的 Pair bytecode 重新计算。8.7 滑点与amountOutMin/amountInMax输出保护swapExact*系列必须设amountOutMin防止价格剧烈波动下的糟糕成交输入保护swap*ForExact*系列必须设amountInMax防止过度消耗预算建议值普通交易 0.5% 以内高波动资产 ≤ 1%8.8skim/sync的使用场景sync用于当外部直接向 Pair 合约转账如错误转账后强制将 ERC20 余额同步为 reserveskim用于提取超出 reserve 的意外余额可用于紧急救援源码仓库v2-core / v2-periphery