Fix same-signature event decoding across contracts with different param names#1286
Conversation
Two contracts emitting the same-signature event with differently named params share one native decoder entry (collectEventParams dedupes by sighash+topicCount, first-contract-wins). The second contract's events then decode under the first contract's param names, so its handler reads undefined. https://claude.ai/code/session_01MyjtCSDfE2XybnkeEa9q9y
Two contracts emitting the same-signature event with differently named params shared one native decoder entry: collectEventParams deduped by (sighash, topicCount) first-contract-wins, so the second contract's events decoded under the first contract's names and its handler read undefined. The native decoder now returns params keyed by contract name. The inner ABI decode stays single (keyed by signature); each contract sharing the signature re-applies its own names over the shared positional values. After the router resolves a log to its contract by address, the source picks params[contractName]. https://claude.ai/code/session_01MyjtCSDfE2XybnkeEa9q9y
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR refactors the Rust decoder to support multiple contract-specific naming variants for identical event signatures and propagates contract-name-keyed decoded params through collection, types, routing, and tests. ChangesMulti-contract event decoding with per-contract parameter naming
Sequence DiagramsequenceDiagram
participant CLI as CLI Rust
participant Rescript as ReScript types
participant Collector as EvmChain.collectEventParams
participant Decoder as DecoderCore
participant Router as HyperSyncSource / RpcSource
participant Queue as makeEventBatchQueueItem / Internal.Event
CLI->>Collector: supply eventParamsInput entries (with contractName)
Collector->>Rescript: eventParamsInput list
Rescript->>Decoder: build Decoder variants map from params
Decoder->>Decoder: decode log once (inner decoder)
Decoder->>Decoder: apply_names per EventVariant -> dict<contractName, params>
Decoder-->>Router: return dict<contractName, params>
Router->>Router: lookup dict[eventConfig.contractName]
Router->>Queue: construct queue/event with contract-specific params
🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/cli/src/hypersync_source/decode.rs`:
- Around line 74-88: The current grouping uses MetaKey (sighash + topic_count)
to pick a single decoder signature via reconstruct_signature, which incorrectly
collapses events that differ only by which params are indexed; change the
grouping key used for signatures so it includes an indexed-layout fingerprint
(e.g., a bitmap derived from ep.params.iter().map(|p| p.indexed) or similar)
instead of or in addition to MetaKey: update the signatures HashMap to use a
composite key (MetaKey + indexed bitmap) or compute/compare the indexed bitmap
before reusing a signature, ensure variants still group by MetaKey (for naming
variants) but decoders are chosen by the composite key, and preserve use of
reconstruct_signature and apply_names when creating/assigning signatures.
In `@packages/envio/src/sources/HyperSyncSource.res`:
- Around line 355-363: The code currently calls Dict.getUnsafe on
Value(paramsByContractName) with eventConfig.contractName inside the
parsedQueueItems -> Array.push call, which will throw if the contract key is
missing; update the mapping so you first safely look up the contract params (use
Dict.get or pattern-match Value(paramsByContractName) to extract the dict and
then Dict.get) and only call makeEventBatchQueueItem when the key exists,
otherwise route the item into the decode-failure handling path (e.g., push a
failure queue item or log/return an error) so decoding errors are preserved;
locate the lookup near parsedQueueItems, Dict.getUnsafe,
eventConfig.contractName and makeEventBatchQueueItem to implement this guard.
In `@packages/envio/src/sources/RpcSource.res`:
- Around line 1136-1138: The code currently uses
Dict.getUnsafe(eventConfig.contractName) in the Value(paramsByContractName)
branch which can throw if the contract key is missing; change this to a safe
lookup using Dict.get and pattern-match the option (e.g., switch on None/Some)
so a missing key yields a safe None/skip for that event rather than throwing;
update the surrounding logic in the Value(...) handling (where decoded is used)
to propagate None or handle the miss gracefully instead of relying on getUnsafe.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ec65d69d-13ff-4c5b-933f-cffc98ac63d3
📒 Files selected for processing (10)
packages/cli/src/hypersync_source/decode.rspackages/cli/src/hypersync_source/types.rspackages/envio/src/sources/EvmChain.respackages/envio/src/sources/HyperSyncClient.respackages/envio/src/sources/HyperSyncSource.respackages/envio/src/sources/RpcSource.resscenarios/test_codegen/test/HyperSyncClient_test.resscenarios/test_codegen/test/SourceBlockHashes_test.resscenarios/test_codegen/test/lib_tests/HyperSyncDecoder_test.resscenarios/test_codegen/test/lib_tests/SameSignatureEventDecode_test.res
Replace Dict.getUnsafe with a safe lookup at both source pick sites. A missing contract key now folds into each source's existing decode-miss path (HyperSyncSource raises via handleDecodeFailure for non-wildcard events; RpcSource skips) instead of silently returning undefined params. Document why decoder signatures are keyed by MetaKey alone: the upstream decoder collapses by (topic0, topic count) too, so an indexed-layout fingerprint can't be distinguished at this layer regardless. https://claude.ai/code/session_01MyjtCSDfE2XybnkeEa9q9y
Problem
When two contracts emit the same-signature event but name its params differently (e.g. OpenZeppelin
Transfer(from, to, value)vs WETHTransfer(src, dst, wad)), the second contract's handler readsundefinedfor its params.EvmChain.collectEventParamsbuilt the native decoder's input by deduping on(sighash, topicCount), first-contract-wins. Only the first contract's param metadata reached the decoder, so every matching log — including the second contract's — decoded under the first contract's names. The router still resolved the correct contract by address, but the params object already had the wrong keys baked in.Worked on
3.0.0-alpha.20(per-contract decoding viaconvertHyperSyncEventArgs); regressed in3.1.0(PR #1235) when naming moved into the native decoder keyed only by(sighash, topicCount).Fix
The native decoder now returns params keyed by contract name (
paramsByContractName):collectEventParamsno longer dedupes; it emits one entry per contract event with acontractName.HyperSyncSourceandRpcSourcepickparams[contractName].No new dynamic state, no rollback coupling, no
indexingAddressesplumbing — routing stays in JS, the decoder stays static.Rust cleanup
param_meta: HashMap<MetaKey, Vec<ParamMeta>>→variants: HashMap<MetaKey, Vec<EventVariant>>with a namedMetaKeystruct (bothparseandfrom_topicsconstructors) and an extractedapply_nameshelper shared across variants.Tests
SameSignatureEventDecode_test.resdrives the full path (collectEventParams→ native decoder) and asserts each contract decodes under its own names.cargo fmt, andtsc --noEmitpass.Generated by Claude Code
Summary by CodeRabbit
New Features
Behavior Change
Tests