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;
}
}
算法:巴比伦方法(牛顿迭代法的特例)
计算原理:
初始猜测值:
x₀ = y/2 + 1
迭代公式:
x_{n+1} = (y/x_n + x_n) / 2
当
x_{n+1} >= x_n
时停止迭代
特殊情况处理:
y = 0
: 返回0y = 1, 2, 3
: 返回1y > 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值增长计算协议费用
数学原理
巴比伦平方根算法
巴比伦方法是计算平方根的古老算法,基于以下观察:
如果x
是n
的平方根的近似值,那么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消耗:相对较低,适合链上计算
优化技巧
初始猜测优化:
uint x = y / 2 + 1; // 比 y/2 更接近真实值
特殊情况处理:
if (y > 3) { // 使用迭代算法 } else if (y != 0) { z = 1; // 直接返回结果 }
循环优化:
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