harb/scripts/harb-evaluator/helpers/assertions.ts
openhands 77a229018a fix: address review feedback on holdout helpers
- Extract rpcCall into helpers/rpc.ts to eliminate the duplicate copy
  in wallet.ts and assertions.ts (warning: code duplication)

- Fix waitForReceipt() in swap.ts to assert receipt.status === '0x1':
  reverted transactions (status 0x0) now throw immediately with a clear
  message instead of letting sellAllKrk silently succeed and fail later
  at the balance assertion (bug)

- Add screen.width debug log to connectWallet() before the isVisible
  check, restoring the regression signal from always-leave.spec.ts (warning)

- Fix expectPoolHasLiquidity() to only assert sqrtPriceX96 > 0 (pool
  is initialised); drop the active-tick liquidity() check which gives
  false negatives when price moves outside all LiquidityManager ranges
  after a sovereign exit (warning)

- Add WETH balance snapshot before/after the swap in sellAllKrk() and
  log a warning when WETH output is 0, making pool health degradation
  visible despite amountOutMinimum: 0n (warning)

- Add before/after screenshots in buyKrk() (holdout-before-buy.png,
  holdout-after-buy.png) to restore CI debugging artefacts (nit)

- Move waitForTimeout(2_000) settle buffer in buyKrk() to the catch
  path only; when the Submitting→idle transition is observed the extra
  wait is redundant (nit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 05:59:21 +00:00

63 lines
2.8 KiB
TypeScript

/**
* Shared assertion helpers for holdout scenarios.
*
* These wrap Playwright's `expect` with protocol-specific checks so that
* scenario specs stay at the "what" level rather than encoding RPC details.
*/
import { expect } from '@playwright/test';
import { Interface } from 'ethers';
import { rpcCall } from './rpc';
// ── Internal helpers ─────────────────────────────────────────────────────────
async function getTokenBalance(rpcUrl: string, token: string, address: string): Promise<bigint> {
if (token.toLowerCase() === 'eth') {
const result = (await rpcCall(rpcUrl, 'eth_getBalance', [address, 'latest'])) as string;
return BigInt(result);
}
const selector = '0x70a08231'; // balanceOf(address)
const data = selector + address.slice(2).padStart(64, '0');
const result = (await rpcCall(rpcUrl, 'eth_call', [{ to: token, data }, 'latest'])) as string;
return BigInt(result);
}
const POOL_ABI = [
'function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)',
];
const poolIface = new Interface(POOL_ABI);
// ── Exported helpers ─────────────────────────────────────────────────────────
/**
* Snapshot the balance of `token` for `address`, run `action`, then assert the
* balance increased.
*
* @param token - An ERC-20 contract address, or the string `'eth'` for native ETH.
*/
export async function expectBalanceIncrease(
rpcUrl: string,
token: string,
address: string,
action: () => Promise<void>,
): Promise<void> {
const before = await getTokenBalance(rpcUrl, token, address);
await action();
const after = await getTokenBalance(rpcUrl, token, address);
expect(after).toBeGreaterThan(before);
}
/**
* Assert that the Uniswap V3 pool at `poolAddress` is initialised.
*
* Checks that sqrtPriceX96 > 0, which confirms the pool has been seeded and
* can execute swaps. Active-tick liquidity is intentionally not checked here:
* after a sovereign exit the LiquidityManager's three range positions (Floor,
* Anchor, Discovery) may all sit outside the current tick while the pool
* itself remains functional.
*/
export async function expectPoolHasLiquidity(rpcUrl: string, poolAddress: string): Promise<void> {
const slot0Encoded = poolIface.encodeFunctionData('slot0', []);
const slot0Hex = (await rpcCall(rpcUrl, 'eth_call', [{ to: poolAddress, data: slot0Encoded }, 'latest'])) as string;
const [sqrtPriceX96] = poolIface.decodeFunctionResult('slot0', slot0Hex);
expect(sqrtPriceX96).toBeGreaterThan(0n);
}