Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-toes-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@codama/nodes-from-anchor': patch
---

Fix `Vec<u8>` 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<u8>` seed.
19 changes: 15 additions & 4 deletions packages/nodes-from-anchor/src/v01/PdaSeedNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
import {
accountValueNode,
argumentValueNode,
bytesTypeNode,
camelCase,
constantPdaSeedNodeFromBytes,
InstructionArgumentNode,
Expand Down Expand Up @@ -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),
Expand Down
13 changes: 13 additions & 0 deletions packages/nodes-from-anchor/test/v01/pdaSeedNode.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
accountValueNode,
argumentValueNode,
bytesTypeNode,
constantPdaSeedNodeFromBytes,
instructionArgumentNode,
numberTypeNode,
Expand Down Expand Up @@ -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')));
});