diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bb5403e31..f097b395f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ The minor version will be incremented upon a breaking change and the patch versi - idl: Add `IdlBuilder` ([#3188](https://github.com/coral-xyz/anchor/pull/3188)). - cli: Make `clean` command also remove the `.anchor` directory ([#3192](https://github.com/coral-xyz/anchor/pull/3192)). - lang: Deprecate `#[interface]` attribute ([#3195](https://github.com/coral-xyz/anchor/pull/3195)). +- ts: Include unresolved accounts in the resolution error message ([#3207](https://github.com/coral-xyz/anchor/pull/3207)). ### Fixes diff --git a/tests/pda-derivation/programs/pda-derivation/src/lib.rs b/tests/pda-derivation/programs/pda-derivation/src/lib.rs index a641904434..603dc51cad 100644 --- a/tests/pda-derivation/programs/pda-derivation/src/lib.rs +++ b/tests/pda-derivation/programs/pda-derivation/src/lib.rs @@ -51,6 +51,10 @@ pub mod pda_derivation { pub fn seed_math_expr(_ctx: Context) -> Result<()> { Ok(()) } + + pub fn resolution_error(_ctx: Context) -> Result<()> { + Ok(()) + } } #[derive(Accounts)] @@ -178,6 +182,15 @@ pub struct SeedMathExpr<'info> { pub math_expr_account: UncheckedAccount<'info>, } +#[derive(Accounts)] +pub struct ResolutionError<'info> { + pub unknown: UncheckedAccount<'info>, + #[account(seeds = [unknown.key.as_ref()], bump)] + pub pda: UncheckedAccount<'info>, + #[account(seeds = [pda.key.as_ref()], bump)] + pub another_pda: UncheckedAccount<'info>, +} + #[account] pub struct MyAccount { data: u64, diff --git a/tests/pda-derivation/tests/typescript.spec.ts b/tests/pda-derivation/tests/typescript.spec.ts index 1c904173b6..ec56ff9159 100644 --- a/tests/pda-derivation/tests/typescript.spec.ts +++ b/tests/pda-derivation/tests/typescript.spec.ts @@ -121,4 +121,17 @@ describe("typescript", () => { it("Can use unsupported expressions", () => { // Compilation test to fix issues like https://github.com/coral-xyz/anchor/issues/2933 }); + + it("Includes the unresolved accounts if resolution fails", async () => { + try { + // `unknown` account is required for account resolution to work, but it's + // intentionally not provided to test the error message + await program.methods.resolutionError().rpc(); + throw new Error("Should throw due to account resolution failure!"); + } catch (e) { + expect(e.message).to.equal( + "Reached maximum depth for account resolution. Unresolved accounts: `pda`, `anotherPda`" + ); + } + }); }); diff --git a/ts/packages/anchor/src/program/accounts-resolver.ts b/ts/packages/anchor/src/program/accounts-resolver.ts index 0b12784b31..5ece611432 100644 --- a/ts/packages/anchor/src/program/accounts-resolver.ts +++ b/ts/packages/anchor/src/program/accounts-resolver.ts @@ -86,7 +86,43 @@ export class AccountsResolver { ) { depth++; if (depth === 16) { - throw new Error("Reached maximum depth for account resolution"); + const isResolvable = (acc: IdlInstructionAccountItem) => { + if (!isCompositeAccounts(acc)) { + return !!(acc.address || acc.pda || acc.relations); + } + + return acc.accounts.some(isResolvable); + }; + + const getPaths = ( + accs: IdlInstructionAccountItem[], + path: string[] = [], + paths: string[][] = [] + ) => { + for (const acc of accs) { + if (isCompositeAccounts(acc)) { + paths.push(...getPaths(acc.accounts, [...path, acc.name])); + } else { + paths.push([...path, acc.name]); + } + } + + return paths; + }; + + const resolvableAccs = this._idlIx.accounts.filter(isResolvable); + const unresolvedAccs = getPaths(resolvableAccs) + .filter((path) => !this.get(path)) + .map((path) => path.reduce((acc, p) => acc + "." + p)) + .map((acc) => `\`${acc}\``) + .join(", "); + + throw new Error( + [ + `Reached maximum depth for account resolution.`, + `Unresolved accounts: ${unresolvedAccs}`, + ].join(" ") + ); } } }