Skip to content

Commit 2d4968d

Browse files
committed
feat: support const program ids for pdas in anchor
Anchor allows for program ID's for PDA's to be specified using the 'seeds::program' parameter. Codama currently ignores these program ID's for PDA instruction accounts in Anchor when converting Anchor IDL nodes to Codama nodes. This commit implements support for constant program ID's for PDA instruction account nodes in Anchor.
1 parent 1ca91a1 commit 2d4968d

File tree

10 files changed

+95
-4
lines changed

10 files changed

+95
-4
lines changed

.changeset/swift-tomatoes-care.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@codama/nodes-from-anchor': patch
3+
'@codama/renderers-js': patch
4+
'@codama/errors': patch
5+
---
6+
7+
Support constant program ID's for PDA instruction accounts in Anchor.

packages/errors/src/codes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export const CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING = 2100001;
6060
export const CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING = 2100002;
6161
export const CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING = 2100003;
6262
export const CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED = 2100004;
63+
export const CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED = 2100005;
6364

6465
// Renderers-related errors.
6566
// Reserve error codes in the range [2800000-2800999].
@@ -83,6 +84,7 @@ export const CODAMA_ERROR__RENDERERS__UNSUPPORTED_NODE = 2800000;
8384
export type CodamaErrorCode =
8485
| typeof CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING
8586
| typeof CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING
87+
| typeof CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED
8688
| typeof CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED
8789
| typeof CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING
8890
| typeof CODAMA_ERROR__ANCHOR__UNRECOGNIZED_IDL_TYPE

packages/errors/src/context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import {
2323
CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING,
2424
CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING,
25+
CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED,
2526
CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED,
2627
CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING,
2728
CODAMA_ERROR__ANCHOR__UNRECOGNIZED_IDL_TYPE,
@@ -68,6 +69,9 @@ export type CodamaErrorContext = DefaultUnspecifiedErrorContextToUndefined<{
6869
[CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING]: {
6970
name: string;
7071
};
72+
[CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED]: {
73+
kind: string;
74+
};
7175
[CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED]: {
7276
kind: string;
7377
};

packages/errors/src/messages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import {
77
CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING,
88
CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING,
9+
CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED,
910
CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED,
1011
CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING,
1112
CODAMA_ERROR__ANCHOR__UNRECOGNIZED_IDL_TYPE,
@@ -48,6 +49,7 @@ export const CodamaErrorMessages: Readonly<{
4849
}> = {
4950
[CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING]: 'Account type [$name] is missing from the IDL types.',
5051
[CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING]: 'Argument name [$name] is missing from the instruction definition.',
52+
[CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED]: 'Program ID kind [$kind] is not implemented.',
5153
[CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED]: 'Seed kind [$kind] is not implemented.',
5254
[CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING]: 'Field type is missing for path [$path] in [$idlType].',
5355
[CODAMA_ERROR__ANCHOR__UNRECOGNIZED_IDL_TYPE]: 'Unrecognized Anchor IDL type [$idlType].',

packages/nodes-from-anchor/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"@codama/errors": "workspace:*",
5353
"@codama/nodes": "workspace:*",
5454
"@codama/visitors": "workspace:*",
55+
"@solana/codecs": "2.0.0",
5556
"@noble/hashes": "^1.7.0"
5657
},
5758
"license": "MIT",

packages/nodes-from-anchor/src/v01/InstructionAccountNode.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
CODAMA_ERROR__ANCHOR__ACCOUNT_TYPE_MISSING,
33
CODAMA_ERROR__ANCHOR__ARGUMENT_TYPE_MISSING,
4+
CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED,
45
CODAMA_ERROR__ANCHOR__SEED_KIND_UNIMPLEMENTED,
56
CODAMA_ERROR__ANCHOR__TYPE_PATH_MISSING,
67
CodamaError,
@@ -26,6 +27,7 @@ import {
2627
resolveNestedTypeNode,
2728
variablePdaSeedNode,
2829
} from '@codama/nodes';
30+
import { getBase58Codec } from '@solana/codecs';
2931

3032
import { hex } from '../utils';
3133
import { IdlV01InstructionAccount, IdlV01InstructionAccountItem, IdlV01Seed } from './idl';
@@ -123,7 +125,24 @@ export function instructionAccountNodeFromAnchorV01(
123125
<[PdaSeedNode[], PdaSeedValueNode[]]>[[], []],
124126
);
125127

126-
defaultValue = pdaValueNode(pdaNode({ name, seeds }), lookups);
128+
let programId: string | undefined;
129+
if (idl.pda.program !== undefined) {
130+
const kind = idl.pda.program.kind;
131+
switch (kind) {
132+
case 'const': {
133+
programId = getBase58Codec().decode(new Uint8Array(idl.pda.program.value));
134+
break;
135+
}
136+
default: {
137+
throw new CodamaError(CODAMA_ERROR__ANCHOR__PROGRAM_ID_KIND_UNIMPLEMENTED, { kind });
138+
}
139+
}
140+
}
141+
142+
defaultValue = pdaValueNode(
143+
pdaNode({ name, seeds, ...(programId !== undefined ? { programId } : {}) }),
144+
lookups,
145+
);
127146
}
128147
}
129148

packages/nodes-from-anchor/test/v01/InstructionAccountNode.test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
sizePrefixTypeNode,
1515
structFieldTypeNode,
1616
structTypeNode,
17-
variablePdaSeedNode,
17+
variablePdaSeedNode
1818
} from '@codama/nodes';
1919
import { expect, test } from 'vitest';
2020

@@ -161,6 +161,57 @@ test('it ignores PDA default values if at least one seed as a path of length gre
161161
]);
162162
});
163163

164+
test('it handles PDAs with a constant program id', () => {
165+
const nodes = instructionAccountNodesFromAnchorV01(
166+
[],
167+
[],
168+
[
169+
{
170+
name: 'program_data',
171+
pda: {
172+
program: {
173+
kind: 'const',
174+
value: [
175+
2, 168, 246, 145, 78, 136, 161, 176, 226, 16, 21, 62, 247, 99, 174, 43, 0, 194, 185, 61, 22,
176+
193, 36, 210, 192, 83, 122, 16, 4, 128, 0, 0,
177+
],
178+
},
179+
seeds: [
180+
{
181+
kind: 'const',
182+
value: [
183+
166, 175, 151, 238, 166, 67, 87, 148, 114, 209, 13, 88, 186, 228, 206, 197, 182, 71,
184+
129, 195, 206, 236, 229, 223, 184, 60, 97, 249, 63, 92, 203, 27,
185+
],
186+
},
187+
],
188+
},
189+
},
190+
],
191+
);
192+
193+
expect(nodes).toEqual([
194+
instructionAccountNode({
195+
defaultValue: pdaValueNode(
196+
pdaNode({
197+
name: 'programData',
198+
programId: 'BPFLoaderUpgradeab1e11111111111111111111111',
199+
seeds: [
200+
constantPdaSeedNodeFromBytes(
201+
'base16',
202+
'a6af97eea643579472d10d58bae4cec5b64781c3ceece5dfb83c61f93f5ccb1b',
203+
),
204+
],
205+
}),
206+
[],
207+
),
208+
isSigner: false,
209+
isWritable: false,
210+
name: 'programData',
211+
}),
212+
]);
213+
});
214+
164215
test.skip('it handles account data paths of length 2', () => {
165216
const nodes = instructionAccountNodesFromAnchorV01(
166217
[

packages/renderers-js/e2e/anchor/src/generated/instructions/createGuard.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,8 @@ export async function getCreateGuardInstructionAsync<
303303
}
304304
if (!accounts.mintTokenAccount.value) {
305305
accounts.mintTokenAccount.value = await getProgramDerivedAddress({
306-
programAddress,
306+
programAddress:
307+
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' as Address<'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'>,
307308
seeds: [
308309
getAddressEncoder().encode(
309310
expectAddress(accounts.guardAuthority.value)

packages/renderers-js/e2e/anchor/src/generated/instructions/updateGuard.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ export async function getUpdateGuardInstructionAsync<
252252
}
253253
if (!accounts.tokenAccount.value) {
254254
accounts.tokenAccount.value = await getProgramDerivedAddress({
255-
programAddress,
255+
programAddress:
256+
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' as Address<'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'>,
256257
seeds: [
257258
getAddressEncoder().encode(
258259
expectAddress(accounts.guardAuthority.value)

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)