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();
expect(keys.account.equals(expectedPDAKey)).is.true;
expect(keys.account!.equals(expectedPDAKey)).is.true;
await tx.rpc();

View File

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

View File

@ -36,7 +36,7 @@ describe("typescript", () => {
await tx.instruction();
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();
});

View File

@ -1,4 +1,5 @@
{
"include": ["./target/types/relations_derivation.ts"],
"compilerOptions": {
"types": ["mocha", "chai"],
"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
// in parallel because there can be dependencies between
// 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() {
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");
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")) {
return;
}
const programId = await this.parseProgramId(accountDesc);
const programId = await this.parseProgramId(accountDesc, path);
if (!programId) {
return;
}
@ -272,7 +268,10 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
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) {
return this._programId;
}
@ -284,7 +283,7 @@ export class AccountsResolver<IDL extends Idl, I extends AllInstructions<IDL>> {
case "arg":
return this.argValue(accountDesc.pda.programId);
case "account":
return await this.accountValue(accountDesc.pda.programId);
return await this.accountValue(accountDesc.pda.programId, path);
default:
throw new Error(
`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) {
case "const":
return this.toBufferConst(seedDesc);
case "arg":
return await this.toBufferArg(seedDesc);
case "account":
return await this.toBufferAccount(seedDesc);
return await this.toBufferAccount(seedDesc, path);
default:
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(
seedDesc: IdlSeed
seedDesc: IdlSeed,
path: string[] = []
): Promise<Buffer | undefined> {
const accountValue = await this.accountValue(seedDesc);
const accountValue = await this.accountValue(seedDesc, path);
if (!accountValue) {
return;
}
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 fieldName = pathComponents[0];
const fieldPubkey = this._accounts[camelCase(fieldName)];
const fieldPubkey = this.get([...path, camelCase(fieldName)]);
// The seed is a pubkey of the account.
if (pathComponents.length === 1) {

View File

@ -7,12 +7,13 @@ import {
TransactionInstruction,
TransactionSignature,
} from "@solana/web3.js";
import { Idl, IdlTypeDef } from "../../idl.js";
import { Idl, IdlAccountItem, IdlAccounts, IdlTypeDef } from "../../idl.js";
import Provider from "../../provider.js";
import {
AccountsResolver,
CustomAccountResolver,
} from "../accounts-resolver.js";
import { Address } from "../common.js";
import { Accounts } from "../context.js";
import { AccountNamespace } from "./account.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>> {
private readonly _accounts: { [name: string]: PublicKey } = {};
private _remainingAccounts: Array<AccountMeta> = [];
@ -112,12 +121,12 @@ export class MethodsBuilder<IDL extends Idl, I extends AllInstructions<IDL>> {
if (this._autoResolveAccounts) {
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;
Object.assign(this._accounts, accounts);
return this;

View File

@ -2,6 +2,8 @@ import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";
import { Idl } from "../../";
import {
IdlAccounts as IdlIdlAccounts,
IdlAccountItem,
IdlEnumFields,
IdlEnumFieldsNamed,
IdlEnumFieldsTuple,
@ -94,10 +96,17 @@ export type InstructionContextFnArgs<
export type InstructionAccountAddresses<
IDL extends 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<
IDL extends Idl,
I extends IDL["instructions"][number],