JAMMLibrary 库
JAMMLibrary是JAMM DEX的核心计算库,提供了AMM系统所需的各种数学计算和工具函数。它包含代币地址排序、交易对地址计算、储备量查询、价格计算等关键功能,是整个协议的数学基础。
库函数分类
JAMMLibrary主要包含以下几类函数:
地址处理函数
储备量查询函数
价格计算函数
多跳路径计算函数
地址处理函数
代币地址排序
function sortTokens(
address tokenA,
address tokenB
) internal pure returns (address token0, address token1) {
require(tokenA != tokenB, "JAMMLibrary: IDENTICAL_ADDRESSES");
(token0, token1) = tokenA < tokenB
? (tokenA, tokenB)
: (tokenB, tokenA);
require(token0 != address(0), "JAMMLibrary: ZERO_ADDRESS");
}
功能说明:
确保两个代币地址不相同
按地址大小排序,较小地址为token0
验证token0不为零地址
返回排序后的地址对
用途:
确保交易对的唯一性
统一代币在交易对中的顺序
为CREATE2地址计算提供一致的输入
交易对地址计算
function pairFor(
address factory,
address tokenA,
address tokenB,
uint24 fee
) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
factory,
keccak256(abi.encodePacked(token0, token1, fee)),
INIT_CODE_PAIR_HASH // 实际哈希值在部署时确定
)
)
)
)
);
}
CREATE2地址计算:
0xff
: CREATE2操作码前缀factory
: Factory合约地址salt
: 由token0、token1和fee组成的哈希值INIT_CODE_PAIR_HASH
: JAMMPair合约的初始化代码哈希(实际值在部署时确定)
特点:
确定性地址生成
无需调用Factory合约即可计算地址
支持离线地址预计算
储备量查询函数
获取储备量
function getReserves(
address factory,
address tokenA,
address tokenB,
uint24 fee
) internal view returns (uint reserveA, uint reserveB) {
(address token0, ) = sortTokens(tokenA, tokenB);
address pair = pairFor(factory, tokenA, tokenB, fee);
(uint reserve0, uint reserve1, ) = IJAMMPair(pair).getReserves();
(reserveA, reserveB) = tokenA == token0
? (reserve0, reserve1)
: (reserve1, reserve0);
}
功能流程:
对代币地址进行排序
计算交易对地址
调用交易对合约获取储备量
根据代币顺序返回对应的储备量
返回值:
reserveA
: tokenA的储备量reserveB
: tokenB的储备量
价格计算函数
等比例计算
function quote(
uint amountA,
uint reserveA,
uint reserveB
) internal pure returns (uint amountB) {
require(amountA > 0, "JAMMLibrary: INSUFFICIENT_AMOUNT");
require(
reserveA > 0 && reserveB > 0,
"JAMMLibrary: INSUFFICIENT_LIQUIDITY"
);
amountB = (amountA * reserveB) / reserveA;
}
计算公式:
amountB = amountA × reserveB / reserveA
用途:
添加流动性时计算最优比例
价格查询和显示
流动性移除时的比例计算
输出数量计算
function getAmountOut(
uint amountIn,
uint reserveIn,
uint reserveOut,
uint24 fee
) internal pure returns (uint amountOut) {
require(amountIn > 0, "JAMMLibrary: INSUFFICIENT_INPUT_AMOUNT");
require(
reserveIn > 0 && reserveOut > 0,
"JAMMLibrary: INSUFFICIENT_LIQUIDITY"
);
uint amountInWithFee = amountIn * (10000 - fee);
uint numerator = amountInWithFee * reserveOut;
uint denominator = reserveIn * 10000 + amountInWithFee;
amountOut = numerator / denominator;
}
计算公式:
amountInWithFee = amountIn × (10000 - fee)
amountOut = (amountInWithFee × reserveOut) / (reserveIn × 10000 + amountInWithFee)
费用处理:
从输入数量中扣除交易费用
费用以基点表示(10000 = 100%)
扣除费用后的数量参与AMM计算
输入数量计算
function getAmountIn(
uint amountOut,
uint reserveIn,
uint reserveOut,
uint24 fee
) internal pure returns (uint amountIn) {
require(amountOut > 0, "JAMMLibrary: INSUFFICIENT_OUTPUT_AMOUNT");
require(
reserveIn > 0 && reserveOut > 0,
"JAMMLibrary: INSUFFICIENT_LIQUIDITY"
);
uint numerator = reserveIn * amountOut * 10000;
uint denominator = (reserveOut - amountOut) * (10000 - fee);
amountIn = (numerator / denominator) + 1;
}
计算公式:
amountIn = (reserveIn × amountOut × 10000) / ((reserveOut - amountOut) × (10000 - fee)) + 1
精度处理:
结果加1确保有足够的输入数量
避免因精度损失导致的交换失败
多跳路径计算
多跳输出计算
function getAmountsOut(
address factory,
uint amountIn,
address[] memory path,
uint24[] memory fees
) internal view returns (uint[] memory amounts) {
require(path.length >= 2, "JAMMLibrary: INVALID_PATH");
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(
factory,
path[i],
path[i + 1],
fees[i]
);
amounts[i + 1] = getAmountOut(
amounts[i],
reserveIn,
reserveOut,
fees[i]
);
}
}
计算流程:
验证路径长度至少为2
初始化数量数组,第一个元素为输入数量
遍历路径中的每一跳
获取当前跳的储备量
计算当前跳的输出数量
输出数量作为下一跳的输入数量
多跳输入计算
function getAmountsIn(
address factory,
uint amountOut,
address[] memory path,
uint24[] memory fees
) internal view returns (uint[] memory amounts) {
require(path.length >= 2, "JAMMLibrary: INVALID_PATH");
amounts = new uint[](path.length);
amounts[amounts.length - 1] = amountOut;
for (uint i = path.length - 1; i > 0; i--) {
(uint reserveIn, uint reserveOut) = getReserves(
factory,
path[i - 1],
path[i],
fees[i - 1]
);
amounts[i - 1] = getAmountIn(
amounts[i],
reserveIn,
reserveOut,
fees[i - 1]
);
}
}
计算流程:
验证路径长度
初始化数量数组,最后一个元素为期望输出数量
从后向前遍历路径
获取当前跳的储备量
计算当前跳需要的输入数量
继续向前计算直到第一跳
使用示例
基本价格查询
// 计算等比例数量
const amountB = await JAMMLibrary.quote(
ethers.utils.parseEther("1"), // 1个tokenA
reserveA,
reserveB
);
// 计算交换输出
const amountOut = await JAMMLibrary.getAmountOut(
ethers.utils.parseEther("1"), // 输入1个代币
reserveIn,
reserveOut,
100 // 1%费率
);
// 计算所需输入
const amountIn = await JAMMLibrary.getAmountIn(
ethers.utils.parseEther("1"), // 期望输出1个代币
reserveIn,
reserveOut,
100 // 1%费率
);
多跳路径计算
// 计算多跳输出
const path = [tokenA, tokenB, tokenC];
const fees = [100, 200]; // 1%和2%费率
const amountIn = ethers.utils.parseEther("1");
const amounts = await JAMMLibrary.getAmountsOut(
factoryAddress,
amountIn,
path,
fees
);
console.log("输入数量:", ethers.utils.formatEther(amounts[0]));
console.log("中间数量:", ethers.utils.formatEther(amounts[1]));
console.log("输出数量:", ethers.utils.formatEther(amounts[2]));
// 计算多跳输入
const amountOut = ethers.utils.parseEther("1");
const amountsIn = await JAMMLibrary.getAmountsIn(
factoryAddress,
amountOut,
path,
fees
);
console.log("需要输入:", ethers.utils.formatEther(amountsIn[0]));
地址计算
// 计算交易对地址
const pairAddress = await JAMMLibrary.pairFor(
factoryAddress,
tokenA,
tokenB,
100 // 1%费率
);
// 获取储备量
const [reserveA, reserveB] = await JAMMLibrary.getReserves(
factoryAddress,
tokenA,
tokenB,
100
);
数学原理
AMM定价公式
JAMM DEX使用恒定乘积公式:
x × y = k
在考虑费用的情况下:
(x + Δx × (1 - fee)) × (y - Δy) = k
其中:
x
,y
: 交换前的储备量Δx
: 输入数量Δy
: 输出数量fee
: 交易费率
价格影响
价格影响可以通过以下公式计算:
价格影响 = 1 - (实际价格 / 理论价格)
其中:
实际价格 = Δy / Δx
理论价格 = y / x
滑点计算
滑点是预期价格与实际执行价格的差异:
滑点 = |实际输出 - 预期输出| / 预期输出
Last updated