Skip to content

Refactor dynamic contract registry to separate addresses table - #1076

Merged
DZakh merged 13 commits into
mainfrom
claude/document-address-table-NUhX7
Apr 14, 2026
Merged

Refactor dynamic contract registry to separate addresses table#1076
DZakh merged 13 commits into
mainfrom
claude/document-address-table-NUhX7

Conversation

@DZakh

@DZakh DZakh commented Apr 1, 2026

Copy link
Copy Markdown
Member

Summary

This PR refactors the dynamic contract registry system by extracting address tracking into a dedicated EnvioAddresses table, removing the internal DynamicContractRegistry entity, and simplifying the data model for tracking dynamically registered contracts.

Key Changes

  • New EnvioAddresses module: Replaces DynamicContractRegistry with a simpler structure containing only essential fields:

    • id (address as string), chainId, contractName, registeringEventBlock, registeringEventLogIndex, and checkpointId
    • Removed redundant fields like registeringEventBlockTimestamp, registeringEventContractName, registeringEventName, and registeringEventSrcAddress
  • Database schema updates:

    • Added envio_addresses table to PgStorage initialization
    • Updated Checkpoints query to fetch dynamic contracts from the new table
    • Added rollback logic for addresses during checkpoint rollbacks
  • Storage layer changes:

    • Modified writeBatch to accept addressesToWrite parameter
    • Implemented address insertion via UNNEST with ON CONFLICT handling
    • Updated InMemoryStore to accumulate addresses in addressesToWrite array instead of storing as entities
  • TestIndexer updates:

    • Added addressesByChain dictionary to track addresses per chain
    • Simplified toIndexingContract conversion to work with EnvioAddresses.t
    • Removed special-case handling for DynamicContractRegistry entity
    • Updated address accumulation logic to use the new structure
  • Test updates:

    • Updated rollback tests to use queryAddresses() instead of querying the registry entity
    • Simplified test assertions to focus on essential address data

Implementation Details

  • Addresses are now stored separately from user entities, improving separation of concerns
  • The id field in EnvioAddresses directly stores the address string (via Address.toString)
  • registeringEventLogIndex is now optional to handle cases where it may not be available
  • Address accumulation during batch processing is tracked in memory and written to the database as a separate operation
  • Composite primary key on (id, chain_id) prevents duplicate address entries per chain

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2

Summary by CodeRabbit

  • Refactor

    • Replaced the prior dynamic-contract registry with an addresses-backed storage and pipeline centered on address records and checkpoint IDs.
  • New Features

    • Batch writes, in-memory pipeline, and worker messages now accept and forward address records (with checkpoint metadata) for persistence.
  • Bug Fixes

    • Rollback and batch-write handling now correctly include/exclude address rows according to checkpoint semantics.
  • Tests

    • Fixtures, mocks and assertions updated to validate the new addresses storage and checkpoint behavior.

claude added 3 commits April 1, 2026 13:27
Remove registering_event_block_timestamp, registering_event_src_address,
registering_event_name, registering_event_contract_name (derivable from
block+log_index). Remove contract_address in favor of id. Rename
registering_event_block_number to registering_event_block. Make
registering_event_log_index nullable for future block handler support.
Use composite primary key (id, chain_id).

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2
Remove EnvioAddresses from the entity framework entirely. The id column
now stores the raw address string. Addresses are persisted via direct
SQL INSERT with ON CONFLICT DO NOTHING and rolled back via DELETE WHERE
envio_checkpoint_id > target. A new envio_checkpoint_id column tracks
when each address was registered.

In-memory: InMemoryStore.addressesToWrite accumulates per-batch entries.
TestIndexer.addressesByChain provides per-chain lookup (chainId -> address
-> entry) to avoid id collision across chains.

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2
@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replace the DynamicContractRegistry entity with a new EnvioAddresses entity; thread an addressesToWrite array from InMemoryStore through Persistence to PgStorage for DB insertion and rollback; update TestIndexer, mocks, and tests to consume EnvioAddresses and the new writeBatch parameter.

Changes

Cohort / File(s) Summary
Type definitions
packages/envio/src/Config.gen.ts, packages/envio/src/db/InternalTable.gen.ts
Renamed DynamicContractRegistry_tEnvioAddresses_t. Reworked fields: added registering_event_block, made registering_event_log_index optional, kept contract_name, added envio_checkpoint_id: bigint, and removed Address_t-typed address fields.
ReScript Config & DB mapping
packages/envio/src/Config.res, packages/envio/src/db/InternalTable.res
Removed DynamicContractRegistry module; added EnvioAddresses table and getAddress helper. SQL/json aggregation now reads from envio_addresses and maps id → address and registering_event_block → block fields.
In-memory store & test harness
packages/envio/src/InMemoryStore.res, packages/envio/src/TestIndexer.res
Added addressesToWrite: array<Config.EnvioAddresses.t> to InMemoryStore; setBatchDcs now builds/appends EnvioAddresses entries. TestIndexer now holds addressesByChain, consumes addressesToWrite, emits per-checkpoint address events, and converts EnvioAddresses into indexing contracts.
Persistence & proxy wiring
packages/envio/src/Persistence.res, packages/envio/src/TestIndexerProxyStorage.res
Extended storage.writeBatch signature with ~addressesToWrite: array<Config.EnvioAddresses.t>; removed DynamicContractRegistry.entityConfig from allEntities; worker payloads and proxy storage now include addressesToWrite.
Postgres storage
packages/envio/src/PgStorage.res
Create envio_addresses table at init; accept addressesToWrite in batch write; bulk INSERT via UNNEST with ON CONFLICT ("id","chain_id") DO NOTHING; on rollback delete rows where envio_checkpoint_id > target.
Mocks, tests & fixtures
scenarios/test_codegen/test/helpers/Mock.res, scenarios/test_codegen/test/*
Updated mock writeBatch signature and Indexer mock queryAddresses. Tests and SQL fixtures updated to use envio_addresses and the new addresses API; expectations adjusted accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant Handler as Event Handler
    participant Memory as InMemoryStore
    participant Persist as Persistence
    participant Storage as PgStorage
    participant DB as PostgreSQL

    Handler->>Memory: setBatchDcs(batch)
    Memory->>Memory: build EnvioAddresses.t entries
    Memory->>Memory: append to addressesToWrite

    Handler->>Persist: writeBatch(..., addressesToWrite)
    Persist->>Storage: storage.writeBatch(..., addressesToWrite)

    Storage->>DB: INSERT INTO envio_addresses via UNNEST (bulk)
    DB-->>Storage: inserted / conflicts ignored

    alt rollback target present
        Storage->>DB: DELETE FROM envio_addresses WHERE envio_checkpoint_id > target
        DB-->>Storage: deleted rows
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • JonoPrest
  • moose-code

"🐇 I hopped through types with a whiskered grin,
Swapped registries for addresses, tucked changes in.
From memory to DB they march in a queue,
EnvioAddresses planted—fresh rows anew.
🥕"

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Refactor dynamic contract registry to separate addresses table' clearly and directly summarizes the main change: extracting address tracking into a dedicated EnvioAddresses table while removing the internal DynamicContractRegistry entity.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/document-address-table-NUhX7

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Keep upstream runChainWorker/runChains sequential execution refactor,
apply addressesByChain and addressesToWrite changes on top.

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
scenarios/test_codegen/test/rollback/Rollback_test.res (1)

752-755: Prefer full-value assertions instead of count-only checks.

Line 752–755 and Line 815–820 only validate count; they can still pass with wrong address rows. Assert exact projected values (as you already do in Line 736–745 / Line 837–846) for stronger rollback coverage.

Based on learnings: Applies to **/*.{test.res,test.js,test.ts,test.tsx,spec.res,spec.js,spec.ts,spec.tsx} : Always use single assert to check the whole value instead of multiple asserts for every field.

Also applies to: 815-820

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scenarios/test_codegen/test/rollback/Rollback_test.res` around lines 752 -
755, The test currently checks only the count using (await
(indexerMock.queryAddresses()))->Array.length which can pass with incorrect
address rows; replace these count-only assertions (the ones using
indexerMock.queryAddresses() and comparing Array.length) with a single
full-value assertion that compares the entire projected address array to the
expected array of objects (same shape as the existing full assertions used
around lines where you already assert full projected values), e.g., call
indexerMock.queryAddresses(), await the result, map/format it the same way the
other full-value tests do, and assert equality against the exact expected array
so the rollback test validates all fields, not just the count.
packages/envio/src/db/InternalTable.res (1)

190-197: Stabilize dynamicContracts ordering in the JSON aggregate.

Line 190–197 uses json_agg(...) without ORDER BY, which can return non-deterministic array ordering across runs/plans.

♻️ Suggested SQL adjustment
 (
-  SELECT COALESCE(json_agg(json_build_object(
+  SELECT COALESCE(json_agg(json_build_object(
     'address', "id",
     'contractName', "contract_name",
     'startBlock', "registering_event_block",
     'registrationBlock', "registering_event_block"
-  )), '[]'::json)
+  ) ORDER BY "registering_event_block", "registering_event_log_index", "id"), '[]'::json)
   FROM "${pgSchema}"."${EnvioAddresses.table.tableName}"
   WHERE "chain_id" = chains."${(`#id`: field :> string)}"
 ) as "dynamicContracts"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/envio/src/db/InternalTable.res` around lines 190 - 197, The
json_agg(...) call that builds the dynamicContracts array is unordered and can
return non-deterministic results; update the SELECT so the aggregation is
deterministic by adding an ORDER BY inside the aggregate (e.g.,
json_agg(json_build_object(... ) ORDER BY "id" OR ORDER BY
"registering_event_block") when querying
"${pgSchema}"."${EnvioAddresses.table.tableName}" filtered by chains."${(`#id`:
field :> string)}"; ensure you pick a stable column (like "id" or
"registering_event_block") for the ordering so the JSON array is produced in a
consistent order across runs.
scenarios/test_codegen/test/lib_tests/PgStorage_test.res (1)

268-269: Consider indexing envio_checkpoint_id for rollback delete performance.

Given rollback deletes by checkpoint threshold, a dedicated index (or (chain_id, envio_checkpoint_id) if queried by chain too) can prevent full scans as envio_addresses grows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scenarios/test_codegen/test/lib_tests/PgStorage_test.res` around lines 268 -
269, Add an index to speed up rollback deletes on envio_addresses: create an
index on envio_checkpoint_id (or a composite index on (chain_id,
envio_checkpoint_id) if rollbacks are scoped by chain) so DELETE ... WHERE
envio_checkpoint_id <= ? (and optional chain_id = ?) can use the index instead
of scanning the whole "test_schema"."envio_addresses" table; modify the
migration/DDL that defines "envio_addresses" to include CREATE INDEX ... ON
"test_schema"."envio_addresses"(envio_checkpoint_id) or CREATE INDEX ... ON
"test_schema"."envio_addresses"(chain_id, envio_checkpoint_id) as appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/envio/src/Config.res`:
- Around line 107-120: The table definition for envio_addresses (created via
Table.mkTable bound to variable table) only has a composite primary key (id,
chain_id), causing queries filtering solely by chain_id or by
envio_checkpoint_id to do sequential scans; add explicit indexes for chain_id
and envio_checkpoint_id (using your Table.mkIndex / index helper alongside the
existing Table.mkField calls) so dynamic-contract bootstrap queries on chain_id
and rollback deletions on envio_checkpoint_id use indexes; update the table
definition to include these two indexes referencing the "chain_id" and
"envio_checkpoint_id" fields.

In `@packages/envio/src/TestIndexer.res`:
- Around line 245-272: The test indexer currently overwrites previously stored
addresses in state.addressesByChain and emits address changes even when
PostgreSQL would have ignored duplicates; to fix, make address writes
first-write-wins by checking existence before accepting/emitting: when building
addressSets from addressesToWrite, skip any addr whose id already exists in
state.addressesByChain for that addr.chainId (and also skip duplicates already
accepted earlier in this batch), and when storing into state.addressesByChain in
the addressesToWrite->Array.forEach loop only insert (Js.Dict.set) the addr if
the chainDict does not already contain addr.id; refer to addressesToWrite,
addressSets, state.addressesByChain, chainDict and change to locate the relevant
code to update.

In `@scenarios/test_codegen/test/helpers/Mock.res`:
- Around line 431-440: The queryAddresses implementation unsafely uses
Utils.magic to cast raw SQL rows to Config.EnvioAddresses.t; change it to use
schema-based decoding like queryCheckpoints/queryRaw: after running the
Postgres.unsafe(PgStorage.makeLoadAllQuery(...)) call, pipe the result through
Postgres.decodeRows (or the existing schema parser used in queryCheckpoints)
with Config.EnvioAddresses.schema, then map/convert to
promise<array<Config.EnvioAddresses.t>> instead of using Utils.magic; update the
pipeline around queryAddresses, referencing queryAddresses,
InternalTable.EnvioAddresses.table.tableName, Config.EnvioAddresses.schema, and
remove Utils.magic usage.

---

Nitpick comments:
In `@packages/envio/src/db/InternalTable.res`:
- Around line 190-197: The json_agg(...) call that builds the dynamicContracts
array is unordered and can return non-deterministic results; update the SELECT
so the aggregation is deterministic by adding an ORDER BY inside the aggregate
(e.g., json_agg(json_build_object(... ) ORDER BY "id" OR ORDER BY
"registering_event_block") when querying
"${pgSchema}"."${EnvioAddresses.table.tableName}" filtered by chains."${(`#id`:
field :> string)}"; ensure you pick a stable column (like "id" or
"registering_event_block") for the ordering so the JSON array is produced in a
consistent order across runs.

In `@scenarios/test_codegen/test/lib_tests/PgStorage_test.res`:
- Around line 268-269: Add an index to speed up rollback deletes on
envio_addresses: create an index on envio_checkpoint_id (or a composite index on
(chain_id, envio_checkpoint_id) if rollbacks are scoped by chain) so DELETE ...
WHERE envio_checkpoint_id <= ? (and optional chain_id = ?) can use the index
instead of scanning the whole "test_schema"."envio_addresses" table; modify the
migration/DDL that defines "envio_addresses" to include CREATE INDEX ... ON
"test_schema"."envio_addresses"(envio_checkpoint_id) or CREATE INDEX ... ON
"test_schema"."envio_addresses"(chain_id, envio_checkpoint_id) as appropriate.

In `@scenarios/test_codegen/test/rollback/Rollback_test.res`:
- Around line 752-755: The test currently checks only the count using (await
(indexerMock.queryAddresses()))->Array.length which can pass with incorrect
address rows; replace these count-only assertions (the ones using
indexerMock.queryAddresses() and comparing Array.length) with a single
full-value assertion that compares the entire projected address array to the
expected array of objects (same shape as the existing full assertions used
around lines where you already assert full projected values), e.g., call
indexerMock.queryAddresses(), await the result, map/format it the same way the
other full-value tests do, and assert equality against the exact expected array
so the rollback test validates all fields, not just the count.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 336ccb2c-f5f0-4c07-916b-c31cab3e74f0

📥 Commits

Reviewing files that changed from the base of the PR and between c2bf0ea and 9e13ca8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • packages/envio/src/Config.gen.ts
  • packages/envio/src/Config.res
  • packages/envio/src/InMemoryStore.res
  • packages/envio/src/Persistence.res
  • packages/envio/src/PgStorage.res
  • packages/envio/src/TestIndexer.res
  • packages/envio/src/TestIndexerProxyStorage.res
  • packages/envio/src/db/InternalTable.gen.ts
  • packages/envio/src/db/InternalTable.res
  • scenarios/test_codegen/test/helpers/Mock.res
  • scenarios/test_codegen/test/integration-raw-events.test.ts
  • scenarios/test_codegen/test/lib_tests/Persistence_test.res
  • scenarios/test_codegen/test/lib_tests/PgStorage_test.res
  • scenarios/test_codegen/test/rollback/Rollback_test.res

Comment thread packages/envio/src/Config.res Outdated
Comment thread packages/envio/src/TestIndexer.res Outdated
…ding

- Add indexes on chain_id and envio_checkpoint_id for envio_addresses
  table to avoid seq scans on bootstrap and rollback queries
- Make TestIndexer address writes first-write-wins to match DB's
  ON CONFLICT DO NOTHING semantics
- Use schema-based decoding in queryAddresses for type-safe bigint
  parsing of envio_checkpoint_id

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/envio/src/TestIndexer.res (1)

245-264: Minor gap: within-batch duplicates not tracked for emitted changes.

The addressSets filtering checks state.addressesByChain (populated from previous batches), but if addressesToWrite contains duplicate addresses with different checkpointIds within the same batch, both would be emitted in processChanges even though only the first is stored.

This is a minor consistency gap since storage is correct. If test assertions rely on emitted changes matching actual persisted data, consider tracking accepted addresses within the batch iteration.

♻️ Optional: Track within-batch duplicates
+  // Track addresses accepted in this batch to avoid emitting duplicates
+  let acceptedInBatch: dict<bool> = Js.Dict.empty()
+
   // Build combined checkpoint + entity changes objects
   for i in 0 to checkpointIds->Array.length - 1 {
     ...
     // Add address changes for this checkpoint (skip already-stored addresses to match ON CONFLICT DO NOTHING)
     let addressSets = addressesToWrite->Array.keepMap(addr => {
       if addr.checkpointId !== checkpointId {
         None
       } else {
         let chainIdStr = addr.chainId->Int.toString
+        let batchKey = chainIdStr ++ "_" ++ addr.id
         let alreadyExists = switch state.addressesByChain->Js.Dict.get(chainIdStr) {
         | Some(chainDict) => chainDict->Js.Dict.get(addr.id) !== None
         | None => false
         }
-        alreadyExists
+        let alreadyAccepted = acceptedInBatch->Js.Dict.get(batchKey) !== None
+        (alreadyExists || alreadyAccepted)
           ? None
-          : Some({"address": addr->Config.EnvioAddresses.getAddress, "contract": addr.contractName})
+          : {
+              acceptedInBatch->Js.Dict.set(batchKey, true)
+              Some({"address": addr->Config.EnvioAddresses.getAddress, "contract": addr.contractName})
+            }
       }
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/envio/src/TestIndexer.res` around lines 245 - 264, The emitted
addressSets currently only checks state.addressesByChain (past batches) so
duplicates inside the current addressesToWrite can still be emitted; fix by
adding a local mutable set (e.g., seenInBatch) inside the
addressesToWrite->Array.keepMap loop and, for each addr (use chainIdStr =
addr.chainId->Int.toString and addr.id or Config.EnvioAddresses.getAddress to
build a unique key), skip if that key is already in seenInBatch, otherwise
insert the key into seenInBatch and include the addr in addressSets; this
ensures addressSets only contains the first occurrence per batch while
preserving the existing state.addressesByChain check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scenarios/test_codegen/test/helpers/Mock.res`:
- Around line 431-460: The cast after Postgres.unsafe in queryAddresses is wrong
— change the Utils.magic cast from "(Utils.magic: array<unknown> =>
array<unknown>)" to match queryCheckpoints by treating the incoming value as
unknown: use "(Utils.magic: unknown => array<unknown>)"; update this in the
queryAddresses Promise.thenResolve mapping so the Postgres.unsafe result is
first cast from unknown to array<unknown> before Js.Array2.map; reference
queryAddresses, queryCheckpoints, Utils.magic, and Postgres.unsafe when making
the change.

---

Nitpick comments:
In `@packages/envio/src/TestIndexer.res`:
- Around line 245-264: The emitted addressSets currently only checks
state.addressesByChain (past batches) so duplicates inside the current
addressesToWrite can still be emitted; fix by adding a local mutable set (e.g.,
seenInBatch) inside the addressesToWrite->Array.keepMap loop and, for each addr
(use chainIdStr = addr.chainId->Int.toString and addr.id or
Config.EnvioAddresses.getAddress to build a unique key), skip if that key is
already in seenInBatch, otherwise insert the key into seenInBatch and include
the addr in addressSets; this ensures addressSets only contains the first
occurrence per batch while preserving the existing state.addressesByChain check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23012fb7-fa67-4209-844e-77727e5ed2d5

📥 Commits

Reviewing files that changed from the base of the PR and between 4f90071 and 3ccb5bf.

📒 Files selected for processing (4)
  • packages/envio/src/Config.res
  • packages/envio/src/TestIndexer.res
  • scenarios/test_codegen/test/helpers/Mock.res
  • scenarios/test_codegen/test/lib_tests/PgStorage_test.res
✅ Files skipped from review due to trivial changes (1)
  • packages/envio/src/Config.res
🚧 Files skipped from review as they are similar to previous changes (1)
  • scenarios/test_codegen/test/lib_tests/PgStorage_test.res

Comment thread scenarios/test_codegen/test/helpers/Mock.res Outdated
- Fix queryAddresses cast from (array<unknown> => array<unknown>) to
  (unknown => array<unknown>) to match queryCheckpoints pattern
- Track within-batch duplicate addresses via seenInBatch dict so
  emitted changes match ON CONFLICT DO NOTHING semantics

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/envio/src/TestIndexer.res (1)

236-243: Annotate the new Utils.magic casts.

The payload-building code added here reintroduces untyped Utils.magic calls for sets, deleted, and addresses. Please add explicit input/output annotations on each new cast so the JS boundary stays inspectable.

As per coding guidelines, "When using Utils.magic for type casting in ReScript, always add explicit type annotations: value->(Utils.magic: inputType => outputType)"

Also applies to: 271-274

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/envio/src/TestIndexer.res` around lines 236 - 243, Replace the
untyped Utils.magic casts with explicit input=>output annotations so the JS
boundary is inspectable: for each occurrence (the casts on sets, deleted, and
addresses used with Js.Dict.set and the similar casts around lines 271-274),
change value->Utils.magic to value->(Utils.magic: inputType => outputType) using
the actual ReScript types you expect (e.g., array/dict shapes or specific record
types) as inputType and the corresponding JS representation as outputType;
update the casts on entityObj->Js.Dict.set("sets", ...),
entityObj->Js.Dict.set("deleted", ...), change->Js.Dict.set(entityName, ...),
and the three similar casts at 271-274 accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/envio/src/TestIndexer.res`:
- Around line 41-45: toIndexingContract currently preserves registrationBlock
but the proxy WriteBatch payload omits rollbackTargetCheckpointId, so
rolled-back addresses never get pruned from state.addressesByChain causing
alreadyExists to block re-registrations; fix by threading
rollbackTargetCheckpointId (rollback target checkpoint id) into the WriteBatch
payload (the code paths that build the batch in TestIndexerProxyStorage
functions) and, before performing duplicate checks and final insert into
state.addressesByChain, remove/prune any existing address rows whose
checkpointId is greater than the provided rollback target; apply the same change
to the other insert paths referenced (around the other occurrences noted: the
115-124 and 248-294 regions) so pruning happens consistently prior to duplicate
detection and insertion, and ensure the inserted/returned
Internal.indexingContract includes the rollback target (e.g., add
rollbackTargetCheckpointId to the data passed through
toIndexingContract/WriteBatch).

---

Nitpick comments:
In `@packages/envio/src/TestIndexer.res`:
- Around line 236-243: Replace the untyped Utils.magic casts with explicit
input=>output annotations so the JS boundary is inspectable: for each occurrence
(the casts on sets, deleted, and addresses used with Js.Dict.set and the similar
casts around lines 271-274), change value->Utils.magic to value->(Utils.magic:
inputType => outputType) using the actual ReScript types you expect (e.g.,
array/dict shapes or specific record types) as inputType and the corresponding
JS representation as outputType; update the casts on
entityObj->Js.Dict.set("sets", ...), entityObj->Js.Dict.set("deleted", ...),
change->Js.Dict.set(entityName, ...), and the three similar casts at 271-274
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a1c8ab17-655f-4ac4-aaa7-b4c214c476df

📥 Commits

Reviewing files that changed from the base of the PR and between 3ccb5bf and 532ddc0.

📒 Files selected for processing (2)
  • packages/envio/src/TestIndexer.res
  • scenarios/test_codegen/test/helpers/Mock.res
🚧 Files skipped from review as they are similar to previous changes (1)
  • scenarios/test_codegen/test/helpers/Mock.res

Comment thread packages/envio/src/TestIndexer.res Outdated
Comment on lines +41 to +45
let toIndexingContract = (dc: Config.EnvioAddresses.t): Internal.indexingContract => {
address: dc->Config.EnvioAddresses.getAddress,
contractName: dc.contractName,
startBlock: dc.registeringEventBlockNumber,
registrationBlock: Some(dc.registeringEventBlockNumber),
startBlock: dc.registeringEventBlock,
registrationBlock: Some(dc.registeringEventBlock),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Thread rollback targets into the address write path.

toIndexingContract still preserves registrationBlock for rollback-aware dynamic contracts, but state.addressesByChain only ever grows here. Because the proxy WriteBatch payload still omits rollbackTargetCheckpointId (packages/envio/src/TestIndexerProxyStorage.res:23-43, packages/envio/src/TestIndexerProxyStorage.res:140-177), rolled-back addresses will stay in memory, alreadyExists will block legitimate re-registrations, and later process() calls will bootstrap contracts that PgStorage has already removed. Please prune rows with checkpointId > target before the duplicate checks and final insert once that target is threaded through.

Also applies to: 115-124, 248-294

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/envio/src/TestIndexer.res` around lines 41 - 45, toIndexingContract
currently preserves registrationBlock but the proxy WriteBatch payload omits
rollbackTargetCheckpointId, so rolled-back addresses never get pruned from
state.addressesByChain causing alreadyExists to block re-registrations; fix by
threading rollbackTargetCheckpointId (rollback target checkpoint id) into the
WriteBatch payload (the code paths that build the batch in
TestIndexerProxyStorage functions) and, before performing duplicate checks and
final insert into state.addressesByChain, remove/prune any existing address rows
whose checkpointId is greater than the provided rollback target; apply the same
change to the other insert paths referenced (around the other occurrences noted:
the 115-124 and 248-294 regions) so pruning happens consistently prior to
duplicate detection and insertion, and ensure the inserted/returned
Internal.indexingContract includes the rollback target (e.g., add
rollbackTargetCheckpointId to the data passed through
toIndexingContract/WriteBatch).

Annotate all Utils.magic casts in handleWriteBatch with explicit
input => output types per project guidelines.

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/envio/src/TestIndexer.res (1)

115-124: ⚠️ Potential issue | 🟠 Major

Address cache is not rollback-aware yet.

state.addressesByChain only grows, and duplicate checks depend on it. Without pruning entries above a rollback target, valid re-registrations can be skipped and stale dynamic contracts can leak into later bootstrap state.

Also applies to: 248-260, 285-299

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/envio/src/TestIndexer.res` around lines 115 - 124,
state.addressesByChain currently only grows and causes duplicate suppression
across rollbacks; make the cache rollback-aware by tracking when an address was
registered and pruning entries above a rollback target before performing
duplicate checks in handleWriteBatch (and the other affected handlers).
Concretely: change state.addressesByChain to store per-address metadata (e.g.
lastSeenCheckpointChainId or tuple with
checkpointChainId/checkpointBlockNumber), add a helper (e.g.
pruneAddressesAboveRollbackTarget) that removes or ignores addresses whose
recorded checkpoint is greater than the rollback target, call this prune helper
at the start of handleWriteBatch (and the other duplicate-check sites), and
ensure rollback logic updates/removes entries so re-registrations after
rollbacks are not skipped. Ensure duplicate-check logic uses the pruned/filtered
view so stale addresses do not leak into later bootstrap state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/envio/src/TestIndexer.res`:
- Around line 115-124: state.addressesByChain currently only grows and causes
duplicate suppression across rollbacks; make the cache rollback-aware by
tracking when an address was registered and pruning entries above a rollback
target before performing duplicate checks in handleWriteBatch (and the other
affected handlers). Concretely: change state.addressesByChain to store
per-address metadata (e.g. lastSeenCheckpointChainId or tuple with
checkpointChainId/checkpointBlockNumber), add a helper (e.g.
pruneAddressesAboveRollbackTarget) that removes or ignores addresses whose
recorded checkpoint is greater than the rollback target, call this prune helper
at the start of handleWriteBatch (and the other duplicate-check sites), and
ensure rollback logic updates/removes entries so re-registrations after
rollbacks are not skipped. Ensure duplicate-check logic uses the pruned/filtered
view so stale addresses do not leak into later bootstrap state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a6bee82-2d98-46f9-a9f7-2064dcef65b0

📥 Commits

Reviewing files that changed from the base of the PR and between 532ddc0 and fbcfda7.

📒 Files selected for processing (1)
  • packages/envio/src/TestIndexer.res

claude added 6 commits April 14, 2026 09:51
Split envio_addresses storage into two tables:
- envio_addresses: current state (no envio_checkpoint_id column)
- envio_history_envio_addresses: append-only log with envio_checkpoint_id
  as part of the composite PK

On write, both tables receive the new address rows. On rollback:
1. DELETE from main table any address whose only history is past the
   rollback target (i.e. was only registered after the target)
2. DELETE history rows past the target

This mirrors the entity framework's history pattern while supporting
composite (id, chain_id) primary keys.

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2
Undo the custom persistence path for envio_addresses. EnvioAddresses is
once again a regular entity in Persistence.allEntities, which means:
- auto-generated envio_history_envio_addresses via the entity framework
- write/rollback go through the same InMemoryTable.Entity + EntityHistory
  paths as every other entity
- the in-memory id is a composite {chainId}-{address} string to stay
  unique across chains; getAddress extracts the raw address

No custom INSERT/DELETE in PgStorage, no addressesToWrite on InMemoryStore,
no per-chain dict in TestIndexer.

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2
- getAddress now uses a single indexOf('-') lookup instead of formatting
  chainId to a string and measuring its length
- Add 'keep in sync with makeId / getAddress' comments on getAddress and
  on the SUBSTRING SQL in makeGetInitialStateQuery so future changes to
  the composite-id format flag both sites
- Rename castFromDcRegistry -> castToEnvioAddresses to match the new
  module name (the old name referenced the pre-rename DynamicContractRegistry)

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2
- Remove @Gentype from EnvioAddresses.t. The generated TS type wasn't
  imported anywhere; dropping it deletes Config.gen.ts and removes the
  unused EnvioAddresses_t export from InternalTable.gen.ts.

- Rename registering_event_block -> registration_block and
  registering_event_log_index -> registration_log_index. Matches the
  existing Internal.indexingContract.registrationBlock name and reads
  more naturally without the awkward "registering_event" prefix.

- registration_log_index goes from optional to a plain int with -1 as
  the sentinel for "registered from a block handler" (no log index).
  Log indices are always >= 0, so -1 is unambiguous. Mirrors the
  convention in envio_chains where uninitialized block numbers are -1.

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2
Upstream (#1103) migrated Js.* bindings to the ReScript 12 stdlib
(Js.Array2/Js.Dict/Js.null -> Array/Dict/Null, Js.String2 -> String, etc.)
and bumped rescript to 12.2.0. Conflicts were all mechanical rewrites
of my PR code to the new stdlib, keeping the EnvioAddresses rename,
simplified schema, and field renames from this branch.

Also replaced Js.String2.indexOf/sliceToEnd in getAddress with
String.indexOf + String.slice to clear the deprecation warnings.

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2
Side effect of rebuilding with rescript 12.2.0: genType now emits
relative imports for RescriptSchema.gen.js instead of the package-name
import. No runtime change.

https://claude.ai/code/session_01XQDfh8ZyX5TSyVDPKbMzx2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants