import { expect, test } from '@playwright/test'; import { getStackConfig, validateStackHealthy } from '../setup/stack'; const STACK_CONFIG = getStackConfig(); const STACK_RPC_URL = STACK_CONFIG.rpcUrl; // Solidity function selectors const GET_LIQUIDITY_PARAMS_SELECTOR = '0xbd53c0dc'; // getLiquidityParams() const POSITIONS_SELECTOR = '0xf86aafc0'; // positions(uint8) // Optimizer.sol invariants (capitalInefficiency + anchorShare = 1e18) const ONE_ETHER = 10n ** 18n; // Position stages const STAGE_FLOOR = 0; const STAGE_ANCHOR = 1; const STAGE_DISCOVERY = 2; // TICK_SPACING from ThreePositionStrategy const TICK_SPACING = 200; interface LiquidityParams { capitalInefficiency: bigint; anchorShare: bigint; anchorWidth: bigint; discoveryDepth: bigint; } async function rpcCall(method: string, params: unknown[]): Promise { const response = await fetch(STACK_RPC_URL, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }), }); const data = await response.json(); if (data.error) throw new Error(`RPC error: ${data.error.message}`); return data.result; } async function readLiquidityParams(optimizerAddress: string): Promise { const result = (await rpcCall('eth_call', [ { to: optimizerAddress, data: GET_LIQUIDITY_PARAMS_SELECTOR }, 'latest', ])) as string; return { capitalInefficiency: BigInt('0x' + result.slice(2, 66)), anchorShare: BigInt('0x' + result.slice(66, 130)), anchorWidth: BigInt('0x' + result.slice(130, 194)), discoveryDepth: BigInt('0x' + result.slice(194, 258)), }; } function toInt24(val: bigint): number { const n = Number(val & 0xffffffn); return n >= 0x800000 ? n - 0x1000000 : n; } test.describe('Optimizer Integration', () => { test.beforeAll(async () => { await validateStackHealthy(STACK_CONFIG); }); test('Optimizer proxy returns valid parameters', async () => { const optimizerAddress = STACK_CONFIG.contracts.OptimizerProxy; if (!optimizerAddress) { console.log( '[TEST] SKIP: OptimizerProxy not in deployments-local.json (older deployment)', ); test.skip(); return; } console.log(`[TEST] OptimizerProxy: ${optimizerAddress}`); const params = await readLiquidityParams(optimizerAddress); console.log(`[TEST] capitalInefficiency: ${params.capitalInefficiency}`); console.log(`[TEST] anchorShare: ${params.anchorShare}`); console.log(`[TEST] anchorWidth: ${params.anchorWidth}`); console.log(`[TEST] discoveryDepth: ${params.discoveryDepth}`); // Optimizer.sol invariants: // capitalInefficiency + anchorShare == 1e18 expect(params.capitalInefficiency + params.anchorShare).toBe(ONE_ETHER); // discoveryDepth == anchorShare expect(params.discoveryDepth).toBe(params.anchorShare); // anchorWidth in [10, 80] expect(params.anchorWidth).toBeGreaterThanOrEqual(10n); expect(params.anchorWidth).toBeLessThanOrEqual(80n); console.log('[TEST] Optimizer returns valid parameters (invariants satisfied)'); }); test('bootstrap positions reflect valid optimizer anchorWidth', async () => { const lmAddress = STACK_CONFIG.contracts.LiquidityManager; const optimizerAddress = STACK_CONFIG.contracts.OptimizerProxy; if (!optimizerAddress) { test.skip(); return; } // Read anchor position from LM (created by bootstrap's recenter call). // Note: optimizer state may have changed since bootstrap (e.g. staking activity in // earlier tests), so we don't read the *current* optimizer params here. Instead // we reverse-calculate the anchorWidth that was in effect when recenter() ran and // verify it falls within Optimizer.sol's valid range [10, 80]. const anchorResult = (await rpcCall('eth_call', [ { to: lmAddress, data: `${POSITIONS_SELECTOR}${'1'.padStart(64, '0')}`, // Stage.ANCHOR = 1 }, 'latest', ])) as string; const tickLower = toInt24(BigInt('0x' + anchorResult.slice(66, 130))); const tickUpper = toInt24(BigInt('0x' + anchorResult.slice(130, 194))); const anchorSpread = tickUpper - tickLower; console.log(`[TEST] Anchor position: ticks=[${tickLower}, ${tickUpper}], spread=${anchorSpread}`); // Reverse the formula to recover anchorWidth: // anchorSpread = 2 * (TICK_SPACING + (34 * anchorWidth * TICK_SPACING / 100)) // => anchorWidth = (anchorSpread / 2 - TICK_SPACING) * 100 / (34 * TICK_SPACING) const halfSpread = anchorSpread / 2; expect(halfSpread).toBeGreaterThan(TICK_SPACING); const impliedAnchorWidth = Math.round(((halfSpread - TICK_SPACING) * 100) / (34 * TICK_SPACING)); console.log(`[TEST] Implied anchorWidth from spread: ${impliedAnchorWidth}`); // Optimizer.sol constrains anchorWidth to [10, 80] expect(impliedAnchorWidth).toBeGreaterThanOrEqual(10); expect(impliedAnchorWidth).toBeLessThanOrEqual(80); // Confirm the implied anchorWidth reproduces the exact spread (no rounding error) const expectedSpacing = TICK_SPACING + (34 * impliedAnchorWidth * TICK_SPACING) / 100; const expectedSpread = expectedSpacing * 2; expect(anchorSpread).toBe(expectedSpread); console.log(`[TEST] Anchor spread ${anchorSpread} corresponds to valid anchorWidth=${impliedAnchorWidth}`); }); test('all three positions have valid relative sizing', async () => { const lmAddress = STACK_CONFIG.contracts.LiquidityManager; const optimizerAddress = STACK_CONFIG.contracts.OptimizerProxy; if (!optimizerAddress) { test.skip(); return; } // Read all three positions const positions = []; const stageNames = ['FLOOR', 'ANCHOR', 'DISCOVERY']; for (const stage of [STAGE_FLOOR, STAGE_ANCHOR, STAGE_DISCOVERY]) { const stageHex = stage.toString(16).padStart(64, '0'); const result = (await rpcCall('eth_call', [ { to: lmAddress, data: `${POSITIONS_SELECTOR}${stageHex}` }, 'latest', ])) as string; const liquidity = BigInt('0x' + result.slice(2, 66)); const tickLower = toInt24(BigInt('0x' + result.slice(66, 130))); const tickUpper = toInt24(BigInt('0x' + result.slice(130, 194))); const spread = tickUpper - tickLower; positions.push({ liquidity, tickLower, tickUpper, spread }); console.log(`[TEST] ${stageNames[stage]}: spread=${spread}, liquidity=${liquidity}`); } // Floor should be narrow (TICK_SPACING width = 200 ticks) expect(positions[0].spread).toBe(TICK_SPACING); console.log('[TEST] Floor has expected narrow width (200 ticks)'); // Anchor should be wider than floor expect(positions[1].spread).toBeGreaterThan(positions[0].spread); console.log('[TEST] Anchor is wider than floor'); // Discovery should have significant spread expect(positions[2].spread).toBeGreaterThan(0); console.log('[TEST] Discovery has positive spread'); // Floor liquidity should be highest (concentrated in narrow range) expect(positions[0].liquidity).toBeGreaterThan(positions[1].liquidity); console.log('[TEST] Floor liquidity > anchor liquidity (as expected for concentrated position)'); console.log('[TEST] All position sizing validated against optimizer parameters'); }); });