import { Fraction } from '@uniswap/sdk-core'; import { ShadowCustomRouter, calculateAmountToSwap, fractionAbsoluteValue, } from '../../src/routers/shadow/shadow-router'; const makePool = (overrides: Record = {}) => ({ token0: jest.fn().mockResolvedValue('0x1'), token1: jest.fn().mockResolvedValue('0x2'), tickSpacing: jest.fn().mockResolvedValue(10), slot0: jest.fn().mockResolvedValue({ sqrtPriceX96: 79228162514264337593543950336n, unlocked: true, }), ...overrides, }); const makeQuoter = () => ({ callStatic: { quoteExactInputSingle: jest.fn().mockResolvedValue({ amountOut: 1n, sqrtPriceX96After: 2n, }), }, getFunction: jest.fn().mockReturnValue({ staticCall: jest.fn().mockResolvedValue({ amountOut: 1n, sqrtPriceX96After: 2n, }), }), }); describe('Shadow router helpers', () => { it('fractionAbsoluteValue returns positive numerator and denominator', () => { const frac = new Fraction(-8, 9); const abs = fractionAbsoluteValue(frac); expect(abs.numerator.toString()).toBe('8'); expect(abs.denominator.toString()).toBe('9'); }); it('calculateAmountToSwap handles zero and infinite ratios', () => { const zeroRatio = new Fraction(0, 1); expect(calculateAmountToSwap(zeroRatio, 1n, true, 6n, 2n)).toBe(6n); expect(() => calculateAmountToSwap(zeroRatio, 1n, false, 6n, 2n)).toThrow( 'wrong parameters', ); const infRatio = new Fraction(1, 0); expect(calculateAmountToSwap(infRatio, 1n, false, 6n, 2n)).toBe(2n); expect(() => calculateAmountToSwap(infRatio, 1n, true, 6n, 2n)).toThrow( 'wrong parameters', ); }); }); describe('ShadowCustomRouter', () => { const positions = [{ lowerTick: -60, upperTick: 60, weight: 1 }]; it('returns zero when already balanced', async () => { const pool = makePool(); const quoter = makeQuoter(); const logger = { info: jest.fn() } as any; const router = new ShadowCustomRouter(quoter as any, logger); const result = await router.getSwapAmount( pool as any, positions as any, 1n, 1n, '0x1', '0x2', 3000, 1, new Fraction(1, 1), ); expect(result.amountToSwap).toBe(0n); }); it('throws when pool is not initialized', async () => { const pool = makePool({ slot0: jest.fn().mockResolvedValue({ sqrtPriceX96: 1n, unlocked: false }), }); const quoter = makeQuoter(); const logger = { info: jest.fn() } as any; const router = new ShadowCustomRouter(quoter as any, logger); await expect( router.getSwapAmount( pool as any, positions as any, 1n, 1n, '0x1', '0x2', 3000, 1, ), ).rejects.toThrow('Pool is not initialized'); }); it('returns zero when swap amount is non-positive', async () => { const pool = makePool(); const quoter = makeQuoter(); const logger = { info: jest.fn() } as any; const router = new ShadowCustomRouter(quoter as any, logger); const module = require('../../src/routers/shadow/shadow-router'); const spy = jest.spyOn(module, 'calculateAmountToSwap').mockReturnValue(0n); const result = await router.getSwapAmount( pool as any, positions as any, 10n, 20n, '0x1', '0x2', 3000, 1, ); spy.mockRestore(); expect(result.amountToSwap).toBe(0n); }); it('throws for invalid parameters', async () => { const pool = makePool(); const quoter = makeQuoter(); const logger = { info: jest.fn() } as any; const router = new ShadowCustomRouter(quoter as any, logger); await expect( router.getSwapAmount( pool as any, [{ lowerTick: -60, upperTick: -60, weight: 1 }] as any, 10n, 20n, '0x1', '0x2', 3000, 1, ), ).rejects.toThrow('lowerTick must be less than upperTick'); }); });