failed to send transaction: Cross-program invocation with unauthorized signer or writable account

Viewed 48

I am trying to build a web3 based e-commerce site using Anchor.

I've just started learning about PDAs and there's a error I've been getting for hours, like the one in the title.

My contract:

#[program]
pub mod dailsap_store_contract {
    use super::*;

    pub fn create_collection(
        ctx: Context<CreateCollection>,
        name: String,
        description: String,
        image_uri: String,
    ) -> Result<()> {
        let collection: &mut Account<Collection> = &mut ctx.accounts.collection;
        let authority: &Signer = &ctx.accounts.authority;
        let clock: Clock = Clock::get().unwrap();
        let bump = *ctx.bumps.get("collection").unwrap();

        if name.chars().count() > 50 {
            return Err(ErrorCode::CollectionNameTooLong.into());
        }
        if description.chars().count() > 250 {
            return Err(ErrorCode::CollectionDescriptionTooLong.into());
        }
        if image_uri.chars().count() > 60 {
            return Err(ErrorCode::CollectionImageUrlTooLong.into());
        }

        collection.authority = *authority.key;
        collection.timestamp = clock.unix_timestamp;
        collection.name = name;
        collection.description = description;
        collection.image = image_uri;
        collection.bump = bump;

        Ok(())
    }

    pub fn update_collection(
        ctx: Context<UpdateCollection>,
        name: String,
        description: String,
        image_uri: String,
    ) -> Result<()> {
        let base_collection: &mut Account<Collection> = &mut ctx.accounts.collection_account;

        base_collection.name = name;
        base_collection.description = description;
        base_collection.image = image_uri;

        Ok(())
    }
}

#[derive(Accounts)]
pub struct CreateCollection<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,

    #[account(init, payer=authority, space = Collection::LEN, seeds=[b"collection", collection.key().as_ref()], bump)]
    pub collection: Account<'info, Collection>,

    #[account(address = system_program::ID)]
    pub system_program: Program<'info, System>,
}
#[account]
pub struct Collection {
    authority: Pubkey,
    timestamp: i64,
    name: String,
    description: String,
    image: String,
    bump: u8,
}

Frontend:

  const collection = anchor.web3.Keypair.generate();

    const [collectionPDA, _] = await anchor.web3.PublicKey.findProgramAddress(
      [
        anchor.utils.bytes.utf8.encode("collection"),
        collection.publicKey.toBuffer(),
      ],
      program.programId
    );

    await program.methods
      .createCollection(
        "This is collection name",
        "This is collection description",
        "Hello World"
      )
      .accounts({
        collection: collectionPDA,
        authority: anchor.AnchorProvider.env().publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      })
      .rpc();

The problem should be here: seeds=[b"collection", collection.key().as_ref()]

The source from which I received help: https://book.anchor-lang.com/anchor_in_depth/PDAs.html

But I'm getting errors in a way I don't understand

can you help?

2 Answers

To be honest, I'm surprised the program got so far! The program-derived address is meant to be derived from a set of seeds and the program id, and in your case, the collection address is using itself as a seed, which would normally mean infinite recursion. But in this case, it only goes down one level and fails to derive itself.

Try any other seeds on your collection address, even something like:

    #[account(init, payer=authority, space = Collection::LEN, seeds=[b"collection", authority.key().as_ref()], bump)]
    pub collection: Account<'info, Collection>,

I came up with a solution like this, I'm not sure how logical it is.

...
#[program]
pub mod dailsap_store_contract {
    use super::*;

    pub fn create_collection(
        ctx: Context<CreateCollection>,
        id: Pubkey,
        name: String,
        description: String,
        image_uri: String,
    ) -> Result<()> {
        let collection: &mut Account<Collection> = &mut ctx.accounts.collection;
        let authority: &Signer = &ctx.accounts.authority;
        let clock: Clock = Clock::get().unwrap();
        let bump = *ctx.bumps.get("collection").unwrap();

        if name.chars().count() > 50 {
            return Err(ErrorCode::CollectionNameTooLong.into());
        }
...

#[derive(Accounts)]
#[instruction(id: Pubkey)]
pub struct CreateCollection<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,

    #[account(init, payer=authority, space = Collection::LEN, seeds=[b"collection", id.as_ref()], bump)]
    pub collection: Account<'info, Collection>,

    #[account(address = system_program::ID)]
    pub system_program: Program<'info, System>,
}

#[account]
pub struct Collection {
    id: Pubkey,
    authority: Pubkey,
    timestamp: i64,
    name: String,
    description: String,
    image: String,
    bump: u8,
}
...

Frontend:

 const collection = anchor.web3.Keypair.generate();

    const [collectionPDA, _] = await anchor.web3.PublicKey.findProgramAddress(
      [
        Buffer.from(anchor.utils.bytes.utf8.encode("collection")),
        collection.publicKey.toBuffer(),
      ],
      program.programId
    );

    await program.methods
      .createCollection(
        collection.publicKey,
        "This is collection name",
        "This is collection description",
        "Hello World"
      )
      .accounts({
        collection: collectionPDA,
        authority: anchor.AnchorProvider.env().publicKey,
        systemProgram: anchor.web3.SystemProgram.programId,
      })
      .rpc();
Related