- Modified sellAllKrk helper to return WETH delta received - Added assertion: WETH received >= 90% of ETH spent (0.09 ETH minimum) - Added log showing actual slippage percentage - This proves 'always leave' with reasonable slippage, not just exit ability
80 lines
4 KiB
TypeScript
80 lines
4 KiB
TypeScript
/**
|
|
* Holdout scenario: sovereign-exit / always-leave
|
|
*
|
|
* Verifies the core protocol invariant: a user can ALWAYS exit their position
|
|
* by buying KRK through the in-app swap widget and then selling it back.
|
|
*
|
|
* Reuses tests/setup/ infrastructure and the shared helpers in
|
|
* scripts/harb-evaluator/helpers/ — no inline wallet, swap, or balance logic.
|
|
*
|
|
* Account 0 from the Anvil test mnemonic is used (same as e2e tests).
|
|
* Deploy scripts also use Account 0, but each test run gets a fresh Anvil stack,
|
|
* so no collision occurs.
|
|
*/
|
|
import { expect, test } from '@playwright/test';
|
|
import { parseEther, Wallet } from 'ethers';
|
|
import { createWalletContext } from '../../../../tests/setup/wallet-provider';
|
|
import { getStackConfig } from '../../../../tests/setup/stack';
|
|
import { connectWallet, getKrkBalance } from '../../helpers/wallet';
|
|
import { buyKrk, sellAllKrk } from '../../helpers/swap';
|
|
|
|
// Anvil account 0 — same as e2e tests (deploy uses it but state is reset per stack)
|
|
const PK = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
|
|
const ACCOUNT_ADDRESS = new Wallet(PK).address;
|
|
|
|
test('I can always leave', async ({ browser }) => {
|
|
const config = getStackConfig();
|
|
const ctx = await createWalletContext(browser, {
|
|
privateKey: PK,
|
|
rpcUrl: config.rpcUrl,
|
|
});
|
|
const page = await ctx.newPage();
|
|
|
|
page.on('console', msg => console.log(`[BROWSER] ${msg.type()}: ${msg.text()}`));
|
|
page.on('pageerror', err => console.log(`[BROWSER ERROR] ${err.message}`));
|
|
|
|
try {
|
|
// ── 1. Load the web app ──────────────────────────────────────────────
|
|
console.log('[TEST] Loading web app...');
|
|
await page.goto(`${config.webAppUrl}/app/`, { waitUntil: 'domcontentloaded' });
|
|
await expect(page.locator('.navbar-title').first()).toBeVisible({ timeout: 30_000 });
|
|
|
|
// ── 2. Connect wallet via the UI ─────────────────────────────────────
|
|
console.log('[TEST] Connecting wallet...');
|
|
await connectWallet(page);
|
|
|
|
// ── 3. Buy KRK via the get-krk page swap widget ───────────────────────
|
|
const krkBefore = await getKrkBalance(config.rpcUrl, config.contracts.Kraiken, ACCOUNT_ADDRESS);
|
|
console.log(`[TEST] KRK balance before buy: ${krkBefore}`);
|
|
|
|
await buyKrk(page, '0.1');
|
|
|
|
const krkAfterBuy = await getKrkBalance(config.rpcUrl, config.contracts.Kraiken, ACCOUNT_ADDRESS);
|
|
console.log(`[TEST] KRK balance after buy: ${krkAfterBuy}`);
|
|
expect(krkAfterBuy).toBeGreaterThan(krkBefore);
|
|
console.log('[TEST] ✅ KRK received');
|
|
|
|
// ── 4. Sell all KRK back (sovereign exit) ────────────────────────────
|
|
const wethReceived = await sellAllKrk(page, {
|
|
rpcUrl: config.rpcUrl,
|
|
krkAddress: config.contracts.Kraiken,
|
|
accountAddress: ACCOUNT_ADDRESS,
|
|
});
|
|
|
|
// ── 5. Assert KRK was sold ────────────────────────────────────────────
|
|
const krkAfterSell = await getKrkBalance(config.rpcUrl, config.contracts.Kraiken, ACCOUNT_ADDRESS);
|
|
console.log(`[TEST] KRK balance after sell: ${krkAfterSell}`);
|
|
expect(krkAfterSell).toBeLessThan(krkAfterBuy);
|
|
console.log('[TEST] ✅ Sovereign exit confirmed: KRK sold back to WETH');
|
|
|
|
// ── 6. Assert reasonable slippage (at least 90% of ETH spent) ─────────
|
|
const ethSpent = parseEther('0.1');
|
|
const minExpected = parseEther('0.09'); // 90% of 0.1 ETH
|
|
expect(wethReceived).toBeGreaterThanOrEqual(minExpected);
|
|
const slippagePercent = ((Number(wethReceived) / Number(ethSpent)) * 100).toFixed(2);
|
|
console.log(`[TEST] ✅ Reasonable slippage: received ${wethReceived} WETH for 0.1 ETH spent (${slippagePercent}%)`);
|
|
|
|
} finally {
|
|
await ctx.close();
|
|
}
|
|
});
|