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);
}

功能流程

  1. 对代币地址进行排序

  2. 计算交易对地址

  3. 调用交易对合约获取储备量

  4. 根据代币顺序返回对应的储备量

返回值

  • 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]
        );
    }
}

计算流程

  1. 验证路径长度至少为2

  2. 初始化数量数组,第一个元素为输入数量

  3. 遍历路径中的每一跳

  4. 获取当前跳的储备量

  5. 计算当前跳的输出数量

  6. 输出数量作为下一跳的输入数量

多跳输入计算

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]
        );
    }
}

计算流程

  1. 验证路径长度

  2. 初始化数量数组,最后一个元素为期望输出数量

  3. 从后向前遍历路径

  4. 获取当前跳的储备量

  5. 计算当前跳需要的输入数量

  6. 继续向前计算直到第一跳

使用示例

基本价格查询

// 计算等比例数量
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