From 943692ca97236c89d4c15ac5b9cac24fcc72b3cf Mon Sep 17 00:00:00 2001 From: latent-9 <296084221+latent-9@users.noreply.github.com> Date: Sat, 22 Aug 2026 06:18:38 +1200 Subject: [PATCH] fix(nodes-from-anchor): strip the Borsh prefix from bytes PDA seeds A Vec instruction argument used as a PDA seed keeps its Borsh sizePrefixTypeNode(u32, bytes) type when converted from an Anchor IDL. Anchor derives byte-array seeds from the raw bytes without the length prefix, so the generated client computes a different PDA than the on-chain program. String seeds already strip the prefix; byte-array seeds were the missing case. Extend the argument-seed conversion to also recognize a size-prefixed bytes argument and emit a raw bytesTypeNode seed. --- .changeset/bright-toes-wave.md | 5 +++++ .../nodes-from-anchor/src/v01/PdaSeedNode.ts | 19 +++++++++++++++---- .../test/v01/pdaSeedNode.test.ts | 13 +++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 .changeset/bright-toes-wave.md diff --git a/.changeset/bright-toes-wave.md b/.changeset/bright-toes-wave.md new file mode 100644 index 000000000..c62a77dc9 --- /dev/null +++ b/.changeset/bright-toes-wave.md @@ -0,0 +1,5 @@ +--- +'@codama/nodes-from-anchor': patch +--- + +Fix `Vec` instruction arguments used as PDA seeds to derive from their raw bytes instead of the Borsh size-prefixed encoding. Anchor derives byte-array seeds from the unprefixed bytes, so the generated seed type previously produced a different PDA than the on-chain program for any program using a `Vec` seed. diff --git a/packages/nodes-from-anchor/src/v01/PdaSeedNode.ts b/packages/nodes-from-anchor/src/v01/PdaSeedNode.ts index fee2730fc..621c2930c 100644 --- a/packages/nodes-from-anchor/src/v01/PdaSeedNode.ts +++ b/packages/nodes-from-anchor/src/v01/PdaSeedNode.ts @@ -6,6 +6,7 @@ import { import { accountValueNode, argumentValueNode, + bytesTypeNode, camelCase, constantPdaSeedNodeFromBytes, InstructionArgumentNode, @@ -51,16 +52,26 @@ export function pdaSeedNodeFromAnchorV01( throw new CodamaError(CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING, { name: originalArgumentName }); } - // Anchor uses unprefixed strings for PDA seeds even though the - // argument itself uses a Borsh size-prefixed string. Thus, we - // must recognize this case and convert the type accordingly. + // Anchor uses unprefixed strings and byte arrays for PDA seeds + // even though the arguments themselves use Borsh size-prefixed + // types. Thus, we must recognize both cases and convert the + // types accordingly. const isBorshString = isNode(argumentNode.type, 'sizePrefixTypeNode') && isNode(argumentNode.type.type, 'stringTypeNode') && argumentNode.type.type.encoding === 'utf8' && isNode(argumentNode.type.prefix, 'numberTypeNode') && argumentNode.type.prefix.format === 'u32'; - const argumentType = isBorshString ? stringTypeNode('utf8') : argumentNode.type; + const isBorshBytes = + isNode(argumentNode.type, 'sizePrefixTypeNode') && + isNode(argumentNode.type.type, 'bytesTypeNode') && + isNode(argumentNode.type.prefix, 'numberTypeNode') && + argumentNode.type.prefix.format === 'u32'; + const argumentType = isBorshString + ? stringTypeNode('utf8') + : isBorshBytes + ? bytesTypeNode() + : argumentNode.type; return { definition: variablePdaSeedNode(argumentNode.name, argumentType), diff --git a/packages/nodes-from-anchor/test/v01/pdaSeedNode.test.ts b/packages/nodes-from-anchor/test/v01/pdaSeedNode.test.ts index 6948bbef0..b685ff528 100644 --- a/packages/nodes-from-anchor/test/v01/pdaSeedNode.test.ts +++ b/packages/nodes-from-anchor/test/v01/pdaSeedNode.test.ts @@ -1,6 +1,7 @@ import { accountValueNode, argumentValueNode, + bytesTypeNode, constantPdaSeedNodeFromBytes, instructionArgumentNode, numberTypeNode, @@ -48,3 +49,15 @@ test('it removes the string prefix from arg Anchor seeds', () => { expect(nodes.definition).toEqual(variablePdaSeedNode('identifier', stringTypeNode('utf8'))); expect(nodes.value).toEqual(pdaSeedValueNode('identifier', argumentValueNode('identifier'))); }); + +test('it removes the bytes prefix from arg Anchor seeds', () => { + const nodes = pdaSeedNodeFromAnchorV01({ kind: 'arg', path: 'seed_data' }, [ + instructionArgumentNode({ + name: 'seed_data', + type: sizePrefixTypeNode(bytesTypeNode(), numberTypeNode('u32')), + }), + ]); + + expect(nodes.definition).toEqual(variablePdaSeedNode('seed_data', bytesTypeNode())); + expect(nodes.value).toEqual(pdaSeedValueNode('seed_data', argumentValueNode('seed_data'))); +});