/** * Holdout scenario: passive-confidence / no-dilution * * Verifies that a passive holder's balance is not diluted when a new buyer enters. * Two wallets buy KRK sequentially. The first buyer's balance must remain exactly * the same after the second buyer purchases. The second buyer should receive fewer * tokens per ETH due to price impact from the AMM curve. * * Uses Anvil accounts 4 and 5 (distinct from e2e tests which use account 0). */ import { expect, test } from '@playwright/test'; import { Wallet } from 'ethers'; import { createWalletContext } from '../../../../tests/setup/wallet-provider'; import { getStackConfig } from '../../../../tests/setup/stack'; import { connectWallet, getKrkBalance } from '../../helpers/wallet'; import { buyKrk } from '../../helpers/swap'; // Anvil account 4 const PK_A = '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a'; const ADDRESS_A = new Wallet(PK_A).address; // 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 // Anvil account 5 const PK_B = '0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba'; const ADDRESS_B = new Wallet(PK_B).address; // 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc test('passive holders are not diluted', async ({ browser }) => { const config = getStackConfig(); // ── 0. Verify both accounts start with 0 KRK ────────────────────────── const krkInitialA = await getKrkBalance(config.rpcUrl, config.contracts.Kraiken, ADDRESS_A); const krkInitialB = await getKrkBalance(config.rpcUrl, config.contracts.Kraiken, ADDRESS_B); console.log(`[TEST] Wallet A initial KRK: ${krkInitialA}`); console.log(`[TEST] Wallet B initial KRK: ${krkInitialB}`); expect(krkInitialA).toBe(0n); expect(krkInitialB).toBe(0n); console.log('[TEST] ✅ Both wallets start with 0 KRK'); // ── 1. Wallet A buys 1 ETH worth of KRK ──────────────────────────────── console.log('[TEST] Creating wallet context for Wallet A...'); const ctxA = await createWalletContext(browser, { privateKey: PK_A, rpcUrl: config.rpcUrl, }); const pageA = await ctxA.newPage(); pageA.on('console', msg => console.log(`[BROWSER A] ${msg.type()}: ${msg.text()}`)); pageA.on('pageerror', err => console.log(`[BROWSER ERROR A] ${err.message}`)); try { console.log('[TEST] Loading web app for Wallet A...'); await pageA.goto(`${config.webAppUrl}/app/`, { waitUntil: 'domcontentloaded' }); await expect(pageA.locator('.navbar-title').first()).toBeVisible({ timeout: 30_000 }); console.log('[TEST] Connecting Wallet A...'); await connectWallet(pageA); console.log('[TEST] Wallet A buying 1 ETH of KRK...'); await buyKrk(pageA, '1', undefined, 'walletA'); const krkBalanceA = await getKrkBalance(config.rpcUrl, config.contracts.Kraiken, ADDRESS_A); console.log(`[TEST] Wallet A KRK balance after buy: ${krkBalanceA}`); expect(krkBalanceA).toBeGreaterThan(0n); console.log('[TEST] ✅ Wallet A received KRK'); // ── 2. Record A's balance and close context ─────────────────────────── const krkAfterFirstBuy = krkBalanceA; console.log(`[TEST] Recorded Wallet A balance: ${krkAfterFirstBuy}`); } finally { await ctxA.close(); console.log('[TEST] Closed Wallet A context'); } // Re-query A's balance after closing context to ensure we have the final value const krkAfterFirstBuy = await getKrkBalance(config.rpcUrl, config.contracts.Kraiken, ADDRESS_A); console.log(`[TEST] Wallet A final balance after first buy: ${krkAfterFirstBuy}`); // ── 3. Wallet B buys 5 ETH worth of KRK ──────────────────────────────── console.log('[TEST] Creating wallet context for Wallet B...'); const ctxB = await createWalletContext(browser, { privateKey: PK_B, rpcUrl: config.rpcUrl, }); const pageB = await ctxB.newPage(); pageB.on('console', msg => console.log(`[BROWSER B] ${msg.type()}: ${msg.text()}`)); pageB.on('pageerror', err => console.log(`[BROWSER ERROR B] ${err.message}`)); try { console.log('[TEST] Loading web app for Wallet B...'); await pageB.goto(`${config.webAppUrl}/app/`, { waitUntil: 'domcontentloaded' }); await expect(pageB.locator('.navbar-title').first()).toBeVisible({ timeout: 30_000 }); console.log('[TEST] Connecting Wallet B...'); await connectWallet(pageB); console.log('[TEST] Wallet B buying 5 ETH of KRK...'); await buyKrk(pageB, '5', undefined, 'walletB'); const krkBalanceB = await getKrkBalance(config.rpcUrl, config.contracts.Kraiken, ADDRESS_B); console.log(`[TEST] Wallet B KRK balance after buy: ${krkBalanceB}`); expect(krkBalanceB).toBeGreaterThan(0n); console.log('[TEST] ✅ Wallet B received KRK'); } finally { await ctxB.close(); console.log('[TEST] Closed Wallet B context'); } // ── 4. Verify A's balance unchanged (no dilution) ────────────────────── const krkBalanceAFinal = await getKrkBalance(config.rpcUrl, config.contracts.Kraiken, ADDRESS_A); console.log(`[TEST] Wallet A balance after Wallet B buy: ${krkBalanceAFinal}`); console.log(`[TEST] Expected: ${krkAfterFirstBuy}`); expect(krkBalanceAFinal).toBe(krkAfterFirstBuy); console.log('[TEST] ✅ Wallet A balance unchanged — no dilution'); // ── 5. Verify B got fewer tokens per ETH due to price impact ─────────── const krkBalanceBFinal = await getKrkBalance(config.rpcUrl, config.contracts.Kraiken, ADDRESS_B); const tokensPerEthA = krkAfterFirstBuy / 1n; // Bought with 1 ETH const tokensPerEthB = krkBalanceBFinal / 5n; // Bought with 5 ETH console.log(`[TEST] Wallet A tokens per ETH: ${tokensPerEthA}`); console.log(`[TEST] Wallet B tokens per ETH: ${tokensPerEthB}`); expect(tokensPerEthB).toBeLessThan(tokensPerEthA); console.log('[TEST] ✅ Wallet B got fewer tokens per ETH (price impact verified)'); // ── Summary ───────────────────────────────────────────────────────────── console.log(''); console.log('═══════════════════════════════════════════════════════════'); console.log(' PASSIVE CONFIDENCE: NO DILUTION TEST RESULTS'); console.log('═══════════════════════════════════════════════════════════'); console.log(` Wallet A (1 ETH): ${krkAfterFirstBuy} KRK (${tokensPerEthA} per ETH)`); console.log(` Wallet B (5 ETH): ${krkBalanceBFinal} KRK (${tokensPerEthB} per ETH)`); console.log(` A's balance after: ${krkBalanceAFinal} KRK (unchanged ✓)`); console.log(` Price impact: ${((10000n - tokensPerEthB * 10000n / tokensPerEthA) / 100n)}% worse for B`); console.log('═══════════════════════════════════════════════════════════'); });