Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PDA ConstraintSeeds error with generated js client #108

Closed
SDPyle opened this issue Nov 29, 2024 · 7 comments
Closed

PDA ConstraintSeeds error with generated js client #108

SDPyle opened this issue Nov 29, 2024 · 7 comments

Comments

@SDPyle
Copy link

SDPyle commented Nov 29, 2024

I love what this project is doing, and I'm very grateful for it.

I'm having an issue with PDAs and the generated js client.

Simple modification to the template scaffold to use a seed from the instruction:

 #[program]
mod drp_test_scaffold {
    use super::*;

    pub fn create(ctx: Context<Create>, name: String, authority: Pubkey) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.name = name;
        counter.authority = authority;
        counter.count = 0;
        Ok(())
    }
}

#[derive(Accounts)]
#[instruction(name: String)]
pub struct Create<'info> {
    #[account(
        init, 
        seeds = [name.as_bytes()],
        bump,
        payer = payer, space = 8 + Counter::INIT_SPACE
    )]
    pub counter: Account<'info, Counter>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[account]
#[derive(InitSpace)]
pub struct Counter {
    #[max_len(50)]
    pub name: String,
    pub authority: Pubkey,
    pub count: u64,
}

Test:

  const client = createDefaultSolanaClient();
  const [authority] = await Promise.all([generateKeyPairSignerWithSol(client)]);

  const createIx = await getCreateInstructionAsync({
    name: 'test',
    authority: authority.address,
    payer: authority,
  });
  await pipe(
    await createDefaultTransaction(client, authority),
    (tx) => appendTransactionMessageInstruction(createIx, tx),
    (tx) => signAndSendTransaction(client, tx)
  );

This generates:

AnchorError caused by account: counter. Error Code: ConstraintSeeds. Error Number: 2006. Error Message: A seeds constraint was violated.',
        'Program log: Left:',
        'Program log: ALMnbtV3pynYhVK3rPrVwA5B3pPMuHu2BQA1HXog5zjv',
        'Program log: Right:',
        'Program log: 8KqoXYYR9irXEi63X1Xm1qA2LcJf19ZeVnmzbDwB6zhA',

I'm relatively new to Solana development, so it's entirely possible that I'm missing something, but it seems like something isn't working as intended.

Thank you for your time and attention.

@swimricky
Copy link

swimricky commented Nov 29, 2024

you haven't done anything wrong i believe there's a bug in codama (which is the package that's used to generate the JS client)

I copied your example and ran it locally and also had the same issue.
if you look at the implementation for getCreateInstructionAsync() that gets generated you'll see that for the accounts.counter.value it uses this

  // Resolve default values.
  if (!accounts.counter.value) {
    accounts.counter.value = await getProgramDerivedAddress({
      programAddress,
      seeds: [
        addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder()).encode(
          expectSome(args.name)
        ),
      ],
    });
  }

the seeds portion is incorrect. it's encoding the size as a u32 but it should be using a u8.
It shouldn't be encoding the length
if you manually calculate it using the below you'll get the correctly derived address for the counter account

 const [counterAddress, _counterBump] = await getProgramDerivedAddress({
    programAddress: MY_PROGRAM_PROGRAM_ADDRESS,
    seeds: [Buffer.from('test')],
  });

I also verified it using the solana/web3.js v1.95.5 (the create-solana-program uses the new solana web3.js v2.0)

  const [legacyWeb3JsPda] = PublicKey.findProgramAddressSync(
    [Buffer.from('test')],
    new PublicKey(MY_PROGRAM_PROGRAM_ADDRESS)
  );

for now to work around your issue, you can do the following

// calculate the counter PDA manually
const [counterAddress, _counterBump] = await getProgramDerivedAddress({
    programAddress: MY_PROGRAM_PROGRAM_ADDRESS,
    seeds: [Buffer.from('test')],
  });
  // explicitly set the counter PDA address in your ix 
  const createIx = await getCreateInstructionAsync({
    name: 'test',
    counter: counterAddress,
    authority: authority.address,
    payer: authority,
  });
  // same as before

@swimricky
Copy link

i submitted an issue on codama - codama-idl/codama#337

@lorisleiva
Copy link
Member

Hi there, thanks for raising this.

However, you are giving conflicting results by saying that the String prefix must be a u8 and then proceeding to giving a working example that uses no prefix at all.

@SDPyle Would you mind sharing the Anchor IDL that gets generated so I can see what type it provides for that seed?

@SDPyle
Copy link
Author

SDPyle commented Nov 29, 2024

Wow. Thank you so much!

I tried @swimricky's update and it worked!

Here's the Anchor IDL:

{
  "address": "F5uiKB4R24UrqQsc4EVbjXnwksmpdPeaGf273uaaSnzN",
  "metadata": {
    "name": "drp_test_scaffold",
    "version": "0.0.0",
    "spec": "0.1.0",
    "address": "F5uiKB4R24UrqQsc4EVbjXnwksmpdPeaGf273uaaSnzN",
    "origin": "anchor",
    "binaryVersion": "0.30.1",
    "libVersion": "0.30.1"
  },
  "instructions": [
    {
      "name": "create",
      "discriminator": [
        24,
        30,
        200,
        40,
        5,
        28,
        7,
        119
      ],
      "accounts": [
        {
          "name": "counter",
          "writable": true,
          "pda": {
            "seeds": [
              {
                "kind": "arg",
                "path": "name"
              }
            ]
          }
        },
        {
          "name": "payer",
          "writable": true,
          "signer": true
        },
        {
          "name": "system_program",
          "address": "11111111111111111111111111111111"
        }
      ],
      "args": [
        {
          "name": "name",
          "type": "string"
        },
        {
          "name": "authority",
          "type": "pubkey"
        }
      ]
    }
  ],
  "accounts": [
    {
      "name": "Counter",
      "discriminator": [
        255,
        176,
        4,
        245,
        188,
        253,
        124,
        25
      ]
    }
  ],
  "errors": [
    {
      "code": 6000,
      "name": "InvalidAuthority",
      "msg": "The provided authority doesn't match the counter account's authority"
    }
  ],
  "types": [
    {
      "name": "Counter",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "name",
            "type": "string"
          },
          {
            "name": "authority",
            "type": "pubkey"
          },
          {
            "name": "count",
            "type": "u64"
          }
        ]
      }
    }
  ]
}

Let me know if there's anything else I can help with.

God Bless!

@swimricky
Copy link

swimricky commented Nov 29, 2024

@lorisleiva sorry it was late and i made a mistake
i was converting the seeds generated by codama to the Buffer.from('test') by converting the the Buffer to a Uint8Array then comparing those instead of comparing the actually derived address.

if you use the utf8encoder without the sized prefix you'll derive the correct address

 const [counterAddress, _counterBump] = await getProgramDerivedAddress({
    programAddress: MY_PROGRAM_PROGRAM_ADDRESS,
    seeds: [Buffer.from('test')],
  });
  const [counderAddress2, _counterBump2] = await getProgramDerivedAddress({
    programAddress: MY_PROGRAM_PROGRAM_ADDRESS,
    seeds: [
      getUtf8Encoder().encode('test'),
    ],
  });
  t.deepEqual(counterAddress, counderAddress2, 'counterAddress should match');

@lorisleiva
Copy link
Member

Thanks for the detailed updates guys!

Yes, I can see now that the Anchor macro says seeds = [name.as_bytes()], which basically means "remove the u32 size prefix of the String type.

The really annoying thing is that this is actually not exposed on the IDL whatsoever.

"pda": {
  "seeds": [
    {
      "kind": "arg",
      "path": "name" // argument "name" is `string`, not `string.as_bytes()` or something.
    }
  ]
}

So from the perspective of Codama, you're just getting the string type which, by default with Anchor, is a u32 prefixed string.

Since I believe this is the way most people using string as seeds anyway, it may be okay to just assume that string seed should never have any prefix. But that would introduce a bug for anyone that currently uses u32 prefixed strings for seeds.

I think it's still worth doing but maybe best to release it in 2.0. 🤔

@lorisleiva
Copy link
Member

Let's close this issue in favour of the new one created by @swimricky. 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants