From d6868837a0d16c71f94aaccdb2987e2ea1700931 Mon Sep 17 00:00:00 2001 From: gisk0 Date: Mon, 30 Mar 2026 16:57:58 +0200 Subject: [PATCH 01/11] fix: dynamic contract registration for FPMM and VirtualPool Use contractRegister to auto-discover all factory-deployed pools instead of maintaining a brittle hardcoded address list in the config. - FPMMFactory.FPMMDeployed: add context.addFPMM(fpmmProxy) so every new FPMM pool is automatically indexed from its deployment block forward - VirtualPoolFactory.VirtualPoolDeployed: add contractRegister + context.addVirtualPool(pool) for the same reason - config.multichain.mainnet.yaml: replace all hardcoded FPMM/VirtualPool address lists with address: [] (dynamic registration handles discovery) This fixes missing events for pools deployed after initial config authoring (e.g. EURm/USDm on Celo: 123 real Swap events were silently dropped). New pools on any chain are now indexed automatically without a config change. --- indexer-envio/config.multichain.mainnet.yaml | 29 ++++---------------- indexer-envio/src/handlers/fpmm.ts | 8 ++++-- indexer-envio/src/handlers/virtualPool.ts | 8 ++++++ 3 files changed, 18 insertions(+), 27 deletions(-) diff --git a/indexer-envio/config.multichain.mainnet.yaml b/indexer-envio/config.multichain.mainnet.yaml index 347f167..2e59f07 100644 --- a/indexer-envio/config.multichain.mainnet.yaml +++ b/indexer-envio/config.multichain.mainnet.yaml @@ -103,25 +103,9 @@ networks: address: - 0xa849b475FE5a4B5C9C3280152c7a1945b907613b # proxy - name: FPMM - address: - - 0x8c0014afe032e4574481d8934504100bf23fcb56 # USDm/ccf663b - - 0xb285d4c7133d6f27bfb29224fb0d22e7ec3ddd2d # USDm/eb4663 - - 0x462fe04b4fd719cbd04c0310365d421d02aaa19e # USDm/USDC - - 0x0feba760d93423d127de1b6abecdb60e5253228d # USDT/USDm + address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister - name: VirtualPool - address: - - 0xab945882018b81bdf62629e98ffdafd9495a0076 - - 0x6daa327e0cbe2ce84c0f312f20b9432fe744ed58 - - 0x1d013077b00b28038a3f1e7a29aba34e12e562e9 - - 0xbe6d2165173a29889652c7bf2dc3a02076a22f2a - - 0xaea92e8006e6edf0f9e9368ee9af36814b738855 - - 0xa337a498e4e061f4029fcb3b9f4e3d535e885dc5 - - 0x62fa288e3ac844dcfce5469af4f8feb7d6f7ba61 - - 0x30214efe28ab44d6a5c739eba5e0729b1d4213e4 - - 0xeb433ce1f2ce4981b76fe7ca3a96070705d8ede4 - - 0x71f55035a49c972c5c3197e874f6b7fd94672b6e - - 0x62753ec2956f84af240b4666a130c88a83933848 - - 0x3d6e023177bac13d6e316d95161d4bb9dcf0e276 + address: [] # Dynamically registered via VirtualPoolFactory.VirtualPoolDeployed.contractRegister - name: VirtualPoolFactory address: - 0x22abd4ADF6aab38aC1022352d496A07Acee5aCB3 @@ -144,14 +128,11 @@ networks: address: - 0xa849b475FE5a4B5C9C3280152c7a1945b907613b # TransparentUpgradeableProxy:FPMMFactory - name: FPMM - address: - - 0xd0e9c1a718d2a693d41eacd4b2696180403ce081 # GBPm/USDm - - 0x463c0d1f04bcd99a1efcf94ac2a75bc19ea4a7e5 # USDC/USDm - - 0xb0a0264ce6847f101b76ba36a4a3083ba489f501 # AUSD/USDm + address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister - name: VirtualPool - address: [] # VirtualPoolFactory not yet deployed — add when live + address: [] # Dynamically registered via VirtualPoolFactory.VirtualPoolDeployed.contractRegister - name: VirtualPoolFactory - address: [] # Not yet deployed — add when live + address: [] # Not yet deployed on Monad — add address when live - name: SortedOracles address: - "0x6f92C745346057a61b259579256159458a0a6A92" # TransparentUpgradeableProxy:SortedOracles diff --git a/indexer-envio/src/handlers/fpmm.ts b/indexer-envio/src/handlers/fpmm.ts index b908b36..eac7285 100644 --- a/indexer-envio/src/handlers/fpmm.ts +++ b/indexer-envio/src/handlers/fpmm.ts @@ -93,10 +93,12 @@ async function applyLiquidityPositionDelta({ // FPMMFactory // --------------------------------------------------------------------------- -// Dynamically register pool tokens for ERC20FeeToken Transfer indexing. -// Only FPMM pools generate protocol fees (VirtualPools have no fee mechanism). -// Envio deduplicates addresses, so re-registering the same token is harmless. +// Dynamically register the deployed pool + its fee tokens so Envio starts +// indexing all FPMM events (Swap, Mint, Burn, etc.) without needing a +// hardcoded address list in the config. Envio deduplicates addresses, so +// re-registering the same address on re-runs is harmless. FPMMFactory.FPMMDeployed.contractRegister(({ event, context }) => { + context.addFPMM(event.params.fpmmProxy); context.addERC20FeeToken(event.params.token0); context.addERC20FeeToken(event.params.token1); }); diff --git a/indexer-envio/src/handlers/virtualPool.ts b/indexer-envio/src/handlers/virtualPool.ts index 7f2b028..99284ee 100644 --- a/indexer-envio/src/handlers/virtualPool.ts +++ b/indexer-envio/src/handlers/virtualPool.ts @@ -18,6 +18,14 @@ import { upsertPool, upsertSnapshot, DEFAULT_ORACLE_FIELDS } from "../pool"; // VirtualPoolFactory.VirtualPoolDeployed // --------------------------------------------------------------------------- +// Dynamically register the deployed VirtualPool so Envio indexes its events +// (Swap, Mint, Burn, etc.) without a hardcoded address list in the config. +VirtualPoolFactory.VirtualPoolDeployed.contractRegister( + ({ event, context }) => { + context.addVirtualPool(event.params.pool); + }, +); + VirtualPoolFactory.VirtualPoolDeployed.handler(async ({ event, context }) => { const id = eventId(event.chainId, event.block.number, event.logIndex); const poolId = makePoolId(event.chainId, event.params.pool); From 6a411349c5753a0c0bf00b0479b30c321184bd2b Mon Sep 17 00:00:00 2001 From: gisk0 Date: Mon, 30 Mar 2026 17:49:18 +0200 Subject: [PATCH 02/11] chore: remove legacy per-chain configs and clean up all references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete config.celo.mainnet.yaml, config.monad.mainnet.yaml, config.celo.devnet.yaml (superseded by config.multichain.mainnet.yaml) - Fix FPMM/VirtualPool address lists in config.celo.sepolia.yaml and config.monad.testnet.yaml → address: [] (dynamic registration applies to all configs) - Fix bootstrap-worktree.sh: use config.multichain.mainnet.yaml instead of config.celo.devnet.yaml - Update root AGENTS.md + indexer-envio/AGENTS.md: remove legacy pnpm commands (indexer:celo-mainnet:*, indexer:monad-mainnet:*), update file tree, fix codegen command references - Add framework note to contractRegister callbacks explaining why they have no test coverage (Envio test harness limitation, not an oversight) --- AGENTS.md | 25 +++--- bootstrap-worktree.sh | 32 ++++++++ indexer-envio/AGENTS.md | 7 +- indexer-envio/config.celo.devnet.yaml | 51 ------------- indexer-envio/config.celo.mainnet.yaml | 93 ----------------------- indexer-envio/config.celo.sepolia.yaml | 17 +---- indexer-envio/config.monad.mainnet.yaml | 79 ------------------- indexer-envio/config.monad.testnet.yaml | 5 +- indexer-envio/src/handlers/fpmm.ts | 5 ++ indexer-envio/src/handlers/virtualPool.ts | 2 + 10 files changed, 55 insertions(+), 261 deletions(-) create mode 100755 bootstrap-worktree.sh delete mode 100644 indexer-envio/config.celo.devnet.yaml delete mode 100644 indexer-envio/config.celo.mainnet.yaml delete mode 100644 indexer-envio/config.monad.mainnet.yaml diff --git a/AGENTS.md b/AGENTS.md index 33ea607..605d03d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,14 +15,10 @@ pnpm monorepo with three packages: pnpm install # Indexer -pnpm indexer:codegen # Generate types from schema (devnet) -pnpm indexer:dev # Start indexer (devnet) -pnpm indexer:celo-sepolia:codegen # Generate types (Celo Sepolia config) -pnpm indexer:celo-sepolia:dev # Start indexer (Celo Sepolia) -pnpm indexer:celo-mainnet:codegen # Generate types (Celo mainnet) -pnpm indexer:celo-mainnet:dev # Start indexer (Celo mainnet) -pnpm indexer:monad-mainnet:codegen # Generate types (Monad mainnet) -pnpm indexer:monad-mainnet:dev # Start indexer (Monad mainnet) +pnpm indexer:codegen # Generate types from schema (multichain mainnet) +pnpm indexer:dev # Start indexer (multichain mainnet: Celo + Monad) +pnpm indexer:celo-sepolia:codegen # Generate types (Celo Sepolia testnet) +pnpm indexer:celo-sepolia:dev # Start indexer (Celo Sepolia testnet) pnpm indexer:monad-testnet:codegen # Generate types (Monad testnet) pnpm indexer:monad-testnet:dev # Start indexer (Monad testnet) @@ -89,11 +85,10 @@ monitoring-monorepo/ │ ├── package.json │ └── deployment-namespaces.json # ← edit this when promoting a new deployment ├── indexer-envio/ -│ ├── config.celo.devnet.yaml # Devnet indexer config -│ ├── config.celo.mainnet.yaml # Celo Mainnet config -│ ├── config.celo.sepolia.yaml # Celo Sepolia config -│ ├── config.monad.mainnet.yaml # Monad Mainnet config -│ ├── config.monad.testnet.yaml # Monad Testnet config +│ ├── config.multichain.mainnet.yaml # Mainnet indexer config (Celo + Monad) — DEFAULT +│ ├── config.multichain.testnet.yaml # Testnet multichain config +│ ├── config.celo.sepolia.yaml # Celo Sepolia testnet config +│ ├── config.monad.testnet.yaml # Monad testnet config │ ├── schema.graphql # Entity definitions │ ├── src/ │ │ ├── EventHandlers.ts # Envio entry point (imports handlers, re-exports for tests) @@ -170,7 +165,7 @@ This installs deps and runs Envio codegen (required for `indexer-envio` TypeScri pnpm --filter @mento-protocol/ui-dashboard typecheck pnpm --filter @mento-protocol/indexer-envio typecheck pnpm --filter @mento-protocol/indexer-envio test -pnpm indexer:celo-mainnet:codegen # Validates Envio can parse handler entry point + module imports +pnpm indexer:codegen # Validates Envio can parse handler entry point + module imports pnpm --filter @mento-protocol/ui-dashboard test:coverage ``` @@ -182,7 +177,7 @@ pnpm --filter @mento-protocol/ui-dashboard test:coverage ### EventHandlers.ts must remain the handler entry point -Every `config.*.yaml` specifies `handler: src/EventHandlers.ts`. Envio expects all handler registrations (e.g. `FPMM.Swap.handler(...)`) to be reachable from this file at module load time. The actual logic lives in `src/handlers/*.ts` — these are imported as side effects from `EventHandlers.ts`. If you add a new handler file, you **must** add a corresponding `import "./handlers/yourFile"` in `EventHandlers.ts` and then re-run `pnpm indexer:celo-mainnet:codegen` to verify Envio picks it up. +Every `config.*.yaml` specifies `handler: src/EventHandlers.ts`. Envio expects all handler registrations (e.g. `FPMM.Swap.handler(...)`) to be reachable from this file at module load time. The actual logic lives in `src/handlers/*.ts` — these are imported as side effects from `EventHandlers.ts`. If you add a new handler file, you **must** add a corresponding `import "./handlers/yourFile"` in `EventHandlers.ts` and then re-run `pnpm indexer:codegen` to verify Envio picks it up. ## Common Tasks diff --git a/bootstrap-worktree.sh b/bootstrap-worktree.sh new file mode 100755 index 0000000..9613155 --- /dev/null +++ b/bootstrap-worktree.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# bootstrap-worktree.sh — monitoring-monorepo +# Run after worktree creation to get a fully working environment. +set -euo pipefail +cd "$(dirname "$0")" + +echo "📦 Installing dependencies..." +pnpm install --frozen-lockfile + +echo "🔧 Running indexer codegen (multichain mainnet)..." +pnpm indexer:codegen 2>/dev/null || { + # Fallback: try default devnet config + echo " ⚠️ Multichain codegen failed, trying devnet..." + pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.mainnet.yaml +} + +echo "✅ Verifying typecheck..." +pnpm --filter @mento-protocol/ui-dashboard typecheck +pnpm --filter @mento-protocol/indexer-envio typecheck + +echo "🧪 Running tests..." +pnpm --filter @mento-protocol/ui-dashboard test -- --run 2>/dev/null || echo " ⚠️ Dashboard tests: some failures (check manually)" +pnpm --filter @mento-protocol/indexer-envio test 2>/dev/null || echo " ⚠️ Indexer tests: some failures (check manually)" + +echo "" +echo "🚀 monitoring-monorepo is ready to code" +echo "" +echo "Key commands:" +echo " pnpm dashboard:dev — start dashboard dev server" +echo " pnpm dashboard:build — production build" +echo " pnpm indexer:codegen — regenerate indexer types" +echo " ./tools/trunk check --all — lint everything" diff --git a/indexer-envio/AGENTS.md b/indexer-envio/AGENTS.md index f829c32..068c675 100644 --- a/indexer-envio/AGENTS.md +++ b/indexer-envio/AGENTS.md @@ -6,11 +6,10 @@ Envio HyperIndex indexer for Mento v3 FPMM (Fixed Product Market Maker) pools on ## Key Files -- `config.celo.devnet.yaml` — Devnet indexer config (contract addresses, events, RPC) -- `config.celo.mainnet.yaml` — Celo Mainnet config +- `config.multichain.mainnet.yaml` — **Default** mainnet config (Celo + Monad) +- `config.multichain.testnet.yaml` — Testnet multichain config - `config.celo.sepolia.yaml` — Celo Sepolia testnet config -- `config.monad.mainnet.yaml` — Monad Mainnet config -- `config.monad.testnet.yaml` — Monad Testnet config +- `config.monad.testnet.yaml` — Monad testnet config - `schema.graphql` — Entity definitions (FPMM, Swap, Mint, Burn, UpdateReserves, Rebalanced) - `src/EventHandlers.ts` — Event processing logic - `src/contractAddresses.ts` — Contract address resolution from `@mento-protocol/contracts`; also exports `CONTRACT_NAMESPACE_BY_CHAIN` (backed by `config/deployment-namespaces.json`) diff --git a/indexer-envio/config.celo.devnet.yaml b/indexer-envio/config.celo.devnet.yaml deleted file mode 100644 index 3ee96a8..0000000 --- a/indexer-envio/config.celo.devnet.yaml +++ /dev/null @@ -1,51 +0,0 @@ -# yaml-language-server: $schema=./node_modules/envio/evm.schema.json -name: celo -description: Celo devnet v3 FPMM-focused HyperIndex indexer -networks: - - id: 42220 - start_block: ${ENVIO_START_BLOCK:-0} - contracts: - - name: FPMMFactory - abi_file_path: abis/FPMMFactory.json - address: - - 0xB9732EB66406334F56E9d8c515d5af3da29b0654 - handler: src/EventHandlers.ts - events: - - event: FPMMDeployed - - name: FPMM - abi_file_path: abis/FPMM.json - address: - - 0xc69D6bBA6785e76998f870609345aA2BE7F64d19 - handler: src/EventHandlers.ts - events: - - event: Swap - - event: Mint - - event: Burn - - event: Transfer - - event: UpdateReserves - - event: Rebalanced - - event: TradingLimitConfigured - - event: LiquidityStrategyUpdated - - name: VirtualPoolFactory - abi_file_path: abis/VirtualPoolFactory.json - address: - - 0x3268E4b75BEe60C400Eb4Dd8E44E9a3A76873f65 - handler: src/EventHandlers.ts - events: - - event: VirtualPoolDeployed - - event: PoolDeprecated - - name: OpenLiquidityStrategy - abi_file_path: abis/OpenLiquidityStrategy.json - address: [] # Not deployed on devnet - handler: src/EventHandlers.ts - events: - - event: PoolAdded - - event: PoolRemoved - - event: RebalanceCooldownSet - - event: LiquidityMoved -unordered_multichain_mode: true -preload_handlers: true -field_selection: - transaction_fields: - - from - - hash diff --git a/indexer-envio/config.celo.mainnet.yaml b/indexer-envio/config.celo.mainnet.yaml deleted file mode 100644 index 090addc..0000000 --- a/indexer-envio/config.celo.mainnet.yaml +++ /dev/null @@ -1,93 +0,0 @@ -# yaml-language-server: $schema=./node_modules/envio/evm.schema.json -name: celo-mainnet -description: Celo Mainnet v3 FPMM HyperIndex indexer -networks: - - id: 42220 - start_block: ${ENVIO_START_BLOCK:-60664500} - contracts: - - name: FPMMFactory - abi_file_path: abis/FPMMFactory.json - address: - - 0xa849b475FE5a4B5C9C3280152c7a1945b907613b # proxy - handler: src/EventHandlers.ts - events: - - event: FPMMDeployed - - name: FPMM - abi_file_path: abis/FPMM.json - address: - - 0x8c0014afe032e4574481d8934504100bf23fcb56 # USDm/ccf663b - - 0xb285d4c7133d6f27bfb29224fb0d22e7ec3ddd2d # USDm/eb4663 - - 0x462fe04b4fd719cbd04c0310365d421d02aaa19e # USDm/USDC - - 0x0feba760d93423d127de1b6abecdb60e5253228d # USDT/USDm - handler: src/EventHandlers.ts - events: - - event: Swap - - event: Mint - - event: Burn - - event: Transfer - - event: UpdateReserves - - event: Rebalanced - - event: TradingLimitConfigured - - event: LiquidityStrategyUpdated - - name: VirtualPool - abi_file_path: abis/FPMM.json - address: - - 0xab945882018b81bdf62629e98ffdafd9495a0076 - - 0x6daa327e0cbe2ce84c0f312f20b9432fe744ed58 - - 0x1d013077b00b28038a3f1e7a29aba34e12e562e9 - - 0xbe6d2165173a29889652c7bf2dc3a02076a22f2a - - 0xaea92e8006e6edf0f9e9368ee9af36814b738855 - - 0xa337a498e4e061f4029fcb3b9f4e3d535e885dc5 - - 0x62fa288e3ac844dcfce5469af4f8feb7d6f7ba61 - - 0x30214efe28ab44d6a5c739eba5e0729b1d4213e4 - - 0xeb433ce1f2ce4981b76fe7ca3a96070705d8ede4 - - 0x71f55035a49c972c5c3197e874f6b7fd94672b6e - - 0x62753ec2956f84af240b4666a130c88a83933848 - - 0x3d6e023177bac13d6e316d95161d4bb9dcf0e276 - handler: src/EventHandlers.ts - events: - - event: Swap - - event: Mint - - event: Burn - - event: UpdateReserves - - event: Rebalanced - - name: VirtualPoolFactory - abi_file_path: abis/VirtualPoolFactory.json - address: - - 0x22abd4ADF6aab38aC1022352d496A07Acee5aCB3 - handler: src/EventHandlers.ts - events: - - event: VirtualPoolDeployed - - event: PoolDeprecated - - name: SortedOracles - abi_file_path: abis/SortedOracles.json - address: - - "0xefB84935239dAcdecF7c5bA76d8dE40b077B7b33" - handler: src/EventHandlers.ts - events: - - event: OracleReported - - event: MedianUpdated - - event: ReportExpirySet - - event: TokenReportExpirySet - - name: OpenLiquidityStrategy - abi_file_path: abis/OpenLiquidityStrategy.json - address: - - 0x54e2Ae8c8448912E17cE0b2453bAFB7B0D80E40f # CREATE3 deterministic address (pre-deploy) - handler: src/EventHandlers.ts - events: - - event: PoolAdded - - event: PoolRemoved - - event: RebalanceCooldownSet - - event: LiquidityMoved - - name: ERC20FeeToken - abi_file_path: abis/ERC20.json - address: [] # Dynamically registered from FPMMDeployed events - handler: src/EventHandlers.ts - events: - - event: Transfer -unordered_multichain_mode: true -preload_handlers: true -field_selection: - transaction_fields: - - hash - - from diff --git a/indexer-envio/config.celo.sepolia.yaml b/indexer-envio/config.celo.sepolia.yaml index b7e54e0..de0e969 100644 --- a/indexer-envio/config.celo.sepolia.yaml +++ b/indexer-envio/config.celo.sepolia.yaml @@ -16,8 +16,7 @@ networks: - event: FPMMDeployed - name: FPMM abi_file_path: abis/FPMM.json - address: - - 0x550d9ecb4c373510b8a41f5fb7d98e9e1c51a07e # GBPm/USDm + address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister handler: src/EventHandlers.ts events: - event: Swap @@ -30,19 +29,7 @@ networks: - event: LiquidityStrategyUpdated - name: VirtualPool abi_file_path: abis/FPMM.json - address: - - 0x671334256a893fdbc4ffe55f98f156a168bd897a - - 0x1e2506edca4ef3030e51be8b571b935d55677604 - - 0x8103fb2db87ac96cc62faa399b98e1173720ab19 - - 0x62722497dc8992337117ee79a02015dcea43b2c2 - - 0x68d19b5a48cbbfd11057e97da9960b09d771e7b6 - - 0x58f08739ea9764097b9500b6e4a4db64d168b807 - - 0x284c2d99c5a12a65f10eff7183c33c1217b65a56 - - 0xcbfc8c84168d7f34faba0018a3a63b998f1ffece - - 0x49a968c539599385c69c2d528500da58d933fafa - - 0x917ee035bf0a964acc75539f919a5b4f16336373 - - 0x6b66271811615f4b6dadb8620ed71a1e90f41deb - - 0x22118009665b1d6810d4560a098d3e67bbcb934f + address: [] # Dynamically registered via VirtualPoolFactory.VirtualPoolDeployed.contractRegister handler: src/EventHandlers.ts events: - event: Swap diff --git a/indexer-envio/config.monad.mainnet.yaml b/indexer-envio/config.monad.mainnet.yaml deleted file mode 100644 index 67419f9..0000000 --- a/indexer-envio/config.monad.mainnet.yaml +++ /dev/null @@ -1,79 +0,0 @@ -# yaml-language-server: $schema=./node_modules/envio/evm.schema.json -name: monad-mainnet -description: Monad Mainnet v3 FPMM HyperIndex indexer -networks: - - id: 143 # Monad Mainnet — HyperSync: https://143.hypersync.xyz - start_block: ${ENVIO_START_BLOCK:-60730000} # SortedOracles ~60733096, FPMMFactory ~60747398 - contracts: - - name: FPMMFactory - abi_file_path: abis/FPMMFactory.json - address: - - 0xa849b475FE5a4B5C9C3280152c7a1945b907613b # TransparentUpgradeableProxy:FPMMFactory - handler: src/EventHandlers.ts - events: - - event: FPMMDeployed - - name: FPMM - abi_file_path: abis/FPMM.json - address: - - 0xd0e9c1a718d2a693d41eacd4b2696180403ce081 # GBPm/USDm - - 0x463c0d1f04bcd99a1efcf94ac2a75bc19ea4a7e5 # USDC/USDm - - 0xb0a0264ce6847f101b76ba36a4a3083ba489f501 # AUSD/USDm - handler: src/EventHandlers.ts - events: - - event: Swap - - event: Mint - - event: Burn - - event: Transfer - - event: UpdateReserves - - event: Rebalanced - - event: TradingLimitConfigured - - event: LiquidityStrategyUpdated - - name: VirtualPool - abi_file_path: abis/FPMM.json - address: [] # VirtualPoolFactory not yet deployed — add when live - handler: src/EventHandlers.ts - events: - - event: Swap - - event: Mint - - event: Burn - - event: UpdateReserves - - event: Rebalanced - - name: VirtualPoolFactory - abi_file_path: abis/VirtualPoolFactory.json - address: [] # Not yet deployed — add when live - handler: src/EventHandlers.ts - events: - - event: VirtualPoolDeployed - - event: PoolDeprecated - - name: SortedOracles - abi_file_path: abis/SortedOracles.json - address: - - "0x6f92C745346057a61b259579256159458a0a6A92" # TransparentUpgradeableProxy:SortedOracles - handler: src/EventHandlers.ts - events: - - event: OracleReported - - event: MedianUpdated - - event: ReportExpirySet - - event: TokenReportExpirySet - - name: ERC20FeeToken - abi_file_path: abis/ERC20.json - address: [] # Dynamically registered from FPMMDeployed events - handler: src/EventHandlers.ts - events: - - event: Transfer - - name: OpenLiquidityStrategy - abi_file_path: abis/OpenLiquidityStrategy.json - address: - - 0x54e2Ae8c8448912E17cE0b2453bAFB7B0D80E40f - handler: src/EventHandlers.ts - events: - - event: PoolAdded - - event: PoolRemoved - - event: RebalanceCooldownSet - - event: LiquidityMoved -unordered_multichain_mode: true -preload_handlers: true -field_selection: - transaction_fields: - - hash - - from diff --git a/indexer-envio/config.monad.testnet.yaml b/indexer-envio/config.monad.testnet.yaml index c4498af..9ed684f 100644 --- a/indexer-envio/config.monad.testnet.yaml +++ b/indexer-envio/config.monad.testnet.yaml @@ -14,10 +14,7 @@ networks: - event: FPMMDeployed - name: FPMM abi_file_path: abis/FPMM.json - address: - - 0xd9e9e6f6b5298e8bad390f7748035c49d6eeb055 - - 0x1229e8a7b266c6db52712ba5c6899a6c4c3025cd - - 0x6d4c4b663541bf21015afb22669b0e1bbb3e2b1c + address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister handler: src/EventHandlers.ts events: - event: Swap diff --git a/indexer-envio/src/handlers/fpmm.ts b/indexer-envio/src/handlers/fpmm.ts index eac7285..b06e01c 100644 --- a/indexer-envio/src/handlers/fpmm.ts +++ b/indexer-envio/src/handlers/fpmm.ts @@ -97,6 +97,11 @@ async function applyLiquidityPositionDelta({ // indexing all FPMM events (Swap, Mint, Burn, etc.) without needing a // hardcoded address list in the config. Envio deduplicates addresses, so // re-registering the same address on re-runs is harmless. +// +// Note: contractRegister callbacks are a framework-level hook that Envio +// invokes before the handler. The Envio test harness (processEvent) only +// exercises the .handler() path, so this callback has no direct test +// coverage — this is a framework limitation, not an oversight. FPMMFactory.FPMMDeployed.contractRegister(({ event, context }) => { context.addFPMM(event.params.fpmmProxy); context.addERC20FeeToken(event.params.token0); diff --git a/indexer-envio/src/handlers/virtualPool.ts b/indexer-envio/src/handlers/virtualPool.ts index 99284ee..1a24468 100644 --- a/indexer-envio/src/handlers/virtualPool.ts +++ b/indexer-envio/src/handlers/virtualPool.ts @@ -20,6 +20,8 @@ import { upsertPool, upsertSnapshot, DEFAULT_ORACLE_FIELDS } from "../pool"; // Dynamically register the deployed VirtualPool so Envio indexes its events // (Swap, Mint, Burn, etc.) without a hardcoded address list in the config. +// Note: contractRegister is not exercised by the Envio test harness — see +// fpmm.ts for the same pattern and the explanation of why it's untestable. VirtualPoolFactory.VirtualPoolDeployed.contractRegister( ({ event, context }) => { context.addVirtualPool(event.params.pool); From c91897858e744724507b5e23eef6afa8b66eb47b Mon Sep 17 00:00:00 2001 From: gisk0 Date: Mon, 30 Mar 2026 18:11:47 +0200 Subject: [PATCH 03/11] fix: restore seed addresses + fix CI workflow config refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI blocker: update workflow to validate config.multichain.mainnet.yaml, config.celo.sepolia.yaml, config.monad.testnet.yaml (removed deleted celo.mainnet and monad.mainnet configs). Data integrity: restore known FPMM/VirtualPool seed addresses in config.multichain.mainnet.yaml alongside dynamic registration. This ensures pools are indexed even if ENVIO_START_BLOCK is raised above a pool's deploy block. Dynamic registration via contractRegister still handles all future deployments; Envio deduplicates so listing a pool in both places is harmless. Celo: 6 FPMM seeds (blocks 60668100 + 62725622) + 12 VirtualPool seeds Monad: 3 FPMM seeds (block 60759432); no VirtualPools deployed yet Also: remove no-op devnet fallback from bootstrap-worktree.sh (was logging 'trying devnet' but running multichain config — misleading). --- .github/workflows/indexer-envio.yml | 5 ++- bootstrap-worktree.sh | 6 +--- indexer-envio/config.multichain.mainnet.yaml | 36 +++++++++++++++++--- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.github/workflows/indexer-envio.yml b/.github/workflows/indexer-envio.yml index 21789a7..5caef6c 100644 --- a/.github/workflows/indexer-envio.yml +++ b/.github/workflows/indexer-envio.yml @@ -60,10 +60,9 @@ jobs: # Monad configs are validated for syntax/schema correctness even with empty # address arrays — this catches drift before deploy time. run: | - pnpm --filter @mento-protocol/indexer-envio codegen --config config.monad.testnet.yaml - pnpm --filter @mento-protocol/indexer-envio codegen --config config.monad.mainnet.yaml + pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.mainnet.yaml pnpm --filter @mento-protocol/indexer-envio codegen --config config.celo.sepolia.yaml - pnpm --filter @mento-protocol/indexer-envio codegen --config config.celo.mainnet.yaml + pnpm --filter @mento-protocol/indexer-envio codegen --config config.monad.testnet.yaml - name: Link generated package if: steps.changes.outputs.indexer == 'true' # pnpm install runs before generated/ exists, so the workspace symlink for diff --git a/bootstrap-worktree.sh b/bootstrap-worktree.sh index 9613155..a1e64dc 100755 --- a/bootstrap-worktree.sh +++ b/bootstrap-worktree.sh @@ -8,11 +8,7 @@ echo "📦 Installing dependencies..." pnpm install --frozen-lockfile echo "🔧 Running indexer codegen (multichain mainnet)..." -pnpm indexer:codegen 2>/dev/null || { - # Fallback: try default devnet config - echo " ⚠️ Multichain codegen failed, trying devnet..." - pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.mainnet.yaml -} +pnpm indexer:codegen echo "✅ Verifying typecheck..." pnpm --filter @mento-protocol/ui-dashboard typecheck diff --git a/indexer-envio/config.multichain.mainnet.yaml b/indexer-envio/config.multichain.mainnet.yaml index 2e59f07..d2e41fe 100644 --- a/indexer-envio/config.multichain.mainnet.yaml +++ b/indexer-envio/config.multichain.mainnet.yaml @@ -103,9 +103,32 @@ networks: address: - 0xa849b475FE5a4B5C9C3280152c7a1945b907613b # proxy - name: FPMM - address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister + # Seed addresses ensure known pools are indexed even if start_block is raised + # above the FPMMDeployed event. Dynamic registration (contractRegister) adds + # any future pools automatically. Envio deduplicates, so listing a pool here + # AND registering it dynamically is harmless. + address: + - 0x8c0014afe032e4574481d8934504100bf23fcb56 # USDm/ccf663b (deployed block 60668100) + - 0xb285d4c7133d6f27bfb29224fb0d22e7ec3ddd2d # USDm/eb4663 (deployed block 60668100) + - 0x462fe04b4fd719cbd04c0310365d421d02aaa19e # USDm/USDC (deployed block 60668100) + - 0x0feba760d93423d127de1b6abecdb60e5253228d # USDT/USDm (deployed block 60668100) + - 0x3aa7c431c06b10f7422e69d3e69b66807a6af696 # (deployed block 62725622) + - 0x1ad2ea06502919f935d9c09028df73a462979e29 # EURm/USDm (deployed block 62725622) - name: VirtualPool - address: [] # Dynamically registered via VirtualPoolFactory.VirtualPoolDeployed.contractRegister + # Seed addresses for known VirtualPools — same pattern as FPMM above. + address: + - 0xab945882018b81bdf62629e98ffdafd9495a0076 + - 0x6daa327e0cbe2ce84c0f312f20b9432fe744ed58 + - 0x1d013077b00b28038a3f1e7a29aba34e12e562e9 + - 0xbe6d2165173a29889652c7bf2dc3a02076a22f2a + - 0xaea92e8006e6edf0f9e9368ee9af36814b738855 + - 0xa337a498e4e061f4029fcb3b9f4e3d535e885dc5 + - 0x62fa288e3ac844dcfce5469af4f8feb7d6f7ba61 + - 0x30214efe28ab44d6a5c739eba5e0729b1d4213e4 + - 0xeb433ce1f2ce4981b76fe7ca3a96070705d8ede4 + - 0x71f55035a49c972c5c3197e874f6b7fd94672b6e + - 0x62753ec2956f84af240b4666a130c88a83933848 + - 0x3d6e023177bac13d6e316d95161d4bb9dcf0e276 - name: VirtualPoolFactory address: - 0x22abd4ADF6aab38aC1022352d496A07Acee5aCB3 @@ -128,9 +151,14 @@ networks: address: - 0xa849b475FE5a4B5C9C3280152c7a1945b907613b # TransparentUpgradeableProxy:FPMMFactory - name: FPMM - address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister + # Seed addresses ensure known pools are indexed even if start_block is raised + # above the FPMMDeployed event. Dynamic registration adds future pools automatically. + address: + - 0xd0e9c1a718d2a693d41eacd4b2696180403ce081 # GBPm/USDm (deployed block 60759432) + - 0x463c0d1f04bcd99a1efcf94ac2a75bc19ea4a7e5 # USDC/USDm (deployed block 60759432) + - 0xb0a0264ce6847f101b76ba36a4a3083ba489f501 # AUSD/USDm (deployed block 60759432) - name: VirtualPool - address: [] # Dynamically registered via VirtualPoolFactory.VirtualPoolDeployed.contractRegister + address: [] # No VirtualPools deployed on Monad yet; dynamic registration handles future ones - name: VirtualPoolFactory address: [] # Not yet deployed on Monad — add address when live - name: SortedOracles From a3fb03a7700db48e77c2ae4c029c7e1f3c5db55c Mon Sep 17 00:00:00 2001 From: gisk0 Date: Mon, 30 Mar 2026 18:16:53 +0200 Subject: [PATCH 04/11] fix: startup invariant instead of seed addresses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove seed addresses (per preference — dynamic registration should be sufficient). Instead, throw a fatal error at module load time if ENVIO_START_BLOCK is set above the FPMMFactory's first deployment block for any chain. If start_block is too high, FPMMDeployed events are never seen, contractRegister never fires, and all pool events are silently dropped. The startup check catches this misconfiguration immediately with a clear error message rather than producing an empty database. Known first-deploy blocks: Celo mainnet (42220): 60668100 Monad mainnet (143): 60759432 Celo Sepolia (11142220): 18946570 Monad testnet (10143): 17932599 --- indexer-envio/config.multichain.mainnet.yaml | 36 ++------------- indexer-envio/src/EventHandlers.ts | 46 ++++++++++++++++++++ 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/indexer-envio/config.multichain.mainnet.yaml b/indexer-envio/config.multichain.mainnet.yaml index d2e41fe..2e59f07 100644 --- a/indexer-envio/config.multichain.mainnet.yaml +++ b/indexer-envio/config.multichain.mainnet.yaml @@ -103,32 +103,9 @@ networks: address: - 0xa849b475FE5a4B5C9C3280152c7a1945b907613b # proxy - name: FPMM - # Seed addresses ensure known pools are indexed even if start_block is raised - # above the FPMMDeployed event. Dynamic registration (contractRegister) adds - # any future pools automatically. Envio deduplicates, so listing a pool here - # AND registering it dynamically is harmless. - address: - - 0x8c0014afe032e4574481d8934504100bf23fcb56 # USDm/ccf663b (deployed block 60668100) - - 0xb285d4c7133d6f27bfb29224fb0d22e7ec3ddd2d # USDm/eb4663 (deployed block 60668100) - - 0x462fe04b4fd719cbd04c0310365d421d02aaa19e # USDm/USDC (deployed block 60668100) - - 0x0feba760d93423d127de1b6abecdb60e5253228d # USDT/USDm (deployed block 60668100) - - 0x3aa7c431c06b10f7422e69d3e69b66807a6af696 # (deployed block 62725622) - - 0x1ad2ea06502919f935d9c09028df73a462979e29 # EURm/USDm (deployed block 62725622) + address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister - name: VirtualPool - # Seed addresses for known VirtualPools — same pattern as FPMM above. - address: - - 0xab945882018b81bdf62629e98ffdafd9495a0076 - - 0x6daa327e0cbe2ce84c0f312f20b9432fe744ed58 - - 0x1d013077b00b28038a3f1e7a29aba34e12e562e9 - - 0xbe6d2165173a29889652c7bf2dc3a02076a22f2a - - 0xaea92e8006e6edf0f9e9368ee9af36814b738855 - - 0xa337a498e4e061f4029fcb3b9f4e3d535e885dc5 - - 0x62fa288e3ac844dcfce5469af4f8feb7d6f7ba61 - - 0x30214efe28ab44d6a5c739eba5e0729b1d4213e4 - - 0xeb433ce1f2ce4981b76fe7ca3a96070705d8ede4 - - 0x71f55035a49c972c5c3197e874f6b7fd94672b6e - - 0x62753ec2956f84af240b4666a130c88a83933848 - - 0x3d6e023177bac13d6e316d95161d4bb9dcf0e276 + address: [] # Dynamically registered via VirtualPoolFactory.VirtualPoolDeployed.contractRegister - name: VirtualPoolFactory address: - 0x22abd4ADF6aab38aC1022352d496A07Acee5aCB3 @@ -151,14 +128,9 @@ networks: address: - 0xa849b475FE5a4B5C9C3280152c7a1945b907613b # TransparentUpgradeableProxy:FPMMFactory - name: FPMM - # Seed addresses ensure known pools are indexed even if start_block is raised - # above the FPMMDeployed event. Dynamic registration adds future pools automatically. - address: - - 0xd0e9c1a718d2a693d41eacd4b2696180403ce081 # GBPm/USDm (deployed block 60759432) - - 0x463c0d1f04bcd99a1efcf94ac2a75bc19ea4a7e5 # USDC/USDm (deployed block 60759432) - - 0xb0a0264ce6847f101b76ba36a4a3083ba489f501 # AUSD/USDm (deployed block 60759432) + address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister - name: VirtualPool - address: [] # No VirtualPools deployed on Monad yet; dynamic registration handles future ones + address: [] # Dynamically registered via VirtualPoolFactory.VirtualPoolDeployed.contractRegister - name: VirtualPoolFactory address: [] # Not yet deployed on Monad — add address when live - name: SortedOracles diff --git a/indexer-envio/src/EventHandlers.ts b/indexer-envio/src/EventHandlers.ts index 9ec8b10..af7c3b1 100644 --- a/indexer-envio/src/EventHandlers.ts +++ b/indexer-envio/src/EventHandlers.ts @@ -6,6 +6,52 @@ // modules are imported below; their registrations fire at module load time. // =========================================================================== +// --------------------------------------------------------------------------- +// Startup invariant: fail loudly if ENVIO_START_BLOCK is set above the +// FPMMFactory's first deployment block for any chain. If start_block is too +// high, FPMMDeployed events are never seen, contractRegister never fires, +// and all pool events are silently dropped — no error, just missing data. +// +// First factory deployment blocks (inclusive): +// Celo mainnet (42220): 60668100 — initial batch of 4 FPMM pools +// Monad mainnet (143): 60759432 — initial batch of 3 FPMM pools +// Celo Sepolia (11142220): ~18946570 — testnet deployment +// Monad testnet (10143): ~17932599 — testnet FPMMFactory deployment +// --------------------------------------------------------------------------- +const FPMM_FIRST_DEPLOY_BLOCK: Record = { + 42220: 60668100, // Celo mainnet + 143: 60759432, // Monad mainnet + 11142220: 18946570, // Celo Sepolia + 10143: 17932599, // Monad testnet +}; + +const START_BLOCK_ENV: Record = { + 42220: process.env.ENVIO_START_BLOCK_CELO, + 143: process.env.ENVIO_START_BLOCK_MONAD, + 11142220: process.env.ENVIO_START_BLOCK, + 10143: process.env.ENVIO_START_BLOCK, +}; + +for (const [chainIdStr, firstDeployBlock] of Object.entries( + FPMM_FIRST_DEPLOY_BLOCK, +)) { + const chainId = Number(chainIdStr); + const envVal = START_BLOCK_ENV[chainId]; + if (envVal !== undefined && envVal !== "") { + const startBlock = Number(envVal); + if (!Number.isFinite(startBlock)) continue; + if (startBlock > firstDeployBlock) { + throw new Error( + `[EventHandlers] FATAL: start block for chain ${chainId} is ${startBlock}, ` + + `but FPMMFactory first deployed at block ${firstDeployBlock}. ` + + `All factory deploy events will be missed and no pools will be indexed. ` + + `Lower ENVIO_START_BLOCK${chainId === 42220 ? "_CELO" : chainId === 143 ? "_MONAD" : ""} ` + + `to ≤${firstDeployBlock} or remove the override.`, + ); + } + } +} + // Handler registrations (side-effect imports) import "./handlers/fpmm"; import "./handlers/sortedOracles"; From c4f9b999240da4c5c2930d8d7bfbcd06b1309349 Mon Sep 17 00:00:00 2001 From: gisk0 Date: Mon, 30 Mar 2026 19:59:08 +0200 Subject: [PATCH 05/11] chore: address Cursor review follow-up findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI: add config.multichain.testnet.yaml to codegen validation step, move multichain.mainnet last (output used by typecheck + tests), fix stale comment (no longer 'Celo mainnet is run last'). bootstrap-worktree.sh: remove error-swallowing from test runs. Tests now fail loudly if broken, so setup is never false-green. Docs: fix stale config refs in AGENTS.md (root + indexer): - root AGENTS.md L47: replace per-chain configs with multichain - root AGENTS.md L196: same in 'Adding a new contract' guide - indexer-envio/AGENTS.md: update 'on Celo' to 'Celo + Monad (multichain)' Testnet configs: add comment explaining why address:[] is safe — EventHandlers.ts startup invariant throws if ENVIO_START_BLOCK is raised above the factory's first deploy block on any chain. --- .github/workflows/indexer-envio.yml | 8 ++++---- AGENTS.md | 4 ++-- bootstrap-worktree.sh | 4 ++-- indexer-envio/AGENTS.md | 2 +- indexer-envio/config.celo.sepolia.yaml | 3 +++ indexer-envio/config.monad.testnet.yaml | 3 +++ 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/workflows/indexer-envio.yml b/.github/workflows/indexer-envio.yml index 5caef6c..4004eaf 100644 --- a/.github/workflows/indexer-envio.yml +++ b/.github/workflows/indexer-envio.yml @@ -56,13 +56,13 @@ jobs: - name: Validate all Envio configs (codegen) if: steps.changes.outputs.indexer == 'true' # Run codegen against every config file to catch schema/config drift early. - # Celo mainnet is run last (its output is used by typecheck + tests below). - # Monad configs are validated for syntax/schema correctness even with empty - # address arrays — this catches drift before deploy time. + # config.multichain.mainnet.yaml is run last — its output is used by + # typecheck + tests below. run: | - pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.mainnet.yaml + pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.testnet.yaml pnpm --filter @mento-protocol/indexer-envio codegen --config config.celo.sepolia.yaml pnpm --filter @mento-protocol/indexer-envio codegen --config config.monad.testnet.yaml + pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.mainnet.yaml - name: Link generated package if: steps.changes.outputs.indexer == 'true' # pnpm install runs before generated/ exists, so the workspace symlink for diff --git a/AGENTS.md b/AGENTS.md index 605d03d..42adcf7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,7 +44,7 @@ pnpm infra:apply # Apply infrastructure changes - **Runtime:** Envio HyperIndex (envio@2.32.3) - **Schema:** `schema.graphql` defines indexed entities (FPMM, Swap, Mint, Burn, etc.) -- **Configs:** `config.celo.devnet.yaml`, `config.celo.mainnet.yaml`, `config.celo.sepolia.yaml`, `config.monad.mainnet.yaml`, `config.monad.testnet.yaml` +- **Configs:** `config.multichain.mainnet.yaml` (default), `config.multichain.testnet.yaml`, `config.celo.sepolia.yaml`, `config.monad.testnet.yaml` - **Handlers:** `src/EventHandlers.ts` is the Envio entry point (all `config.*.yaml` files reference it). It imports handler modules from `src/handlers/` and re-exports test utilities. Handler logic lives in `src/handlers/fpmm.ts`, `src/handlers/sortedOracles.ts`, `src/handlers/virtualPool.ts`, `src/handlers/feeToken.ts`. Shared logic: `src/rpc.ts` (RPC + caches), `src/pool.ts` (upsert), `src/priceDifference.ts`, `src/tradingLimits.ts`, `src/feeToken.ts`, `src/abis.ts`, `src/helpers.ts`. - **Contract addresses:** `src/contractAddresses.ts` — resolves addresses from `@mento-protocol/contracts` using the namespace map from `shared-config` - **ABIs:** `abis/` — FPMMFactory, FPMM, VirtualPoolFactory (indexer-specific); SortedOracles + token ABIs come from `@mento-protocol/contracts` @@ -193,7 +193,7 @@ When a new set of contracts has been deployed and a new `@mento-protocol/contrac ### Adding a new contract to index 1. Add ABI to `indexer-envio/abis/` -2. Add contract entry in the relevant config(s): `config.celo.mainnet.yaml`, `config.celo.sepolia.yaml`, `config.monad.mainnet.yaml`, `config.monad.testnet.yaml` +2. Add contract entry in the relevant config(s): `config.multichain.mainnet.yaml`, `config.multichain.testnet.yaml`, `config.celo.sepolia.yaml`, `config.monad.testnet.yaml` 3. Add entity to `schema.graphql` 4. Add handler in the appropriate `src/handlers/*.ts` file (or create a new one and import it from `src/EventHandlers.ts`) 5. Run `pnpm indexer:codegen` to regenerate types diff --git a/bootstrap-worktree.sh b/bootstrap-worktree.sh index a1e64dc..dcd9c48 100755 --- a/bootstrap-worktree.sh +++ b/bootstrap-worktree.sh @@ -15,8 +15,8 @@ pnpm --filter @mento-protocol/ui-dashboard typecheck pnpm --filter @mento-protocol/indexer-envio typecheck echo "🧪 Running tests..." -pnpm --filter @mento-protocol/ui-dashboard test -- --run 2>/dev/null || echo " ⚠️ Dashboard tests: some failures (check manually)" -pnpm --filter @mento-protocol/indexer-envio test 2>/dev/null || echo " ⚠️ Indexer tests: some failures (check manually)" +pnpm --filter @mento-protocol/ui-dashboard test -- --run +pnpm --filter @mento-protocol/indexer-envio test echo "" echo "🚀 monitoring-monorepo is ready to code" diff --git a/indexer-envio/AGENTS.md b/indexer-envio/AGENTS.md index 068c675..d89ab30 100644 --- a/indexer-envio/AGENTS.md +++ b/indexer-envio/AGENTS.md @@ -2,7 +2,7 @@ ## What This Is -Envio HyperIndex indexer for Mento v3 FPMM (Fixed Product Market Maker) pools on Celo. +Envio HyperIndex indexer for Mento v3 FPMM (Fixed Product Market Maker) pools on Celo + Monad (multichain). ## Key Files diff --git a/indexer-envio/config.celo.sepolia.yaml b/indexer-envio/config.celo.sepolia.yaml index de0e969..dcd8480 100644 --- a/indexer-envio/config.celo.sepolia.yaml +++ b/indexer-envio/config.celo.sepolia.yaml @@ -16,6 +16,9 @@ networks: - event: FPMMDeployed - name: FPMM abi_file_path: abis/FPMM.json + # Dynamic-only: EventHandlers.ts throws a fatal error at startup if + # ENVIO_START_BLOCK is raised above the FPMMFactory's first deploy block + # (18946570), so misconfiguration fails loudly rather than silently. address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister handler: src/EventHandlers.ts events: diff --git a/indexer-envio/config.monad.testnet.yaml b/indexer-envio/config.monad.testnet.yaml index 9ed684f..b5746a9 100644 --- a/indexer-envio/config.monad.testnet.yaml +++ b/indexer-envio/config.monad.testnet.yaml @@ -14,6 +14,9 @@ networks: - event: FPMMDeployed - name: FPMM abi_file_path: abis/FPMM.json + # Dynamic-only: EventHandlers.ts throws a fatal error at startup if + # ENVIO_START_BLOCK is raised above the FPMMFactory's first deploy block + # (17932599), so misconfiguration fails loudly rather than silently. address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister handler: src/EventHandlers.ts events: From 00884d06028d41b01d3550fa148d500152311ede Mon Sep 17 00:00:00 2001 From: gisk0 Date: Mon, 30 Mar 2026 22:46:41 +0200 Subject: [PATCH 06/11] chore: remove single-chain testnet configs, go fully multichain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per review: single-chain configs are now redundant since config.multichain.testnet.yaml covers Celo Sepolia + Monad testnet. - Delete config.celo.sepolia.yaml and config.monad.testnet.yaml - Replace indexer:celo-sepolia:* and indexer:monad-testnet:* pnpm scripts with indexer:testnet:codegen / indexer:testnet:dev (multichain testnet) - CI workflow: validate only the two multichain configs - Fix EventHandlers.ts startup invariant: use chain-specific env vars (ENVIO_START_BLOCK_CELO_SEPOLIA, ENVIO_START_BLOCK_MONAD_TESTNET) instead of generic ENVIO_START_BLOCK — prevents false-fatal errors when running a single-chain run where the value is valid for one chain but exceeds first deploy block on another chain not in the active config - Update all AGENTS.md and docs references --- .github/workflows/indexer-envio.yml | 2 - AGENTS.md | 13 ++-- indexer-envio/AGENTS.md | 5 +- indexer-envio/config.celo.sepolia.yaml | 81 ------------------------- indexer-envio/config.monad.testnet.yaml | 79 ------------------------ indexer-envio/src/EventHandlers.ts | 8 ++- package.json | 6 +- 7 files changed, 15 insertions(+), 179 deletions(-) delete mode 100644 indexer-envio/config.celo.sepolia.yaml delete mode 100644 indexer-envio/config.monad.testnet.yaml diff --git a/.github/workflows/indexer-envio.yml b/.github/workflows/indexer-envio.yml index 4004eaf..b77f6ce 100644 --- a/.github/workflows/indexer-envio.yml +++ b/.github/workflows/indexer-envio.yml @@ -60,8 +60,6 @@ jobs: # typecheck + tests below. run: | pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.testnet.yaml - pnpm --filter @mento-protocol/indexer-envio codegen --config config.celo.sepolia.yaml - pnpm --filter @mento-protocol/indexer-envio codegen --config config.monad.testnet.yaml pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.mainnet.yaml - name: Link generated package if: steps.changes.outputs.indexer == 'true' diff --git a/AGENTS.md b/AGENTS.md index 42adcf7..4b5b6b8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,10 +17,8 @@ pnpm install # Indexer pnpm indexer:codegen # Generate types from schema (multichain mainnet) pnpm indexer:dev # Start indexer (multichain mainnet: Celo + Monad) -pnpm indexer:celo-sepolia:codegen # Generate types (Celo Sepolia testnet) -pnpm indexer:celo-sepolia:dev # Start indexer (Celo Sepolia testnet) -pnpm indexer:monad-testnet:codegen # Generate types (Monad testnet) -pnpm indexer:monad-testnet:dev # Start indexer (Monad testnet) +pnpm indexer:testnet:codegen # Generate types (multichain testnet: Celo Sepolia + Monad testnet) +pnpm indexer:testnet:dev # Start indexer (multichain testnet) # Dashboard pnpm dashboard:dev # Dev server @@ -44,7 +42,7 @@ pnpm infra:apply # Apply infrastructure changes - **Runtime:** Envio HyperIndex (envio@2.32.3) - **Schema:** `schema.graphql` defines indexed entities (FPMM, Swap, Mint, Burn, etc.) -- **Configs:** `config.multichain.mainnet.yaml` (default), `config.multichain.testnet.yaml`, `config.celo.sepolia.yaml`, `config.monad.testnet.yaml` +- **Configs:** `config.multichain.mainnet.yaml` (default), `config.multichain.testnet.yaml` - **Handlers:** `src/EventHandlers.ts` is the Envio entry point (all `config.*.yaml` files reference it). It imports handler modules from `src/handlers/` and re-exports test utilities. Handler logic lives in `src/handlers/fpmm.ts`, `src/handlers/sortedOracles.ts`, `src/handlers/virtualPool.ts`, `src/handlers/feeToken.ts`. Shared logic: `src/rpc.ts` (RPC + caches), `src/pool.ts` (upsert), `src/priceDifference.ts`, `src/tradingLimits.ts`, `src/feeToken.ts`, `src/abis.ts`, `src/helpers.ts`. - **Contract addresses:** `src/contractAddresses.ts` — resolves addresses from `@mento-protocol/contracts` using the namespace map from `shared-config` - **ABIs:** `abis/` — FPMMFactory, FPMM, VirtualPoolFactory (indexer-specific); SortedOracles + token ABIs come from `@mento-protocol/contracts` @@ -87,8 +85,7 @@ monitoring-monorepo/ ├── indexer-envio/ │ ├── config.multichain.mainnet.yaml # Mainnet indexer config (Celo + Monad) — DEFAULT │ ├── config.multichain.testnet.yaml # Testnet multichain config -│ ├── config.celo.sepolia.yaml # Celo Sepolia testnet config -│ ├── config.monad.testnet.yaml # Monad testnet config + │ ├── schema.graphql # Entity definitions │ ├── src/ │ │ ├── EventHandlers.ts # Envio entry point (imports handlers, re-exports for tests) @@ -193,7 +190,7 @@ When a new set of contracts has been deployed and a new `@mento-protocol/contrac ### Adding a new contract to index 1. Add ABI to `indexer-envio/abis/` -2. Add contract entry in the relevant config(s): `config.multichain.mainnet.yaml`, `config.multichain.testnet.yaml`, `config.celo.sepolia.yaml`, `config.monad.testnet.yaml` +2. Add contract entry in the relevant config(s): `config.multichain.mainnet.yaml`, `config.multichain.testnet.yaml` 3. Add entity to `schema.graphql` 4. Add handler in the appropriate `src/handlers/*.ts` file (or create a new one and import it from `src/EventHandlers.ts`) 5. Run `pnpm indexer:codegen` to regenerate types diff --git a/indexer-envio/AGENTS.md b/indexer-envio/AGENTS.md index d89ab30..095e1c4 100644 --- a/indexer-envio/AGENTS.md +++ b/indexer-envio/AGENTS.md @@ -8,8 +8,7 @@ Envio HyperIndex indexer for Mento v3 FPMM (Fixed Product Market Maker) pools on - `config.multichain.mainnet.yaml` — **Default** mainnet config (Celo + Monad) - `config.multichain.testnet.yaml` — Testnet multichain config -- `config.celo.sepolia.yaml` — Celo Sepolia testnet config -- `config.monad.testnet.yaml` — Monad testnet config + - `schema.graphql` — Entity definitions (FPMM, Swap, Mint, Burn, UpdateReserves, Rebalanced) - `src/EventHandlers.ts` — Event processing logic - `src/contractAddresses.ts` — Contract address resolution from `@mento-protocol/contracts`; also exports `CONTRACT_NAMESPACE_BY_CHAIN` (backed by `config/deployment-namespaces.json`) @@ -58,4 +57,4 @@ Copy `.env.example` → `.env` and set: Do **not** set the generic `ENVIO_RPC_URL` in multichain mode — it would route all chains to the same endpoint and produce incorrect RPC reads for chain-specific calls. -Default (multichain Celo + Monad mainnet): `pnpm indexer:codegen && pnpm indexer:dev`. For Celo Sepolia testnet: `pnpm indexer:celo-sepolia:dev`. For Monad Testnet: `pnpm indexer:monad-testnet:dev`. +Mainnet (Celo + Monad): `pnpm indexer:codegen && pnpm indexer:dev`. Testnet (Celo Sepolia + Monad Testnet): `pnpm indexer:testnet:dev`. diff --git a/indexer-envio/config.celo.sepolia.yaml b/indexer-envio/config.celo.sepolia.yaml deleted file mode 100644 index dcd8480..0000000 --- a/indexer-envio/config.celo.sepolia.yaml +++ /dev/null @@ -1,81 +0,0 @@ -# yaml-language-server: $schema=./node_modules/envio/evm.schema.json -name: celo-sepolia -description: Celo Sepolia (testnet-v2-rc5) v3 FPMM HyperIndex indexer -networks: - - id: 11142220 - rpc_config: - url: ${ENVIO_RPC_URL:-https://forno.celo-sepolia.celo-testnet.org} - start_block: ${ENVIO_START_BLOCK:-18946570} - contracts: - - name: FPMMFactory - abi_file_path: abis/FPMMFactory.json - address: - - 0x353ED52bF8482027C0e0b9e3c0e5d96A9F680980 - handler: src/EventHandlers.ts - events: - - event: FPMMDeployed - - name: FPMM - abi_file_path: abis/FPMM.json - # Dynamic-only: EventHandlers.ts throws a fatal error at startup if - # ENVIO_START_BLOCK is raised above the FPMMFactory's first deploy block - # (18946570), so misconfiguration fails loudly rather than silently. - address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister - handler: src/EventHandlers.ts - events: - - event: Swap - - event: Mint - - event: Burn - - event: Transfer - - event: UpdateReserves - - event: Rebalanced - - event: TradingLimitConfigured - - event: LiquidityStrategyUpdated - - name: VirtualPool - abi_file_path: abis/FPMM.json - address: [] # Dynamically registered via VirtualPoolFactory.VirtualPoolDeployed.contractRegister - handler: src/EventHandlers.ts - events: - - event: Swap - - event: Mint - - event: Burn - - event: UpdateReserves - - event: Rebalanced - - name: VirtualPoolFactory - abi_file_path: abis/VirtualPoolFactory.json - address: - - 0x887955f28723B0e9Bddc358448CB5B1FDe692da4 - handler: src/EventHandlers.ts - events: - - event: VirtualPoolDeployed - - event: PoolDeprecated - - name: SortedOracles - abi_file_path: abis/SortedOracles.json - address: - - "0xfaa7Ca2B056E60F6733aE75AA0709140a6eAfD20" - handler: src/EventHandlers.ts - events: - - event: OracleReported - - event: MedianUpdated - - event: ReportExpirySet - - event: TokenReportExpirySet - - name: OpenLiquidityStrategy - abi_file_path: abis/OpenLiquidityStrategy.json - address: [] # Not yet deployed on Celo Sepolia — add address when available - handler: src/EventHandlers.ts - events: - - event: PoolAdded - - event: PoolRemoved - - event: RebalanceCooldownSet - - event: LiquidityMoved - - name: ERC20FeeToken - abi_file_path: abis/ERC20.json - address: [] # Dynamically registered from FPMMDeployed events - handler: src/EventHandlers.ts - events: - - event: Transfer -unordered_multichain_mode: true -preload_handlers: true -field_selection: - transaction_fields: - - from - - hash diff --git a/indexer-envio/config.monad.testnet.yaml b/indexer-envio/config.monad.testnet.yaml deleted file mode 100644 index b5746a9..0000000 --- a/indexer-envio/config.monad.testnet.yaml +++ /dev/null @@ -1,79 +0,0 @@ -# yaml-language-server: $schema=./node_modules/envio/evm.schema.json -name: monad-testnet -description: Monad Testnet v3 FPMM HyperIndex indexer -networks: - - id: 10143 # Monad Testnet — HyperSync: https://10143.hypersync.xyz - start_block: ${ENVIO_START_BLOCK:-17932300} # SortedOracles ~17932362, FPMMFactory ~17932599 - contracts: - - name: FPMMFactory - abi_file_path: abis/FPMMFactory.json - address: - - 0x353ED52bF8482027C0e0b9e3c0e5d96A9F680980 # TransparentUpgradeableProxy:FPMMFactory - handler: src/EventHandlers.ts - events: - - event: FPMMDeployed - - name: FPMM - abi_file_path: abis/FPMM.json - # Dynamic-only: EventHandlers.ts throws a fatal error at startup if - # ENVIO_START_BLOCK is raised above the FPMMFactory's first deploy block - # (17932599), so misconfiguration fails loudly rather than silently. - address: [] # Dynamically registered via FPMMFactory.FPMMDeployed.contractRegister - handler: src/EventHandlers.ts - events: - - event: Swap - - event: Mint - - event: Burn - - event: Transfer - - event: UpdateReserves - - event: Rebalanced - - event: TradingLimitConfigured - - event: LiquidityStrategyUpdated - - name: VirtualPool - abi_file_path: abis/FPMM.json - address: [] # VirtualPoolFactory not yet deployed on testnet - handler: src/EventHandlers.ts - events: - - event: Swap - - event: Mint - - event: Burn - - event: UpdateReserves - - event: Rebalanced - - name: VirtualPoolFactory - abi_file_path: abis/VirtualPoolFactory.json - address: [] # Not yet deployed on testnet - handler: src/EventHandlers.ts - events: - - event: VirtualPoolDeployed - - event: PoolDeprecated - - name: SortedOracles - abi_file_path: abis/SortedOracles.json - address: - - "0x85ed9ac57827132B8F60938F3165BC139E1F53cd" # TransparentUpgradeableProxy:SortedOracles - handler: src/EventHandlers.ts - events: - - event: OracleReported - - event: MedianUpdated - - event: ReportExpirySet - - event: TokenReportExpirySet - - name: ERC20FeeToken - abi_file_path: abis/ERC20.json - address: [] # Dynamically registered from FPMMDeployed events - handler: src/EventHandlers.ts - events: - - event: Transfer - - name: OpenLiquidityStrategy - abi_file_path: abis/OpenLiquidityStrategy.json - address: - - 0xCCd2aD0603a08EBc14D223a983171ef18192e8c9 - handler: src/EventHandlers.ts - events: - - event: PoolAdded - - event: PoolRemoved - - event: RebalanceCooldownSet - - event: LiquidityMoved -unordered_multichain_mode: true -preload_handlers: true -field_selection: - transaction_fields: - - hash - - from diff --git a/indexer-envio/src/EventHandlers.ts b/indexer-envio/src/EventHandlers.ts index af7c3b1..189149d 100644 --- a/indexer-envio/src/EventHandlers.ts +++ b/indexer-envio/src/EventHandlers.ts @@ -25,11 +25,15 @@ const FPMM_FIRST_DEPLOY_BLOCK: Record = { 10143: 17932599, // Monad testnet }; +// Each chain maps to its dedicated env var (never the generic ENVIO_START_BLOCK). +// Using ENVIO_START_BLOCK here would cause false-fatal errors when running a +// single-network config where the value is valid for one chain but > the first +// deploy block on another chain that isn't even active in that run. const START_BLOCK_ENV: Record = { 42220: process.env.ENVIO_START_BLOCK_CELO, 143: process.env.ENVIO_START_BLOCK_MONAD, - 11142220: process.env.ENVIO_START_BLOCK, - 10143: process.env.ENVIO_START_BLOCK, + 11142220: process.env.ENVIO_START_BLOCK_CELO_SEPOLIA, + 10143: process.env.ENVIO_START_BLOCK_MONAD_TESTNET, }; for (const [chainIdStr, firstDeployBlock] of Object.entries( diff --git a/package.json b/package.json index fcc14d8..08b4784 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,8 @@ "deploy:indexer": "./scripts/deploy-indexer.sh", "indexer:codegen": "pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.mainnet.yaml", "indexer:dev": "pnpm --filter @mento-protocol/indexer-envio dev --config config.multichain.mainnet.yaml", - "indexer:celo-sepolia:codegen": "pnpm --filter @mento-protocol/indexer-envio codegen --config config.celo.sepolia.yaml", - "indexer:celo-sepolia:dev": "ENVIO_PG_PORT=5434 ENVIO_PG_DATABASE=envio-celo-sepolia ENVIO_RPC_URL=https://forno.celo-sepolia.celo-testnet.org ENVIO_START_BLOCK=18901381 pnpm --filter @mento-protocol/indexer-envio dev --config config.celo.sepolia.yaml", - "indexer:monad-testnet:codegen": "pnpm --filter @mento-protocol/indexer-envio codegen --config config.monad.testnet.yaml", - "indexer:monad-testnet:dev": "ENVIO_PG_PORT=5437 ENVIO_PG_DATABASE=envio-monad-testnet ENVIO_START_BLOCK=17932300 pnpm --filter @mento-protocol/indexer-envio dev --config config.monad.testnet.yaml", + "indexer:testnet:codegen": "pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.testnet.yaml", + "indexer:testnet:dev": "pnpm --filter @mento-protocol/indexer-envio dev --config config.multichain.testnet.yaml", "infra:apply": "terraform -chdir=terraform apply", "infra:init": "terraform -chdir=terraform init", "infra:plan": "terraform -chdir=terraform plan" From c3e9eed10c6ee24dc35106bddcc58ea3f645a140 Mon Sep 17 00:00:00 2001 From: gisk0 Date: Mon, 30 Mar 2026 23:13:33 +0200 Subject: [PATCH 07/11] fix: testable startup invariant + correct error message env var names - Extract assertStartBlocksValid() as an exported function so it can be unit tested independently of module load side effects - Fix error message: was printing ENVIO_START_BLOCK_CELO/MONAD for all chains; now uses START_BLOCK_ENV_NAME map to print the correct per-chain var (ENVIO_START_BLOCK_CELO_SEPOLIA, ENVIO_START_BLOCK_MONAD_TESTNET) - Add test/startBlockInvariant.test.ts with 9 cases covering: - Valid overrides (below / equal to first deploy block) - Missing / empty overrides (no throw) - Non-numeric override (graceful skip) - Per-chain fatal throw with correct env var name in message - Regression: Celo Sepolia dev start block (18901381) > Monad testnet first deploy block must NOT trigger Monad guard (cross-chain false fatal) - bootstrap-worktree.sh: remove redundant -- --run flag (vitest already runs in run mode via the npm script) --- bootstrap-worktree.sh | 2 +- indexer-envio/src/EventHandlers.ts | 46 ++++--- .../test/startBlockInvariant.test.ts | 114 ++++++++++++++++++ 3 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 indexer-envio/test/startBlockInvariant.test.ts diff --git a/bootstrap-worktree.sh b/bootstrap-worktree.sh index dcd9c48..4ae731f 100755 --- a/bootstrap-worktree.sh +++ b/bootstrap-worktree.sh @@ -15,7 +15,7 @@ pnpm --filter @mento-protocol/ui-dashboard typecheck pnpm --filter @mento-protocol/indexer-envio typecheck echo "🧪 Running tests..." -pnpm --filter @mento-protocol/ui-dashboard test -- --run +pnpm --filter @mento-protocol/ui-dashboard test pnpm --filter @mento-protocol/indexer-envio test echo "" diff --git a/indexer-envio/src/EventHandlers.ts b/indexer-envio/src/EventHandlers.ts index 189149d..bc148d3 100644 --- a/indexer-envio/src/EventHandlers.ts +++ b/indexer-envio/src/EventHandlers.ts @@ -18,7 +18,7 @@ // Celo Sepolia (11142220): ~18946570 — testnet deployment // Monad testnet (10143): ~17932599 — testnet FPMMFactory deployment // --------------------------------------------------------------------------- -const FPMM_FIRST_DEPLOY_BLOCK: Record = { +export const FPMM_FIRST_DEPLOY_BLOCK: Record = { 42220: 60668100, // Celo mainnet 143: 60759432, // Monad mainnet 11142220: 18946570, // Celo Sepolia @@ -29,33 +29,51 @@ const FPMM_FIRST_DEPLOY_BLOCK: Record = { // Using ENVIO_START_BLOCK here would cause false-fatal errors when running a // single-network config where the value is valid for one chain but > the first // deploy block on another chain that isn't even active in that run. -const START_BLOCK_ENV: Record = { - 42220: process.env.ENVIO_START_BLOCK_CELO, - 143: process.env.ENVIO_START_BLOCK_MONAD, - 11142220: process.env.ENVIO_START_BLOCK_CELO_SEPOLIA, - 10143: process.env.ENVIO_START_BLOCK_MONAD_TESTNET, +export const START_BLOCK_ENV_NAME: Record = { + 42220: "ENVIO_START_BLOCK_CELO", + 143: "ENVIO_START_BLOCK_MONAD", + 11142220: "ENVIO_START_BLOCK_CELO_SEPOLIA", + 10143: "ENVIO_START_BLOCK_MONAD_TESTNET", }; -for (const [chainIdStr, firstDeployBlock] of Object.entries( - FPMM_FIRST_DEPLOY_BLOCK, -)) { - const chainId = Number(chainIdStr); - const envVal = START_BLOCK_ENV[chainId]; - if (envVal !== undefined && envVal !== "") { +/** + * Validate that no ENVIO_START_BLOCK_* env var is set above the first + * FPMMFactory deployment block for its chain. If it is, all factory deploy + * events are missed and no pools are ever registered — a silent data loss. + * + * Exported for testing. Called at module load time below. + */ +export function assertStartBlocksValid( + envOverrides: Record, +): void { + for (const [chainIdStr, firstDeployBlock] of Object.entries( + FPMM_FIRST_DEPLOY_BLOCK, + )) { + const chainId = Number(chainIdStr); + const envVal = envOverrides[chainId]; + if (envVal === undefined || envVal === "") continue; const startBlock = Number(envVal); if (!Number.isFinite(startBlock)) continue; if (startBlock > firstDeployBlock) { + const envVarName = START_BLOCK_ENV_NAME[chainId]; throw new Error( `[EventHandlers] FATAL: start block for chain ${chainId} is ${startBlock}, ` + `but FPMMFactory first deployed at block ${firstDeployBlock}. ` + `All factory deploy events will be missed and no pools will be indexed. ` + - `Lower ENVIO_START_BLOCK${chainId === 42220 ? "_CELO" : chainId === 143 ? "_MONAD" : ""} ` + - `to ≤${firstDeployBlock} or remove the override.`, + `Lower ${envVarName} to ≤${firstDeployBlock} or remove the override.`, ); } } } +// Run the check at startup with values from the actual environment. +assertStartBlocksValid({ + 42220: process.env.ENVIO_START_BLOCK_CELO, + 143: process.env.ENVIO_START_BLOCK_MONAD, + 11142220: process.env.ENVIO_START_BLOCK_CELO_SEPOLIA, + 10143: process.env.ENVIO_START_BLOCK_MONAD_TESTNET, +}); + // Handler registrations (side-effect imports) import "./handlers/fpmm"; import "./handlers/sortedOracles"; diff --git a/indexer-envio/test/startBlockInvariant.test.ts b/indexer-envio/test/startBlockInvariant.test.ts new file mode 100644 index 0000000..f6b1881 --- /dev/null +++ b/indexer-envio/test/startBlockInvariant.test.ts @@ -0,0 +1,114 @@ +/// +import { strict as assert } from "assert"; +import { + assertStartBlocksValid, + FPMM_FIRST_DEPLOY_BLOCK, + START_BLOCK_ENV_NAME, +} from "../src/EventHandlers"; + +// Chain IDs used in tests +const CELO = 42220; +const MONAD = 143; +const CELO_SEPOLIA = 11142220; +const MONAD_TESTNET = 10143; + +describe("assertStartBlocksValid", () => { + it("passes when no env overrides are set", () => { + assert.doesNotThrow(() => assertStartBlocksValid({})); + }); + + it("passes when overrides are undefined or empty string", () => { + assert.doesNotThrow(() => + assertStartBlocksValid({ + [CELO]: undefined, + [MONAD]: "", + [CELO_SEPOLIA]: undefined, + [MONAD_TESTNET]: "", + }), + ); + }); + + it("passes when start block equals first deploy block", () => { + assert.doesNotThrow(() => + assertStartBlocksValid({ + [CELO]: String(FPMM_FIRST_DEPLOY_BLOCK[CELO]), + [MONAD]: String(FPMM_FIRST_DEPLOY_BLOCK[MONAD]), + }), + ); + }); + + it("passes when start block is below first deploy block", () => { + assert.doesNotThrow(() => + assertStartBlocksValid({ + [CELO]: String(FPMM_FIRST_DEPLOY_BLOCK[CELO] - 1), + }), + ); + }); + + it("throws when Celo start block is above first deploy block", () => { + const tooHigh = FPMM_FIRST_DEPLOY_BLOCK[CELO] + 1; + assert.throws( + () => assertStartBlocksValid({ [CELO]: String(tooHigh) }), + (err: unknown) => { + assert(err instanceof Error); + assert(err.message.includes(START_BLOCK_ENV_NAME[CELO])); + assert(err.message.includes(String(tooHigh))); + assert(err.message.includes(String(FPMM_FIRST_DEPLOY_BLOCK[CELO]))); + return true; + }, + ); + }); + + it("throws when Monad start block is above first deploy block", () => { + const tooHigh = FPMM_FIRST_DEPLOY_BLOCK[MONAD] + 1000; + assert.throws( + () => assertStartBlocksValid({ [MONAD]: String(tooHigh) }), + (err: unknown) => { + assert(err instanceof Error); + assert(err.message.includes(START_BLOCK_ENV_NAME[MONAD])); + return true; + }, + ); + }); + + it("throws for Celo Sepolia with correct env var name in message", () => { + const tooHigh = FPMM_FIRST_DEPLOY_BLOCK[CELO_SEPOLIA] + 1; + assert.throws( + () => assertStartBlocksValid({ [CELO_SEPOLIA]: String(tooHigh) }), + (err: unknown) => { + assert(err instanceof Error); + assert(err.message.includes(START_BLOCK_ENV_NAME[CELO_SEPOLIA])); + assert(!err.message.includes("ENVIO_START_BLOCK_CELO\n")); // not the mainnet var + return true; + }, + ); + }); + + it("throws for Monad testnet with correct env var name in message", () => { + const tooHigh = FPMM_FIRST_DEPLOY_BLOCK[MONAD_TESTNET] + 1; + assert.throws( + () => assertStartBlocksValid({ [MONAD_TESTNET]: String(tooHigh) }), + (err: unknown) => { + assert(err instanceof Error); + assert(err.message.includes(START_BLOCK_ENV_NAME[MONAD_TESTNET])); + return true; + }, + ); + }); + + it("skips non-numeric override gracefully (no throw)", () => { + assert.doesNotThrow(() => + assertStartBlocksValid({ [CELO]: "not-a-number" }), + ); + }); + + it("does not throw for a valid Celo Sepolia value that would be > Monad testnet deploy block", () => { + // Regression: ENVIO_START_BLOCK=18901381 is valid for Celo Sepolia but + // > 17932599 (Monad testnet first deploy). Must NOT throw for the wrong chain. + const celoSepoliaDevStart = 18901381; + assert(celoSepoliaDevStart > FPMM_FIRST_DEPLOY_BLOCK[MONAD_TESTNET]); + assert.doesNotThrow(() => + assertStartBlocksValid({ [CELO_SEPOLIA]: String(celoSepoliaDevStart) }), + ); + }); +}); From 96f63b8ea687f64d3c4eaf44210786dafa614b46 Mon Sep 17 00:00:00 2001 From: gisk0 Date: Tue, 31 Mar 2026 00:37:54 +0200 Subject: [PATCH 08/11] fix: scope start-block guard to mainnet only + fix test + update all docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Startup guard: scope assertStartBlocksValid to mainnet chains only (42220, 143). Testnet env vars (ENVIO_START_BLOCK_CELO_SEPOLIA, MONAD_TESTNET) are excluded — a leftover testnet value in .env must not block a mainnet or unrelated testnet run. Testnet configs are dev-only and not deployed to hosted Envio production. Tests: update to reflect mainnet-only scope; replace the ineffective newline-suffix check with a clear 'does not throw for testnet env vars' regression test. Docs: update README.md and indexer-envio/README.md to replace all old single-chain pnpm commands (indexer:celo-sepolia:*, indexer:monad-testnet:*) with the new multichain testnet commands (indexer:testnet:codegen/dev). --- README.md | 9 ++-- indexer-envio/README.md | 10 ++--- indexer-envio/src/EventHandlers.ts | 26 +++++------ .../test/startBlockInvariant.test.ts | 44 ++++--------------- 4 files changed, 25 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 0b85d41..a058f55 100644 --- a/README.md +++ b/README.md @@ -57,14 +57,11 @@ pnpm install ### Run the Indexer (local) ```bash -# Multichain (Celo + Monad mainnet) — default +# Multichain mainnet (Celo + Monad) — default pnpm indexer:codegen && pnpm indexer:dev -# Celo Sepolia (testnet) -pnpm indexer:celo-sepolia:codegen && pnpm indexer:celo-sepolia:dev - -# Monad Testnet -pnpm indexer:monad-testnet:codegen && pnpm indexer:monad-testnet:dev +# Multichain testnet (Celo Sepolia + Monad testnet) +pnpm indexer:testnet:codegen && pnpm indexer:testnet:dev ``` ### Run the Dashboard diff --git a/indexer-envio/README.md b/indexer-envio/README.md index efc20f3..a74c451 100644 --- a/indexer-envio/README.md +++ b/indexer-envio/README.md @@ -76,12 +76,10 @@ GraphQL endpoint: `http://localhost:8080/v1/graphql` ### Available Commands (from repo root) ```bash -pnpm indexer:codegen # Generate types (multichain — Celo + Monad mainnet) -pnpm indexer:dev # Start local multichain indexer -pnpm indexer:celo-sepolia:codegen # Generate types for Celo Sepolia testnet -pnpm indexer:celo-sepolia:dev # Start local Celo Sepolia indexer -pnpm indexer:monad-testnet:codegen # Generate types for Monad Testnet -pnpm indexer:monad-testnet:dev # Start local Monad Testnet indexer +pnpm indexer:codegen # Generate types (multichain mainnet — Celo + Monad) +pnpm indexer:dev # Start local multichain mainnet indexer +pnpm indexer:testnet:codegen # Generate types (multichain testnet — Celo Sepolia + Monad testnet) +pnpm indexer:testnet:dev # Start local multichain testnet indexer pnpm deploy:indexer # Push to envio branch → triggers hosted reindex ``` diff --git a/indexer-envio/src/EventHandlers.ts b/indexer-envio/src/EventHandlers.ts index bc148d3..62f68f1 100644 --- a/indexer-envio/src/EventHandlers.ts +++ b/indexer-envio/src/EventHandlers.ts @@ -12,28 +12,24 @@ // high, FPMMDeployed events are never seen, contractRegister never fires, // and all pool events are silently dropped — no error, just missing data. // -// First factory deployment blocks (inclusive): -// Celo mainnet (42220): 60668100 — initial batch of 4 FPMM pools -// Monad mainnet (143): 60759432 — initial batch of 3 FPMM pools -// Celo Sepolia (11142220): ~18946570 — testnet deployment -// Monad testnet (10143): ~17932599 — testnet FPMMFactory deployment +// First factory deployment blocks for production mainnet chains only. +// We only validate mainnet chains here to avoid false-fatal errors when a +// testnet env var (e.g. ENVIO_START_BLOCK_MONAD_TESTNET) is left set in +// .env from a previous testnet run — that should never block a mainnet start. +// Testnet configs are dev-only and not deployed to hosted Envio production. +// +// Celo mainnet (42220): 60668100 — initial batch of 4 FPMM pools +// Monad mainnet (143): 60759432 — initial batch of 3 FPMM pools // --------------------------------------------------------------------------- export const FPMM_FIRST_DEPLOY_BLOCK: Record = { 42220: 60668100, // Celo mainnet 143: 60759432, // Monad mainnet - 11142220: 18946570, // Celo Sepolia - 10143: 17932599, // Monad testnet }; -// Each chain maps to its dedicated env var (never the generic ENVIO_START_BLOCK). -// Using ENVIO_START_BLOCK here would cause false-fatal errors when running a -// single-network config where the value is valid for one chain but > the first -// deploy block on another chain that isn't even active in that run. +// Each mainnet chain maps to its dedicated env var. export const START_BLOCK_ENV_NAME: Record = { 42220: "ENVIO_START_BLOCK_CELO", 143: "ENVIO_START_BLOCK_MONAD", - 11142220: "ENVIO_START_BLOCK_CELO_SEPOLIA", - 10143: "ENVIO_START_BLOCK_MONAD_TESTNET", }; /** @@ -66,12 +62,10 @@ export function assertStartBlocksValid( } } -// Run the check at startup with values from the actual environment. +// Run the check at startup with mainnet env var values only. assertStartBlocksValid({ 42220: process.env.ENVIO_START_BLOCK_CELO, 143: process.env.ENVIO_START_BLOCK_MONAD, - 11142220: process.env.ENVIO_START_BLOCK_CELO_SEPOLIA, - 10143: process.env.ENVIO_START_BLOCK_MONAD_TESTNET, }); // Handler registrations (side-effect imports) diff --git a/indexer-envio/test/startBlockInvariant.test.ts b/indexer-envio/test/startBlockInvariant.test.ts index f6b1881..5a59703 100644 --- a/indexer-envio/test/startBlockInvariant.test.ts +++ b/indexer-envio/test/startBlockInvariant.test.ts @@ -6,11 +6,9 @@ import { START_BLOCK_ENV_NAME, } from "../src/EventHandlers"; -// Chain IDs used in tests +// Chain IDs used in tests (mainnet only — startup guard only covers mainnet) const CELO = 42220; const MONAD = 143; -const CELO_SEPOLIA = 11142220; -const MONAD_TESTNET = 10143; describe("assertStartBlocksValid", () => { it("passes when no env overrides are set", () => { @@ -22,8 +20,6 @@ describe("assertStartBlocksValid", () => { assertStartBlocksValid({ [CELO]: undefined, [MONAD]: "", - [CELO_SEPOLIA]: undefined, - [MONAD_TESTNET]: "", }), ); }); @@ -71,44 +67,20 @@ describe("assertStartBlocksValid", () => { ); }); - it("throws for Celo Sepolia with correct env var name in message", () => { - const tooHigh = FPMM_FIRST_DEPLOY_BLOCK[CELO_SEPOLIA] + 1; - assert.throws( - () => assertStartBlocksValid({ [CELO_SEPOLIA]: String(tooHigh) }), - (err: unknown) => { - assert(err instanceof Error); - assert(err.message.includes(START_BLOCK_ENV_NAME[CELO_SEPOLIA])); - assert(!err.message.includes("ENVIO_START_BLOCK_CELO\n")); // not the mainnet var - return true; - }, - ); - }); - - it("throws for Monad testnet with correct env var name in message", () => { - const tooHigh = FPMM_FIRST_DEPLOY_BLOCK[MONAD_TESTNET] + 1; - assert.throws( - () => assertStartBlocksValid({ [MONAD_TESTNET]: String(tooHigh) }), - (err: unknown) => { - assert(err instanceof Error); - assert(err.message.includes(START_BLOCK_ENV_NAME[MONAD_TESTNET])); - return true; - }, - ); - }); - it("skips non-numeric override gracefully (no throw)", () => { assert.doesNotThrow(() => assertStartBlocksValid({ [CELO]: "not-a-number" }), ); }); - it("does not throw for a valid Celo Sepolia value that would be > Monad testnet deploy block", () => { - // Regression: ENVIO_START_BLOCK=18901381 is valid for Celo Sepolia but - // > 17932599 (Monad testnet first deploy). Must NOT throw for the wrong chain. - const celoSepoliaDevStart = 18901381; - assert(celoSepoliaDevStart > FPMM_FIRST_DEPLOY_BLOCK[MONAD_TESTNET]); + it("does not throw for testnet env vars set to high values (mainnet guard is mainnet-only)", () => { + // Regression: a leftover ENVIO_START_BLOCK_MONAD_TESTNET in .env from a + // prior testnet run must NOT block a mainnet startup. The guard is scoped + // to mainnet chains only — testnet vars are outside FPMM_FIRST_DEPLOY_BLOCK. assert.doesNotThrow(() => - assertStartBlocksValid({ [CELO_SEPOLIA]: String(celoSepoliaDevStart) }), + // Pass testnet chain IDs — they're not in FPMM_FIRST_DEPLOY_BLOCK, + // so the loop simply has no entry for them and does nothing. + assertStartBlocksValid({ 10143: "99999999", 11142220: "99999999" }), ); }); }); From 0d226607ad334669e1f096e7286e0fe1663ae052 Mon Sep 17 00:00:00 2001 From: gisk0 Date: Tue, 31 Mar 2026 09:07:31 +0200 Subject: [PATCH 09/11] fix: warn instead of throw for start-block guard + add handler tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Startup guard (EventHandlers.ts): - Change to warn-by-default, throw only when ENVIO_STRICT_START_BLOCK=true (set in CI/hosted runs). A leftover mainnet ENVIO_START_BLOCK_CELO in .env no longer hard-fails an unrelated testnet pnpm indexer:testnet:dev. - The strict path is still available for hosted Envio production to enforce correctness at deploy time. Tests (startBlockInvariant.test.ts): - Add test for warn-mode (does not throw, console.warn called with message) - Add test for strict-mode (throws with correct env var name) - Separate Monad strict-mode throw test New test file (dynamicRegistration.test.ts): - Documents contractRegister testing limitation (framework harness does not expose processContractRegister — this is an Envio limitation) - Adds two handler tests: FPMMDeployed.handler and VirtualPoolDeployed.handler each create a Pool entity, proving the handler path works for dynamically discovered pools Docs: fix indexer-envio/README.md config table (remove deleted config.celo.sepolia.yaml and config.monad.testnet.yaml entries) --- indexer-envio/README.md | 9 +- indexer-envio/src/EventHandlers.ts | 28 ++-- .../test/dynamicRegistration.test.ts | 122 ++++++++++++++++++ .../test/startBlockInvariant.test.ts | 25 +++- 4 files changed, 166 insertions(+), 18 deletions(-) create mode 100644 indexer-envio/test/dynamicRegistration.test.ts diff --git a/indexer-envio/README.md b/indexer-envio/README.md index a74c451..6bd618a 100644 --- a/indexer-envio/README.md +++ b/indexer-envio/README.md @@ -44,11 +44,10 @@ This prevents collisions when the same contract address is deployed on multiple ## Configuration -| File | Networks | -| -------------------------------- | ------------------------------ | -| `config.multichain.mainnet.yaml` | Celo Mainnet + Monad (default) | -| `config.celo.sepolia.yaml` | Celo Sepolia (testnet) | -| `config.monad.testnet.yaml` | Monad Testnet | +| File | Networks | +| -------------------------------- | ------------------------------------------------- | +| `config.multichain.mainnet.yaml` | Celo Mainnet + Monad Mainnet (default/production) | +| `config.multichain.testnet.yaml` | Celo Sepolia + Monad Testnet | Deploy branch: `envio` → triggers hosted reindex on push. diff --git a/indexer-envio/src/EventHandlers.ts b/indexer-envio/src/EventHandlers.ts index 62f68f1..8966669 100644 --- a/indexer-envio/src/EventHandlers.ts +++ b/indexer-envio/src/EventHandlers.ts @@ -33,14 +33,20 @@ export const START_BLOCK_ENV_NAME: Record = { }; /** - * Validate that no ENVIO_START_BLOCK_* env var is set above the first - * FPMMFactory deployment block for its chain. If it is, all factory deploy - * events are missed and no pools are ever registered — a silent data loss. + * Warn (or throw in CI) if an ENVIO_START_BLOCK_* env var is set above the + * first FPMMFactory deployment block for its chain. If start_block is too + * high, FPMMDeployed events are never seen, contractRegister never fires, + * and pool events are silently dropped. + * + * Throws only when ENVIO_STRICT_START_BLOCK=true (e.g. in CI/hosted runs). + * In local dev mode it logs a warning instead, so a leftover mainnet env var + * doesn't hard-fail an unrelated testnet run. * * Exported for testing. Called at module load time below. */ export function assertStartBlocksValid( envOverrides: Record, + strict = process.env.ENVIO_STRICT_START_BLOCK === "true", ): void { for (const [chainIdStr, firstDeployBlock] of Object.entries( FPMM_FIRST_DEPLOY_BLOCK, @@ -52,12 +58,16 @@ export function assertStartBlocksValid( if (!Number.isFinite(startBlock)) continue; if (startBlock > firstDeployBlock) { const envVarName = START_BLOCK_ENV_NAME[chainId]; - throw new Error( - `[EventHandlers] FATAL: start block for chain ${chainId} is ${startBlock}, ` + - `but FPMMFactory first deployed at block ${firstDeployBlock}. ` + - `All factory deploy events will be missed and no pools will be indexed. ` + - `Lower ${envVarName} to ≤${firstDeployBlock} or remove the override.`, - ); + const msg = + `[EventHandlers] start block for chain ${chainId} is ${startBlock}, ` + + `but FPMMFactory first deployed at block ${firstDeployBlock}. ` + + `All factory deploy events will be missed and no pools will be indexed. ` + + `Lower ${envVarName} to ≤${firstDeployBlock} or remove the override.`; + if (strict) { + throw new Error(`FATAL: ${msg}`); + } else { + console.warn(`⚠️ WARNING: ${msg}`); + } } } } diff --git a/indexer-envio/test/dynamicRegistration.test.ts b/indexer-envio/test/dynamicRegistration.test.ts new file mode 100644 index 0000000..8277857 --- /dev/null +++ b/indexer-envio/test/dynamicRegistration.test.ts @@ -0,0 +1,122 @@ +/// +/** + * Dynamic Contract Registration — Behavioral Documentation + * + * This file documents the testing strategy (and limitations) for the + * contractRegister callbacks in fpmm.ts and virtualPool.ts. + * + * The core behavioral change in this PR: + * Before: FPMM/VirtualPool addresses were hardcoded in config YAML. + * After: Addresses are registered dynamically via contractRegister hooks + * triggered by FPMMFactory.FPMMDeployed and + * VirtualPoolFactory.VirtualPoolDeployed events. + * + * WHY contractRegister CALLBACKS CANNOT BE UNIT TESTED: + * Envio's TestHelpers.processEvent() only exercises the .handler() path. + * The .contractRegister() callback is a framework-level hook that Envio + * invokes before the handler during real indexing. The test harness does + * not expose a processContractRegister() equivalent — this is a framework + * limitation, not an oversight. + * + * WHAT IS TESTED: + * - FPMMDeployed.handler creates a Pool entity (swap-reserves.test.ts) + * - VirtualPoolDeployed.handler creates a Pool entity (swap-reserves.test.ts) + * - The startup start-block guard (startBlockInvariant.test.ts) + * + * WHAT IS VERIFIED BY INSPECTION: + * fpmm.ts: FPMMFactory.FPMMDeployed.contractRegister calls + * context.addFPMM(event.params.fpmmProxy) — the correct Envio API for + * dynamic registration. This is the same API used for ERC20FeeToken + * (context.addERC20FeeToken) which was already present and working. + * + * virtualPool.ts: VirtualPoolFactory.VirtualPoolDeployed.contractRegister + * calls context.addVirtualPool(event.params.pool) — correct Envio API. + * + * INTEGRATION EVIDENCE: + * On-chain verification (2026-03-30): EURm/USDm pool on Celo mainnet + * (0x1ad2ea06...) had 123 real Swap events but 0 in the indexer because + * it was missing from the hardcoded config list. After deploying this fix, + * a full reindex will capture all events for all factory-deployed pools. + */ +import { strict as assert } from "assert"; +import generated from "generated"; + +type MockDb = { + entities: { + Pool: { get: (id: string) => unknown }; + FactoryDeployment: { get: (id: string) => unknown }; + }; +}; + +type EventProcessor = { + createMockEvent: (args: unknown) => unknown; + processEvent: (args: { event: unknown; mockDb: MockDb }) => Promise; +}; + +type GeneratedModule = { + TestHelpers: { + MockDb: { createMockDb: () => MockDb }; + FPMMFactory: { FPMMDeployed: EventProcessor }; + VirtualPoolFactory: { VirtualPoolDeployed: EventProcessor }; + }; +}; + +const { TestHelpers } = generated as unknown as GeneratedModule; +const { MockDb, FPMMFactory, VirtualPoolFactory } = TestHelpers; + +const POOL_ADDR = "0x1ad2ea06502919f935d9c09028df73a462979e29"; +const VPOOL_ADDR = "0xab945882018b81bdf62629e98ffdafd9495a0076"; +const TOKEN0 = "0x765de816845861e75a25fca122bb6898b8b1282a"; +const TOKEN1 = "0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73"; + +describe("Dynamic contract registration — handler coverage", () => { + it("FPMMDeployed.handler creates a Pool entity for the deployed pool", async () => { + // This tests the .handler() path. The .contractRegister() path + // (context.addFPMM) cannot be tested via this harness — see file header. + let mockDb = MockDb.createMockDb(); + const event = FPMMFactory.FPMMDeployed.createMockEvent({ + fpmmProxy: POOL_ADDR, + fpmmImplementation: "0x00000000000000000000000000000000000000bc", + token0: TOKEN0, + token1: TOKEN1, + mockEventData: { + chainId: 42220, + logIndex: 1, + srcAddress: "0x00000000000000000000000000000000000000cc", + block: { number: 62725622, timestamp: 1_700_010_000 }, + }, + }); + mockDb = await FPMMFactory.FPMMDeployed.processEvent({ event, mockDb }); + const poolId = `42220-${POOL_ADDR}`; + const pool = mockDb.entities.Pool.get(poolId); + assert.ok( + pool, + `Pool entity must exist after FPMMDeployed (id: ${poolId})`, + ); + }); + + it("VirtualPoolDeployed.handler creates a Pool entity for the deployed pool", async () => { + let mockDb = MockDb.createMockDb(); + const event = VirtualPoolFactory.VirtualPoolDeployed.createMockEvent({ + pool: VPOOL_ADDR, + token0: TOKEN0, + token1: TOKEN1, + mockEventData: { + chainId: 42220, + logIndex: 1, + srcAddress: "0x00000000000000000000000000000000000000cc", + block: { number: 60668100, timestamp: 1_700_020_000 }, + }, + }); + mockDb = await VirtualPoolFactory.VirtualPoolDeployed.processEvent({ + event, + mockDb, + }); + const poolId = `42220-${VPOOL_ADDR}`; + const pool = mockDb.entities.Pool.get(poolId); + assert.ok( + pool, + `Pool entity must exist after VirtualPoolDeployed (id: ${poolId})`, + ); + }); +}); diff --git a/indexer-envio/test/startBlockInvariant.test.ts b/indexer-envio/test/startBlockInvariant.test.ts index 5a59703..e691e51 100644 --- a/indexer-envio/test/startBlockInvariant.test.ts +++ b/indexer-envio/test/startBlockInvariant.test.ts @@ -41,10 +41,27 @@ describe("assertStartBlocksValid", () => { ); }); - it("throws when Celo start block is above first deploy block", () => { + it("warns (does not throw) when start block is too high in non-strict mode", () => { + const tooHigh = FPMM_FIRST_DEPLOY_BLOCK[CELO] + 1; + const warnings: string[] = []; + const origWarn = console.warn; + console.warn = (msg: string) => warnings.push(msg); + try { + assert.doesNotThrow(() => + assertStartBlocksValid({ [CELO]: String(tooHigh) }, false), + ); + assert.equal(warnings.length, 1); + assert(warnings[0].includes(START_BLOCK_ENV_NAME[CELO])); + assert(warnings[0].includes(String(tooHigh))); + } finally { + console.warn = origWarn; + } + }); + + it("throws when start block is too high in strict mode", () => { const tooHigh = FPMM_FIRST_DEPLOY_BLOCK[CELO] + 1; assert.throws( - () => assertStartBlocksValid({ [CELO]: String(tooHigh) }), + () => assertStartBlocksValid({ [CELO]: String(tooHigh) }, true), (err: unknown) => { assert(err instanceof Error); assert(err.message.includes(START_BLOCK_ENV_NAME[CELO])); @@ -55,10 +72,10 @@ describe("assertStartBlocksValid", () => { ); }); - it("throws when Monad start block is above first deploy block", () => { + it("throws for Monad in strict mode with correct env var name", () => { const tooHigh = FPMM_FIRST_DEPLOY_BLOCK[MONAD] + 1000; assert.throws( - () => assertStartBlocksValid({ [MONAD]: String(tooHigh) }), + () => assertStartBlocksValid({ [MONAD]: String(tooHigh) }, true), (err: unknown) => { assert(err instanceof Error); assert(err.message.includes(START_BLOCK_ENV_NAME[MONAD])); From 77e9a275cd3923342b571a0ea4a09ce9556dfa4e Mon Sep 17 00:00:00 2001 From: gisk0 Date: Tue, 31 Mar 2026 09:28:05 +0200 Subject: [PATCH 10/11] refactor: extract startup checks, address all review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Startup checks (EventHandlers.ts → src/startupChecks.ts): - Extract assertStartBlocksValid + constants into dedicated module - Gate module-level call with NODE_ENV=test skip to prevent shell env vars from interfering with unrelated test suites - EventHandlers.ts re-exports all startup check symbols for test backwards compat CI (.github/workflows/indexer-envio.yml): - Add ENVIO_STRICT_START_BLOCK=true to codegen validation step so the guard throws (not warns) in CI, catching start-block misconfig before deploy virtualPool.ts: - Fix misleading 'handle defensively' comment on VirtualPool.Rebalanced — the event is real when emitted; comment now explains why handler is simpler than FPMM version (no oracle/RPC fetch) AGENTS.md + indexer-envio/AGENTS.md: - Remove blank line artifact breaking ASCII file tree config.multichain.testnet.yaml: - Document the mixed static+dynamic address model; note that testnet pools deployed before contractRegister support was added may be missing on fresh sync (same pattern as mainnet, same fix path: add to hardcoded list) config.multichain.mainnet.yaml: - Confirm OpenLiquidityStrategy is deployed on Monad mainnet (verified via eth_getCode — bytecode present), remove precautionary 'verify before going live' comment dynamicRegistration.test.ts: - Document why the generated module type cast is unavoidable (generated/index.d.ts does not export clean standalone interface types) --- .github/workflows/indexer-envio.yml | 4 + AGENTS.md | 1 - indexer-envio/AGENTS.md | 1 - indexer-envio/config.multichain.mainnet.yaml | 5 +- indexer-envio/config.multichain.testnet.yaml | 8 ++ indexer-envio/src/EventHandlers.ts | 81 +++---------------- indexer-envio/src/handlers/virtualPool.ts | 4 +- indexer-envio/src/startupChecks.ts | 81 +++++++++++++++++++ .../test/dynamicRegistration.test.ts | 7 ++ 9 files changed, 116 insertions(+), 76 deletions(-) create mode 100644 indexer-envio/src/startupChecks.ts diff --git a/.github/workflows/indexer-envio.yml b/.github/workflows/indexer-envio.yml index b77f6ce..9d05083 100644 --- a/.github/workflows/indexer-envio.yml +++ b/.github/workflows/indexer-envio.yml @@ -58,6 +58,10 @@ jobs: # Run codegen against every config file to catch schema/config drift early. # config.multichain.mainnet.yaml is run last — its output is used by # typecheck + tests below. + # ENVIO_STRICT_START_BLOCK=true ensures the startup guard throws (not warns) + # if start-block env vars are misconfigured, catching the issue before deploy. + env: + ENVIO_STRICT_START_BLOCK: "true" run: | pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.testnet.yaml pnpm --filter @mento-protocol/indexer-envio codegen --config config.multichain.mainnet.yaml diff --git a/AGENTS.md b/AGENTS.md index 4b5b6b8..f34485f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,7 +85,6 @@ monitoring-monorepo/ ├── indexer-envio/ │ ├── config.multichain.mainnet.yaml # Mainnet indexer config (Celo + Monad) — DEFAULT │ ├── config.multichain.testnet.yaml # Testnet multichain config - │ ├── schema.graphql # Entity definitions │ ├── src/ │ │ ├── EventHandlers.ts # Envio entry point (imports handlers, re-exports for tests) diff --git a/indexer-envio/AGENTS.md b/indexer-envio/AGENTS.md index 095e1c4..d400f43 100644 --- a/indexer-envio/AGENTS.md +++ b/indexer-envio/AGENTS.md @@ -8,7 +8,6 @@ Envio HyperIndex indexer for Mento v3 FPMM (Fixed Product Market Maker) pools on - `config.multichain.mainnet.yaml` — **Default** mainnet config (Celo + Monad) - `config.multichain.testnet.yaml` — Testnet multichain config - - `schema.graphql` — Entity definitions (FPMM, Swap, Mint, Burn, UpdateReserves, Rebalanced) - `src/EventHandlers.ts` — Event processing logic - `src/contractAddresses.ts` — Contract address resolution from `@mento-protocol/contracts`; also exports `CONTRACT_NAMESPACE_BY_CHAIN` (backed by `config/deployment-namespaces.json`) diff --git a/indexer-envio/config.multichain.mainnet.yaml b/indexer-envio/config.multichain.mainnet.yaml index 2e59f07..9ef10cd 100644 --- a/indexer-envio/config.multichain.mainnet.yaml +++ b/indexer-envio/config.multichain.mainnet.yaml @@ -140,9 +140,8 @@ networks: address: [] # Dynamically registered from FPMMDeployed events - name: OpenLiquidityStrategy address: - # CREATE2 deterministic — same address as Celo. Verify deployment - # at https://monad-explorer.monad.xyz/address/0x54e2Ae8c8448912E17cE0b2453bAFB7B0D80E40f - # before going live. Set to [] if not yet deployed on Monad. + # CREATE2 deterministic — same address as Celo. + # Verified deployed on Monad mainnet (eth_getCode returned bytecode). - 0x54e2Ae8c8448912E17cE0b2453bAFB7B0D80E40f unordered_multichain_mode: true diff --git a/indexer-envio/config.multichain.testnet.yaml b/indexer-envio/config.multichain.testnet.yaml index c702141..77b27b6 100644 --- a/indexer-envio/config.multichain.testnet.yaml +++ b/indexer-envio/config.multichain.testnet.yaml @@ -4,6 +4,14 @@ description: Mento v3 multichain testnet indexer — Celo Sepolia (11142220) + M # # Per-chain RPC overrides: ENVIO_RPC_URL_11142220 and ENVIO_RPC_URL_10143. # Do NOT use the generic ENVIO_RPC_URL in multichain mode. +# +# NOTE — Mixed static + dynamic address model: +# FPMM and VirtualPool addresses here are a mix of hardcoded seed addresses +# (for pools deployed before dynamic contractRegister support was added) and +# dynamic registration via contractRegister hooks (for all new deployments). +# Any testnet pool deployed before this config was updated that is NOT in the +# hardcoded list will be silently missing on a fresh sync. New pools are +# automatically discovered going forward. This is the same pattern as mainnet. # --------------------------------------------------------------------------- # Global contract definitions (shared ABI + handler across all networks) diff --git a/indexer-envio/src/EventHandlers.ts b/indexer-envio/src/EventHandlers.ts index 8966669..ebdea1e 100644 --- a/indexer-envio/src/EventHandlers.ts +++ b/indexer-envio/src/EventHandlers.ts @@ -6,77 +6,11 @@ // modules are imported below; their registrations fire at module load time. // =========================================================================== -// --------------------------------------------------------------------------- -// Startup invariant: fail loudly if ENVIO_START_BLOCK is set above the -// FPMMFactory's first deployment block for any chain. If start_block is too -// high, FPMMDeployed events are never seen, contractRegister never fires, -// and all pool events are silently dropped — no error, just missing data. -// -// First factory deployment blocks for production mainnet chains only. -// We only validate mainnet chains here to avoid false-fatal errors when a -// testnet env var (e.g. ENVIO_START_BLOCK_MONAD_TESTNET) is left set in -// .env from a previous testnet run — that should never block a mainnet start. -// Testnet configs are dev-only and not deployed to hosted Envio production. -// -// Celo mainnet (42220): 60668100 — initial batch of 4 FPMM pools -// Monad mainnet (143): 60759432 — initial batch of 3 FPMM pools -// --------------------------------------------------------------------------- -export const FPMM_FIRST_DEPLOY_BLOCK: Record = { - 42220: 60668100, // Celo mainnet - 143: 60759432, // Monad mainnet -}; +import { runStartupChecks } from "./startupChecks"; -// Each mainnet chain maps to its dedicated env var. -export const START_BLOCK_ENV_NAME: Record = { - 42220: "ENVIO_START_BLOCK_CELO", - 143: "ENVIO_START_BLOCK_MONAD", -}; - -/** - * Warn (or throw in CI) if an ENVIO_START_BLOCK_* env var is set above the - * first FPMMFactory deployment block for its chain. If start_block is too - * high, FPMMDeployed events are never seen, contractRegister never fires, - * and pool events are silently dropped. - * - * Throws only when ENVIO_STRICT_START_BLOCK=true (e.g. in CI/hosted runs). - * In local dev mode it logs a warning instead, so a leftover mainnet env var - * doesn't hard-fail an unrelated testnet run. - * - * Exported for testing. Called at module load time below. - */ -export function assertStartBlocksValid( - envOverrides: Record, - strict = process.env.ENVIO_STRICT_START_BLOCK === "true", -): void { - for (const [chainIdStr, firstDeployBlock] of Object.entries( - FPMM_FIRST_DEPLOY_BLOCK, - )) { - const chainId = Number(chainIdStr); - const envVal = envOverrides[chainId]; - if (envVal === undefined || envVal === "") continue; - const startBlock = Number(envVal); - if (!Number.isFinite(startBlock)) continue; - if (startBlock > firstDeployBlock) { - const envVarName = START_BLOCK_ENV_NAME[chainId]; - const msg = - `[EventHandlers] start block for chain ${chainId} is ${startBlock}, ` + - `but FPMMFactory first deployed at block ${firstDeployBlock}. ` + - `All factory deploy events will be missed and no pools will be indexed. ` + - `Lower ${envVarName} to ≤${firstDeployBlock} or remove the override.`; - if (strict) { - throw new Error(`FATAL: ${msg}`); - } else { - console.warn(`⚠️ WARNING: ${msg}`); - } - } - } -} - -// Run the check at startup with mainnet env var values only. -assertStartBlocksValid({ - 42220: process.env.ENVIO_START_BLOCK_CELO, - 143: process.env.ENVIO_START_BLOCK_MONAD, -}); +// Run startup invariant checks (skipped in NODE_ENV=test). +// See src/startupChecks.ts for details and rationale. +runStartupChecks(); // Handler registrations (side-effect imports) import "./handlers/fpmm"; @@ -123,3 +57,10 @@ export { // Trading limits constant (used by decimals.test.ts) export { TRADING_LIMITS_INTERNAL_DECIMALS } from "./tradingLimits"; + +// Startup checks (used by startBlockInvariant.test.ts) +export { + assertStartBlocksValid, + FPMM_FIRST_DEPLOY_BLOCK, + START_BLOCK_ENV_NAME, +} from "./startupChecks"; diff --git a/indexer-envio/src/handlers/virtualPool.ts b/indexer-envio/src/handlers/virtualPool.ts index 1a24468..3e42269 100644 --- a/indexer-envio/src/handlers/virtualPool.ts +++ b/indexer-envio/src/handlers/virtualPool.ts @@ -295,7 +295,9 @@ VirtualPool.UpdateReserves.handler(async ({ event, context }) => { // --------------------------------------------------------------------------- VirtualPool.Rebalanced.handler(async ({ event, context }) => { - // VirtualPools shouldn't normally rebalance, but handle defensively + // VirtualPools rarely emit Rebalanced — handler is intentionally minimal + // (no oracle/RPC fetch) compared to the FPMM version, since VirtualPools + // manage reserves differently. If the contract emits this event, it's real. const id = eventId(event.chainId, event.block.number, event.logIndex); const poolId = makePoolId(event.chainId, event.srcAddress); const blockNumber = asBigInt(event.block.number); diff --git a/indexer-envio/src/startupChecks.ts b/indexer-envio/src/startupChecks.ts new file mode 100644 index 0000000..2164a73 --- /dev/null +++ b/indexer-envio/src/startupChecks.ts @@ -0,0 +1,81 @@ +/** + * startupChecks.ts — Startup invariant validation + * + * Checks that ENVIO_START_BLOCK_* env vars are not set above the first + * FPMMFactory deployment block for each mainnet chain. If start_block is too + * high, FPMMDeployed events are never seen, contractRegister never fires, and + * all pool events are silently dropped with no error. + * + * Only mainnet chains are checked here. Testnet env vars are excluded to + * avoid false-fatal errors when a leftover value from a prior testnet run is + * still in .env during a mainnet or unrelated testnet startup. + * + * Behavior: + * - Default (local dev): logs a console.warn, does not throw. + * - Strict mode (ENVIO_STRICT_START_BLOCK=true, set in CI/hosted): throws. + * - Test runs (NODE_ENV=test): skipped entirely at module load time to prevent + * env vars set in the shell from interfering with unrelated test suites. + * + * First factory deployment blocks: + * Celo mainnet (42220): 60668100 — initial batch of 4 FPMM pools + * Monad mainnet (143): 60759432 — initial batch of 3 FPMM pools + */ + +export const FPMM_FIRST_DEPLOY_BLOCK: Record = { + 42220: 60668100, // Celo mainnet + 143: 60759432, // Monad mainnet +}; + +/** Maps mainnet chain IDs to their dedicated ENVIO_START_BLOCK_* env var name. */ +export const START_BLOCK_ENV_NAME: Record = { + 42220: "ENVIO_START_BLOCK_CELO", + 143: "ENVIO_START_BLOCK_MONAD", +}; + +/** + * Validate that no ENVIO_START_BLOCK_* override is set above the first + * FPMMFactory deployment block for its chain. + * + * @param envOverrides - Map from chain ID to env var value string. + * @param strict - If true, throws on violation. Defaults to ENVIO_STRICT_START_BLOCK=true. + */ +export function assertStartBlocksValid( + envOverrides: Record, + strict = process.env.ENVIO_STRICT_START_BLOCK === "true", +): void { + for (const [chainIdStr, firstDeployBlock] of Object.entries( + FPMM_FIRST_DEPLOY_BLOCK, + )) { + const chainId = Number(chainIdStr); + const envVal = envOverrides[chainId]; + if (envVal === undefined || envVal === "") continue; + const startBlock = Number(envVal); + if (!Number.isFinite(startBlock)) continue; + if (startBlock > firstDeployBlock) { + const envVarName = START_BLOCK_ENV_NAME[chainId]; + const msg = + `[startupChecks] start block for chain ${chainId} is ${startBlock}, ` + + `but FPMMFactory first deployed at block ${firstDeployBlock}. ` + + `All factory deploy events will be missed and no pools will be indexed. ` + + `Lower ${envVarName} to ≤${firstDeployBlock} or remove the override.`; + if (strict) { + throw new Error(`FATAL: ${msg}`); + } else { + console.warn(`⚠️ WARNING: ${msg}`); + } + } + } +} + +/** + * Run the start-block check at startup unless running in test mode. + * Call this once from the Envio entry point (EventHandlers.ts). + */ +export function runStartupChecks(): void { + // Skip in test mode — shell env vars must not interfere with test suites. + if (process.env.NODE_ENV === "test") return; + assertStartBlocksValid({ + 42220: process.env.ENVIO_START_BLOCK_CELO, + 143: process.env.ENVIO_START_BLOCK_MONAD, + }); +} diff --git a/indexer-envio/test/dynamicRegistration.test.ts b/indexer-envio/test/dynamicRegistration.test.ts index 8277857..69a468f 100644 --- a/indexer-envio/test/dynamicRegistration.test.ts +++ b/indexer-envio/test/dynamicRegistration.test.ts @@ -41,6 +41,13 @@ import { strict as assert } from "assert"; import generated from "generated"; +// The `generated` module ships as CommonJS with its own hand-rolled type +// definitions in generated/index.d.ts. It does not export clean standalone +// interface types for MockDb or EventProcessor — only the full namespace shape. +// Importing and narrowing inline avoids duplicating the entire generated +// module's type surface while still giving TS enough to catch call-site errors. +// If generated/index.d.ts gains proper exports in a future Envio version, +// replace these local types with direct imports. type MockDb = { entities: { Pool: { get: (id: string) => unknown }; From 1b614ce9ab70ea225efb281b16673f02ac32e57a Mon Sep 17 00:00:00 2001 From: gisk0 Date: Tue, 31 Mar 2026 09:35:21 +0200 Subject: [PATCH 11/11] test: add contractRegister introspection tests that catch registration removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The core risk of this PR is that someone removes context.addFPMM() or context.addVirtualPool() from the contractRegister callbacks — which would silently break pool discovery with no test failure. Added registry introspection tests that directly query the Envio handler registry after importing EventHandlers.ts. If either contractRegister() call is removed from fpmm.ts or virtualPool.ts, EventRegister.getContractRegister() returns undefined and the test fails with a clear message. Uses envio/src/EventRegister.res.js (internal Envio module) and generated/src/Types.res.js to read the handler registers directly. These are stable CommonJS internals that have been present across multiple Envio versions — if they change, the test fails loudly. Also clarifies the distinction between the two test suites: - Registry introspection: catches removed contractRegister() calls - Handler smoke tests: verifies handler() creates correct DB entities (TestHelpers.processEvent does not exercise contractRegister) --- .../test/dynamicRegistration.test.ts | 129 ++++++++++++------ 1 file changed, 88 insertions(+), 41 deletions(-) diff --git a/indexer-envio/test/dynamicRegistration.test.ts b/indexer-envio/test/dynamicRegistration.test.ts index 69a468f..baab82f 100644 --- a/indexer-envio/test/dynamicRegistration.test.ts +++ b/indexer-envio/test/dynamicRegistration.test.ts @@ -1,46 +1,31 @@ /// /** - * Dynamic Contract Registration — Behavioral Documentation + * Dynamic Contract Registration * - * This file documents the testing strategy (and limitations) for the - * contractRegister callbacks in fpmm.ts and virtualPool.ts. + * Tests for the contractRegister hooks that auto-discover new pools from + * factory deploy events, replacing the old hardcoded address list approach. * - * The core behavioral change in this PR: - * Before: FPMM/VirtualPool addresses were hardcoded in config YAML. - * After: Addresses are registered dynamically via contractRegister hooks - * triggered by FPMMFactory.FPMMDeployed and - * VirtualPoolFactory.VirtualPoolDeployed events. + * Two complementary test strategies: * - * WHY contractRegister CALLBACKS CANNOT BE UNIT TESTED: - * Envio's TestHelpers.processEvent() only exercises the .handler() path. - * The .contractRegister() callback is a framework-level hook that Envio - * invokes before the handler during real indexing. The test harness does - * not expose a processContractRegister() equivalent — this is a framework - * limitation, not an oversight. + * 1. REGISTRATION INTROSPECTION (new — will catch removed contractRegister calls) + * Read the Envio handler registry directly after importing EventHandlers. + * If fpmm.ts stops calling FPMMFactory.FPMMDeployed.contractRegister(...), + * EventRegister.getContractRegister(handlerRegister) returns undefined and + * these tests fail. * - * WHAT IS TESTED: - * - FPMMDeployed.handler creates a Pool entity (swap-reserves.test.ts) - * - VirtualPoolDeployed.handler creates a Pool entity (swap-reserves.test.ts) - * - The startup start-block guard (startBlockInvariant.test.ts) - * - * WHAT IS VERIFIED BY INSPECTION: - * fpmm.ts: FPMMFactory.FPMMDeployed.contractRegister calls - * context.addFPMM(event.params.fpmmProxy) — the correct Envio API for - * dynamic registration. This is the same API used for ERC20FeeToken - * (context.addERC20FeeToken) which was already present and working. - * - * virtualPool.ts: VirtualPoolFactory.VirtualPoolDeployed.contractRegister - * calls context.addVirtualPool(event.params.pool) — correct Envio API. - * - * INTEGRATION EVIDENCE: - * On-chain verification (2026-03-30): EURm/USDm pool on Celo mainnet - * (0x1ad2ea06...) had 123 real Swap events but 0 in the indexer because - * it was missing from the hardcoded config list. After deploying this fix, - * a full reindex will capture all events for all factory-deployed pools. + * 2. HANDLER SMOKE TESTS (existing) + * TestHelpers.processEvent() exercises .handler() — proves the handler + * creates DB entities correctly. Does NOT test .contractRegister() path + * (Envio test harness limitation — no processContractRegister() equivalent). */ import { strict as assert } from "assert"; import generated from "generated"; +// Import EventHandlers to trigger handler registrations (side-effect import). +// This causes fpmm.ts / virtualPool.ts to call their .contractRegister() and +// .handler() setup — which is what the introspection tests below verify. +import "../src/EventHandlers.ts"; + // The `generated` module ships as CommonJS with its own hand-rolled type // definitions in generated/index.d.ts. It does not export clean standalone // interface types for MockDb or EventProcessor — only the full namespace shape. @@ -69,19 +54,81 @@ type GeneratedModule = { }; const { TestHelpers } = generated as unknown as GeneratedModule; -const { MockDb, FPMMFactory, VirtualPoolFactory } = TestHelpers; +const { + MockDb, + FPMMFactory: TestFPMMFactory, + VirtualPoolFactory: TestVirtualPoolFactory, +} = TestHelpers; + +// --------------------------------------------------------------------------- +// Registry introspection +// +// Access the Envio handler registry directly to assert that contractRegister +// callbacks are wired up. These tests WILL FAIL if someone removes the +// contractRegister() calls from fpmm.ts or virtualPool.ts. +// +// Envio types.res.js exposes Types.FPMMFactory.FPMMDeployed.handlerRegister +// and EventRegister.getContractRegister(). We use the internal JS modules +// since the TypeScript types don't expose these. If the Envio package changes +// its internal structure, these imports will fail — that's intentional. +// --------------------------------------------------------------------------- + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const EventRegister = require("envio/src/EventRegister.res.js") as { + getContractRegister: (reg: unknown) => unknown; +}; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const GeneratedTypes = require("generated/src/Types.res.js") as { + FPMMFactory: { FPMMDeployed: { handlerRegister: unknown } }; + VirtualPoolFactory: { VirtualPoolDeployed: { handlerRegister: unknown } }; +}; + +describe("Dynamic contract registration — registry introspection", () => { + it("FPMMFactory.FPMMDeployed has a contractRegister callback registered", () => { + // This test FAILS if fpmm.ts removes: + // FPMMFactory.FPMMDeployed.contractRegister(({ event, context }) => { + // context.addFPMM(event.params.fpmmProxy); + // }) + const reg = EventRegister.getContractRegister( + GeneratedTypes.FPMMFactory.FPMMDeployed.handlerRegister, + ); + assert.ok( + reg !== undefined && reg !== null, + "FPMMFactory.FPMMDeployed must have a contractRegister callback. " + + "If this test fails, check that fpmm.ts calls .contractRegister() " + + "with context.addFPMM() — removing it silently breaks pool discovery.", + ); + }); + + it("VirtualPoolFactory.VirtualPoolDeployed has a contractRegister callback registered", () => { + // This test FAILS if virtualPool.ts removes: + // VirtualPoolFactory.VirtualPoolDeployed.contractRegister(({ event, context }) => { + // context.addVirtualPool(event.params.pool); + // }) + const reg = EventRegister.getContractRegister( + GeneratedTypes.VirtualPoolFactory.VirtualPoolDeployed.handlerRegister, + ); + assert.ok( + reg !== undefined && reg !== null, + "VirtualPoolFactory.VirtualPoolDeployed must have a contractRegister callback. " + + "If this test fails, check that virtualPool.ts calls .contractRegister() " + + "with context.addVirtualPool() — removing it silently breaks VirtualPool discovery.", + ); + }); +}); const POOL_ADDR = "0x1ad2ea06502919f935d9c09028df73a462979e29"; const VPOOL_ADDR = "0xab945882018b81bdf62629e98ffdafd9495a0076"; const TOKEN0 = "0x765de816845861e75a25fca122bb6898b8b1282a"; const TOKEN1 = "0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73"; -describe("Dynamic contract registration — handler coverage", () => { +describe("Dynamic contract registration — handler smoke tests", () => { it("FPMMDeployed.handler creates a Pool entity for the deployed pool", async () => { - // This tests the .handler() path. The .contractRegister() path - // (context.addFPMM) cannot be tested via this harness — see file header. + // Tests .handler() path only. .contractRegister() path is verified by the + // introspection suite above (registry-level assertion) rather than via + // TestHelpers.processEvent, which does not exercise contractRegister. let mockDb = MockDb.createMockDb(); - const event = FPMMFactory.FPMMDeployed.createMockEvent({ + const event = TestFPMMFactory.FPMMDeployed.createMockEvent({ fpmmProxy: POOL_ADDR, fpmmImplementation: "0x00000000000000000000000000000000000000bc", token0: TOKEN0, @@ -93,7 +140,7 @@ describe("Dynamic contract registration — handler coverage", () => { block: { number: 62725622, timestamp: 1_700_010_000 }, }, }); - mockDb = await FPMMFactory.FPMMDeployed.processEvent({ event, mockDb }); + mockDb = await TestFPMMFactory.FPMMDeployed.processEvent({ event, mockDb }); const poolId = `42220-${POOL_ADDR}`; const pool = mockDb.entities.Pool.get(poolId); assert.ok( @@ -104,7 +151,7 @@ describe("Dynamic contract registration — handler coverage", () => { it("VirtualPoolDeployed.handler creates a Pool entity for the deployed pool", async () => { let mockDb = MockDb.createMockDb(); - const event = VirtualPoolFactory.VirtualPoolDeployed.createMockEvent({ + const event = TestVirtualPoolFactory.VirtualPoolDeployed.createMockEvent({ pool: VPOOL_ADDR, token0: TOKEN0, token1: TOKEN1, @@ -115,7 +162,7 @@ describe("Dynamic contract registration — handler coverage", () => { block: { number: 60668100, timestamp: 1_700_020_000 }, }, }); - mockDb = await VirtualPoolFactory.VirtualPoolDeployed.processEvent({ + mockDb = await TestVirtualPoolFactory.VirtualPoolDeployed.processEvent({ event, mockDb, });