From 6c0857b39d83bc5de2ef48cdabc4482bbaa2531a Mon Sep 17 00:00:00 2001 From: Loris Leiva Date: Sat, 29 Aug 2026 11:17:15 +0100 Subject: [PATCH] Fix variable string and bytes PDA seeds in legacy Anchor IDLs This PR is a follow-up to #1126, which fixed byte-array PDA seeds for Anchor v01 IDLs. The legacy v00 path has the same bug: `pdaNodeFromAnchorV00` strips the Borsh size prefix from constant `string` and `bytes` seeds but not from variable ones, so a variable seed derives a different PDA than the on-chain program. This change hoists the seed type conversion into a single helper used by both branches, so constant and variable seeds are now consistent by construction. --- .changeset/long-stamps-bow.md | 5 +++++ packages/nodes-from-anchor/src/v00/PdaNode.ts | 20 +++++++++++-------- .../test/v00/PdaNode.test.ts | 20 +++++++++++++++++++ 3 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 .changeset/long-stamps-bow.md diff --git a/.changeset/long-stamps-bow.md b/.changeset/long-stamps-bow.md new file mode 100644 index 000000000..fb2f758c5 --- /dev/null +++ b/.changeset/long-stamps-bow.md @@ -0,0 +1,5 @@ +--- +'@codama/nodes-from-anchor': patch +--- + +Fix variable `string` and `bytes` PDA seeds from legacy Anchor IDLs to derive from their raw bytes instead of their Borsh size-prefixed encoding, matching the existing behaviour of constant seeds. diff --git a/packages/nodes-from-anchor/src/v00/PdaNode.ts b/packages/nodes-from-anchor/src/v00/PdaNode.ts index cec5be7f3..03a05bd61 100644 --- a/packages/nodes-from-anchor/src/v00/PdaNode.ts +++ b/packages/nodes-from-anchor/src/v00/PdaNode.ts @@ -10,32 +10,28 @@ import { PdaSeedNode, stringTypeNode, stringValueNode, + TypeNode, variablePdaSeedNode, } from '@codama/nodes'; -import { IdlV00PdaDef } from './idl'; +import { IdlV00PdaDef, IdlV00Type } from './idl'; import { typeNodeFromAnchorV00 } from './typeNodes'; export function pdaNodeFromAnchorV00(idl: IdlV00PdaDef): PdaNode { const name = camelCase(idl.name ?? ''); const seeds = (idl.seeds ?? []).map((seed): PdaSeedNode => { if (seed.kind === 'constant') { - const type = (() => { - if (seed.type === 'string') return stringTypeNode('utf8'); - if (seed.type === 'bytes') return bytesTypeNode(); - return typeNodeFromAnchorV00(seed.type); - })(); const value = (() => { if (typeof seed.value === 'string') return stringValueNode(seed.value); if (typeof seed.value === 'number') return numberValueNode(seed.value); return booleanValueNode(seed.value); })(); - return constantPdaSeedNode(type, value); + return constantPdaSeedNode(pdaSeedTypeNodeFromAnchorV00(seed.type), value); } if (seed.kind === 'variable') { return variablePdaSeedNode( seed.name, - typeNodeFromAnchorV00(seed.type), + pdaSeedTypeNodeFromAnchorV00(seed.type), seed.description ? [seed.description] : [], ); } @@ -43,3 +39,11 @@ export function pdaNodeFromAnchorV00(idl: IdlV00PdaDef): PdaNode { }); return pdaNode({ name, seeds }); } + +function pdaSeedTypeNodeFromAnchorV00(type: IdlV00Type): TypeNode { + // Anchor derives PDA seeds from raw bytes, so strings and byte + // arrays lose their Borsh size prefix when used as seeds. + if (type === 'string') return stringTypeNode('utf8'); + if (type === 'bytes') return bytesTypeNode(); + return typeNodeFromAnchorV00(type); +} diff --git a/packages/nodes-from-anchor/test/v00/PdaNode.test.ts b/packages/nodes-from-anchor/test/v00/PdaNode.test.ts index d3c6b48a5..be85a409c 100644 --- a/packages/nodes-from-anchor/test/v00/PdaNode.test.ts +++ b/packages/nodes-from-anchor/test/v00/PdaNode.test.ts @@ -1,9 +1,11 @@ import { + bytesTypeNode, constantPdaSeedNode, constantPdaSeedNodeFromProgramId, numberTypeNode, numberValueNode, pdaNode, + stringTypeNode, variablePdaSeedNode, } from '@codama/nodes'; import { expect, test } from 'vitest'; @@ -31,3 +33,21 @@ test('it creates PDA nodes', () => { }), ); }); + +test('it removes the string prefix from variable seeds', () => { + const node = pdaNodeFromAnchorV00({ + name: 'myPda', + seeds: [{ description: '', kind: 'variable', name: 'label', type: 'string' }], + }); + + expect(node).toEqual(pdaNode({ name: 'myPda', seeds: [variablePdaSeedNode('label', stringTypeNode('utf8'))] })); +}); + +test('it removes the bytes prefix from variable seeds', () => { + const node = pdaNodeFromAnchorV00({ + name: 'myPda', + seeds: [{ description: '', kind: 'variable', name: 'seedData', type: 'bytes' }], + }); + + expect(node).toEqual(pdaNode({ name: 'myPda', seeds: [variablePdaSeedNode('seedData', bytesTypeNode())] })); +});