Uniswap V3 第5章 工厂:费率档位与 tickSpacing

讲解 Uniswap V3 Factory:按 (token0, token1, fee) 创建池子、费率档位与 tickSpacing 的对应关系,以及同一对代币的多池设计。

4 分钟阅读
Uniswap V3 第5章 工厂:费率档位与 tickSpacing

Uniswap V3 第 5 章:工厂(Factory)

V3 的 Factory 和 V2 类似,但多了一个维度——费率档位(fee tier)。同一对代币可以有多个池子,每个对应不同费率。这一章讲清费率档位、tickSpacing 的对应关系,以及 createPool / getPool


目录


1. V3 Factory 与 V2 的区别

V2 FactoryV3 Factory
池子键(token0, token1)(token0, token1, fee)
同对池子数唯一每个费率档位一个
创建createPaircreatePool
查询getPairgetPool
额外概念费率档位 ↔ tickSpacing

最大不同:V3 的池子多了”费率”这个维度getPool(tokenA, tokenB, fee) 要传费率才能定位到具体池子。

主网 V3 Factory:0x1F98431c8aD98523631AE4a59f267346ea31F984


2. 四个标准费率档位

V3 初始内置四个费率档位(治理可新增):

fee 参数费率典型用途
1000.01%稳定币对(USDC/DAI、USDC/USDT)
5000.05%高相关/低波动对(ETH/稳定币、WBTC/ETH)
30000.3%普通对(大多数代币,对应 V2 默认费率)
100001%高波动/长尾代币

注意 fee 参数以百万分之一为单位:3000 = 3000/1,000,000 = 0.3%

为什么要多档?不同资产对的波动和无常损失风险不同:

  • 稳定币对几乎无波动,LP 风险小,低费率就能吸引流动性,交易者也更划算。
  • 长尾高波动代币,LP 承担大无常损失,需要高费率补偿。

3. tickSpacing:费率决定 tick 的稀疏程度

每个费率档位绑定一个 tickSpacing:流动性区间的端点只能落在 tickSpacing 的整数倍的 tick 上。

feetickSpacing含义
100 (0.01%)1最密,区间可以非常精细
500 (0.05%)10
3000 (0.3%)60
10000 (1%)200最稀疏

为什么费率高的 tickSpacing 大?

  • tickSpacing 越小,可放头寸的 tick 越多 → swap 跨 tick 时要遍历的边界越多 → gas 越高。
  • 低费率池追求极致精细(稳定币对要把流动性挤在极窄区间),用 tickSpacing=1。
  • 高费率池本来就是高波动场景,不需要那么精细,用大 tickSpacing 省 gas。

4. 案例:为什么稳定币对用 0.01%

USDC/DAI 价格几乎永远在 0.9999~1.0001 之间。

  • 0.01% 档(tickSpacing=1):LP 可以把流动性集中在 [0.9999, 1.0001] 这种极窄区间,深度极高、滑点极低,0.01% 的低费率对交易者也友好。
  • 如果用 0.3% 档:费率太高,稳定币兑换不划算;tickSpacing=60 也太粗,无法精细集中。

所以稳定币对的主流动性都在 0.01% 池。这也解释了为什么”同一对代币会有多个池子”——不同费率档各有适配的资产类型,流动性会自然聚集到最合适的那个档。


5. getPool 与 createPool

interface IUniswapV3Factory {
    function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address);
    function createPool(address tokenA, address tokenB, uint24 fee) external returns (address);
    function feeAmountTickSpacing(uint24 fee) external view returns (int24);
}
  • getPool:传入两种代币和费率,返回池子地址(不存在则返回零地址)。内部也会排序 token0<token1,所以 (A,B)(B,A) 等价。
  • createPool:部署一个新池子(同样用 CREATE2,地址可预测)。要求该 (token0, token1, fee) 还没有池子、且 fee 是已启用的档位。
  • 创建后需要调用 pool.initialize(sqrtPriceX96) 设置初始价格,池子才能用。

6. 本章小结

  1. V3 池子的键是 (token0, token1, fee),同一对代币按费率档位有多个池子
  2. 四个标准档位:0.01%(稳定币)/ 0.05%(低波动)/ 0.3%(普通)/ 1%(长尾),fee 以百万分之一为单位。
  3. 每个费率绑定一个 tickSpacing(1/10/60/200):费率越低越精细、费率越高越稀疏(省 gas)。
  4. 稳定币对用 0.01% + tickSpacing=1,把流动性挤在极窄区间。
  5. getPool / createPool 需传 fee;createPool 后要 initialize 初始价格。

7. 动手练习

对应课程的 Factory 练习:查池子、建池子。

练习:getPool 与 createPool

主网分叉,Factory = 0x1F98431c8aD98523631AE4a59f267346ea31F984

interface IUniswapV3Factory {
    function getPool(address a, address b, uint24 fee) external view returns (address);
    function createPool(address a, address b, uint24 fee) external returns (address);
}
interface IUniswapV3Pool {
    function token0() external view returns (address);
    function token1() external view returns (address);
    function fee() external view returns (uint24);
}

思路:

  1. 查已有池getPool(DAI, USDC, 100),断言等于已知的 DAI/USDC 0.01% 池地址。
  2. 建新池:用两个 MockERC20(tokenA、tokenB),调 createPool(tokenA, tokenB, 100)
  3. 验证新池的 token0()/token1() 是按地址排序的、fee() 等于 100。
  4. (进阶)对比同一对代币不同费率(如 100 和 3000)的 getPool 返回是不同地址,体会”多池”。

运行

forge test --evm-version cancun --fork-url $FORK_URL \
  --match-path test/UniswapV3Factory.t.sol -vvv

下一章(第 6 章 Liquidity)讲:怎么用 NonfungiblePositionManager 铸造头寸 NFT、增减流动性、领取手续费。

💬 评论