4

When calling a CPI that calls another instruction in the same program, you need to pass the program into the program, which is awkward. For example, removing this_program as an account here causes the cpi to fail with "unknown program", even though that AccountInfo is otherwise unused:

#[derive(Accounts)]
pub struct Whatever<'info> {
    /// CHECK: ignore
    pub whatever: AccountInfo<'info>,

    /// CHECK: Validated against the crate's ID
    #[account(address = crate::ID)]
    pub this_program: AccountInfo<'info>,
}

pub fn handler(ctx: Context<Whatever>) -> Result<()> {
    let ix = Instruction {
        program_id,
        accounts: vec![
            AccountMeta::new_readonly(ctx.accounts.whatever.key(), false),
        ],
        data: get_sighash(), // sha256 of "global":"functioname"
    };
    let account_infos = [
        ctx.accounts.whatever.to_account_info(),
    ];

    invoke(&ix, &account_infos)?;

    Ok(())
}

But it's the same program, so why the requirement to pass it...

3 Answers 3

6

I consider this as a requirements of the current version of Solana runtime. If you want to use the CPI call then you need to provide the AccountInfo of all accounts used within the call (where "the program" is an account).

I assume you can do the direct Rust call when you work within the same program binary and not using the CPI at all.

Recently there was introduced the work in progress on new the Solana RuntimeV2 that seems to be fixing these limitations: https://www.youtube.com/watch?v=SQXHgiUlU6E

2

If you find yourself calling a CPI to your program inside your program. Then you might as well make that instruction a simple rust function that gets called by your different instructions. I recommend Separating the instructions code from the implementation.

4
  • 2
    Redirecting via CPI lets you use constraints in accounts, so A can take an UncheckedAccount and perform no validation, and pass it via CPI to B which reads it like a pda and validates the seeds, or passes it to C which reads it like a TokenAccount and validates something else. You can do the same thing without constraints, but it's a neat utility.
    – Whiteseal
    Commented Feb 16, 2023 at 20:13
  • Use the constraints in the parent instruction. verify that the accounts you passed are all valid. Then do whatever you need based on what your program has.
    – vicyyn
    Commented Feb 17, 2023 at 8:07
  • 2
    If you have different constraints in B and C, but they accept the same accounts, for example B requires a tokenAccount owned by some key, C requires the same kind of token account but owned by another key, it can be cleaner to split them.
    – Whiteseal
    Commented Feb 18, 2023 at 7:03
  • @Whiteseal is spot on. There are cases where the constraints are different, or perhaps you want to use things like init macros etc that may be different depending on the inputs, even though they require the same accounts to be passed in. So CPI within the same program seems like a completely valid use case.
    – neft
    Commented Jul 26, 2023 at 22:35
0

I'm trying to call another instruction within same program. I want to allocate more space by handling the limitation of 10kb space extension per instruction.

pub fn allocate_space(ctx: Context<AllocateSpaceStruct>, computed_space: u64) -> Result<()> {
    ctx.accounts.privy_user.to_account_info().realloc(computed_space as usize, false)?;
    Ok(())
}

pub fn create_user(ctx: Context<CreateUser>, username: String) -> Result<()> {
    let owner = &ctx.accounts.owner;
    let privy_user = &mut ctx.accounts.privy_user;

    privy_user.username = username;

    let computed_space: u64 = 1024;
    let this_program = ctx.accounts.this_program.clone();

    let allocate_space_ix = Instruction {
        program_id: *this_program.key,
        accounts: vec![
            AccountMeta::new(privy_user.key(), true),
            AccountMeta::new(owner.key(), true),
        ],
        data: privy::instruction::AllocateSpace { computed_space }.try_to_vec().unwrap(),
    };

    anchor_lang::solana_program::program::invoke(
        &allocate_space_ix,
        &[
            privy_user.to_account_info().clone(),
            owner.to_account_info().clone(),
            this_program.clone()
        ],
    )?;
    Ok(())
}

#[derive(Accounts)]
pub struct CreateUser<'info> {
    #[account(mut)]
    pub owner: Signer<'info>,
    #[account(
        init,
        payer = owner,
        space = PrivyUser::USER_SPACE,
        seeds = [b"privy-user", owner.key().as_ref()],
        bump
    )]
    pub privy_user: Account<'info, PrivyUser>,
    #[account(mut)]
    pub privy_config: Account<'info, PrivyConfig>,
    
    /// CHECK: Validated against the crate's ID
    #[account(address = crate::ID)]
    pub this_program: AccountInfo<'info>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
}


#[derive(Accounts)]
pub struct AllocateSpaceStruct<'info> {
    #[account(mut)]
    pub owner: Signer<'info>,
    #[account(mut)]
    pub privy_user: Account<'info, PrivyUser>,
}

Getting this below error

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

Any idea what I'm missing here?

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.