Skip to content

fix(traces): scope function lookup by called address to fix selector collisions#14787

Closed
figtracer wants to merge 1 commit into
foundry-rs:masterfrom
figtracer:fix/trace-decoder-address-scoped-functions
Closed

fix(traces): scope function lookup by called address to fix selector collisions#14787
figtracer wants to merge 1 commit into
foundry-rs:masterfrom
figtracer:fix/trace-decoder-address-scoped-functions

Conversation

@figtracer
Copy link
Copy Markdown
Collaborator

@figtracer figtracer commented May 15, 2026

Bug

Two contracts sharing a function selector but with different return struct types caused trace decoding to use the wrong struct/field names.

// Wow.items returns WowStruct{a, b}; MuchWow.items returns MuchWowStruct{c, d}
├─ [5190] Wow::items(0) [staticcall]
│   └─ ← [Return] MuchWowStruct({ c: 11, d: 22 })   // wrong struct

Cause

self.functions is a global HashMap<Selector, Vec<Function>>. On collisions, select_contract_function only disambiguates by trying to decode the inputs; identical input types → first registered candidate wins → its return ABI is used to format the output.

Fix

Add a per-address functions_by_address: HashMap<Address, HashMap<Selector, Function>>, populated in collect_abi, and consult it first in decode_function. Falls back to the existing collision heuristic for unknown addresses.

After:

├─ [5190] Wow::items(0) [staticcall]
│   └─ ← [Return] WowStruct({ a: 11, b: 22 })

Includes a regression test.

…collisions

When two contracts in the same project define a function with the same selector
(e.g. `items(uint256)` mapping getters or accessor functions) but with
different return-type metadata (different struct/field names), trace decoding
could format the return value using the wrong contract's ABI.

The decoder kept all known functions in a single `HashMap<Selector, Vec<Function>>`
and disambiguated collisions by trying `abi_decode_input` against each candidate.
When the inputs were identical, the first registered function won and its
return-type metadata was used to format the trace output, producing wrong
struct/field names like `MuchWowStruct({ c: 11, d: 22 })` for a call to
`Wow::items(0)` returning `WowStruct`.

Add a parallel `functions_by_address: HashMap<Address, HashMap<Selector, Function>>`
populated in `collect_abi` and prefer it in `decode_function` when the called
contract is identified. The legacy collision heuristic still runs for unknown
addresses or selectors that aren't bound to a specific contract.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019e2cbe-2eb1-7643-b8f3-71b02f08cb85
@figtracer figtracer force-pushed the fix/trace-decoder-address-scoped-functions branch from 0004267 to 5d02f21 Compare May 15, 2026 18:42
@figtracer
Copy link
Copy Markdown
Collaborator Author

superseded by #14788

@figtracer figtracer closed this May 16, 2026
@github-project-automation github-project-automation Bot moved this to Done in Foundry May 16, 2026
@figtracer figtracer deleted the fix/trace-decoder-address-scoped-functions branch May 16, 2026 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant