本文深入探讨了Uniswap V3的流动性池,涵盖核心机制、数学基础、实现、工作流程、安全与优化等方面。重点介绍了集中流动性、价格范围与Ticks、非同质化仓位等核心概念,并提供了WBTC/ETH池的部署示例和管理指南,旨在帮助开发者、LP和审计人员更好地理解和应用Uniswap V3。
Uniswap V3 于 2021 年 5 月推出,通过集中流动性重新定义了去中心化交易所 (DEX),使流动性提供者 (LPs) 能够在特定价格范围内分配资本,与 V2 相比效率高达 4,000 倍。截至 2025 年 8 月 19 日,Uniswap V3 在 DeFi 中占据主导地位,在以太坊、Polygon 和 Arbitrum 上的交易量超过 1.2 万亿美元,TVL 为 38 亿美元。2025 年 7 月的治理更新加强了对多链的支持,而 Uniswap V4 (2024 年第三季度) 引入了用于自定义逻辑的 hooks,但 V3 仍然是 LP 的主要选择。
本指南提供了对 Uniswap V3 流动性池的清晰、全面的探索,非常适合开发人员、LP 和审计员。主要议题包括:
先决条件:基本的 Solidity 知识、一个测试网钱包(例如 MetaMask)以及对 DeFi 的熟悉程度。图表和代码片段确保清晰度。
Uniswap V3 引入了从 V2 的统一流动性的范式转变:
Uniswap V3 修改了范围限制流动性的恒定乘积公式 ( x * y = k
),使用平方根价格来提高精度。
L = √(x * y)
Δx = L * (1/√P_a - 1/√P_b) (token0)
Δy = L * (√P_b - √P_a) (token1)
√P_a = √0.046 ≈ 0.214, √P_b = √0.054 ≈ 0.232
对于 L = 1000:
Δx = 1000 * (1/0.214 - 1/0.232) ≈ 356 WBTC
Δy = 1000 * (0.232 - 0.214) ≈ 18 ETH√P_a = √0.046 ≈ 0.214, √P_b = √0.054 ≈ 0.232 对于 L = 1000: Δx = 1000 * (1/0.214 - 1/0.232) ≈ 356 WBTC Δy = 1000 * (0.232 - 0.214) ≈ 18 ETH
√P = √(token1 / token0)
sqrtPriceX96 = √P * 2^96
function sqrtPriceX96(price) {
const sqrtP = Math.sqrt(price);
return BigInt(Math.floor(sqrtP * (2 ** 96)));
}
// WBTC/ETH = 0.05
console.log(sqrtPriceX96(0.05).toString()); // ~5268440671087059666841605632
当价格超出范围时,会发生 IL:
IL = 2 * √(P_current / P_initial) / (1 + P_current / P_initial) - 1
function calculateIL(initialPrice, currentPrice) {
const ratio = currentPrice / initialPrice;
const sqrt = Math.sqrt(ratio);
return (2 * sqrt) / (1 + ratio) - 1;
}
// 增加 20%: 0.05 到 0.06
console.log(calculateIL(0.05, 0.06)); // ~0.007 (0.7% 损失)
费用在范围内累积:
Gross Fee = f * V * (L_position / L_total) * E
其中 f = 费用等级(0.3% 为 0.003),V = 交易量,E ≈ 1/范围宽度。
示例:对于 [-8%, +8%](宽度 0.16),E ≈ 6.25;交易量 10 万美元,份额 10%:
Fee = 0.003 * 100,000 * 0.1 * 6.25 ≈ $1.875
净收益:
Net Yield = Gross Fee - IL - Gas Costs
Gas:~0.44 美元(以太坊),0.0075 美元(Polygon,2025 年 8 月)。
Ticks 离散化价格(相差约 0.01%,1.0001^tick):
公式:
Tick = floor(log(P) / log(1.0001))
P = 1.0001^Tick
代码(类似 Solidity):
function priceToTick(uint256 price) pure returns (int24) {
return int24(Math.log(price) / Math.log(1.0001));
}
function tickToPrice(int24 tick) pure returns (uint256) {
return uint256(Math.pow(1.0001, tick));
}
// WBTC/ETH = 0.05
tick = priceToTick(0.05); // ~50600
费用在范围内每次交易时累积:
Fee0 = tradeAmount1 * f * (L_position / L_total)
Fee1 = tradeAmount0 * f * (L_position / L_total)
代码 (Solidity):
function calculateFee(uint256 tradeAmount, uint24 feeTier, uint128 positionL, uint128 totalL) pure returns (uint256) {
return (tradeAmount * feeTier / 1_000_000) * (positionL / totalL);
}
// 10 万美元交易,0.3% 等级,10% 份额
fee = calculateFee(100000, 3000, 1000, 10000); // $30
// 10 万美元交易,0.3% 等级,10% 份额
fee = calculateFee(100000, 3000, 1000, 10000); // $30
通过 NonfungiblePositionManager 管理:
代码:
nonfungiblePositionManager.mint(params); // 返回 tokenId
使用安全功能管理 Sepolia 上的 WBTC/ETH 持仓。
代码:
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;
import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-core/contracts/libraries/TickMath.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
import '@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol';
import '@openzeppelin/contracts/security/ReentrancyGuard.sol';
contract UniswapV3LiquidityManager is IERC721Receiver, ReentrancyGuard {
address public constant WBTC = 0xYOUR_WBTC_ADDRESS; // 替换
address public constant WETH = 0xYOUR_WETH_ADDRESS; // 替换
uint24 public constant poolFee = 3000; // 0.3%
INonfungiblePositionManager public immutable nonfungiblePositionManager;
IUniswapV3Pool public pool;
struct LiquidityPosition {
address owner;
int24 tickLower;
int24 tickUpper;
uint128 liquidity;
uint256 token0Amount;
uint256 token1Amount;
uint256 feeGrowthInside0LastX128;
uint256 feeGrowthInside1LastX128;
}
mapping(uint256 => LiquidityPosition) public positions;
constructor(
INonfungiblePositionManager _nonfungiblePositionManager,
address _pool
) {
nonfungiblePositionManager = _nonfungiblePositionManager;
pool = IUniswapV3Pool(_pool);
}
function onERC721Received(
address operator,
address,
uint256 tokenId,
bytes calldata
) external override returns (bytes4) {
require(msg.sender == address(nonfungiblePositionManager), "Invalid sender");
_createPosition(operator, tokenId);
return this.onERC721Received.selector;
}
function _createPosition(address owner, uint256 tokenId) internal {
(, , address token0, address token1, , int24 tickLower, int24 tickUpper, uint128 liquidity, , , , ) =
nonfungiblePositionManager.positions(tokenId);
positions[tokenId] = LiquidityPosition({
owner: owner,
tickLower: tickLower,
tickUpper: tickUpper,
liquidity: liquidity,
token0Amount: 0,
token1Amount: 0,
feeGrowthInside0LastX128: pool.feeGrowthGlobal0X128(),
feeGrowthInside1LastX128: pool.feeGrowthGlobal1X128()
});
emit PositionCreated(tokenId, owner, tickLower, tickUpper, liquidity);
}
function createPosition(
address token0,
address token1,
int24 tickLower,
int24 tickUpper,
uint256 amount0Desired,
uint256 amount1Desired
) external nonReentrant returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) {
require(tickLower < tickUpper && tickLower >= -887272 && tickUpper <= 887272, "Invalid tick range");
(uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
liquidity = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
TickMath.getSqrtRatioAtTick(tickLower),
TickMath.getSqrtRatioAtTick(tickUpper),
amount0Desired,
amount1Desired
);
require(liquidity > 0, "Zero liquidity");
TransferHelper.safeApprove(token0, address(nonfungiblePositionManager), amount0Desired);
TransferHelper.safeApprove(token1, address(nonfungiblePositionManager), amount1Desired);
try nonfungiblePositionManager.mint(
INonfungiblePositionManager.MintParams({
token0: token0,
token1: token1,
fee: poolFee,
tickLower: tickLower,
tickUpper: tickUpper,
amount0Desired: amount0Desired,
amount1Desired: amount1Desired,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp + 120
})
) returns (uint256 _tokenId, uint128 _liquidity, uint256 _amount0, uint256 _amount1) {
tokenId = _tokenId;
liquidity = _liquidity;
amount0 = _amount0;
amount1 = _amount1;
} catch {
revert("Mint failed");
}
_createPosition(msg.sender, tokenId);
}
function collectFees(uint256 tokenId) external nonReentrant returns (uint256 amount0, uint256 amount1) {
require(positions[tokenId].owner == msg.sender, "Not owner");
(amount0, amount1) = nonfungiblePositionManager.collect(
INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: msg.sender,
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
positions[tokenId].feeGrowthInside0LastX128 = pool.feeGrowthGlobal0X128();
positions[tokenId].feeGrowthInside1LastX128 = pool.feeGrowthGlobal1X128();
emit FeesCollected(tokenId, amount0, amount1);
}
function decreaseLiquidity(uint256 tokenId, uint128 liquidity) external nonReentrant returns (uint256 amount0, uint256 amount1) {
require(positions[tokenId].owner == msg.sender, "Not owner");
require(liquidity <= positions[tokenId].liquidity, "Invalid liquidity");
(amount0, amount1) = nonfungiblePositionManager.decreaseLiquidity(
INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: tokenId,
liquidity: liquidity,
amount0Min: 0,
amount1Min: 0,
deadline: block.timestamp + 120
})
);
positions[tokenId].liquidity -= liquidity;
emit LiquidityChanged(tokenId, liquidity, false);
}
function emergencyWithdraw(uint256 tokenId) external nonReentrant {
require(positions[tokenId].owner == msg.sender, "Not owner");
(uint256 amount0, uint256 amount1) = nonfungiblePositionManager.decreaseLiquidity(
INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: tokenId,
liquidity: positions[tokenId].liquidity,
amount0Min: 0,
amount1Min: 0,
deadline: block.timestamp
})
);
nonfungiblePositionManager.collect(
INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: msg.sender,
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
delete positions[tokenId];
emit PositionCreated(tokenId, msg.sender, 0, 0, 0);
}
event PositionCreated(uint256 indexed tokenId, address indexed owner, int24 tickLower, int24 tickUpper, uint128 liquidity);
event FeesCollected(uint256 indexed tokenId, uint256 amount0, uint256 amount1);
event LiquidityChanged(uint256 indexed tokenId, uint128 liquidityDelta, bool increased);
}import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-core/contracts/libraries/TickMath.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
import '@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/libraries/LiquidityAmounts.sol';
import '@openzeppelin/contracts/security/ReentrancyGuard.sol';
适用于 Sepolia 上的 WBTC/ETH 池(0.3% 费用等级)。
代码:
// SPDX-License-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract WETH is ERC20 {
constructor() ERC20("Wrapped Ether", "WETH") {
_mint(msg.sender, 1000000 * 10**18);
}
}
contract WBTC is ERC20 {
constructor() ERC20("Wrapped Bitcoin", "WBTC") {
_mint(msg.sender, 100000 * 10**8);
}
}
工作流程:
部署 Factory、NonfungiblePositionManager。
池创建器:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
contract PoolCreator {
IUniswapV3Factory public factory;
constructor(address _factory) {
factory = IUniswapV3Factory(_factory);
}
function createAndInitializePool(
address token0,
address token1,
uint24 fee,
uint160 sqrtPriceX96
) external returns (address pool) {
pool = factory.createPool(token0, token1, fee);
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
}
}
工作流程:
const price = 0.05;
const sqrtPrice = Math.sqrt(price) * (2 ** 96);
createAndInitializePool(WBTC, WETH, 3000, sqrtPrice)
。使用上述合约;更新 WBTC/WETH 地址。
代码 (Python):
import numpy as np
import pandas as pd
prices = pd.read_csv('wbtc_eth_prices.csv')
monthly_returns = prices['price'].pct_change(periods=30)
ranges = [0.04, 0.08, 0.18]
for r in ranges:
coverage = np.mean((monthly_returns <= r) & (monthly_returns >= -r))
efficiency = 1 / r
daily_fee = 0.0003 * 0.01
gross_fee = daily_fee * coverage * efficiency * 365
imp_loss = np.mean([2 * np.sqrt(abs(x)) / (1 + np.sqrt(abs(x))) - 1 for x in monthly_returns if abs(x) > r])
print(f"Range ±{r*100}%: Yield={gross_fee:.2%}, IL={imp_loss:.2%}")
输出:
选择 ±8% 以保持平衡。
代码 (Python):
def rebalance_bl_sh(prices, initial_range=0.08, step=0.08):
position = {'tick_lower': -800, 'tick_upper': 800, 'liquidity': 1}
for i, price in enumerate(prices[1:]):
prev_price = prices[i]
if price < 1.0001 ** position['tick_lower']:
position['tick_lower'] += int(np.log(1 + step) / np.log(1.0001))
position['tick_upper'] += int(np.log(1 + step) / np.log(1.0001))
elif price > 1.0001 ** position['tick_upper']:
position['tick_lower'] -= int(np.log(1 + step) / np.log(1.0001))
position['tick_upper'] -= int(np.log(1 + step) / np.log(1.0001))
return position
代码 (JavaScript):
const { ethers } = require('ethers');
const provider = new ethers.providers.JsonRpcProvider('https://eth-sepolia.g.alchemy.com/v2/KEY');
const poolContract = new ethers.Contract(POOL_ADDRESS, poolAbi, provider);
async function getPoolData() {
const slot0 = await poolContract.slot0();
const price = (slot0.sqrtPriceX96 / 2**96) ** 2;
console.log(`Current price: ${price} WBTC/ETH`);
}
代码:
describe("UniswapV3LiquidityManager", () => {
it("creates position", async () => {
const tickLower = -800;
const tickUpper = 800;
const amount0Desired = ethers.utils.parseEther("1");
const amount1Desired = ethers.utils.parseEther("20");
await liquidityManager.createPosition(
WBTC.address,
WETH.address,
tickLower,
tickUpper,
amount0Desired,
amount1Desired
);
const position = await liquidityManager.positions(1);
expect(position.liquidity).to.be.gt(0);
});
});
Uniswap V3 的集中流动性提供了无与伦比的效率,在优化后,WBTC/ETH 等池中的年化收益率可达 8-12%。托管合约通过安全功能(重入保护、紧急提款)简化了管理。通过利用精确的数学、战略性再平衡和 L2(Polygon、Arbitrum),LP 可以在 DeFi 的 3120 亿美元市场中蓬勃发展。在 Sepolia 上进行测试,为主网进行审计,并积极监控以最大限度地提高回报。
- 原文链接: medium.com/@ankitacode11...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!