4

I have ProgramA and ProgramB. ProgramA initializes an account using InstructionA. When ProgramB uses a CPI to call instructionA I'm getting AccountNotInitialized error.

I'm using the following code:

ProgramA:

pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
    Ok(())
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,
    #[account(
        init,
        seeds = [PDA_SEED.as_ref()],
        bump,
        payer = authority,
        space = 16 + 8
    )]
    pub account_a: Account<'info, AccountA>,
    pub system_program: Program<'info, System>,
}

ProgramB:

pub fn call_program_a(_ctx: Context<CallProgramA>) -> Result<()> {
    let cpi_program = self.program_a.to_account_info();
    let cpi_accounts = Initialize {
        authority: self.authority.to_account_info(),
        account_a: self.account_a.to_account_info(),
        system_program: self.system_program.to_account_info(),
        };
        CpiContext::new(cpi_program, cpi_accounts)
    Ok(())
}

#[derive(Accounts)]
pub struct CallProgramA<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,
    #[account(mut)]
    pub account_a: Account<'info, AccountA>,
    pub program_a: Program<'info, ProgramA>,
    pub system_program: Program<'info, System>,
}

Client side:

const [pdaAccountA, _bump] = await PublicKey.findProgramAddress(
    [Buffer.from(PDA_SEED)],
     ProgramA.programId
);

await ProgramA.rpc.callProgramA(
    {
        accounts: {
            authority: authority,
            accountA: pdaAccountA,
            programA: ProgramA,
            systemProgram: SystemProgram.programId,
        },
        signers: []
    }
);

The AccountNotInitialized error arises when making the CPI call, since accountA has to be initialized and this is not possible since it is initialized when making the CPI call.

2 Answers 2

3

Since you are using program B to initialize a PDA derived from program A's address, try passing in the address of account_a into program B as an unchecked account.

The address you provide as account_a will be checked in the Initialize struct of program A through the CPI.

#[derive(Accounts)]
pub struct CallProgramA<'info> {
    #[account(mut)]
    pub authority: Signer<'info>,
    /// CHECK: PDA checked in program A
    #[account(mut)]
    pub account_a: UncheckedAccount<'info>,
    pub program_a: Program<'info, ProgramA>,
    pub system_program: Program<'info, System>,
}

If you wanted to validate the PDA in program B, one approach is to include a check within the instruction and return a custom Anchor Error if the provided PDA is not the expected address.

   pub fn call_program_a(_ctx: Context<CallProgramA>) -> Result<()> {
        let (pda, _bump_seed) =
            Pubkey::find_program_address(&[PDA_SEED.as_ref()], &ctx.accounts.program_a.key());

        if pda != ctx.accounts.account_a.key() {
            return err!(ErrorCode::IncorrectPDA);
        }

        ...
        Ok(())
    }

You can implement a custom Anchor Error like this:

#[error_code]
pub enum ErrorCode {
    #[msg("Incorrect PDA")]
    IncorrectPDA,
}

If the PDA is incorrect, then program B would return something like this:

Error Code: IncorrectPDA. Error Number: 6000. Error Message: Incorrect PDA.
3
  • I have set pub account_a: UncheckedAccount<'info> and I get the following errors: Transfer: `from` must not carry data failed: invalid program argument. I have also made sure that the PDA sent from the client (pdaAccountA) is the correct one. I think this solution is closer to the correct one.
    – cris.sol
    Jan 1, 2023 at 22:46
  • 1
    Is there more to your program than what's provided in the question? Not sure what would be causing that error. Here is a reference where I tried to initializing a pda via a cpi using Anchor 0.25.0, github.com/ZYJLiu/cpi-init
    – john
    Jan 2, 2023 at 7:50
  • This example has been very useful. The last error was caused by the use of .with_signer in the cpi call from ProgramB (this is not shown in the question because I tried to simplify it). I have copied your code and now everything works. Thanks :)
    – cris.sol
    Jan 3, 2023 at 3:16
0

ProgramB expects account_a to be initialized.

In ProgramB, This code checks if account_a is initialized and checks the account discriminator to make sure that's the expected structure.

    #[account(mut)]
    pub account_a: Account<'info, AccountA>,

Change it to

    #[account(
        seeds = [PDA_SEED.as_ref()],
        bump,
    )]
    pub account_a: SystemAccount<'info>
5
  • With your solution I'm getting: The given account is not owned by the system program I also tried: #[account( seeds = [PDA_SEED.as_ref()], bump, )] pub account_a: Account<'info, AccountA>, getting the error: A seeds constraint was violated
    – cris.sol
    Jan 1, 2023 at 20:19
  • does changing SystemAccount<'info> to AccountInfo<'info> work?
    – vicyyn
    Jan 1, 2023 at 20:53
  • I don't think you would be able to use #[account( seeds = [PDA_SEED.as_ref()], bump, )] in program B in this example since it would derive the PDA using the address of program B instead of program A.
    – john
    Jan 1, 2023 at 21:34
  • using AccountInfo<'info> along with #[account( seeds = [PDA_SEED.as_ref()], bump, )] I get the same error, I think that @Hi_I'mJohn is right as it explains the error A seeds constraint was violated error
    – cris.sol
    Jan 1, 2023 at 22:40
  • Considering that the seeds are correct, You have to pass it as a SystemAccount or AccountInfo in programB
    – vicyyn
    Jan 2, 2023 at 14:53

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.