Resolve nested seed paths in v01 PDA extraction#990
Conversation
🦋 Changeset detectedLatest commit: 48b8986 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
4902b1e to
60bfce9
Compare
trevor-cortex
left a comment
There was a problem hiding this comment.
Summary
This PR resolves nested seed paths (e.g. mint.authority, args.owner) in v01 PDA extraction, which were previously left as a TODO and silently skipped. It introduces a resolveNestedFieldType helper that walks struct fields (inline or via IDL type definitions) to determine the leaf type. The function pdaSeedNodeFromAnchorV01 can now return undefined when resolution fails, and callers handle this gracefully by skipping the PDA default value.
Additionally, the PR adds self-referential seed detection — if a seed references the account being derived, the PDA default is skipped with a warning.
Key things to watch
-
Breaking return type change:
pdaSeedNodeFromAnchorV01now returns| undefined. This is a well-handled breaking change internally — all call sites check forundefined— but any external consumers of this export would need updating. -
Self-referential detection: The new self-referential check (lines 131–138 in
InstructionAccountNode.ts) correctly catches cases likevaultreferencing itself, and nested paths likeguard.mintwhere the account name matches the seed's account reference. The test formy_guardwithpath: 'my_guard.mint'is a good edge case. -
Nested program paths: Program seeds with nested paths are still explicitly skipped with a warning — this is a reasonable incremental choice since program seeds with nested paths are rare.
Notes for subsequent reviewers
- The
resolveNestedFieldTypehelper only handlesstructTypeNodeanddefinedTypeLinkNode. Other wrappers (e.g.optionTypeNode) would fall through and returnundefined. This seems fine for now — real-world IDL seeds shouldn't reference optional fields — but worth keeping in mind. - The
InstructionNode.test.tschange for thedistributionaccount is interesting: it confirms the self-referential detection works at the integration level (the accountdistributionhas a seedpath: 'distribution.group_mint'referencing itself, so the PDA default is correctly omitted). - Test coverage is thorough: nested account paths, nested arg paths, unresolvable types, self-referential accounts, prefixed nested groups, and program seed edge cases are all covered.
* found while rebasing onto main
|
I think I addressed all comments outside of the |
lorisleiva
left a comment
There was a problem hiding this comment.
Thank you for all the changes and sorry for the late re-review.
Just one last little thing that caught my attention and after that this is good to go. Thank you for your patience. 🙏
| expect(nodes?.definition).toEqual(variablePdaSeedNode('0Bar', numberTypeNode('u8'))); | ||
| expect(nodes?.value).toEqual(pdaSeedValueNode('0Bar', argumentValueNode('0Bar'))); |
There was a problem hiding this comment.
This worries me a bit because most languages will refuse to render a variable that starts with a numerical value. I wonder if we should just use the whole path as the seed name. So here it would become foo0Bar. That way, it would technically not be possible for a variable to start with a number because we always start with the array of InstructionArgumentNodes.
This is a direct follow-up to #984 which left the nested path handling as a TODO.
PDA seeds with nested paths like
mint.authorityorargs.ownerwere skipped. This PR resolves them by looking up the field type from IDL type definitions.{ kind: 'account', path: 'mint.authority' }+ aMinttype def withauthority: pubkey->variablePdaSeedNode('mintAuthority', publicKeyTypeNode()){ kind: 'arg', path: 'args.owner' }whereargsis a struct withowner: pubkey->variablePdaSeedNode('owner', publicKeyTypeNode())Program seeds with nested paths still skip (with a warning).
pdaSeedNodeFromAnchorV01can now returnundefined.