diff --git a/.github/workflows/indexer-envio.yml b/.github/workflows/indexer-envio.yml index 21789a78..9d050833 100644 --- a/.github/workflows/indexer-envio.yml +++ b/.github/workflows/indexer-envio.yml @@ -56,14 +56,15 @@ 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. + # 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.monad.testnet.yaml - pnpm --filter @mento-protocol/indexer-envio codegen --config config.monad.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.multichain.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 33ea607f..f34485f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,16 +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:monad-testnet:codegen # Generate types (Monad testnet) -pnpm indexer:monad-testnet:dev # Start indexer (Monad testnet) +pnpm indexer:codegen # Generate types from schema (multichain mainnet) +pnpm indexer:dev # Start indexer (multichain mainnet: Celo + Monad) +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 @@ -48,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.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` - **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` @@ -89,11 +83,8 @@ 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 │ ├── schema.graphql # Entity definitions │ ├── src/ │ │ ├── EventHandlers.ts # Envio entry point (imports handlers, re-exports for tests) @@ -170,7 +161,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 +173,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 @@ -198,7 +189,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` 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/README.md b/README.md index 0b85d418..a058f55c 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/bootstrap-worktree.sh b/bootstrap-worktree.sh new file mode 100755 index 00000000..4ae731fa --- /dev/null +++ b/bootstrap-worktree.sh @@ -0,0 +1,28 @@ +#!/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 + +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 +pnpm --filter @mento-protocol/indexer-envio test + +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 f829c329..d400f433 100644 --- a/indexer-envio/AGENTS.md +++ b/indexer-envio/AGENTS.md @@ -2,15 +2,12 @@ ## 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 -- `config.celo.devnet.yaml` — Devnet indexer config (contract addresses, events, RPC) -- `config.celo.mainnet.yaml` — Celo Mainnet config -- `config.celo.sepolia.yaml` — Celo Sepolia testnet config -- `config.monad.mainnet.yaml` — Monad Mainnet config -- `config.monad.testnet.yaml` — Monad Testnet config +- `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`) @@ -59,4 +56,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/README.md b/indexer-envio/README.md index efc20f3e..6bd618ac 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. @@ -76,12 +75,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/config.celo.devnet.yaml b/indexer-envio/config.celo.devnet.yaml deleted file mode 100644 index 3ee96a80..00000000 --- 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 090addcd..00000000 --- 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 deleted file mode 100644 index b7e54e02..00000000 --- a/indexer-envio/config.celo.sepolia.yaml +++ /dev/null @@ -1,91 +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 - address: - - 0x550d9ecb4c373510b8a41f5fb7d98e9e1c51a07e # GBPm/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: - - 0x671334256a893fdbc4ffe55f98f156a168bd897a - - 0x1e2506edca4ef3030e51be8b571b935d55677604 - - 0x8103fb2db87ac96cc62faa399b98e1173720ab19 - - 0x62722497dc8992337117ee79a02015dcea43b2c2 - - 0x68d19b5a48cbbfd11057e97da9960b09d771e7b6 - - 0x58f08739ea9764097b9500b6e4a4db64d168b807 - - 0x284c2d99c5a12a65f10eff7183c33c1217b65a56 - - 0xcbfc8c84168d7f34faba0018a3a63b998f1ffece - - 0x49a968c539599385c69c2d528500da58d933fafa - - 0x917ee035bf0a964acc75539f919a5b4f16336373 - - 0x6b66271811615f4b6dadb8620ed71a1e90f41deb - - 0x22118009665b1d6810d4560a098d3e67bbcb934f - 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.mainnet.yaml b/indexer-envio/config.monad.mainnet.yaml deleted file mode 100644 index 67419f97..00000000 --- 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 deleted file mode 100644 index c4498af8..00000000 --- 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 - address: - - 0xd9e9e6f6b5298e8bad390f7748035c49d6eeb055 - - 0x1229e8a7b266c6db52712ba5c6899a6c4c3025cd - - 0x6d4c4b663541bf21015afb22669b0e1bbb3e2b1c - 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/config.multichain.mainnet.yaml b/indexer-envio/config.multichain.mainnet.yaml index 347f1676..9ef10cd9 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 @@ -159,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 c702141f..77b27b62 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 9ec8b100..ebdea1ed 100644 --- a/indexer-envio/src/EventHandlers.ts +++ b/indexer-envio/src/EventHandlers.ts @@ -6,6 +6,12 @@ // modules are imported below; their registrations fire at module load time. // =========================================================================== +import { runStartupChecks } from "./startupChecks"; + +// 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"; import "./handlers/sortedOracles"; @@ -51,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/fpmm.ts b/indexer-envio/src/handlers/fpmm.ts index b908b36f..b06e01cb 100644 --- a/indexer-envio/src/handlers/fpmm.ts +++ b/indexer-envio/src/handlers/fpmm.ts @@ -93,10 +93,17 @@ 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. +// +// 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); context.addERC20FeeToken(event.params.token1); }); diff --git a/indexer-envio/src/handlers/virtualPool.ts b/indexer-envio/src/handlers/virtualPool.ts index 7f2b0286..3e422691 100644 --- a/indexer-envio/src/handlers/virtualPool.ts +++ b/indexer-envio/src/handlers/virtualPool.ts @@ -18,6 +18,16 @@ 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. +// 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); + }, +); + VirtualPoolFactory.VirtualPoolDeployed.handler(async ({ event, context }) => { const id = eventId(event.chainId, event.block.number, event.logIndex); const poolId = makePoolId(event.chainId, event.params.pool); @@ -285,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 00000000..2164a731 --- /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 new file mode 100644 index 00000000..baab82f3 --- /dev/null +++ b/indexer-envio/test/dynamicRegistration.test.ts @@ -0,0 +1,176 @@ +/// +/** + * Dynamic Contract Registration + * + * Tests for the contractRegister hooks that auto-discover new pools from + * factory deploy events, replacing the old hardcoded address list approach. + * + * Two complementary test strategies: + * + * 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. + * + * 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. +// 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 }; + 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: 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 smoke tests", () => { + it("FPMMDeployed.handler creates a Pool entity for the deployed pool", async () => { + // 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 = TestFPMMFactory.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 TestFPMMFactory.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 = TestVirtualPoolFactory.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 TestVirtualPoolFactory.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 new file mode 100644 index 00000000..e691e51a --- /dev/null +++ b/indexer-envio/test/startBlockInvariant.test.ts @@ -0,0 +1,103 @@ +/// +import { strict as assert } from "assert"; +import { + assertStartBlocksValid, + FPMM_FIRST_DEPLOY_BLOCK, + START_BLOCK_ENV_NAME, +} from "../src/EventHandlers"; + +// Chain IDs used in tests (mainnet only — startup guard only covers mainnet) +const CELO = 42220; +const MONAD = 143; + +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]: "", + }), + ); + }); + + 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("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) }, true), + (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 for Monad in strict mode with correct env var name", () => { + const tooHigh = FPMM_FIRST_DEPLOY_BLOCK[MONAD] + 1000; + assert.throws( + () => assertStartBlocksValid({ [MONAD]: String(tooHigh) }, true), + (err: unknown) => { + assert(err instanceof Error); + assert(err.message.includes(START_BLOCK_ENV_NAME[MONAD])); + return true; + }, + ); + }); + + it("skips non-numeric override gracefully (no throw)", () => { + assert.doesNotThrow(() => + assertStartBlocksValid({ [CELO]: "not-a-number" }), + ); + }); + + 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(() => + // 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" }), + ); + }); +}); diff --git a/package.json b/package.json index fcc14d83..08b47849 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"