UNPKG

nextgen-web3-precision

Version:

NextGen precision calculation library with BigNumber.js v9 and logarithm extensions for high-precision financial calculations

253 lines (209 loc) 9.23 kB
/** * nextgen-web3-precision 测试套件 */ const { toWei, fromWei, priceToSqrtPriceX96, sqrtPriceX96ToPrice, tickToPrice, priceToTick, formatNative, formatUSD, BigNumber, DEFAULT_DECIMALS, DEFAULT_DISPLAY_DP, Q96, MIN_TICK, MAX_TICK, } = require("../index"); describe("nextgen-web3-precision", () => { describe("toWei/fromWei conversions", () => { test("toWei should convert decimal to wei string", () => { expect(toWei("1")).toBe("1000000000000000000"); expect(toWei("0.5")).toBe("500000000000000000"); expect(toWei("1", 6)).toBe("1000000"); expect(toWei(new BigNumber("1.5"))).toBe("1500000000000000000"); }); test("fromWei should convert wei to decimal", () => { const result = fromWei("1000000000000000000"); expect(result.toString()).toBe("1"); const result2 = fromWei("500000000000000000"); expect(result2.toString()).toBe("0.5"); const result3 = fromWei("1000000", 6); expect(result3.toString()).toBe("1"); }); test("toWei/fromWei round trip should preserve value", () => { const original = "1.234567"; const wei = toWei(original); const back = fromWei(wei, DEFAULT_DECIMALS, 6); expect(back.toString()).toBe(original); }); test("toWei should throw on invalid input", () => { expect(() => toWei("invalid")).toThrow(); expect(() => toWei(NaN)).toThrow(); }); }); describe("Price/SqrtPriceX96 conversions", () => { test("priceToSqrtPriceX96 should convert price correctly", () => { const price = "1"; const sqrtPriceX96 = priceToSqrtPriceX96(price); expect(sqrtPriceX96).toBe(Q96.toFixed(0)); }); test("sqrtPriceX96ToPrice should convert back correctly", () => { const originalPrice = new BigNumber("2"); const sqrtPriceX96 = priceToSqrtPriceX96(originalPrice); const backPrice = sqrtPriceX96ToPrice(sqrtPriceX96); // 允许小的精度误差 expect(backPrice.minus(originalPrice).abs().isLessThan(1e-10)).toBe(true); }); test("priceToSqrtPriceX96 should handle different decimals", () => { const price = "1"; const sqrtPriceX96_18_18 = priceToSqrtPriceX96(price, 18, 18); const sqrtPriceX96_6_18 = priceToSqrtPriceX96(price, 6, 18); // 不同精度应该产生不同结果 expect(sqrtPriceX96_18_18).not.toBe(sqrtPriceX96_6_18); }); test("should throw on invalid price", () => { expect(() => priceToSqrtPriceX96("0")).toThrow(); expect(() => priceToSqrtPriceX96("-1")).toThrow(); expect(() => priceToSqrtPriceX96("invalid")).toThrow(); }); }); describe("Tick/Price conversions", () => { test("tickToPrice should convert tick 0 to price 1", () => { const price = tickToPrice(0); expect(price.toString()).toBe("1"); }); test("priceToTick should convert price 1 to tick 0", () => { const tick = priceToTick("1"); expect(tick).toBe(0); }); test("tick/price round trip should preserve value", () => { const originalTick = 1000; const price = tickToPrice(originalTick); const backTick = priceToTick(price); // 允许1个tick的误差(由于浮点精度) expect(Math.abs(backTick - originalTick)).toBeLessThanOrEqual(1); }); test("should handle tick range limits", () => { expect(() => tickToPrice(MIN_TICK)).not.toThrow(); expect(() => tickToPrice(MAX_TICK)).not.toThrow(); expect(() => tickToPrice(MIN_TICK - 1)).toThrow(); expect(() => tickToPrice(MAX_TICK + 1)).toThrow(); }); test("should handle different decimals", () => { const tick = 1000; const price_18_18 = tickToPrice(tick, 18, 18); const price_6_18 = tickToPrice(tick, 6, 18); // 不同精度应该产生不同结果 expect(price_18_18.toString()).not.toBe(price_6_18.toString()); }); }); describe("Format functions", () => { test("formatNative should format to 4 significant digits", () => { expect(formatNative("1.23456789")).toBe("1.235"); expect(formatNative("0.001234")).toBe("0.001234"); expect(formatNative("1234.5678")).toBe("1235"); expect(formatNative("0")).toBe("0.000"); }); test("formatUSD should format to 8 significant digits", () => { expect(formatUSD("1.23456789012")).toBe("1.2345679"); expect(formatUSD("0.00123456789")).toBe("0.0012345679"); expect(formatUSD("12345678.9")).toBe("12345679"); }); test("format functions should handle edge cases", () => { expect(formatNative("NaN")).toBe("NaN"); expect(formatNative("Infinity")).toBe("Infinity"); expect(formatNative("-Infinity")).toBe("-Infinity"); expect(formatUSD("NaN")).toBe("NaN"); expect(formatUSD("Infinity")).toBe("Infinity"); expect(formatUSD("-Infinity")).toBe("-Infinity"); }); test("format functions should handle BigNumber input", () => { const bn = new BigNumber("1.23456789"); expect(formatNative(bn)).toBe("1.235"); expect(formatUSD(bn)).toBe("1.2345679"); }); }); describe("BigNumber configuration", () => { test("BigNumber should be configured correctly", () => { const config = BigNumber.config(); expect(config.DECIMAL_PLACES).toBe(40); expect(config.ROUNDING_MODE).toBe(4); }); test("BigNumber should handle high precision calculations", () => { const a = new BigNumber("0.1"); const b = new BigNumber("0.2"); const result = a.plus(b); // 应该精确等于0.3,不是0.30000000000000004 expect(result.toString()).toBe("0.3"); }); }); describe("Constants", () => { test("constants should be defined correctly", () => { expect(DEFAULT_DECIMALS).toBe(18); expect(DEFAULT_DISPLAY_DP).toBe(6); expect(MIN_TICK).toBe(-887272); expect(MAX_TICK).toBe(887272); expect(Q96.toFixed()).toBe("79228162514264337593543950336"); }); }); describe("Error handling", () => { test("functions should provide meaningful error messages", () => { expect(() => toWei("invalid")).toThrow(/toWei conversion failed/); expect(() => fromWei("invalid")).toThrow(/fromWei conversion failed/); expect(() => priceToSqrtPriceX96("invalid")).toThrow( /priceToSqrtPriceX96 conversion failed/ ); expect(() => sqrtPriceX96ToPrice("invalid")).toThrow( /sqrtPriceX96ToPrice conversion failed/ ); expect(() => tickToPrice(MIN_TICK - 1)).toThrow( /tickToPrice conversion failed/ ); expect(() => priceToTick("invalid")).toThrow( /priceToTick conversion failed/ ); }); }); describe("Directionality (T1/T0)", () => { const DECIMALS_T0 = 18; // e.g., ETH const DECIMALS_T1 = 6; // e.g., USDC const PRICE_T1_T0 = new BigNumber("2000"); // 2000 USDC per 1 ETH test("priceToSqrtPriceX96 should correctly handle T1/T0 direction", () => { // This is the core check for the bug fix. // We convert the human-readable price (T1/T0) to the internal SqrtPriceX96 format. const sqrtPriceX96 = priceToSqrtPriceX96(PRICE_T1_T0, DECIMALS_T0, DECIMALS_T1); // Now, convert it back to a price. const price_restored = sqrtPriceX96ToPrice(sqrtPriceX96, DECIMALS_T0, DECIMALS_T1); // The restored price should be very close to the original price. expect(price_restored.minus(PRICE_T1_T0).abs().isLessThan(1e-9)).toBe(true); }); test("priceToTick should correctly handle T1/T0 direction", () => { // Convert the T1/T0 price to a tick. const tick = priceToTick(PRICE_T1_T0, DECIMALS_T0, DECIMALS_T1); // Convert the tick back to a price. const price_restored = tickToPrice(tick, DECIMALS_T0, DECIMALS_T1); // The restored price should be close to the original, allowing for tick precision loss. // The tolerance is higher here because ticks are discrete and cause some precision loss. expect(price_restored.minus(PRICE_T1_T0).abs().dividedBy(PRICE_T1_T0).isLessThan(0.001)).toBe(true); }); test("sqrtPriceX96ToPrice should produce a T1/T0 price", () => { // Manually calculate the expected raw ratio for internal calculations const raw_ratio = PRICE_T1_T0.multipliedBy(new BigNumber(10).pow(DECIMALS_T1 - DECIMALS_T0)); const sqrtPrice = raw_ratio.sqrt(); const sqrtPriceX96 = sqrtPrice.multipliedBy(Q96).integerValue(); const price = sqrtPriceX96ToPrice(sqrtPriceX96, DECIMALS_T0, DECIMALS_T1); expect(price.minus(PRICE_T1_T0).abs().isLessThan(1e-9)).toBe(true); }); test("tickToPrice should produce a T1/T0 price", () => { // We'll use a known tick and see if it produces the correct T1/T0 price. // Let's find the tick for our 2000 USDC/ETH price first. const expectedTick = priceToTick(PRICE_T1_T0, DECIMALS_T0, DECIMALS_T1); const price = tickToPrice(expectedTick, DECIMALS_T0, DECIMALS_T1); // Check if the resulting price is close to our target price. expect(price.minus(PRICE_T1_T0).abs().dividedBy(PRICE_T1_T0).isLessThan(0.001)).toBe(true); }); }); });