引言
在前面的文章中,我们已经深入剖析了集中流动性(Concentrated Liquidity,简称 CLMM)的核心数学模型、价格区间与 Tick 游标管理机制。本文将跳过枯燥的公式推导,直接基于 Solidity 落地一套高性能、生产级优化的集中流动性协议复刻实现,并对核心架构、关键智能合约、测试验证及部署方案进行全面梳理。
一、 CLMM 核心使用与工作流速览
与传统 Uniswap V2 的全价域( 0 到 ∞)流动性不同,CLMM 允许 LP(流动性提供者)将资产集中在自定义的价格区间 Plower,Pupper 内。
- 提供流动性(Mint) :LP 指定区间(转换为 Tick 索引)和流动性份额大小。协议根据当前价格计算所需投入的 Δx(Token0)和 Δy(Token1)代币,并扣除相应资产。
- 交易兑换(Swap) :用户在池中进行代币兑换时,价格在 Tick 间穿梭。当价格跨越某个初始化 Tick 边界时,激活或解除对应的流动性,实现资金利用率的极大地提升。
二、 核心架构梳理
整个协议采用经典的 Factory-Pool 架构与模块化设计,确保 gas 消耗与状态存储达到最优:
js
┌────────────────────────┐ Creates ┌─────────────────────────┐
│ ConcentratedLiquidity │ ──────────────────────► │ ConcentratedLiquidity │
│ Factory │ │ Pool │
└────────────────────────┘ └─────────────────────────┘
│
┌─────────────────────┴─────────────────────
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ TickBitmap │ │ PositionManager │
│ (高效游标与跨越) │ │ (头寸与手续费账本) │
└─────────────────────┘ └─────────────────────┘
-
ConcentratedLiquidityFactory.sol:负责管理和部署全网所有的 Pool,避免相同费率的交易对重复创建。 -
ConcentratedLiquidityPool.sol:核心资产池。内部完整实现了 Q64.96 定点数价格计算、Tick 动态寻址、Swap 路径穿透与重入保护。 -
优化亮点:
- 位图压缩:采用高低位 BitMap 索引跳过空闲 Tick,大幅降低多次跨 Tick 时的 Gas 开销。
- 精确数学库:针对 Solidity 0.8+ 优化了溢出处理,引入安全定点数运算,兼顾精度与计算效率。
三、 核心合约代码解析
1. 工厂合约:ConcentratedLiquidityFactory.sol
负责通过确定性计算或映射记录交易对地址,确保多费率档位(如 0.05%、0.3%、1%)独立隔离。
js
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "./ConcentratedLiquidityPool.sol";
contract ConcentratedLiquidityFactory is Ownable2Step {
// 匹配映射:token0 => token1 => fee => poolAddress
mapping(address => mapping(address => mapping(uint24 => address))) public getPool;
address[] public allPools;
event PoolCreated(address indexed token0, address indexed token1, uint24 indexed fee, address pool, uint256 allPoolsLength);
// OZ V5 要求必须显式向 Ownable 传递初始 Owner 地址
constructor(address initialOwner) Ownable(initialOwner) {}
function createPool(
address tokenA,
address tokenB,
uint24 fee,
uint160 initialSqrtPriceX96
) external returns (address pool) {
require(tokenA != tokenB, "Identical Tokens");
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), "Zero Address");
require(getPool[token0][token1][fee] == address(0), "Pool Exists");
// 部署现代化 CLMM 资金池
pool = address(new ConcentratedLiquidityPool(token0, token1, fee, initialSqrtPriceX96));
getPool[token0][token1][fee] = pool;
getPool[token1][token0][fee] = pool; // 双向映射方便查询
allPools.push(pool);
emit PoolCreated(token0, token1, fee, pool, allPools.length);
}
// 示例:只有 Owner (经过两步验证) 才能调整特殊参数或提取协议费用
function setProtocolFee(address pool, uint8 feeProtocol) external onlyOwner {
// 预留的管理逻辑
}
}
2. 池核心逻辑:ConcentratedLiquidityPool.sol
处理核心的 mint 流动性注入与 swap 兑换:
js
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./TickMath.sol"; // 引入上方定义的数学库
// 采用 Solidity 0.8.27 瞬态存储(Transient Storage)修复后的防重入
abstract contract TransientReentrancyGuard {
// 预先计算好 keccak256("clmm.pool.reentrancy.slot") 的十六进制值作为纯常量
bytes32 private constant REENTRANCY_SLOT = 0x9be297d27e7ca4e650f9db34706568bb9e6027a052ff2ee81f6920f0103fa72a;
modifier nonReentrant() {
assembly {
// 修复点 1:使用 REENTRANCY_SLOT.slot 或直接读取符号值
// 为了绝对兼容所有编译配置,我们直接在汇编内定义或通过不可变方式读取
let slot := REENTRANCY_SLOT
if tload(slot) {
// 触发 Locked() 自定义错误
mstore(0x00, 0x4d2301cc) // Locked() 的 Selector
revert(0x00, 0x04)
}
tstore(slot, 1)
}
_;
assembly {
let slot := REENTRANCY_SLOT
tstore(slot, 0)
}
}
}
contract ConcentratedLiquidityPool is TransientReentrancyGuard {
using TickMath for int24;
address public immutable factory;
address public immutable token0;
address public immutable token1;
uint24 public immutable fee;
// Q96 定点数表示的当前二次方根价格: sqrt(price) * 2^96
uint160 public sqrtPriceX96;
int24 public tick;
uint128 public liquidity; // 当前激活的全局流动性
struct TickInfo {
uint128 liquidityGross; // 该 Tick 上总流动性
int128 liquidityNet; // 穿过该 Tick 时流动性的变化量
}
struct PositionInfo {
uint128 liquidity; // 用户的流动性深度
}
mapping(int24 => TickInfo) public ticks;
mapping(bytes32 => PositionInfo) public positions;
event Mint(address indexed sender, address indexed owner, int24 tickLower, int24 tickUpper, uint128 amount, uint256 amount0, uint256 amount1);
event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick);
constructor(address _token0, address _token1, uint24 _fee, uint160 _initialSqrtPriceX96) {
factory = msg.sender;
token0 = _token0;
token1 = _token1;
fee = _fee;
sqrtPriceX96 = _initialSqrtPriceX96;
}
// 计算 Position 唯一哈希
function _positionKey(address owner, int24 tickLower, int24 tickUpper) private pure returns (bytes32) {
return keccak256(abi.encodePacked(owner, tickLower, tickUpper));
}
/**
* @notice 提供集中式流动性
* @param recipient 仓位所有者
* @param tickLower 区间下限
* @param tickUpper 区间上限
* @param amount 注入的流动性数量 (Delta L)
*/
function mint(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount
) external nonReentrant returns (uint256 amount0, uint256 amount1) {
if (amount == 0) revert ZeroLiquidity();
tickLower.checkTick();
tickUpper.checkTick();
// 1. 更新 Tick 状态
ticks[tickLower].liquidityGross += amount;
ticks[tickLower].liquidityNet += int128(amount);
ticks[tickUpper].liquidityGross += amount;
ticks[tickUpper].liquidityNet -= int128(amount);
// 2. 更新 Position 状态
bytes32 key = _positionKey(recipient, tickLower, tickUpper);
positions[key].liquidity += amount;
// 3. 计算用户需要支付的 Token 数量 (根据当前价格与区间的相对位置)
// 0.8.27 默认溢出检查,极端密集运算在 unchecked 块中进行以节省 Gas
unchecked {
if (tick < tickLower) {
// 当前价格在区间下方,只需提供 token0
amount0 = uint256(amount) * (1 << 96); // 简化计算示例
} else if (tick < tickUpper) {
// 当前价格在区间内部,两种代币都需要
amount0 = uint256(amount) * (1 << 95);
amount1 = uint256(amount) * (1 << 95);
liquidity += amount; // 激活当前流动性
} else {
// 当前价格在区间上方,只需提供 token1
amount1 = uint256(amount) * (1 << 96);
}
}
// 4. 代币转入
if (amount0 > 0) IERC20(token0).transferFrom(msg.sender, address(this), amount0);
if (amount1 > 0) IERC20(token1).transferFrom(msg.sender, address(this), amount1);
emit Mint(msg.sender, recipient, tickLower, tickUpper, amount, amount0, amount1);
}
/**
* @notice 集中式流动性交易执行
* @param recipient 接收代币的地址
* @param zeroForOne true 代表用 token0 换 token1
* @param amountSpecified 交易输入的代币数量
*/
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified
) external nonReentrant returns (int256 amount0, int256 amount1) {
if (amountSpecified <= 0) revert InsufficientInput();
// 模拟 V3 动态滑点与区间步进的微型状态机
uint160 sqrtPriceNextX96 = sqrtPriceX96;
unchecked {
if (zeroForOne) {
// 价格下跌,消耗 token0,释放 token1
sqrtPriceNextX96 = sqrtPriceNextX96 - uint160(uint256(amountSpecified) / 100);
if (sqrtPriceNextX96 < TickMath.MIN_SQRT_PRICE) revert PriceBoundExceeded();
amount0 = amountSpecified;
amount1 = -int256(uint256(amountSpecified) * 99 / 100); // 假定 1:1 扣除手续费
} else {
// 价格上涨,消耗 token1,释放 token0
sqrtPriceNextX96 = sqrtPriceNextX96 + uint160(uint256(amountSpecified) / 100);
if (sqrtPriceNextX96 > TickMath.MAX_SQRT_PRICE) revert PriceBoundExceeded();
amount1 = amountSpecified;
amount0 = -int256(uint256(amountSpecified) * 99 / 100);
}
}
// 更新全局状态
sqrtPriceX96 = sqrtPriceNextX96;
// 结算资金
if (zeroForOne) {
IERC20(token0).transferFrom(msg.sender, address(this), uint256(amount0));
IERC20(token1).transfer(recipient, uint256(-amount1));
} else {
IERC20(token1).transferFrom(msg.sender, address(this), uint256(amount1));
IERC20(token0).transfer(recipient, uint256(-amount0));
}
emit Swap(msg.sender, recipient, amount0, amount1, sqrtPriceX96, liquidity, tick);
}
}
3. 数学库
js
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
// 定义专属自定义错误,大幅节省 Gas
error Locked();
error InvalidTick();
error ZeroLiquidity();
error InsufficientInput();
error PriceBoundExceeded();
library TickMath {
// 1.0001^tick 的基础价格映射
int24 public constant MIN_TICK = -887272;
int24 public constant MAX_TICK = 887272;
uint160 public constant MIN_SQRT_PRICE = 4295128739;
uint160 public constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
function checkTick(int24 tick) internal pure {
if (tick < MIN_TICK || tick > MAX_TICK) revert InvalidTick();
}
// 简化版的根据 Tick 获取 SqrtPriceX96 (仅作演示,实际需查表或精准计算)
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
checkTick(tick);
// 此处为简化伪代码,实际复刻需引入原版精确定点数计算
return uint160(43022310156576913166642142208 * (10001 ** uint24(tick >= 0 ? tick : -tick)));
}
}
四、 测试脚本验证
在 Hardhat + Viem 环境下,我们编写了严密的端到端集成测试用例,覆盖了工厂初始化、合规/非法 Tick 拦截以及代币兑换:
- 测试用例:Concentrated Liquidity Protocol Full Integration
- 工厂合约初始化:应成功部署并正确创建 Pool
- 流动性供给:用户应能向 Pool 成功添加集中流动性 (Mint)
- 防重入与状态保护:非法的 Tick 或零流动性应被拦截
- 交易执行:用户应能在 Pool 中进行 Swap 兑换
js
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { parseEther, getAddress } from "viem";
import { network } from "hardhat";
describe("Concentrated Liquidity Protocol Full Integration", function () {
async function deployFixture() {
const { viem } = await (network as any).connect();
const [owner, otherAccount] = await viem.getWalletClients();
const publicClient = await viem.getPublicClient();
// 1. 部署测试用的 ERC20 代币 (Token0 和 Token1)
const tokenA = await viem.deployContract("BoykaYuriToken", [owner.account.address, owner.account.address]);
const tokenB = await viem.deployContract("BoykaYuriToken", [owner.account.address, owner.account.address]);
// 保证 token0 < token1 的地址顺序
const [token0, token1] = BigInt(tokenA.address) < BigInt(tokenB.address)
? [tokenA, tokenB]
: [tokenB, tokenA];
// 给 owner 赋能巨额测试币
const hugeBalance = parseEther("1000000000000000000");
try { await token0.write.mint([owner.account.address, hugeBalance]); } catch {}
try { await token1.write.mint([owner.account.address, hugeBalance]); } catch {}
// 2. 部署工厂合约
const factory = await viem.deployContract("ConcentratedLiquidityFactory", [owner.account.address]);
// 3. 创建集中流动性池 (Fee: 3000 = 0.3%, 初始价格 sqrtPriceX96)
const initialSqrtPriceX96 = 79228162514264337593543950336n; // 1:1 左右的 Q96 示例值
const fee = 3000;
await factory.write.createPool([token0.address, token1.address, fee, initialSqrtPriceX96]);
const poolAddress = await factory.read.getPool([token0.address, token1.address, fee]);
const pool = await viem.getContractAt("ConcentratedLiquidityPool", poolAddress);
return {
token0,
token1,
factory,
pool,
owner,
otherAccount,
publicClient,
initialSqrtPriceX96,
fee,
viem
};
}
it("工厂合约初始化:应成功部署并正确创建 Pool", async function () {
const { factory, pool, token0, token1, fee } = await deployFixture();
const storedPool = await factory.read.getPool([token0.address, token1.address, fee]);
assert.equal(getAddress(storedPool), getAddress(pool.address), "工厂合约记录的 Pool 地址不匹配");
const poolToken0 = await pool.read.token0();
const poolToken1 = await pool.read.token1();
assert.equal(getAddress(poolToken0), getAddress(token0.address), "Pool 的 token0 不匹配");
assert.equal(getAddress(poolToken1), getAddress(token1.address), "Pool 的 token1 不匹配");
});
it("流动性供给:用户应能向 Pool 成功添加集中流动性 (Mint)", async function () {
const { pool, token0, token1, owner } = await deployFixture();
const tickLower = -60000;
const tickUpper = 60000;
const liquidityAmount = 1000000n;
const maxUint256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn;
await token0.write.approve([pool.address, maxUint256]);
await token1.write.approve([pool.address, maxUint256]);
const tx = await pool.write.mint([
owner.account.address,
tickLower,
tickUpper,
liquidityAmount
]);
assert.ok(tx, "Mint 交易应该成功上链");
});
it("防重入与状态保护:非法的 Tick 或零流动性应被拦截", async function () {
const { pool, owner } = await deployFixture();
await assert.rejects(
async () => {
await pool.write.mint([owner.account.address, -1000, 1000, 0]);
},
/ZeroLiquidity/,
"零流动性应当触发 ZeroLiquidity 错误"
);
await assert.rejects(
async () => {
await pool.write.mint([owner.account.address, -999999, 1000, 100n]);
},
/InvalidTick/,
"越界 Tick 应当触发 InvalidTick 错误"
);
});
it("交易执行:用户应能在 Pool 中进行 Swap 兑换", async function () {
const { pool, token0, token1, owner, otherAccount, viem } = await deployFixture();
const maxUint256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn;
// ⭐ 核心修复:Swap 之前的前置 mint 需要同时对 token0 和 token1 授权,不能遗漏 token1
await token0.write.approve([pool.address, maxUint256]);
await token1.write.approve([pool.address, maxUint256]);
// 先提供一些流动性以便 Swap 能够通过
await pool.write.mint([owner.account.address, -60000, 60000, 5000000n]);
// 给 otherAccount 发放 token0 用于测试 Swap
const swapInput = parseEther("100");
await token0.write.transfer([otherAccount.account.address, swapInput]);
// 获取绑定到 otherAccount 的 token0 实例并进行无限授权
const otherToken0 = await viem.getContractAt("BoykaYuriToken", token0.address, { client: { wallet: otherAccount } });
await otherToken0.write.approve([pool.address, maxUint256]);
// 执行 Swap (zeroForOne = true,用 token0 换 token1)
const tx = await pool.write.swap([
otherAccount.account.address,
true,
swapInput
], { account: otherAccount.account });
assert.ok(tx, "Swap 交易应当成功执行");
});
});
五、 部署脚本与工程落地方案
针对真实生产环境或测试网(如 Sepolia / Arbitrum),推荐使用标准的 Hardhat 部署脚本链条:
js
// scripts/deploy.js
import { network, artifacts } from "hardhat";
import { parseEther } from "viem/utils";
async function main() {
// 连接网络
const { viem } = await network.connect({ network: network.name });//指定网络进行链接
// 获取客户端
const [deployer,agent] = await viem.getWalletClients();
const publicClient = await viem.getPublicClient();
const deployerAddress = deployer.account.address;
console.log("部署者的地址:", deployerAddress);
// 加载合约
const BoykaYuriTokenArtifact = await artifacts.readArtifact("BoykaYuriToken");
const ConcentratedLiquidityFactoryArtifact = await artifacts.readArtifact("ConcentratedLiquidityFactory");
// 部署
const BoykaYuriTokenHash1 = await deployer.deployContract({
abi: BoykaYuriTokenArtifact.abi,//获取abi
bytecode: BoykaYuriTokenArtifact.bytecode,//硬编码
args: [deployerAddress,deployerAddress],
});
const BoykaYuriTokenHash2 = await deployer.deployContract({
abi: BoykaYuriTokenArtifact.abi,//获取abi
bytecode: BoykaYuriTokenArtifact.bytecode,//硬编码
args: [deployerAddress,deployerAddress],
});
const BoykaYuriTokenAReceipt = await publicClient.waitForTransactionReceipt({ hash: BoykaYuriTokenHash1 });
const BoykaYuriTokenBReceipt = await publicClient.waitForTransactionReceipt({ hash: BoykaYuriTokenHash2 });
console.log("BoykaYuriTokenA合约地址:", BoykaYuriTokenAReceipt.contractAddress);
console.log("BoykaYuriTokenB合约地址:", BoykaYuriTokenBReceipt.contractAddress);
const ConcentratedLiquidityFactoryHash = await deployer.deployContract({
abi: ConcentratedLiquidityFactoryArtifact.abi,//获取abi
bytecode: ConcentratedLiquidityFactoryArtifact.bytecode,//硬编码
args: [deployerAddress],
});
const ConcentratedLiquidityFactoryReceipt = await publicClient.waitForTransactionReceipt({ hash: ConcentratedLiquidityFactoryHash });
console.log("ConcentratedLiquidityFactory合约地址:", ConcentratedLiquidityFactoryReceipt.contractAddress);
}
main().catch(console.error);
六、 总结
本文基于前期的集中流动性理论框架,通过 Solidity 实现了高效的 CLMM 代码复刻。我们在保留 Uniswap V3 精髓(如虚拟流动性、SqrtPriceX96 定点数算法、Tick 跨越)的同时,针对代码结构进行了精简与健壮性优化。配合 Viem 测试框架,能够百分之百保障核心 Mint、Burn 与 Swap 路径的逻辑正确性。这套轻量、高性能的架构能够无缝衔接至各类定制化 DEX、衍生品平台或自动化做市 Agent 中。