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

This commit is contained in:
Noah Prince 2022-11-14 02:45:41 +00:00 committed by GitHub
parent 91a2b7ec96
commit feff131ab0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 51 additions and 25 deletions

View File

@ -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();

View File

@ -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"],

View File

@ -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();
}); });

View File

@ -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"],

View File

@ -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) {

View File

@ -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( public accounts(accounts: PartialAccounts): MethodsBuilder<IDL, I> {
accounts: Partial<Accounts<I["accounts"][number]>>
): MethodsBuilder<IDL, I> {
this._autoResolveAccounts = true; this._autoResolveAccounts = true;
Object.assign(this._accounts, accounts); Object.assign(this._accounts, accounts);
return this; return this;

View File

@ -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>
> = { > = InstructionAccountsAddresses<I["accounts"][number]>;
[N in keyof Accounts<I["accounts"][number]>]: PublicKey;
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],