Skip to content

Commit 5913906

Browse files
ts: Fix parsing IDL with multiple const generics (#3665)
1 parent 4d815e4 commit 5913906

3 files changed

Lines changed: 202 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ The minor version will be incremented upon a breaking change and the patch versi
1616
### Fixes
1717

1818
- idl: Update `proc-macro2` usage for latest nightly ([#3663](https://github.com/solana-foundation/anchor/pull/3663))
19+
- ts: Fix parsing IDL with multiple const generics ([#3665](https://github.com/solana-foundation/anchor/pull/3665))
1920
- cli, docker: Replace `backpackapp/build` Docker image with `solanafoundation/anchor` ([#3619](https://github.com/coral-xyz/anchor/pull/3619)).
2021

2122
### Breaking

ts/packages/anchor/src/coder/borsh/idl.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -449,24 +449,25 @@ export class IdlCoder {
449449
const [elTy, len] = type.array;
450450
const isGenericLen = typeof len === "object";
451451

452-
const args = IdlCoder.resolveGenericArgs({
453-
type: elTy,
454-
typeDef,
455-
genericArgs,
456-
isDefined,
457-
});
458-
if (args) {
459-
// Has generic type, also check for generic length
460-
for (const i in typeDef.generics.slice(+index)) {
461-
const curIndex = +index + +i;
462-
if (
463-
isGenericLen &&
464-
typeDef.generics[curIndex].name === len.generic
465-
) {
466-
args.push(genericArgs[curIndex]);
467-
}
452+
const args =
453+
IdlCoder.resolveGenericArgs({
454+
type: elTy,
455+
typeDef,
456+
genericArgs,
457+
isDefined,
458+
}) || [];
459+
460+
// Check all generics for matching const generic length
461+
if (isGenericLen) {
462+
const matchingGeneric = typeDef.generics.findIndex(
463+
(g) => g.name === len.generic
464+
);
465+
if (matchingGeneric !== -1) {
466+
args.push(genericArgs[matchingGeneric]);
468467
}
468+
}
469469

470+
if (args.length > 0) {
470471
if (!isDefined) return args;
471472

472473
if (args[0].kind === "type" && args[1].kind === "const") {

ts/packages/anchor/tests/coder-accounts.spec.ts

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,188 @@ describe("coder.accounts", () => {
4949
assert.deepEqual(coder.accounts.decode("MemberDAO", encoded), memberDAO);
5050
});
5151
});
52+
53+
test("Can encode and decode user-defined accounts, including those with more nested & multiple const generics", () => {
54+
const idl: Idl = {
55+
address: "EQoYLkj17hXm8yc9qLq5Cm7FgCqujRVHd2ZhdEfAmrMF",
56+
metadata: {
57+
name: "gen_idl",
58+
version: "0.1.0",
59+
spec: "0.1.0",
60+
description: "Created with Anchor",
61+
},
62+
instructions: [
63+
{
64+
name: "initialize",
65+
discriminator: [175, 175, 109, 31, 13, 152, 155, 237],
66+
accounts: [
67+
{
68+
name: "my_acc",
69+
pda: {
70+
seeds: [
71+
{
72+
kind: "const",
73+
value: [
74+
115, 109, 97, 108, 108, 95, 109, 101, 109, 112, 111, 111,
75+
108,
76+
],
77+
},
78+
],
79+
},
80+
},
81+
],
82+
args: [],
83+
},
84+
],
85+
accounts: [
86+
{
87+
name: "MyAcc",
88+
discriminator: [123, 153, 151, 118, 126, 71, 73, 92],
89+
},
90+
],
91+
types: [
92+
{
93+
name: "MaxHeap",
94+
serialization: "bytemuckunsafe",
95+
repr: {
96+
kind: "c",
97+
},
98+
generics: [
99+
{
100+
kind: "type",
101+
name: "T",
102+
},
103+
{
104+
kind: "const",
105+
name: "SIZE",
106+
type: "usize",
107+
},
108+
{
109+
kind: "const",
110+
name: "PADDING",
111+
type: "usize",
112+
},
113+
],
114+
type: {
115+
kind: "struct",
116+
fields: [
117+
{
118+
name: "entries",
119+
type: {
120+
array: [
121+
{
122+
generic: "T",
123+
},
124+
{
125+
generic: "SIZE",
126+
},
127+
],
128+
},
129+
},
130+
{
131+
name: "count",
132+
type: "u16",
133+
},
134+
{
135+
name: "padding",
136+
type: {
137+
array: [
138+
"u8",
139+
{
140+
generic: "PADDING",
141+
},
142+
],
143+
},
144+
},
145+
],
146+
},
147+
},
148+
{
149+
name: "MyStruct",
150+
serialization: "bytemuck",
151+
repr: {
152+
kind: "c",
153+
},
154+
type: {
155+
kind: "struct",
156+
fields: [
157+
{
158+
name: "a",
159+
type: "u16",
160+
},
161+
{
162+
name: "b",
163+
type: "u16",
164+
},
165+
],
166+
},
167+
},
168+
{
169+
name: "MyAcc",
170+
serialization: "bytemuck",
171+
repr: {
172+
kind: "transparent",
173+
},
174+
type: {
175+
kind: "struct",
176+
fields: [
177+
{
178+
name: "inner",
179+
type: {
180+
defined: {
181+
name: "MaxHeap",
182+
generics: [
183+
{
184+
kind: "type",
185+
type: {
186+
defined: {
187+
name: "MyStruct",
188+
},
189+
},
190+
},
191+
{
192+
kind: "const",
193+
value: "3",
194+
},
195+
{
196+
kind: "const",
197+
value: "10",
198+
},
199+
],
200+
},
201+
},
202+
},
203+
],
204+
},
205+
},
206+
],
207+
};
208+
209+
const coder = new BorshCoder(idl);
210+
211+
const myAcc = {
212+
inner: {
213+
entries: [
214+
{
215+
a: 1,
216+
b: 2,
217+
},
218+
{
219+
a: 3,
220+
b: 4,
221+
},
222+
{
223+
a: 5,
224+
b: 6,
225+
},
226+
],
227+
count: 2,
228+
padding: new Array(10).fill(0),
229+
},
230+
};
231+
232+
coder.accounts.encode("MyAcc", myAcc).then((encoded) => {
233+
assert.deepEqual(coder.accounts.decode("MyAcc", encoded), myAcc);
234+
});
235+
});
52236
});

0 commit comments

Comments
 (0)