fix: AbiEvent.decode mis-indexes args when an indexed input isn't a declaration-order prefix - #436
Merged
Conversation
…eclaration-order prefix `decode`'s indexed-topic loop keyed writes off the loop index within the indexed-subset array, not the input's original declaration index. Whenever a non-indexed input appeared before an indexed one, this caused: - array-mode (unnamed events): args returned in indexed-subset order instead of declaration order (e.g. `event Foo(uint256, bool indexed)` returned `[bool, uint256]` instead of `[uint256, bool]`). - mixed-mode (some named inputs): an indexed value silently overwritten by an unrelated non-indexed value sharing the same numeric key, with no error raised (e.g. `event Foo(uint256 indexed a, bool, address indexed)` dropped the address entirely - 3 inputs decoded to 2 values). The non-indexed side of the same function already tracked original declaration index (`nonIndexedOriginalIndex`) for exactly this reason; the indexed side had no equivalent. This adds `indexedOriginalIndex` and keys both the indexed-topic writes and the non-indexed array-mode fill by declaration index, matching what the non-indexed named-mode branch and viem's `decodeEventLog` already do. Canonical events (ERC-20 Transfer, Approval, etc.) are unaffected - their indexed inputs are already a declaration-order prefix, which is exactly the shape the four pre-existing snapshot tests exercised, so the bug was invisible to them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@gomesalexandre is attempting to deploy a commit to the Wevm Team on Vercel. A member of the Team first needs to authorize it. |
gomesalexandre
marked this pull request as ready for review
September 1, 2026 15:33
commit: |
jxom
approved these changes
Sep 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AbiEvent.decodemis-indexes args whenever a non-indexed input appears before an indexed one in an event's declaration order — the indexed-topic loop keyed writes off the loop index within the indexed subset, not the input's original declaration index.The bug
src/core/AbiEvent.ts. The partition loop already tracked original declaration index for the non-indexed side (nonIndexedOriginalIndex), for exactly the reason this bug exists — but had no equivalent for the indexed side:For a canonical event where indexed inputs are a declaration-order prefix (ERC-20
Transfer,Approval, etc.) the indexed-subset index and the declaration index are identical, so this was invisible. It breaks the moment they diverge.Repro (real, run against the published behavior before this fix)
The second case is the dangerous one: three inputs decoded to two values, silently. Mechanism:
indexedInputs = [a (decl. 0), address (decl. 2)]; the loop'si=1write foraddresscollides with the non-indexed loop's write forbool(decl. index 1) — the address is overwritten with no error raised.Fix
Added
indexedOriginalIndex, mirroringnonIndexedOriginalIndex, and keyed both the indexed-topic writes and the non-indexed array-mode fill by declaration index — matching what the non-indexed named-mode branch already did, and what viem'sdecodeEventLog(utils/abi/decodeEventLog.js) does.Tests
Two new cases in
src/core/_test/AbiEvent.test.tsreproducing both failure modes above (array-mode reordering, mixed-mode silent drop). Verified genuine red-before/green-after by stashing just the source fix and rerunning: both new tests fail on unfixed code — the silent-drop case shows the"2"key (the address) entirely absent from the received snapshot — while all 67 pre-existing tests (including the canonical-prefix cases and the anonymous-event case) still pass unaffected.Also ran the two direct consumer test files (
AbiItem.test.ts,Log.test.ts): 95/95 passing.pnpm check:types(tsc -b) andpnpm check(vp check --fix) both clean — 0 errors (2 pre-existing unrelated warnings in the docs site, untouched by this diff).Scope
Real-world exposure is unquantified — canonical ERC-20/721 events decode correctly today and are unaffected by this bug or this fix. It only fires for non-canonical/hand-written ABIs where a non-indexed param isn't after all the indexed ones. Framing this as a correctness bug with unmeasured frequency, not "everyone's Transfer decoding is broken" (it isn't).
Related, out of scope
While checking this file for the same bug pattern elsewhere, I found
AbiEvent.encode's object-mode indexed-arg lookup (indexedInputs.map((x, i) => args[x.name ?? i]), around line 1237) has the same subset-index-vs-declaration-index confusion — for an unnamed/mixed event, it reads a caller's args object by indexed-subset index rather than declaration index. I confirmed this is real with a standalone repro:AbiEvent.encodeonevent Foo(uint256, bool indexed, address indexed)given{1: true, 2: '0x1234...'}(the natural shape, matching whatdecode's own fixed output now produces) throws trying to encode theboolvalue as anaddress, because it read the wrong key. Leaving this out of this PR since it's a separate function with its own test surface and deserves its own focused fix — happy to open a follow-up if useful.Confirmed unclaimed before starting: no open or closed issues/PRs on
AbiEvent decode unnamed,decodeEventLog unnamed,AbiEvent unnamed indexed,unnamed, orindexedtouch this. Only other open PRs on this repo are unrelated (#433 changesets bot, and two of my own in different files: #434, #435).