Uniswap V3 第 5 章:工厂(Factory)
V3 的 Factory 和 V2 类似,但多了一个维度——费率档位(fee tier)。同一对代币可以有多个池子,每个对应不同费率。这一章讲清费率档位、tickSpacing 的对应关系,以及
createPool/getPool。
目录
- 1. V3 Factory 与 V2 的区别
- 2. 四个标准费率档位
- 3. tickSpacing:费率决定 tick 的稀疏程度
- 4. 案例:为什么稳定币对用 0.01%
- 5. getPool 与 createPool
- 6. 本章小结
- 7. 动手练习
1. V3 Factory 与 V2 的区别
| V2 Factory | V3 Factory | |
|---|---|---|
| 池子键 | (token0, token1) | (token0, token1, fee) |
| 同对池子数 | 唯一 | 每个费率档位一个 |
| 创建 | createPair | createPool |
| 查询 | getPair | getPool |
| 额外概念 | 无 | 费率档位 ↔ tickSpacing |
最大不同:V3 的池子多了”费率”这个维度。getPool(tokenA, tokenB, fee) 要传费率才能定位到具体池子。
主网 V3 Factory:0x1F98431c8aD98523631AE4a59f267346ea31F984。
2. 四个标准费率档位
V3 初始内置四个费率档位(治理可新增):
| fee 参数 | 费率 | 典型用途 |
|---|---|---|
100 | 0.01% | 稳定币对(USDC/DAI、USDC/USDT) |
500 | 0.05% | 高相关/低波动对(ETH/稳定币、WBTC/ETH) |
3000 | 0.3% | 普通对(大多数代币,对应 V2 默认费率) |
10000 | 1% | 高波动/长尾代币 |
注意 fee 参数以百万分之一为单位:3000 = 3000/1,000,000 = 0.3%。
为什么要多档?不同资产对的波动和无常损失风险不同:
- 稳定币对几乎无波动,LP 风险小,低费率就能吸引流动性,交易者也更划算。
- 长尾高波动代币,LP 承担大无常损失,需要高费率补偿。
3. tickSpacing:费率决定 tick 的稀疏程度
每个费率档位绑定一个 tickSpacing:流动性区间的端点只能落在 tickSpacing 的整数倍的 tick 上。
| fee | tickSpacing | 含义 |
|---|---|---|
| 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. 本章小结
- V3 池子的键是 (token0, token1, fee),同一对代币按费率档位有多个池子。
- 四个标准档位:0.01%(稳定币)/ 0.05%(低波动)/ 0.3%(普通)/ 1%(长尾),fee 以百万分之一为单位。
- 每个费率绑定一个 tickSpacing(1/10/60/200):费率越低越精细、费率越高越稀疏(省 gas)。
- 稳定币对用 0.01% + tickSpacing=1,把流动性挤在极窄区间。
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);
}
思路:
- 查已有池:
getPool(DAI, USDC, 100),断言等于已知的 DAI/USDC 0.01% 池地址。 - 建新池:用两个 MockERC20(tokenA、tokenB),调
createPool(tokenA, tokenB, 100)。 - 验证新池的
token0()/token1()是按地址排序的、fee()等于 100。 - (进阶)对比同一对代币不同费率(如 100 和 3000)的
getPool返回是不同地址,体会”多池”。
运行
forge test --evm-version cancun --fork-url $FORK_URL \
--match-path test/UniswapV3Factory.t.sol -vvv
下一章(第 6 章 Liquidity)讲:怎么用 NonfungiblePositionManager 铸造头寸 NFT、增减流动性、领取手续费。