3

What is the correct way to initialize a PDA using data from another account in the seeds that is simultaneously being initialized?

In the [#derive(Accounts)] section below, I am trying to use the state.name as an input seed for the PDA, but it doesn't seem to work.

#[program]
pub mod auction {
    use super::*;
    pub fn initialize(
        ctx: Context<Initialize>,
        name: String,
    ) -> Result<()> {
        ctx.accounts.state.treasury_key = ctx.accounts.treasury.key();
        ctx.accounts.state.seller_key = ctx.accounts.seller.key();
        ctx.accounts.state.name = name;
        ctx.accounts.state.bump = *ctx.bumps.get("treasury").unwrap();
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = seller,
        space = 8 + std::mem::size_of::<State>()
    )]
    pub state: Account<'info, State>,
    #[account(mut)]
    pub seller: Signer<'info>,
    #[account(
        init,
        payer = seller,
        seeds = [&state.name.as_bytes(), seller.key().as_ref()], // does not work
        // seeds = [b"test", seller.key().as_ref()], // this works
        bump,
        space = 8 + 0,
    )]
    pub treasury: AccountInfo<'info>,
}

#[account]
pub struct State {
    pub treasury_key: Pubkey,
    pub seller_key: Pubkey,
    pub name: String,
    pub bump: u8,
}

My javascript test snippet has the following:

  const name = "please work";
  // const name = "test"; // only works if I keep this as "test" since the seed is hardcoded
  
  it("init", async () => {

    const [treasuryKey, _] = await PublicKey.findProgramAddress(
      [
        anchor.utils.bytes.utf8.encode(name),
        sellerAcc.publicKey.toBuffer(),
      ],
      program.programId
    )

    const tx = await program.rpc.initialize(
      name,
      {
        accounts: {
          state: state1Acc.publicKey,
          seller: sellerAcc.publicKey,
          treasury: treasuryKey,
          systemProgram: SystemProgram.programId,
        },
        signers: [stateAcc, sellerAcc],
      });
  });

This test only works if I use the hardcoded "test" seed and then feed in the name exactly as "test" as well... But I would like to use a custom name instead.

The error I receive is:

Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account

1 Answer 1

5

The issue here is that both accounts are being initialized in the same instruction. In the validator, state.name hasn't been set to the name argument in the instruction yet(validation comes before the instruction is called) so the first seed of treasury is not what you expect it to be. What you need is a way to access the name argument in the validator. Do it like this:

#[derive(Accounts)]
#[instruction(name: String)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = seller,
        space = 8 + std::mem::size_of::<State>()
    )]
    pub state: Account<'info, State>,
    #[account(mut)]
    pub seller: Signer<'info>,
    #[account(
        init,
        payer = seller,
        seeds = [name.as_bytes().as_ref(), seller.key().as_ref()], // does not work
        // seeds = [b"test", seller.key().as_ref()], // this works
        bump,
        space = 8 + 0,
    )]
    pub treasury: AccountInfo<'info>,
}

#[instruction(name: String)] lets you access your instruction argument(s) in your validator. Now as long as you derive your treasury PDA from the same name value you pass into your instruction on the client, it should work.

2
  • Brilliant... Really well explained, and such an elegant fix. I didn't know we could access arguments like that... this opens up a lot of possibilities! Thanks for the help.
    – Omega
    Aug 4, 2022 at 23:08
  • 1
    Just make sure you’re careful about using arguments in the seeds for already initialized accounts. Rule of thumb is that you should store all seed values in the account so that you can use them later.
    – zfedoran
    Aug 5, 2022 at 1:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.