Math 库

Math库是JAMM DEX中的数学运算工具库,提供了安全的基础数学运算函数。它包含了平方根计算、最小值比较以及带溢出保护的四则运算,为整个协议提供了可靠的数学基础。

库函数详解

最小值函数

function min(uint x, uint y) internal pure returns (uint z) {
    z = x < y ? x : y;
}

功能:返回两个无符号整数中的较小值

用途

  • 流动性计算中选择较小的比例

  • 限制最大值设置

  • 边界条件处理

示例

uint minAmount = Math.min(amountA, amountB);

平方根函数

function sqrt(uint y) internal pure returns (uint z) {
    if (y > 3) {
        z = y;
        uint x = y / 2 + 1;
        while (x < z) {
            z = x;
            x = (y / x + x) / 2;
        }
    } else if (y != 0) {
        z = 1;
    }
}

算法:巴比伦方法(牛顿迭代法的特例)

计算原理

  1. 初始猜测值:x₀ = y/2 + 1

  2. 迭代公式:x_{n+1} = (y/x_n + x_n) / 2

  3. x_{n+1} >= x_n时停止迭代

特殊情况处理

  • y = 0: 返回0

  • y = 1, 2, 3: 返回1

  • y > 3: 使用迭代算法

用途

  • 计算几何平均数

  • LP代币数量计算:√(amount0 × amount1)

  • 协议费用计算中的k值平方根

加法函数

function add(uint x, uint y) internal pure returns (uint z) {
    require((z = x + y) >= x, "ds-math-add-overflow");
}

安全特性

  • 检查加法溢出

  • 如果x + y < x则说明发生溢出

  • 溢出时抛出异常

注意:在Solidity 0.8.x中,内置了溢出检查,此函数主要用于兼容性

减法函数

function sub(uint x, uint y) internal pure returns (uint z) {
    require((z = x - y) <= x, "ds-math-sub-underflow");
}

安全特性

  • 检查减法下溢

  • 如果x - y > x则说明发生下溢

  • 下溢时抛出异常

乘法函数

function mul(uint x, uint y) internal pure returns (uint z) {
    require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");
}

安全特性

  • 检查乘法溢出

  • 通过除法验证:如果(x × y) / y ≠ x则发生溢出

  • 特殊处理y = 0的情况

除法函数

function div(uint x, uint y) internal pure returns (uint z) {
    require(y > 0, "ds-math-div-zero");
    z = x / y;
}

安全特性

  • 检查除零错误

  • 确保除数大于0

在JAMM DEX中的应用

流动性计算

初始流动性计算

// 在JAMMPair.mint()中
if (_totalSupply == 0) {
    liquidity = Math.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY;
    _mint(address(0), MINIMUM_LIQUIDITY);
}

几何平均数

  • 使用√(amount0 × amount1)计算初始LP代币数量

  • 几何平均数比算术平均数更适合乘积恒定的AMM系统

  • 减去MINIMUM_LIQUIDITY防止池子被完全清空

后续流动性计算

// 在JAMMPair.mint()中
liquidity = Math.min(
    (amount0 * _totalSupply) / _reserve0,
    (amount1 * _totalSupply) / _reserve1
);

最小值选择

  • 确保按现有比例添加流动性

  • 防止单边添加流动性破坏价格

  • 保护流动性提供者利益

协议费用计算

// 在JAMMPair._mintFee()中
uint rootK = Math.sqrt(uint(_reserve0) * uint(_reserve1));
uint rootKLast = Math.sqrt(_kLast);
if (rootK > rootKLast) {
    uint numerator = totalSupply * (rootK - rootKLast) * 8;
    uint denominator = rootK * 17 + rootKLast * 8;
    uint liquidity = numerator / denominator;
    if (liquidity > 0) _mint(mintTo, liquidity);
}

k值增长计算

  • 计算当前k值的平方根

  • 计算上次k值的平方根

  • 基于k值增长计算协议费用

数学原理

巴比伦平方根算法

巴比伦方法是计算平方根的古老算法,基于以下观察:

如果xn的平方根的近似值,那么n/x也是一个近似值,且:

  • 如果x > √n,则n/x < √n

  • 如果x < √n,则n/x > √n

因此,(x + n/x) / 2是一个更好的近似值。

收敛性

  • 算法具有二次收敛性

  • 每次迭代有效数字大约翻倍

  • 对于256位整数,通常只需要几次迭代

溢出检测原理

加法溢出检测

如果 x + y < x,则发生溢出

原理:在无符号整数中,如果加法结果小于任一操作数,说明发生了环绕。

乘法溢出检测

如果 y ≠ 0 且 (x * y) / y ≠ x,则发生溢出

原理:如果没有溢出,(x * y) / y应该等于x

使用示例

平方根计算

// 计算几何平均数
const amount0 = ethers.utils.parseEther("100");
const amount1 = ethers.utils.parseEther("200");

// 在合约中会调用 Math.sqrt(amount0 * amount1)
const geometricMean = Math.sqrt(amount0.mul(amount1));
console.log("几何平均数:", ethers.utils.formatEther(geometricMean));

最小值选择

// 选择较小的流动性比例
const ratio0 = amount0.mul(totalSupply).div(reserve0);
const ratio1 = amount1.mul(totalSupply).div(reserve1);

// 在合约中会调用 Math.min(ratio0, ratio1)
const liquidity = ratio0.lt(ratio1) ? ratio0 : ratio1;

安全运算

// 虽然Solidity 0.8.x有内置检查,但了解原理很重要

// 加法溢出检查
function safeAdd(a, b) {
    const result = a.add(b);
    if (result.lt(a)) {
        throw new Error("Addition overflow");
    }
    return result;
}

// 乘法溢出检查
function safeMul(a, b) {
    if (b.eq(0)) return ethers.BigNumber.from(0);
    const result = a.mul(b);
    if (!result.div(b).eq(a)) {
        throw new Error("Multiplication overflow");
    }
    return result;
}

性能考虑

平方根算法效率

巴比伦方法的时间复杂度:

  • 最坏情况:O(log log n)

  • 平均情况:通常3-4次迭代

  • Gas消耗:相对较低,适合链上计算

优化技巧

  1. 初始猜测优化

    uint x = y / 2 + 1; // 比 y/2 更接近真实值
  2. 特殊情况处理

    if (y > 3) {
        // 使用迭代算法
    } else if (y != 0) {
        z = 1; // 直接返回结果
    }
  3. 循环优化

    while (x < z) { // 使用 < 而不是 !=
        z = x;
        x = (y / x + x) / 2;
    }

测试用例

平方根测试

describe("Math.sqrt", function() {
    it("should calculate square root correctly", function() {
        expect(Math.sqrt(0)).to.equal(0);
        expect(Math.sqrt(1)).to.equal(1);
        expect(Math.sqrt(4)).to.equal(2);
        expect(Math.sqrt(9)).to.equal(3);
        expect(Math.sqrt(16)).to.equal(4);
        expect(Math.sqrt(100)).to.equal(10);
    });
    
    it("should handle large numbers", function() {
        const large = ethers.BigNumber.from("1000000000000000000"); // 10^18
        const result = Math.sqrt(large);
        expect(result).to.equal(ethers.BigNumber.from("1000000000")); // 10^9
    });
});

溢出测试

describe("Math overflow protection", function() {
    it("should detect addition overflow", function() {
        const maxUint = ethers.constants.MaxUint256;
        expect(() => Math.add(maxUint, 1)).to.throw("ds-math-add-overflow");
    });
    
    it("should detect multiplication overflow", function() {
        const large = ethers.BigNumber.from("2").pow(128);
        expect(() => Math.mul(large, large)).to.throw("ds-math-mul-overflow");
    });
});

Last updated