import { parseAbi } from 'viem' import { tempoTestnet } from 'viem/tempo/chains' import * as TestEarn from '../../test/Earn.js' import * as Runtime from '../../test/runtime.js' import * as TestViem from '../../test/Viem.js' import * as Zones from '../apps/Zones.js' import * as EarnVaults from './EarnVaults.js' import * as Viem from './Viem.js' const vaultAddress = '0xF4ae63687d6753A78e7f551d2EDA1D0d31A5ea3A' describe('schema.ResolveInput', () => { test('normalizes the vault address', () => { expect( EarnVaults.schema.ResolveInput.parse({ chainId: tempoTestnet.id, vaultAddress, }), ).toMatchInlineSnapshot(` { "chainId": 42431, "vaultAddress": "0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a", } `) }) test('rejects obsolete resolver fields', () => { const result = EarnVaults.schema.ResolveInput.safeParse({ chainId: tempoTestnet.id, vault: vaultAddress, vaultAddress, }) expect(result.error?.issues[0]).toMatchInlineSnapshot(` { "code": "unrecognized_keys", "keys": [ "vault", ], "message": "Invalid input", "path": [], } `) }) }) describe('schema.ZoneRoute', () => { test('normalizes the router address', () => { expect( EarnVaults.schema.ZoneRoute.parse({ ...TestEarn.zoneRoute, earnRouter: TestEarn.zoneRoute.earnRouter.toUpperCase().replace('0X', '0x'), }), ).toStrictEqual(TestEarn.zoneRoute) }) }) describe('classifyEngineType', () => { test('classifies supported capability fingerprints', () => { expect([ EarnVaults.classifyEngineType({ asyncRedeem: false, inKindDeposit: true }), EarnVaults.classifyEngineType({ asyncRedeem: true, inKindDeposit: false }), ]).toMatchInlineSnapshot(` [ "erc4626", "veda", ] `) }) test('leaves ambiguous capability fingerprints unknown', () => { expect([ EarnVaults.classifyEngineType({ asyncRedeem: false, inKindDeposit: false }), EarnVaults.classifyEngineType({ asyncRedeem: true, inKindDeposit: true }), ]).toMatchInlineSnapshot(` [ null, null, ] `) }) }) describe('resolve', () => { test.runIf(Runtime.get().mode === 'testnet')( 'reads the current Moderato Earn v1 vault', async () => { const getClient = Viem.createGetClient({ defaultChainId: tempoTestnet.id, rpc: TestViem.rpc, zones: [Zones.zoneModeratoInternal], }) const { instantLiquidity, sharePrice, state, ...discovery } = await EarnVaults.resolve({ getClient, input: { chainId: tempoTestnet.id, vaultAddress, }, zones: [Zones.zoneModeratoInternal], }) expect({ depositsPaused: state.depositsPaused, feesActive: state.feesActive, isAccountingAligned: state.isAccountingAligned, openRedeemRequestCount: state.openRedeemRequestCount, pricedPerShare: sharePrice !== null && BigInt(sharePrice) > 0n, }).toMatchInlineSnapshot(` { "depositsPaused": false, "feesActive": true, "isAccountingAligned": true, "openRedeemRequestCount": 0, "pricedPerShare": true, } `) expect(discovery).toMatchInlineSnapshot(` { "access": { "status": "allowlisted", }, "assetToken": { "address": "0x20c000000000000000000000ff04042ee92fd449", "currency": "USD", "decimals": 6, "name": "Bridge Test PATHUSD", "symbol": "btPATHUSD", }, "capabilities": { "asyncRedeem": false, "boundedRedeem": true, "deposit": true, "exactWithdraw": true, "inKindDeposit": true, "privateRouting": false, "redeem": true, "routerSwaps": false, }, "chainId": 42431, "engine": { "address": "0xd85fc943333a6cd7c0e3391acd54fc4e572f0baf", "type": "erc4626", "venue": "0xa8b4f0e69cd4b0e56b676343f02acd8c3b355322", }, "shareToken": { "address": "0x20c0000000000000000000006d04bb298da7edb2", "currency": "USD", "decimals": 6, "name": "Tempo Earn Live Vault (Earn)", "symbol": "teLIVEE", }, "vaultAddress": "0xf4ae63687d6753a78e7f551d2eda1d0d31a5ea3a", } `) // Instant liquidity mirrors the venue's own withdrawal limit capped at the // vault's backing, never a restatement of `state.totalAssets`. The pinned // venue exposes no limit view, so the field stays null here. const venue = discovery.engine.venue expect(venue).not.toBeNull() if (!venue) return const withdrawable = await getClient(tempoTestnet.id) .readContract({ abi: parseAbi(['function maxWithdraw(address owner) view returns (uint256)']), address: venue, args: [discovery.engine.address], functionName: 'maxWithdraw', }) .catch(() => undefined) const backing = BigInt(state.totalAssets) expect([withdrawable === undefined, instantLiquidity]).toStrictEqual([ true, withdrawable === undefined ? null : (withdrawable < backing ? withdrawable : backing).toString(), ]) }, 60_000, ) test.runIf(Runtime.get().mode === 'testnet')( 'resolves the same vault without private routing', async () => { const discovery = await EarnVaults.resolve({ getClient: Viem.createGetClient({ defaultChainId: tempoTestnet.id, rpc: TestViem.rpc, }), input: { chainId: tempoTestnet.id, vaultAddress, }, zones: [], }) expect({ privateRouting: discovery.capabilities.privateRouting, routerSwaps: discovery.capabilities.routerSwaps, }).toMatchInlineSnapshot(` { "privateRouting": false, "routerSwaps": false, } `) }, 60_000, ) }) describe('resolveZoneRoutes', () => { test.runIf(Runtime.get().mode === 'testnet')( 'verifies immutable router bindings and derives private tokens', async () => { const getClient = Viem.createGetClient({ defaultChainId: tempoTestnet.id, rpc: TestViem.rpc, zones: [TestEarn.zone], }) const discovery = await EarnVaults.resolve({ getClient, input: { chainId: tempoTestnet.id, vaultAddress: TestEarn.zoneVaultAddress, }, zones: [TestEarn.zone], }) const routes = await EarnVaults.resolveZoneRoutes({ discovery, getClient, routes: [TestEarn.zoneRoute], zones: [TestEarn.zone], }) expect(routes).toHaveLength(1) expect(routes[0]).toMatchObject({ ...TestEarn.zoneRoute, name: TestEarn.zone.name, }) expect(routes[0]?.inputTokens).toHaveLength(1) expect(routes[0]?.outputTokens).toStrictEqual(routes[0]?.inputTokens) await expect( EarnVaults.resolveZoneRoutes({ discovery, getClient, routes: [TestEarn.zoneRoute, TestEarn.zoneRoute], zones: [TestEarn.zone], }), ).rejects.toThrowErrorMatchingInlineSnapshot( `[EarnVaults.InvalidRegistryError: Zone 1424310003 is configured more than once.]`, ) await expect( EarnVaults.resolveZoneRoutes({ discovery, getClient, routes: [ { ...TestEarn.zoneRoute, deploymentBlock: TestEarn.zoneRoute.deploymentBlock + 1 }, ], zones: [TestEarn.zone], }), ).rejects.toMatchObject({ result: { issues: [ { actual: String(TestEarn.zoneRoute.deploymentBlock + 1), code: 'zone-deployment-block-mismatch', message: 'Earn router deployment block does not match its code boundary.', }, ], status: 'mismatch', }, }) }, 60_000, ) })