一、 引言与架构设计
在传统 AMM (Automated Market Maker) 协议(如 Uniswap V2)中,流动性均匀分布在整个价格区间 ,导致资金利用率低下。随后的 集中式流动性(Concentrated Liquidity,如 Uniswap V3)引入了价格区间(Ticks),允许LP将流动性集中在特定价格范围内,大幅提升了资金效率,但也带来了复杂的 Tick 管理与手续费燃烧问题。
而 离散流动性协议(Discrete Liquidity Protocol,参考 Trader Joe Liquidity Book 核心思想)将价格拆分为一个个离散的 Bin(价格箱)。每个 Bin 代表一个固定的价格点(基于 的阶梯),资产以离散的仓位代币(基于 ERC1155)进行管理。
核心架构组成
-
LBMath.sol(定点数与数学库) :采用无符号Q64.128定点数,实现高效的二进制幂算法计算 ,保障高精度与常数 Gas 消耗。 -
LBBitmap.sol(位图路由与汇编优化) :利用二维坐标(Bucket + Bit)与纯 Yul 汇编实现的最高有效位(MSB)查找,实现 复杂度的跨箱跳跃检索。 -
LBPool.sol(流动性全生命周期与兑换清算核心) :- 采用 OpenZeppelin V5 的
ERC1155实现仓位代币化。 - 严格的价格边界防范(活跃箱、上方纯 X、下方纯 Y)。
- 跨 Bin 清算兑换引擎(
swapXToY/swapYToX),完美集成位图跳跃与向上取整安全防线。
- 采用 OpenZeppelin V5 的
二、 核心智能合约代码
1. 数学库 LBMath.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
/**
* @title 工业级离散流动性数学库
* @notice 采用无符号 Q64.128 定点数,提供高精度、常数 Gas 的价格与数量计算
*/
library LBMath {
// 2^128,作为 Q64.128 的定点数单位 (1.0)
uint256 internal constant SCALE = 1 << 128;
// 假设固定 Bin Step 为 1bp (0.01%),基底为 1.0001 的 Q64.128 形式
// 1.0001 * 2^128 = 340319237610600115049449339474751433221
uint256 internal constant BASE_HEX = 0x10001a413c2f0f8ffffffffffffffff;
/**
* @notice 根据 Bin ID 计算 Q64.128 精度价格: P = (1.0001)^binId
* @dev 使用二进制幂算法,时间复杂度 O(log N),最大执行 24 次位运算
* @param binId 价格箱ID(带符号)
* @return priceX128 Q64.128 定点数价格
*/
function getPriceFromId(int24 binId) internal pure returns (uint256 priceX128) {
unchecked {
uint256 absId = binId < 0 ? uint256(-int256(binId)) : uint256(int256(binId));
require(absId <= 8388607, "LBMath: ID_OVERFLOW"); // int24 最大值
// 初始化基底与结果
uint256 base = BASE_HEX;
priceX128 = SCALE;
// 二进制幂展开
if (absId & 0x1 != 0) priceX128 = (priceX128 * base) >> 128;
if (absId & 0x2 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x4 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x8 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x10 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x20 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x40 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x80 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x100 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x200 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x400 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x800 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x1000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x2000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x4000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x8000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x10000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x20000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x40000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x80000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x100000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x200000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x400000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
if (absId & 0x800000 != 0) priceX128 = (priceX128 * (base = (base * base) >> 128)) >> 128;
// 如果 binId 为负,价格取倒数: 1 / P
if (binId < 0) {
priceX128 = (SCALE * SCALE) / priceX128;
}
require(priceX128 > 0, "LBMath: PRICE_UNDERFLOW");
}
}
/**
* @notice 安全乘法并向上取整 (Ceil),用于协议向用户扣款,防范闪电贷微量攻击
*/
function mulDivRoundUp(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
uint256 product = x * y;
if (product == 0) return 0;
result = (product - 1) / denominator + 1;
}
}
}
2. 位图路由库 LBBitmap.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
/**
* @title 工业级高性能流动性位图路由(语法校准版)
* @notice 运用纯 EVM 原生汇编算法实现 O(1) 复杂度的跳跃式活跃 Bin 查找
*/
library LBBitmap {
/**
* @notice 将 Bin ID 拆分为位图的二维坐标
*/
function getBucketAndBit(int24 binId) internal pure returns (int16 bucket, uint8 bit) {
unchecked {
bucket = int16(binId >> 8);
bit = uint8(uint24(binId & 0xFF));
}
}
/**
* @notice 在位图中激活或关闭某个价格箱
*/
function flipBit(mapping(int16 => uint256) storage self, int24 binId) internal {
(int16 bucket, uint8 bit) = getBucketAndBit(binId);
self[bucket] ^= (1 << bit);
}
/**
* @notice 向下(价格变低方向)寻找下一个包含流动性的有效 Bin ID
* @param current 当前的 Bin ID
* @return nextId 找到的最近的有效 Bin ID
* @return found 是否找到
*/
function findNextBinBelow(
mapping(int16 => uint256) storage self,
int24 current
) internal view returns (int24 nextId, bool found) {
unchecked {
(int16 bucket, uint8 bit) = getBucketAndBit(current);
// 掩码:只保留当前位下方的所有位(将当前及上方置 0)
uint256 mask = (1 << bit) - 1;
uint256 masked = self[bucket] & mask;
if (masked != 0) {
uint8 highestBit = getMostSignificantBit(masked);
nextId = (int24(bucket) << 8) | int24(uint24(highestBit));
found = true;
return (nextId, found);
}
// 如果当前 Bucket 没找到,向更低的 Bucket 检索(最多向下检索 4 个分箱组)
for (int16 b = bucket - 1; b >= bucket - 4; b--) {
uint256 val = self[b];
if (val != 0) {
uint8 highestBit = getMostSignificantBit(val);
nextId = (int24(b) << 8) | int24(uint24(highestBit));
found = true;
return (nextId, found);
}
}
nextId = 0;
found = false;
return (nextId, found);
}
}
/**
* @notice 纯 Yul 汇编实现的高性能最高有效位(MSB)查找算法
* @dev 通过二分搜索在恒定 Gas 内代替 clz 缺陷
*/
function getMostSignificantBit(uint256 x) internal pure returns (uint8 r) {
require(x > 0, "LBBitmap: ZERO_INPUT");
assembly {
// 如果 x 大于等于 2^128,说明最高有效位在高 128 位
if iszero(lt(x, 0x100000000000000000000000000000000)) {
x := shr(128, x)
r := or(r, 128)
}
// 依次对剩余区间进行二分探测
if iszero(lt(x, 0x10000000000000001)) {
x := shr(64, x)
r := or(r, 64)
}
if iszero(lt(x, 0x100000001)) {
x := shr(32, x)
r := or(r, 32)
}
if iszero(lt(x, 0x10001)) {
x := shr(16, x)
r := or(r, 16)
}
if iszero(lt(x, 0x101)) {
x := shr(8, x)
r := or(r, 8)
}
if iszero(lt(x, 0x11)) {
x := shr(4, x)
r := or(r, 4)
}
if iszero(lt(x, 0x5)) {
x := shr(2, x)
r := or(r, 2)
}
if iszero(lt(x, 0x2)) {
r := or(r, 1)
}
}
}
}
3. 主池合约 LBPool.sol(基于 OpenZeppelin V5)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {LBMath} from "./LBMath.sol";
import {LBBitmap} from "./LBBitmap.sol";
/**
* @title 工业级离散集中流动性池 (上篇:流动性全生命周期管理)
* @notice 基于 OpenZeppelin V5 ERC1155 实现仓位代币化
*/
contract LBPool is ERC1155, ReentrancyGuard {
using SafeERC20 for IERC20;
using LBBitmap for mapping(int16 => uint256);
// 存储槽打包(Slot Packing):控制单个 Bin 的存储成本
struct Bin {
uint128 reserveX; // 代币 X 的库存余额
uint128 reserveY; // 代币 Y 的库存余额
uint256 totalSupply; // 该 Bin 铸造出的 ERC1155 总流动性份额
}
// 核心代币对偶
address public immutable tokenX;
address public immutable tokenY;
// 全局状态
int24 public activeId; // 当前市场价格所处的活跃 Bin ID
mapping(int24 => Bin) public bins; // Bin ID => 离散箱账目
mapping(int16 => uint256) internal binBitmap; // 高效位图路由表
// 工业级业务事件
event LiquidityAdded(address indexed provider, int24 indexed binId, uint256 amountX, uint256 amountY, uint256 shares);
event LiquidityRemoved(address indexed provider, int24 indexed binId, uint256 amountX, uint256 amountY, uint256 shares);
constructor(
address _tokenX,
address _tokenY,
int24 _initialActiveId
) ERC1155("") {
require(_tokenX < _tokenY, "LBPool: INVALID_PAIR_ORDER");
tokenX = _tokenX;
tokenY = _tokenY;
activeId = _initialActiveId;
}
/**
* @notice 向指定的 Price Bin 注入集中流动性
* @param binId 目标注入的价格箱 ID
* @param amountXMax 预备投入的 X 代币最大值
* @param amountYMax 预备投入的 Y 代币最大值
* @return shares 最终对应该 Bin ID 铸造出来的 ERC1155 流动性凭证数量
*/
function deposit(
int24 binId,
uint256 amountXMax,
uint256 amountYMax
) external nonReentrant returns (uint256 shares) {
// 1. 工业级价格边界防范
if (binId < activeId) require(amountXMax == 0, "LBPool: BELOW_ACTIVE_ONLY_Y");
if (binId > activeId) require(amountYMax == 0, "LBPool: ABOVE_ACTIVE_ONLY_X");
Bin storage bin = bins[binId];
uint256 priceX128 = LBMath.getPriceFromId(binId);
uint256 amountX;
uint256 amountY;
// 2. 根据当前活跃状态,严格按比例计算流动性份额(防范先跑攻击引起的比例失衡)
if (binId == activeId) {
// 在活跃箱中,若已有流动性,需严格按现有储备比例注入
if (bin.totalSupply > 0) {
// 计算依据:amountX / bin.reserveX == amountY / bin.reserveY
// 生产环境通常由 Router 预先算好,这里实施强校准
amountX = amountXMax;
amountY = (amountX * bin.reserveY) / bin.reserveX;
require(amountY <= amountYMax, "LBPool: INSUFFICIENT_Y_MAX");
shares = (amountX * bin.totalSupply) / bin.reserveX;
} else {
// 首次初始化活跃箱
amountX = amountXMax;
amountY = amountYMax;
// 将 Y 转换成 X 等价面,共同决定初始总份额 (使用 Q128 价格计算)
shares = amountX + ((amountY * (1 << 128)) / priceX128);
}
} else if (binId > activeId) {
// 活跃箱上方:只接受 X 代币
amountX = amountXMax;
shares = amountX; // 纯 X 状态下,份额与数量 1:1 映射
} else {
// 活跃箱下方:只接受 Y 代币
amountY = amountYMax;
// 为了保持各个 Bin 份额资产尺度的横向一致性,Y 存入时通常转换为主币面额
shares = (amountY * (1 << 128)) / priceX128;
}
require(shares > 0, "LBPool: ZERO_SHARES_MINTED");
// 3. 更新位图索引:如果该 Bin 原本为空,将其标记为“有资产”(触发 0 -> 1)
if (bin.totalSupply == 0) {
binBitmap.flipBit(binId);
}
// 4. 严防溢出并更新底层储备
unchecked {
bin.reserveX = uint128(uint256(bin.reserveX) + amountX);
bin.reserveY = uint128(uint256(bin.reserveY) + amountY);
bin.totalSupply += shares;
}
// 5. 转移资产并铸造 ERC1155 凭证(使用 uint256(uint24) 作为独立 Token ID)
if (amountX > 0) IERC20(tokenX).safeTransferFrom(msg.sender, address(this), amountX);
if (amountY > 0) IERC20(tokenY).safeTransferFrom(msg.sender, address(this), amountY);
_mint(msg.sender, uint256(uint24(binId)), shares, "");
emit LiquidityAdded(msg.sender, binId, amountX, amountY, shares);
}
/**
* @notice 销毁凭证,按实时资产比例赎回流动性
* @param binId 赎回的价格箱 ID
* @param shares 准备销毁的 ERC1155 流动性数量
*/
function withdraw(int24 binId, uint256 shares) external nonReentrant returns (uint256 amountX, uint256 amountY) {
require(shares > 0, "LBPool: ZERO_SHARES_BURNED");
Bin storage bin = bins[binId];
// 1. 严格计算按资产比例应得的数量
amountX = (uint256(bin.reserveX) * shares) / bin.totalSupply;
amountY = (uint256(bin.reserveY) * shares) / bin.totalSupply;
// 2. 更新合约底层账目
unchecked {
bin.reserveX -= uint128(amountX);
bin.reserveY -= uint128(amountY);
bin.totalSupply -= shares;
}
// 3. 更新位图索引:如果流动性被抽干,将其标记为“空”(触发 1 -> 0)
if (bin.totalSupply == 0) {
binBitmap.flipBit(binId);
}
// 4. 销毁凭证并返还真实代币
_burn(msg.sender, uint256(uint24(binId)), shares);
if (amountX > 0) IERC20(tokenX).safeTransfer(msg.sender, amountX);
if (amountY > 0) IERC20(tokenY).safeTransfer(msg.sender, amountY);
emit LiquidityRemoved(msg.sender, binId, amountX, amountY, shares);
}
/**
* @notice 离散集中流动性核心兑换函数(单向:仅展示 X 换 Y 的清算引擎,Y 换 X 逻辑镜像对称)
* @dev 完美集成了位图跳跃式搜索与严格的向上取整舍入防线
* @param amountXIn 用户输入的代币 X 的绝对数量
* @return amountYOut 最终输出给用户的代币 Y 的绝对数量
*/
function swapXToY(uint256 amountXIn) external nonReentrant returns (uint256 amountYOut) {
require(amountXIn > 0, "LBPool: ZERO_INPUT");
// 1. 乐观转账安全保障:首先将用户的代币 X 安全划转至本池
IERC20(tokenX).safeTransferFrom(msg.sender, address(this), amountXIn);
uint256 remainingX = amountXIn;
int24 currentId = activeId;
// 2. 跨 Bin 循环清算,直至输入金额耗尽或流动性枯竭
while (remainingX > 0) {
Bin storage bin = bins[currentId];
// 【工业级性能优化】如果当前 Bin 没有可供兑换的 Y,利用位图加速跨越空白价格区
if (bin.reserveY == 0) {
(int24 nextId, bool found) = binBitmap.findNextBinBelow(currentId);
if (!found) {
// 价格下方的所有 Bin 均已干涸,终止清算(剩余部分作为滑点未完全成交或回滚)
break;
}
currentId = nextId;
continue;
}
// 获取当前 Bin 在 Q64.128 精度下的绝对价格 P
uint256 priceX128 = LBMath.getPriceFromId(currentId);
// 计算当前价格箱能吃下的最大 X 输入: MaxX = (ReserveY * 2^128) / P
// 为防池子吃亏,对此最大承接能力进行向下取整
uint256 maxXInThisBin = (uint256(bin.reserveY) << 128) / priceX128;
if (remainingX >= maxXInThisBin) {
// 情况 A:当前 Bin 的流动性将被完全抽干(吃满该箱)
uint256 yAllocated = bin.reserveY;
amountYOut += yAllocated;
remainingX -= maxXInThisBin;
// 账目更新
unchecked {
bin.reserveX = uint128(uint256(bin.reserveX) + maxXInThisBin);
}
bin.reserveY = 0;
// 价格被彻底砸穿,推向下一个更低的 Bin ID
currentId--;
} else {
// 情况 B:当前 Bin 的流动性足够承接剩余的全部输入(交易在此箱内终结)
// 核心安全计算:根据 X 算出应得的 Y。 Y = (RemainingX * P) / 2^128
// 【核心防线】给用户的钱必须向下取整截断!
uint256 yAllocated = (remainingX * priceX128) >> 128;
// 【安全双检】再次确保扣减后池子里的储备不会因精度问题产生负数
if (yAllocated > bin.reserveY) {
yAllocated = bin.reserveY;
}
amountYOut += yAllocated;
unchecked {
bin.reserveX = uint128(uint256(bin.reserveX) + remainingX);
bin.reserveY = uint128(uint256(bin.reserveY) - yAllocated);
}
remainingX = 0; // 输入清零,兑换圆满结束
}
}
// 3. 资金链未完全耗尽,说明深度不足,执行滑点保护拦截
require(remainingX == 0, "LBPool: INSUFFICIENT_LIQUIDITY_DEPTH");
require(amountYOut > 0, "LBPool: ZERO_OUTPUT_BURN");
// 4. 更新全局资产负债表的活跃价格指针
if (activeId != currentId) {
activeId = currentId;
}
// 5. 最终将清算产出的 Y 代币发放给兑换发起者
IERC20(tokenY).safeTransfer(msg.sender, amountYOut);
emit SwapEvent(msg.sender, amountXIn, amountYOut, currentId);
}
// 辅助事件定义
event SwapEvent(address indexed sender, uint256 amountIn, uint256 amountOut, int24 finalActiveId);
}
三、 测试脚本 test/LB.ts
通过 viem 与 node:test 实现对合约全生命周期的自动化测试验证:
- LBPool & LBMath & LBBitmap Full Integration
- 初始化验证:Token 排序与初始 ActiveId 应正确
- 流动性注入:在活跃箱(ActiveId = 0)首次成功添加双代币流动性
- 单边流动性注入:在活跃箱上方(binId > 0)仅允许存入 X 代币
- 流动性赎回:销毁 ERC1155 凭证按比例取回底层资产
- 核心兑换引擎:执行 swapXToY 跨箱清算并正确触发位图跳跃
- 异常拦截:输入为 0 或滑点深度不足时应当回滚
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { parseEther, getAddress } from "viem";
import { network } from "hardhat";
describe("LBPool & LBMath & LBBitmap 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. 部署两个 Mock ERC20 代币作为流动性对 (确保 tokenX < tokenY 排序)
const tokenA = await viem.deployContract("MockERC20", ["Token X", "TKX", 18]);
const tokenB = await viem.deployContract("MockERC20", ["Token Y", "TKY", 18]);
let tokenX = tokenA;
let tokenY = tokenB;
if (tokenA.address.toLowerCase() > tokenB.address.toLowerCase()) {
tokenX = tokenB;
tokenY = tokenA;
}
// 2. 部署 LBPool 合约,初始 ActiveId 设为 0
const initialActiveId = 0;
const lbPool = await viem.deployContract("LBPool", [
tokenX.address,
tokenY.address,
initialActiveId,
]);
const mintAmount = parseEther("1000000");
await tokenA.write.mint([owner.account.address, mintAmount]);
await tokenB.write.mint([owner.account.address, mintAmount]);
await tokenA.write.mint([otherAccount.account.address, mintAmount]);
await tokenB.write.mint([otherAccount.account.address, mintAmount]);
await tokenX.write.approve([lbPool.address, mintAmount], { account: owner.account });
await tokenY.write.approve([lbPool.address, mintAmount], { account: owner.account });
await tokenX.write.approve([lbPool.address, mintAmount], { account: otherAccount.account });
await tokenY.write.approve([lbPool.address, mintAmount], { account: otherAccount.account });
return {
tokenX,
tokenY,
lbPool,
owner,
otherAccount,
publicClient,
initialActiveId,
};
}
it("初始化验证:Token 排序与初始 ActiveId 应正确", async function () {
const { tokenX, tokenY, lbPool, initialActiveId } = await deployFixture();
const txX = await lbPool.read.tokenX();
const txY = await lbPool.read.tokenY();
const activeId = await lbPool.read.activeId();
assert.equal(getAddress(txX), getAddress(tokenX.address), "tokenX 地址不匹配");
assert.equal(getAddress(txY), getAddress(tokenY.address), "tokenY 地址不匹配");
assert.equal(activeId, initialActiveId, "初始 activeId 不匹配");
});
it("流动性注入:在活跃箱(ActiveId = 0)首次成功添加双代币流动性", async function () {
const { lbPool, owner, initialActiveId } = await deployFixture();
const amountXMax = parseEther("100");
const amountYMax = parseEther("100");
await lbPool.write.deposit([initialActiveId, amountXMax, amountYMax], {
account: owner.account,
});
// viem 读取 struct 返回对象或数组,同时兼容两者取值
const bin: any = await lbPool.read.bins([initialActiveId]);
const totalSupply = Array.isArray(bin) ? bin[2] : bin.totalSupply;
const reserveX = Array.isArray(bin) ? bin[0] : bin.reserveX;
const reserveY = Array.isArray(bin) ? bin[1] : bin.reserveY;
assert.equal(totalSupply > 0n, true, "应成功铸造出流动性份额 (totalSupply)");
assert.equal(reserveX > 0n, true, "应记录 X 储备");
assert.equal(reserveY > 0n, true, "应记录 Y 储备");
const tokenId = BigInt(initialActiveId & 0xffffff);
const balance = await lbPool.read.balanceOf([owner.account.address, tokenId]);
assert.equal(balance, totalSupply, "ERC1155 份额应与 totalSupply 一致");
});
it("单边流动性注入:在活跃箱上方(binId > 0)仅允许存入 X 代币", async function () {
const { lbPool, owner } = await deployFixture();
const upperBinId = 10;
await assert.rejects(
async () => {
await lbPool.write.deposit([upperBinId, parseEther("100"), parseEther("100")], {
account: owner.account,
});
},
/LBPool: ABOVE_ACTIVE_ONLY_X/,
"上方箱子应拒绝 Y 代币输入"
);
await lbPool.write.deposit([upperBinId, parseEther("100"), 0n], {
account: owner.account,
});
const bin: any = await lbPool.read.bins([upperBinId]);
const reserveX = Array.isArray(bin) ? bin[0] : bin.reserveX;
const reserveY = Array.isArray(bin) ? bin[1] : bin.reserveY;
assert.equal(reserveX, parseEther("100"), "上方箱子应正确存入 X 储备");
assert.equal(reserveY, 0n, "上方箱子 Y 储备应为 0");
});
it("流动性赎回:销毁 ERC1155 凭证按比例取回底层资产", async function () {
const { lbPool, owner, initialActiveId } = await deployFixture();
const amountXMax = parseEther("100");
const amountYMax = parseEther("100");
await lbPool.write.deposit([initialActiveId, amountXMax, amountYMax], {
account: owner.account,
});
const tokenId = BigInt(initialActiveId & 0xffffff);
const sharesBefore = await lbPool.read.balanceOf([owner.account.address, tokenId]);
const withdrawShares = sharesBefore / 2n;
await lbPool.write.withdraw([initialActiveId, withdrawShares], {
account: owner.account,
});
const sharesAfter = await lbPool.read.balanceOf([owner.account.address, tokenId]);
assert.equal(sharesAfter, sharesBefore - withdrawShares, "凭证数量应正确扣减");
});
it("核心兑换引擎:执行 swapXToY 跨箱清算并正确触发位图跳跃", async function () {
const { lbPool, owner } = await deployFixture();
const targetBin = 0;
await lbPool.write.deposit([targetBin, 0n, parseEther("500")], {
account: owner.account,
});
const amountIn = parseEther("10");
const amountYOut = await lbPool.read.swapXToY([amountIn], {
account: owner.account,
});
assert.ok(amountYOut > 0n, "兑换应成功产出 Y 代币");
});
it("异常拦截:输入为 0 或滑点深度不足时应当回滚", async function () {
const { lbPool, owner } = await deployFixture();
await assert.rejects(
async () => {
await lbPool.write.swapXToY([0n], { account: owner.account });
},
/LBPool: ZERO_INPUT/,
"零输入应被拒绝"
);
await assert.rejects(
async () => {
await lbPool.write.swapXToY([parseEther("1000")], { account: owner.account });
},
/LBPool: INSUFFICIENT_LIQUIDITY_DEPTH/,
"流动性深度不足时应报错"
);
});
});
四、部署脚本
// scripts/deploy.js
import { network, artifacts } from "hardhat";
async function main() {
// 连接网络
const { viem } = await network.connect({ network: network.name });//指定网络进行链接
const initialActiveId = 0;
// 获取客户端
const [deployer, user, treasury] = await viem.getWalletClients();
const publicClient = await viem.getPublicClient();
const deployerAddress = deployer.account.address;
console.log("部署者的地址:", deployerAddress);
// 加载合约
const MockERC20Artifact = await artifacts.readArtifact("MockERC20");
const LBPoolArtifact = await artifacts.readArtifact("LBPool");
// 部署(构造函数参数:recipient, initialOwner)
const MockERC20Hash1 = await deployer.deployContract({
abi: MockERC20Artifact.abi,//获取abi
bytecode: MockERC20Artifact.bytecode,//硬编码
args: ["Token X", "TKX", 18n],
});
const MockERC20Hash2 = await deployer.deployContract({
abi: MockERC20Artifact.abi,//获取abi
bytecode: MockERC20Artifact.bytecode,//硬编码
args: ["Token y", "TKY", 18n],
});
// 等待确认并打印地址
const TKXReceipt = await publicClient.waitForTransactionReceipt({ hash: MockERC20Hash1 });
console.log("TKXReceipt合约地址:", TKXReceipt.contractAddress);
const TKYReceipt = await publicClient.waitForTransactionReceipt({ hash: MockERC20Hash2 });
console.log("TKYReceipt合约地址:", TKYReceipt.contractAddress);
const LBPoolHash = await deployer.deployContract({
abi: LBPoolArtifact.abi,//获取abi
bytecode: LBPoolArtifact.bytecode,//硬编码
args: [TKXReceipt.contractAddress,TKYReceipt.contractAddress,initialActiveId],
});
const LBPoolReceipt = await publicClient.waitForTransactionReceipt({ hash: LBPoolHash });
console.log("LBPool合约地址:", LBPoolReceipt.contractAddress);
}
main().catch(console.error);
总结与展望
总结:离散流动性协议的工程演进
离散流动性协议(Liquidity Book)通过将连续的价格曲线离散化为独立的 Bin,从根本上重塑了 DeFi 基础设施的设计范式:
- 极致的资金效率与零无常损失兑换:LP 能够在极窄的价格区间内集中部署流动性;而在单个 Bin 内,资产兑换遵循固定价格,彻底消除了该区间内的无常损失(Impermanent Loss)。
- ERC1155 的仓位创新:摒弃了 Uniswap V3 复杂的不可替代非同质化 Token(NFT)仓位结构,通过 ERC1155 将相同 Bin 内的流动性份额化,大幅简化了组合金融(Composability)与 Yield Farming 的对接逻辑。
- EVM 原生性能优化:结合 Q64.128 高精度定点数算法与 Yul 汇编级 BitMap 路由检索,将跨价格箱清算的复杂度降低至 ,实现了常数级 Gas 消耗与工业级清算体验。
展望:离散流动性的下一阶段
随着模块化区块链(Modular Blockchains)与 Layer 2 / Layer 3 架构的普及,离散流动性协议将迎来更广阔的演进空间:
- 动态波动率收费(Dynamic Volatility Fee)机制:结合预言机与离散 Bin 跨度频率,实时感知市场波动率。在行情剧烈波动时动态提高手续费,有效防范 LVR(Loss Versus Rebalancing,再平衡损失)并保护 LP 收益。
- 自动化流动性再平衡策略(Automated Liquidity Management) :未来的离散池将深度集成 Intent(意图)与 AI Agent 架构,根据市场深度自动挂单、平摊以及跨箱转移流动性,将原本复杂的操作降低至“一键理财”体验。
- 全链统一流动性层(Omnichain Discrete Liquidity) :借助全链通信协议(如 LayerZero, Chainlink CCIP),离散的 Bin 可以天然地跨链分布在不同的 Rollup 上,实现跨链统一深度撮合与瞬间清算。
通过 Solidity 与 OpenZeppelin V5 的复刻实战,我们不仅验证了离散流动性底层算法的可行性,更为搭建下一代高性能 DEX 奠定了坚实且安全的代码基石。