import * as http from 'node:http' import * as consumers from 'node:stream/consumers' import type { Address } from 'viem' import { Abis } from 'viem/tempo' import * as TestEarn from '../../../test/Earn.js' import * as Runtime from '../../../test/runtime.js' import * as TestViem from '../../../test/Viem.js' import * as Viem from '../Viem.js' import * as Runner from './Runner.js' describe.runIf(Runtime.get().mode === 'testnet')('capitalStates', () => { test('batches a full 300-second interval while retaining each historical state', async ({ onTestFinished, }) => { const { batches, client, close } = await observer() onTestFinished(close) const head = await client.getBlockNumber() const boundaries = Array.from({ length: 300 }, (_, index) => ({ blockNumber: Number(head) - 299 + index, timestamp: index, })) batches.length = 0 const states = await Runner.capitalStates(client, { boundaries, vaultAddress: TestEarn.zoneVaultAddress, }) expect(states.map(({ blockNumber, timestamp }) => ({ blockNumber, timestamp }))).toStrictEqual( boundaries, ) expect({ maximumBatch: Math.max(...batches.map((batch) => batch.length)), requests: batches.length, totalCalls: batches.flat().length, }).toMatchInlineSnapshot(` { "maximumBatch": 64, "requests": 10, "totalCalls": 600, } `) expect(batches.flat().sort((a, b) => a - b)).toStrictEqual( boundaries.flatMap(({ blockNumber }) => [blockNumber, blockNumber]), ) for (const index of [0, 149, 299]) { const state = states[index]! const blockNumber = BigInt(state.blockNumber) const vault = { abi: Abis.earnVault, address: TestEarn.zoneVaultAddress, blockNumber, } as const const [assets, fees, price, supply] = await Promise.all([ client.readContract({ ...vault, functionName: 'totalAssets' }), client.readContract({ ...vault, functionName: 'earnFees' }), client.readContract({ ...vault, args: [10n ** 18n], functionName: 'previewRedeem' }), client.readContract({ ...vault, functionName: 'totalEarnShares' }), ]) const preview = await client.readContract({ abi: Abis.earnFees, address: fees, blockNumber, functionName: 'previewAccruedFees', }) expect(state).toStrictEqual({ ...boundaries[index], openingAssets: assets, openingEarnShareSupply: supply + preview.totalFeeEarnShares, openingValuePerEarnShare: price, }) } }) test('keeps separate clients at the same RPC URL in separate HTTP batches', async ({ onTestFinished, }) => { const { batches, client, close, rpc } = await observer() onTestFinished(close) const head = await client.getBlockNumber() const other = Viem.createBatchClient({ chainId: TestEarn.chain.id, rpc }) batches.length = 0 const options = { boundaries: [{ blockNumber: Number(head), timestamp: 0 }], vaultAddress: TestEarn.zoneVaultAddress, } as const const states = await Promise.all([ Runner.capitalStates(client, options), Runner.capitalStates(other, options), ]) expect(states[0]).toStrictEqual(states[1]) expect(batches.map((batch) => batch.length)).toMatchInlineSnapshot(` [ 1, 1, 1, 1, ] `) const signal = new AbortController().signal batches.length = 0 await Promise.all([ client.request({ method: 'eth_blockNumber' }, { signal }), other.request({ method: 'eth_blockNumber' }, { signal }), ]) expect(batches.map((batch) => batch.length)).toMatchInlineSnapshot(` [ 1, 1, ] `) }) test('rejects an unavailable historical vault without starting subsequent waves', async ({ onTestFinished, }) => { const { batches, client, close } = await observer() onTestFinished(close) const head = await client.getBlockNumber() batches.length = 0 await expect( Runner.capitalStates(client, { boundaries: Array.from({ length: 300 }, (_, index) => ({ blockNumber: Number(head) - 299 + index, timestamp: index, })), vaultAddress: `0x${'00'.repeat(18)}dead` as Address, }), ).rejects.toThrow() expect({ maximumBatch: Math.max(...batches.map((batch) => batch.length)), requests: batches.length, }).toMatchInlineSnapshot(` { "maximumBatch": 64, "requests": 1, } `) }) }) async function observer() { const { basicAuth, bearerAuth, url } = Viem.resolveRpc(TestViem.rpc, { chainId: TestEarn.chain.id, principal: null, }) if (!url) throw new Error('Missing test RPC URL.') const batches: number[][] = [] const server = http.createServer((request, response) => { void (async () => { const body = await consumers.text(request) type Call = { params?: readonly unknown[] | undefined } const parsed = JSON.parse(body) as Call | Call[] const calls = Array.isArray(parsed) ? parsed : [parsed] batches.push(calls.map((call) => Number(call.params?.[1]))) const upstream = await fetch(url, { body, headers: { ...(request.headers.authorization ? { Authorization: request.headers.authorization } : {}), 'Content-Type': 'application/json', }, method: 'POST', }) response.writeHead(upstream.status, { 'Content-Type': 'application/json' }) response.end(await upstream.text()) })().catch((error) => response.destroy(error)) }) await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) const address = server.address() if (!address || typeof address === 'string') throw new Error('Missing test server address.') const rpc = { auth: basicAuth ?? bearerAuth, url: `http://127.0.0.1:${address.port}` } const client = Viem.createBatchClient({ chainId: TestEarn.chain.id, rpc, }) return { batches, client, close: () => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())), ), rpc, } }