3

I have this instruction with the associated context accounts:

pub fn create_battleground(
    ctx: Context<CreateBattleground>,
    participants_cap: u32,
    entry_fee: u64,
) -> Result<()> {
    *ctx.accounts.battleground_state = BattlegroundState {
        bump: *ctx.bumps.get("battleground_state").unwrap(),
        id: ctx.accounts.battle_royale_state.last_battleground_id,
        participants_cap,
        participants: 0,
        status: BattlegroundStatus::Preparing,
        pot_mint: ctx.accounts.pot_mint.key(),
        entry_fee,
    };

    ctx.accounts.battle_royale_state.last_battleground_id += 1;

    Ok(())
}

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

    /// CHECK: Checking correspondance with battle royale state
    #[account(mut)]
    pub game_master: AccountInfo<'info>,

    #[account(
        mut,
        seeds = [
            BATTLE_ROYALE_STATE_SEEDS.as_bytes(),
            battle_royale_state.game_master.as_ref(),
        ],
        bump = battle_royale_state.bump,
        has_one = game_master,
    )]
    pub battle_royale_state: Account<'info, BattleRoyaleState>,

    /// CHECK: Checking correspondance with battle royale state
    #[account(
        mut,
        seeds = [
            BATTLEGROUND_AUTHORITY_SEEDS.as_bytes(),
            battle_royale_state.key().as_ref(),
            battle_royale_state.last_battleground_id.to_be_bytes().as_ref(),
        ],
        bump,
    )]
    pub authority: AccountInfo<'info>,

    /// The mint of the token used to pay the entry fee
    #[account(owner = token::ID)]
    pub pot_mint: Account<'info, Mint>,

    #[account(
        init,
        payer = creator,
        space = BattlegroundState::LEN,
        seeds = [
            BATTLEGROUND_STATE_SEEDS.as_bytes(),
            battle_royale_state.key().as_ref(),
            battle_royale_state.last_battleground_id.to_be_bytes().as_ref(),
        ],
        bump,
    )]
    pub battleground_state: Account<'info, BattlegroundState>,

    /// The account receiving entry fees
    #[account(
        init,
        payer = creator,
        associated_token::mint = pot_mint,
        associated_token::authority = authority,
    )]
    pub pot_account: Account<'info, TokenAccount>,

    /// The account receiving the fee taken on each entry fee
    #[account(
        init_if_needed,
        payer = creator,
        associated_token::mint = pot_mint,
        associated_token::authority = game_master,
    )]
    pub dev_account: Account<'info, TokenAccount>,

    /// Solana ecosystem program addresses
    pub token_program: Program<'info, Token>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
    pub rent: Sysvar<'info, Rent>,
}

In my tests, I call the function like so:

this.addresses = {
  authority: anchor.web3.PublicKey.findProgramAddressSync(
    [
      BATTLEGROUND_AUTHORITY_SEEDS,
      this.battleRoyale.addresses.battleRoyale.toBuffer(),
      new anchor.BN(this.id).toBuffer("le", 8),
    ],
    BATTLE_ROYALE_PROGRAM_ID
  )[0],
  battleground: anchor.web3.PublicKey.findProgramAddressSync(
    [
      BATTLEGROUND_STATE_SEEDS,
      this.battleRoyale.addresses.battleRoyale.toBuffer(),
      new anchor.BN(this.id).toBuffer("le", 8),
    ],
    BATTLE_ROYALE_PROGRAM_ID
  )[0],
};
const potAccount = await getAssociatedTokenAddress(potMint, this.addresses.authority, true);
const devAccount = await getAssociatedTokenAddress(
  potMint,
  this.battleRoyale.addresses.gameMaster,
  true
);

await program.methods
  .createBattleground(participantsCap, entryFee)
  .accounts({
    gameMaster: this.battleRoyale.addresses.gameMaster,
    creator: this.provider.publicKey,
    battleRoyaleState: this.battleRoyale.addresses.battleRoyaleStateKey,
    authority: this.addresses.authority,
    potMint,
    battlegroundState: battlegroundAddress,
    potAccount,
    devAccount,
    tokenProgram: TOKEN_PROGRAM_ID,
    associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
    rent: anchor.web3.SYSVAR_RENT_PUBKEY,
    systemProgram: anchor.web3.SystemProgram.programId,
  })
  .rpc();

However I get this error:

     Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Cross-program invocation with unauthorized signer or writable account
      at Connection.sendEncodedTransaction (node_modules/@solana/web3.js/src/connection.ts:4546:13)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at Connection.sendRawTransaction (node_modules/@solana/web3.js/src/connection.ts:4505:20)
      at sendAndConfirmRawTransaction (node_modules/@project-serum/anchor/src/provider.ts:288:21)
      at AnchorProvider.sendAndConfirm (node_modules/@project-serum/anchor/src/provider.ts:148:14)
      at MethodsBuilder.rpc [as _rpcFn] (node_modules/@project-serum/anchor/src/program/namespace/rpc.ts:29:16)

I tried adding mut on every account, double checked seeds, remo

2 Answers 2

3

you provided public keys for your accounts, but you still need to sign the transaction. try adding .signers([this.provider.wallet.payer]) before .rpc()

3
  • I want to be close to the front-end and not access private keys. I created different provider for each signer and the one I use for that transaction has the creator as its payer. This should be enough right? In my case, the this.provider.wallet (which is a anchor.AnchorProvider) does not have a payer attribute Aug 1, 2022 at 22:36
  • sorry i dont know the latest anchor-approved flow for that, in the past id construct the unsigned transaction and pass it off to the wallet to sign and send. id recommend testing with signers this way tho, to determine if this actually is the issue, before figuring out the wallet integration tho
    – hana
    Aug 2, 2022 at 22:46
  • I tried but it did not change the error Aug 3, 2022 at 9:25
2

The issue is with PDA generation of your battleground_state account. It gets tricky when you're generating PDAs with numbers as seeds. What's happening here is that the PDA being generated in your client doesn't match what the program expects.

/// CHECK: Checking correspondance with battle royale state
#[account(
    mut,
    seeds = [
        BATTLEGROUND_AUTHORITY_SEEDS.as_bytes(),
        battle_royale_state.key().as_ref(),
        (battle_royale_state.last_battleground_id as u64).to_le_bytes().as_ref(),
    ],
    bump,
)]
pub authority: AccountInfo<'info>,

Replace that part of your code with the code above. We specify that the seed is generated from the u64 representation of the number so we can get the exact matching representation on the client. We also use u64 instead of a u8 because it's easier to convert it to a Buffer on the client side. It also should be to_le_bytes(), not to_be_bytes().

In your client code the equivalent seed for finding the program address would be new anchor.BN(number).toBuffer('le', 8), where number is a plain javascript number like 8, 9.

13
  • I tried this, using u64 for the ID instead of u32 or changing endianness, but I still get the same error Aug 1, 2022 at 22:33
  • Did you change the corresponding typescript code, rebuild and redeploy?
    – Ademola
    Aug 1, 2022 at 22:58
  • I reran the anchor test command. I think anchor clean should not be necessary Aug 2, 2022 at 23:00
  • I tried removing most accounts and the error seems to come from the battleground_state. When it's not included in the contexts accounts, Anchor gives me an error saying: Error: Account does not exist GzeywaN9PU3mjPzKr4AHgm6f5H3RPRcDWJ8VJePtqH7B, where the address is that of the battleground state... Aug 3, 2022 at 8:10
  • Yh I expected that the error would be from there. Accounts are created by cpi to the system program's create instruction. An account needs to sign its own creation so the issue here is that the account you're passing in your tests(battleground_state`) doesn't match what the program expects. The error is very likely in your PDA derivation. How are you deriving the account in your tests? Send the code sample
    – Ademola
    Aug 3, 2022 at 21:39

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.