import { Fraction } from '@uniswap/sdk-core'; import { AlgebraCustomRouter, calculateAmountToSwap, fractionAbsoluteValue, } from '../../src/routers/algebra/algebra-router'; const makePool = (overrides: Record = {}) => ({ globalState: jest .fn() .mockResolvedValue({ price: 79228162514264337593543950336n }), ...overrides, }); const makeQuoter = () => ({ callStatic: { quoteExactInputSingle: jest.fn().mockResolvedValue({ amountOut: 1n, sqrtPriceX96After: 2n, }), }, getFunction: jest.fn().mockReturnValue({ staticCall: jest.fn().mockResolvedValue({ amountOut: 2n, sqrtPriceX96After: 3n, }), }), }); describe('Algebra router helpers', () => { it('fractionAbsoluteValue returns positive numerator and denominator', () => { const frac = new Fraction(-2, 3); const abs = fractionAbsoluteValue(frac); expect(abs.numerator.toString()).toBe('2'); expect(abs.denominator.toString()).toBe('3'); }); it('calculateAmountToSwap handles zero and infinite ratios', () => { const zeroRatio = new Fraction(0, 1); expect(calculateAmountToSwap(zeroRatio, 1n, true, 9n, 5n)).toBe(9n); expect(() => calculateAmountToSwap(zeroRatio, 1n, false, 9n, 5n)).toThrow( 'wrong parameters', ); const infRatio = new Fraction(1, 0); expect(calculateAmountToSwap(infRatio, 1n, false, 9n, 5n)).toBe(5n); expect(() => calculateAmountToSwap(infRatio, 1n, true, 9n, 5n)).toThrow( 'wrong parameters', ); }); }); describe('AlgebraCustomRouter', () => { const positions = [{ lowerTick: -60, upperTick: 60, weight: 1 }]; it('returns zero when already balanced', async () => { const pool = makePool({ globalState: jest .fn() .mockResolvedValue({ price: 79228162514264337593543950336n }), }); const quoter = makeQuoter(); const logger = { info: jest.fn(), warn: jest.fn(), error: jest.fn(), } as any; const router = new AlgebraCustomRouter(quoter as any, logger); const result = await router.getSwapAmount( pool as any, positions as any, 1n, 1n, '0x1', '0x2', 1, new Fraction(1, 1), ); expect(result.amountToSwap).toBe(0n); }); it('falls back to AlgebraQuoteEngine when primary quote fails', async () => { const pool = makePool({ globalState: jest .fn() .mockResolvedValue({ price: 79228162514264337593543950336n }), }); const quoter = makeQuoter(); // Fail primary on first call only; AlgebraQuoteEngine fallback uses getFunction too quoter.getFunction = jest.fn() .mockReturnValueOnce({ staticCall: jest.fn().mockRejectedValue(new Error('primary fail')), }) .mockReturnValue({ staticCall: jest.fn().mockResolvedValue({ amountOut: 2n, sqrtPriceX96After: 3n }), }); const logger = { info: jest.fn(), warn: jest.fn(), error: jest.fn(), } as any; const router = new AlgebraCustomRouter(quoter as any, logger); const result = await router.getSwapAmount( pool as any, positions as any, 10n, 1n, '0x1', '0x2', 2, ); expect(result.amountToSwap).toBeGreaterThanOrEqual(0n); expect(logger.warn).toHaveBeenCalled(); }); it('throws when fallback quote fails', async () => { const pool = makePool({ globalState: jest .fn() .mockResolvedValue({ price: 79228162514264337593543950336n }), }); const quoter = makeQuoter(); quoter.getFunction = jest.fn().mockReturnValue({ staticCall: jest.fn().mockRejectedValue(new Error('primary fail')), }); const logger = { info: jest.fn(), warn: jest.fn(), error: jest.fn(), } as any; const router = new AlgebraCustomRouter(quoter as any, logger); const engineModule = require('../../src/engines/algebra-quote-engine'); jest .spyOn(engineModule.AlgebraQuoteEngine.prototype, 'quoteExactInputSingle') .mockRejectedValueOnce(new Error('fallback fail')); await expect( router.getSwapAmount( pool as any, positions as any, 10n, 1n, '0x1', '0x2', 2, ), ).rejects.toThrow('fallback fail'); expect(logger.error).toHaveBeenCalled(); }); });