Browse Source

ts: Include unresolved accounts in the resolution error message (#3207)

acheron 1 year ago
parent
commit
65c159fb3c

+ 1 - 0
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
 

+ 13 - 0
tests/pda-derivation/programs/pda-derivation/src/lib.rs

@@ -51,6 +51,10 @@ pub mod pda_derivation {
     pub fn seed_math_expr(_ctx: Context<SeedMathExpr>) -> Result<()> {
         Ok(())
     }
+
+    pub fn resolution_error(_ctx: Context<ResolutionError>) -> 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,

+ 13 - 0
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`"
+      );
+    }
+  });
 });

+ 37 - 1
ts/packages/anchor/src/program/accounts-resolver.ts

@@ -86,7 +86,43 @@ export class AccountsResolver<IDL extends Idl> {
     ) {
       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(" ")
+        );
       }
     }
   }