fix: Update E2E tests for Optimizer (base) — drop OptimizerV3 bear-market constants
- 01-acquire-and-stake: replace flat 3 s wait with a 30 s polling loop so
Ponder indexing lag no longer causes a spurious positions.length=0 failure.
- 05-optimizer-integration test 1: replace hard-coded OptimizerV3 bear-market
constants (anchorShare=3e17, anchorWidth=100, discoveryDepth=3e17) with
Optimizer.sol invariant checks:
capitalInefficiency + anchorShare == 1e18
discoveryDepth == anchorShare
anchorWidth ∈ [10, 80]
- 05-optimizer-integration test 2: decouple bootstrap-position assertion from
current optimizer state. Earlier tests change staking state, so the current
optimizer anchorWidth differs from the one used at bootstrap time. Instead,
reverse-calculate the implied anchorWidth from the observed anchor spread and
verify it lies within [10, 80].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d9bfedcfcc
commit
2d25200582
2 changed files with 45 additions and 31 deletions
|
|
@ -260,11 +260,15 @@ test.describe('Acquire & Stake', () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('[TEST] Transaction may have completed instantly');
|
console.log('[TEST] Transaction may have completed instantly');
|
||||||
}
|
}
|
||||||
await page.waitForTimeout(3_000);
|
// Poll for Ponder to index the staking transaction (Ponder has indexing latency)
|
||||||
|
console.log('[TEST] Polling GraphQL for staking position (Ponder indexing latency)...');
|
||||||
// Verify staking position via GraphQL
|
let positions: Awaited<ReturnType<typeof fetchPositions>> = [];
|
||||||
console.log('[TEST] Verifying staking position via GraphQL...');
|
for (let attempt = 0; attempt < 15; attempt++) {
|
||||||
const positions = await fetchPositions(request, ACCOUNT_ADDRESS);
|
await page.waitForTimeout(2_000);
|
||||||
|
positions = await fetchPositions(request, ACCOUNT_ADDRESS);
|
||||||
|
if (positions.length > 0) break;
|
||||||
|
console.log(`[TEST] Ponder not yet indexed (attempt ${attempt + 1}/15), retrying...`);
|
||||||
|
}
|
||||||
console.log(`[TEST] Found ${positions.length} position(s)`);
|
console.log(`[TEST] Found ${positions.length} position(s)`);
|
||||||
|
|
||||||
expect(positions.length).toBeGreaterThan(0);
|
expect(positions.length).toBeGreaterThan(0);
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,9 @@ const STACK_RPC_URL = STACK_CONFIG.rpcUrl;
|
||||||
// Solidity function selectors
|
// Solidity function selectors
|
||||||
const GET_LIQUIDITY_PARAMS_SELECTOR = '0xbd53c0dc'; // getLiquidityParams()
|
const GET_LIQUIDITY_PARAMS_SELECTOR = '0xbd53c0dc'; // getLiquidityParams()
|
||||||
const POSITIONS_SELECTOR = '0xf86aafc0'; // positions(uint8)
|
const POSITIONS_SELECTOR = '0xf86aafc0'; // positions(uint8)
|
||||||
// OptimizerV3 known bear-market parameters
|
|
||||||
const BEAR_ANCHOR_SHARE = 3n * 10n ** 17n; // 3e17 = 30%
|
// Optimizer.sol invariants (capitalInefficiency + anchorShare = 1e18)
|
||||||
const BEAR_ANCHOR_WIDTH = 100n;
|
const ONE_ETHER = 10n ** 18n;
|
||||||
const BEAR_DISCOVERY_DEPTH = 3n * 10n ** 17n; // 3e17
|
|
||||||
|
|
||||||
// Position stages
|
// Position stages
|
||||||
const STAGE_FLOOR = 0;
|
const STAGE_FLOOR = 0;
|
||||||
|
|
@ -62,7 +61,7 @@ test.describe('Optimizer Integration', () => {
|
||||||
await validateStackHealthy(STACK_CONFIG);
|
await validateStackHealthy(STACK_CONFIG);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('OptimizerV3 proxy returns valid bear-market parameters', async () => {
|
test('Optimizer proxy returns valid parameters', async () => {
|
||||||
const optimizerAddress = STACK_CONFIG.contracts.OptimizerProxy;
|
const optimizerAddress = STACK_CONFIG.contracts.OptimizerProxy;
|
||||||
if (!optimizerAddress) {
|
if (!optimizerAddress) {
|
||||||
console.log(
|
console.log(
|
||||||
|
|
@ -80,16 +79,19 @@ test.describe('Optimizer Integration', () => {
|
||||||
console.log(`[TEST] anchorWidth: ${params.anchorWidth}`);
|
console.log(`[TEST] anchorWidth: ${params.anchorWidth}`);
|
||||||
console.log(`[TEST] discoveryDepth: ${params.discoveryDepth}`);
|
console.log(`[TEST] discoveryDepth: ${params.discoveryDepth}`);
|
||||||
|
|
||||||
// With no staking activity, OptimizerV3 should return bear-market defaults
|
// Optimizer.sol invariants:
|
||||||
expect(params.capitalInefficiency).toBe(0n);
|
// capitalInefficiency + anchorShare == 1e18
|
||||||
expect(params.anchorShare).toBe(BEAR_ANCHOR_SHARE);
|
expect(params.capitalInefficiency + params.anchorShare).toBe(ONE_ETHER);
|
||||||
expect(params.anchorWidth).toBe(BEAR_ANCHOR_WIDTH);
|
// discoveryDepth == anchorShare
|
||||||
expect(params.discoveryDepth).toBe(BEAR_DISCOVERY_DEPTH);
|
expect(params.discoveryDepth).toBe(params.anchorShare);
|
||||||
|
// anchorWidth in [10, 80]
|
||||||
|
expect(params.anchorWidth).toBeGreaterThanOrEqual(10n);
|
||||||
|
expect(params.anchorWidth).toBeLessThanOrEqual(80n);
|
||||||
|
|
||||||
console.log('[TEST] OptimizerV3 returns correct bear-market parameters');
|
console.log('[TEST] Optimizer returns valid parameters (invariants satisfied)');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('bootstrap positions reflect optimizer anchorWidth=100 parameter', async () => {
|
test('bootstrap positions reflect valid optimizer anchorWidth', async () => {
|
||||||
const lmAddress = STACK_CONFIG.contracts.LiquidityManager;
|
const lmAddress = STACK_CONFIG.contracts.LiquidityManager;
|
||||||
const optimizerAddress = STACK_CONFIG.contracts.OptimizerProxy;
|
const optimizerAddress = STACK_CONFIG.contracts.OptimizerProxy;
|
||||||
if (!optimizerAddress) {
|
if (!optimizerAddress) {
|
||||||
|
|
@ -97,12 +99,11 @@ test.describe('Optimizer Integration', () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read optimizer params
|
// Read anchor position from LM (created by bootstrap's recenter call).
|
||||||
const params = await readLiquidityParams(optimizerAddress);
|
// Note: optimizer state may have changed since bootstrap (e.g. staking activity in
|
||||||
const anchorWidth = Number(params.anchorWidth);
|
// earlier tests), so we don't read the *current* optimizer params here. Instead
|
||||||
console.log(`[TEST] Optimizer anchorWidth: ${anchorWidth}`);
|
// we reverse-calculate the anchorWidth that was in effect when recenter() ran and
|
||||||
|
// verify it falls within Optimizer.sol's valid range [10, 80].
|
||||||
// Read anchor position from LM (created by bootstrap's recenter call)
|
|
||||||
const anchorResult = (await rpcCall('eth_call', [
|
const anchorResult = (await rpcCall('eth_call', [
|
||||||
{
|
{
|
||||||
to: lmAddress,
|
to: lmAddress,
|
||||||
|
|
@ -117,16 +118,25 @@ test.describe('Optimizer Integration', () => {
|
||||||
|
|
||||||
console.log(`[TEST] Anchor position: ticks=[${tickLower}, ${tickUpper}], spread=${anchorSpread}`);
|
console.log(`[TEST] Anchor position: ticks=[${tickLower}, ${tickUpper}], spread=${anchorSpread}`);
|
||||||
|
|
||||||
// Verify the anchor spread matches the optimizer formula:
|
// Reverse the formula to recover anchorWidth:
|
||||||
// anchorSpacing = TICK_SPACING + (34 * anchorWidth * TICK_SPACING / 100)
|
// anchorSpread = 2 * (TICK_SPACING + (34 * anchorWidth * TICK_SPACING / 100))
|
||||||
// For anchorWidth=100: anchorSpacing = 200 + (34 * 100 * 200 / 100) = 200 + 6800 = 7000
|
// => anchorWidth = (anchorSpread / 2 - TICK_SPACING) * 100 / (34 * TICK_SPACING)
|
||||||
// Full anchor = 2 * anchorSpacing = 14000 ticks
|
const halfSpread = anchorSpread / 2;
|
||||||
const expectedSpacing = TICK_SPACING + (34 * anchorWidth * TICK_SPACING) / 100;
|
expect(halfSpread).toBeGreaterThan(TICK_SPACING);
|
||||||
const expectedSpread = expectedSpacing * 2;
|
|
||||||
console.log(`[TEST] Expected anchor spread: ${expectedSpread} (anchorSpacing=${expectedSpacing})`);
|
|
||||||
|
|
||||||
|
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);
|
expect(anchorSpread).toBe(expectedSpread);
|
||||||
console.log('[TEST] Anchor spread matches optimizer anchorWidth=100 formula');
|
|
||||||
|
console.log(`[TEST] Anchor spread ${anchorSpread} corresponds to valid anchorWidth=${impliedAnchorWidth}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('all three positions have valid relative sizing', async () => {
|
test('all three positions have valid relative sizing', async () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue