Skip to content

gnd: Support tuple/struct event params in subgraph scaffolding#6661

Open
incrypto32 wants to merge 5 commits into
masterfrom
incrypto32/gnd-tuples
Open

gnd: Support tuple/struct event params in subgraph scaffolding#6661
incrypto32 wants to merge 5 commits into
masterfrom
incrypto32/gnd-tuples

Conversation

@incrypto32

@incrypto32 incrypto32 commented Jun 30, 2026

Copy link
Copy Markdown
Member

Base is master now that #6660 is merged.

A tuple/struct event param used to collapse into a single Bytes field and got dropped from the event signature, so the scaffolded subgraph didn't build and its topic0 never matched the on-chain event. This adds tuple support to the scaffolder.

The first commit swaps the scaffold's hand-rolled ABI parsing for alloy's typed Event/EventParam, which codegen already used. The fixes build on that.

  • Parse scaffold ABIs with alloy. Unnamed params get param{index} fields (matching graph-cli) instead of every one collapsing onto value, and Foundry/Hardhat/Truffle artifact ABIs work.
  • Event signatures expand tuples, Filled((address,uint256),uint256), so topic0 matches. Uses a shared signature builder lifted from validation, which had the same code.
  • A tuple unrolls into one field per component (data becomes data_account, data_amount) in both schema and mapping, with nested accessors.
  • An indexed reference type (tuple, array, string, bytes) isn't in the log, only its keccak hash is, so it stays a single Bytes field with no unroll and no cast. indexed_input_type already encoded this for codegen and moves to the abi module so both sides share one definition.
  • address[] gets changetype<Bytes[]>. Only address: Address extends Bytes, so the cast is an upcast. ethereum.Tuple extends Array<Value> and isn't a Bytes, so casting tuple[] would be an unchecked reinterpret that compiles and writes heap pointers into the entity. It gets no cast and fails to build instead. This diverges from graph-cli, whose typecast map casts tuple[].

Tests: unit coverage for the unroll, signatures, indexed handling and the cast; plus an e2e that scaffolds a tuple ABI (indexed and non-indexed) through real codegen and checks the mapping accessors against the generated getters.

Follow-up, tracked separately: the scaffold still carries Solidity types as strings where codegen uses DynSolType.

Base automatically changed from incrypto32/gnd-add-fix to master July 15, 2026 09:36
@incrypto32
incrypto32 force-pushed the incrypto32/gnd-tuples branch 3 times, most recently from 390be45 to 9bea540 Compare July 17, 2026 10:29
Replace the scaffold module's hand-rolled ABI parsing with alloy's typed
Event/EventParam, mirroring the codegen side (which already uses them). Events
are parsed in file order via serde; a malformed event is skipped with a warning
instead of silently dropping individual params.

The shared preprocessing (artifact-format unwrapping, anonymous/param{index}
defaults) moves from commands/codegen.rs to the abi module so scaffold and
codegen share it. An unnamed param is one solc wrote as "name": "" or a
hand-written ABI omitted entirely; both now yield param{index} entity fields
(matching graph-cli) rather than every unnamed param collapsing onto a single
value field, which is not valid GraphQL once an event has two of them.
Scaffolding also accepts Foundry/Hardhat/Truffle artifact ABIs.
Build the manifest event signature with a shared abi::event_signature_with_indexed
helper instead of a scaffold-local copy that printed the raw ABI type. The helper
uses each param's canonical type (selector_type), so a tuple expands to its
components, e.g. Filled((address,uint256),uint256). Previously a tuple param
produced Filled(tuple,uint256), whose topic0 hash did not match the on-chain
event, so the handler never fired.

The helper is lifted from validation, which already built the same string, so the
two cannot drift apart on the manifest's signature format.
A tuple parameter now expands to one field per component (data -> data_account,
data_amount) in both the schema and the mapping, with nested accessors
(event.params.data.account), via a shared flatten_event_inputs helper.
Previously a tuple collapsed to a single Bytes field that did not match the
generated event params.

An address[] is changetype'd to Bytes[] so the assignment type-checks. Only
address qualifies: Address extends Bytes, so the cast is an upcast of something
that already is a Bytes. ethereum.Tuple extends Array<Value> and is not a Bytes,
so casting a tuple[] would be an unchecked reinterpret that compiles and then
writes heap pointers into the entity; without a cast it fails to compile, which
is the honest outcome until tuple[] is unrolled or encoded properly.

An indexed reference type is exempt from unrolling: a topic is 32 bytes, so the
EVM stores keccak256(encoding) and the value itself is absent from the log. The
binding getter yields Bytes accordingly, so such a param becomes a single bytes32
leaf with no unroll and no cast. indexed_input_type, which already encoded this
rule for the codegen, moves to the abi module so both sides share one definition.
Scaffold an ABI whose event carries a non-indexed tuple alongside an indexed
tuple and an indexed array, run codegen, and check the generated output: the
non-indexed tuple unrolls into per-component fields read through the nested
accessor, while the indexed params, which the EVM reduces to a topic hash,
each stay a single Bytes field read whole. Asserting against the generated
bindings pins the accessors to the getters codegen actually emits.
Regression guard for the LHS/RHS escaping mismatch: codegen a contract with
a reserved-word param (`new`) and an unnamed param, then assert the mapping's
event.params accessor matches the getter the bindings actually expose
(new_, param1).
@incrypto32
incrypto32 force-pushed the incrypto32/gnd-tuples branch from 9bea540 to f22ffc5 Compare July 17, 2026 12:43
@incrypto32
incrypto32 marked this pull request as ready for review July 20, 2026 09:43
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.

1 participant