Browse Source

fix: Nested pda types and account resolution for pdas (#2259)

Noah Prince 2 years ago
parent
commit
feff131ab0

+ 1 - 1
tests/pda-derivation/tests/typescript.spec.ts

@@ -57,7 +57,7 @@ describe("typescript", () => {
     });
     });
 
 
     const keys = await tx.pubkeys();
     const keys = await tx.pubkeys();
-    expect(keys.account.equals(expectedPDAKey)).is.true;
+    expect(keys.account!.equals(expectedPDAKey)).is.true;
 
 
     await tx.rpc();
     await tx.rpc();
 
 

+ 1 - 0
tests/pda-derivation/tsconfig.json

@@ -1,4 +1,5 @@
 {
 {
+  "include": ["./target/types/pda_derivation.ts"],
   "compilerOptions": {
   "compilerOptions": {
     "types": ["mocha", "chai"],
     "types": ["mocha", "chai"],
     "typeRoots": ["./node_modules/@types"],
     "typeRoots": ["./node_modules/@types"],

+ 1 - 1
tests/relations-derivation/tests/typescript.spec.ts

@@ -36,7 +36,7 @@ describe("typescript", () => {
     await tx.instruction();
     await tx.instruction();
     const keys = await tx.pubkeys();
     const keys = await tx.pubkeys();
 
 
-    expect(keys.myAccount.equals(provider.wallet.publicKey)).is.true;
+    expect(keys.myAccount!.equals(provider.wallet.publicKey)).is.true;
 
 
     await tx.rpc();
     await tx.rpc();
   });
   });

+ 1 - 0
tests/relations-derivation/tsconfig.json

@@ -1,4 +1,5 @@
 {
 {
+  "include": ["./target/types/relations_derivation.ts"],
   "compilerOptions": {
   "compilerOptions": {
     "types": ["mocha", "chai"],
     "types": ["mocha", "chai"],
     "typeRoots": ["./node_modules/@types"],
     "typeRoots": ["./node_modules/@types"],

+ 22 - 16
ts/packages/anchor/src/program/accounts-resolver.ts

@@ -73,12 +73,6 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
   // Note: We serially resolve PDAs one by one rather than doing them
   // Note: We serially resolve PDAs one by one rather than doing them
   //       in parallel because there can be dependencies between
   //       in parallel because there can be dependencies between
   //       addresses. That is, one PDA can be used as a seed in another.
   //       addresses. That is, one PDA can be used as a seed in another.
-  //
-  // TODO: PDAs need to be resolved in topological order. For now, we
-  //       require the developer to simply list the accounts in the
-  //       correct order. But in future work, we should create the
-  //       dependency graph and resolve automatically.
-  //
   public async resolve() {
   public async resolve() {
     await this.resolveConst(this._idlIx.accounts);
     await this.resolveConst(this._idlIx.accounts);
 
 
@@ -253,14 +247,16 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
       throw new Error("Must have seeds");
       throw new Error("Must have seeds");
 
 
     const seeds: (Buffer | undefined)[] = await Promise.all(
     const seeds: (Buffer | undefined)[] = await Promise.all(
-      accountDesc.pda.seeds.map((seedDesc: IdlSeed) => this.toBuffer(seedDesc))
+      accountDesc.pda.seeds.map((seedDesc: IdlSeed) =>
+        this.toBuffer(seedDesc, path)
+      )
     );
     );
 
 
     if (seeds.some((seed) => typeof seed == "undefined")) {
     if (seeds.some((seed) => typeof seed == "undefined")) {
       return;
       return;
     }
     }
 
 
-    const programId = await this.parseProgramId(accountDesc);
+    const programId = await this.parseProgramId(accountDesc, path);
     if (!programId) {
     if (!programId) {
       return;
       return;
     }
     }
@@ -272,7 +268,10 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
     this.set([...path, camelCase(accountDesc.name)], pubkey);
     this.set([...path, camelCase(accountDesc.name)], pubkey);
   }
   }
 
 
-  private async parseProgramId(accountDesc: IdlAccount): Promise<PublicKey> {
+  private async parseProgramId(
+    accountDesc: IdlAccount,
+    path: string[] = []
+  ): Promise<PublicKey> {
     if (!accountDesc.pda?.programId) {
     if (!accountDesc.pda?.programId) {
       return this._programId;
       return this._programId;
     }
     }
@@ -284,7 +283,7 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
       case "arg":
       case "arg":
         return this.argValue(accountDesc.pda.programId);
         return this.argValue(accountDesc.pda.programId);
       case "account":
       case "account":
-        return await this.accountValue(accountDesc.pda.programId);
+        return await this.accountValue(accountDesc.pda.programId, path);
       default:
       default:
         throw new Error(
         throw new Error(
           `Unexpected program seed kind: ${accountDesc.pda.programId.kind}`
           `Unexpected program seed kind: ${accountDesc.pda.programId.kind}`
@@ -292,14 +291,17 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
     }
     }
   }
   }
 
 
-  private async toBuffer(seedDesc: IdlSeed): Promise<Buffer | undefined> {
+  private async toBuffer(
+    seedDesc: IdlSeed,
+    path: string[] = []
+  ): Promise<Buffer | undefined> {
     switch (seedDesc.kind) {
     switch (seedDesc.kind) {
       case "const":
       case "const":
         return this.toBufferConst(seedDesc);
         return this.toBufferConst(seedDesc);
       case "arg":
       case "arg":
         return await this.toBufferArg(seedDesc);
         return await this.toBufferArg(seedDesc);
       case "account":
       case "account":
-        return await this.toBufferAccount(seedDesc);
+        return await this.toBufferAccount(seedDesc, path);
       default:
       default:
         throw new Error(`Unexpected seed kind: ${seedDesc.kind}`);
         throw new Error(`Unexpected seed kind: ${seedDesc.kind}`);
     }
     }
@@ -361,20 +363,24 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
   }
   }
 
 
   private async toBufferAccount(
   private async toBufferAccount(
-    seedDesc: IdlSeed
+    seedDesc: IdlSeed,
+    path: string[] = []
   ): Promise<Buffer | undefined> {
   ): Promise<Buffer | undefined> {
-    const accountValue = await this.accountValue(seedDesc);
+    const accountValue = await this.accountValue(seedDesc, path);
     if (!accountValue) {
     if (!accountValue) {
       return;
       return;
     }
     }
     return this.toBufferValue(seedDesc.type, accountValue);
     return this.toBufferValue(seedDesc.type, accountValue);
   }
   }
 
 
-  private async accountValue(seedDesc: IdlSeed): Promise<any> {
+  private async accountValue(
+    seedDesc: IdlSeed,
+    path: string[] = []
+  ): Promise<any> {
     const pathComponents = seedDesc.path.split(".");
     const pathComponents = seedDesc.path.split(".");
 
 
     const fieldName = pathComponents[0];
     const fieldName = pathComponents[0];
-    const fieldPubkey = this._accounts[camelCase(fieldName)];
+    const fieldPubkey = this.get([...path, camelCase(fieldName)]);
 
 
     // The seed is a pubkey of the account.
     // The seed is a pubkey of the account.
     if (pathComponents.length === 1) {
     if (pathComponents.length === 1) {

+ 14 - 5
ts/packages/anchor/src/program/namespace/methods.ts

@@ -7,12 +7,13 @@ import {
   TransactionInstruction,
   TransactionInstruction,
   TransactionSignature,
   TransactionSignature,
 } from "@solana/web3.js";
 } from "@solana/web3.js";
-import { Idl, IdlTypeDef } from "../../idl.js";
+import { Idl, IdlAccountItem, IdlAccounts, IdlTypeDef } from "../../idl.js";
 import Provider from "../../provider.js";
 import Provider from "../../provider.js";
 import {
 import {
   AccountsResolver,
   AccountsResolver,
   CustomAccountResolver,
   CustomAccountResolver,
 } from "../accounts-resolver.js";
 } from "../accounts-resolver.js";
+import { Address } from "../common.js";
 import { Accounts } from "../context.js";
 import { Accounts } from "../context.js";
 import { AccountNamespace } from "./account.js";
 import { AccountNamespace } from "./account.js";
 import { InstructionFn } from "./instruction.js";
 import { InstructionFn } from "./instruction.js";
@@ -64,6 +65,14 @@ export class MethodsBuilderFactory {
   }
   }
 }
 }
 
 
+type PartialAccounts<A extends IdlAccountItem = IdlAccountItem> = Partial<{
+  [N in A["name"]]: PartialAccount<A & { name: N }>;
+}>;
+
+type PartialAccount<A extends IdlAccountItem> = A extends IdlAccounts
+  ? Partial<Accounts<A["accounts"][number]>>
+  : Address;
+
 export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
 export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
   private readonly _accounts: { [name: string]: PublicKey } = {};
   private readonly _accounts: { [name: string]: PublicKey } = {};
   private _remainingAccounts: Array<AccountMeta> = [];
   private _remainingAccounts: Array<AccountMeta> = [];
@@ -112,12 +121,12 @@ export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
     if (this._autoResolveAccounts) {
     if (this._autoResolveAccounts) {
       await this._accountsResolver.resolve();
       await this._accountsResolver.resolve();
     }
     }
-    return this._accounts as Partial<InstructionAccountAddresses<IDL, I>>;
+    return this._accounts as unknown as Partial<
+      InstructionAccountAddresses<IDL, I>
+    >;
   }
   }
 
 
-  public accounts(
-    accounts: Partial<Accounts<I["accounts"][number]>>
-  ): MethodsBuilder<IDL, I> {
+  public accounts(accounts: PartialAccounts): MethodsBuilder<IDL, I> {
     this._autoResolveAccounts = true;
     this._autoResolveAccounts = true;
     Object.assign(this._accounts, accounts);
     Object.assign(this._accounts, accounts);
     return this;
     return this;

+ 11 - 2
ts/packages/anchor/src/program/namespace/types.ts

@@ -2,6 +2,8 @@ import { PublicKey } from "@solana/web3.js";
 import BN from "bn.js";
 import BN from "bn.js";
 import { Idl } from "../../";
 import { Idl } from "../../";
 import {
 import {
+  IdlAccounts as IdlIdlAccounts,
+  IdlAccountItem,
   IdlEnumFields,
   IdlEnumFields,
   IdlEnumFieldsNamed,
   IdlEnumFieldsNamed,
   IdlEnumFieldsTuple,
   IdlEnumFieldsTuple,
@@ -94,10 +96,17 @@ export type InstructionContextFnArgs<
 export type InstructionAccountAddresses<
 export type InstructionAccountAddresses<
   IDL extends Idl,
   IDL extends Idl,
   I extends AllInstructions<IDL>
   I extends AllInstructions<IDL>
-> = {
-  [N in keyof Accounts<I["accounts"][number]>]: PublicKey;
+> = InstructionAccountsAddresses<I["accounts"][number]>;
+
+type InstructionAccountsAddresses<A extends IdlAccountItem = IdlAccountItem> = {
+  [N in A["name"]]: InstructionAccountsAddress<A & { name: N }>;
 };
 };
 
 
+type InstructionAccountsAddress<A extends IdlAccountItem> =
+  A extends IdlIdlAccounts
+    ? InstructionAccountsAddresses<A["accounts"][number]>
+    : PublicKey;
+
 export type MethodsFn<
 export type MethodsFn<
   IDL extends Idl,
   IDL extends Idl,
   I extends IDL["instructions"][number],
   I extends IDL["instructions"][number],