2

I am working on an Anchor Solana program, and I've encountered an error that I'm having trouble resolving. The error is:

"Cross-program invocation with unauthorized signer or writable account." This error occurs when I try to execute a send_sol instruction in my program. Below is the relevant part of my code for context:

Program Structure:

#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        let new_account = &mut ctx.accounts.new_account;
        new_account.data = data;
        new_account.bump = ctx.bumps.new_account;
        msg!("Changed data to: {}!", data);

        Ok(())
    }

    pub fn send_sol(ctx: Context<SendSol>) -> Result<()> {
        let sender = &mut ctx.accounts.sender;
        let reciever = &mut ctx.accounts.reciever;
        let system_program = &mut ctx.accounts.system_program;

        invoke_signed(
            &transfer(&sender.key(), &reciever.key(), 100000),
            &[
                sender.to_account_info(),
                reciever.to_account_info(),
                system_program.to_account_info(),
            ],
            &[&[b"sender", &[sender.bump]]],
        )?;

        Ok(())
    }
}

#[derive(Accounts)]
pub struct SendSol<'info> {
    #[account(mut)]
    pub sender: Account<'info, SigningAccount>,
    pub reciever: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8 + 1, seeds = [b"sender"], bump)]
    pub new_account: Account<'info, SigningAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[account]
pub struct SigningAccount {
    data: u64,
    bump: u8,
}

The initialize function works as expected, but the send_sol function, which is supposed to send SOL from a PDA (sender) to a receiver, triggers the error.

Key Points:

I am using a PDA as the sender in the send_sol function. I have ensured that the seeds and bump used for the PDA are consistent. The PDA (sender) should have sufficient lamports for the transaction. I suspect the issue might be related to how I'm handling the PDA or the accounts in the send_sol function, but I'm not sure what I'm missing.

Has anyone encountered a similar issue or can spot what might be going wrong in my approach? Any insights or suggestions would be greatly appreciated.

Thank you in advance!

3 Answers 3

3

try this instead:

        **ctx
            .accounts
            .sender
            .to_account_info()
            .try_borrow_mut_lamports()? -= AMOUNT;
        **ctx
            .accounts
            .reciever
            .to_account_info()
            .try_borrow_mut_lamports()? += AMOUNT;

If the sender is not owned by the system program you will not be able to use a cpi to the system program for the transfer. You need to instead change hte lamports directly if you want to substract from a PDA.

Here is an example explaining the difference: https://beta.solpg.io/tutorials/tiny-adventure-two

2
  • Thank you! After i replaced the invoke_signed() with directly manipulation lamports of the sender and the reciever and modifying the reviever to be a mutable account. #[derive(Accounts)] pub struct SendSol<'info> { #[account(mut)] pub reciever: AccountInfo<'info>, } The transfer worked. But im still very confused why the invoke_signed did not work. According to all the documents online, if i have the seeds for the PDA that is doing the signature. I should be able to invoke_signed.
    – Gregz
    Jan 22 at 22:27
  • 1
    You can invoke signed, but the system program expects accounts that are owned by the system program, so basically normal wallet addresses. Your PDA is owned by your program so only your program can change it.
    – Jonas H.
    Jan 22 at 22:28
3

The issue is that the sender account is owned by your program and not the System Program. On Solana, only the program that owns an account is allowed to modify the data on the account or to deduct the lamport balance from the account.

Since your program owns the sender account (which you've specified as the SigningAccount type defined in your program), you can directly modify the lamport balance of the account.

#[derive(Accounts)]
pub struct SendSol<'info> {
    #[account(mut)]
    pub sender: Account<'info, SigningAccount>, // <--- here
    pub reciever: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

If you are using Anchor 0.29, then the updated syntax would be:

ctx.accounts.sender.sub_lamports(amount)?;
ctx.accounts.reciever.add_lamports(amount)?;

You can reference this example program from the Anchor repo.

0

Hey there if you are using anchor, you can do cpis in a "anchor way". Also the account that is sending sol either the account is owned by the program(cpi with signer seeds) or needs to be the Signer/Owner.(simple cpi) ( ill show you the both options )

use anchor_lang::{prelude::*, system_program::{Transfer, transfer}};

#[program]
mod hello_anchor {
use super::*;
pub fn initialize(ctx: Context<Initialize>, data: u64) -> 
Result<()> {
    ctx.accounts.init(&ctx.bumps, data)?;
    msg!("Changed data to: {}!", data);
    Ok(())
}

pub fn send_sol(ctx: Context<SendSol>, amount: u64) -> Result<()> 
{
    ctx.accounts.send_sol(amount)
}
pub fn send_sol_pda(ctx: Context<SendSolPda>, amount: u64) -> 
Result<()> {
    ctx.accounts.send_sol_pda(amount)
}
}
//Owner Signer way
#[derive(Accounts)]
pub struct SendSol<'info> {
#[account(mut)]
pub sender: Signer<'info>,
pub receiver: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}

impl<'info> SendSol<'info> {    
pub fn send_sol(
    &mut self,
    amount:u64,
) -> Result<()> {       
    let accounts = Transfer {
        from: self.sender.to_account_info(),
        to: self.receiver.to_account_info()
    };

    let ctx = CpiContext::new(
        self.system_program.to_account_info(),
        accounts
    );

    transfer(ctx, amount)
}
}
//PDa way
#[derive(Accounts)]
pub struct SendSolPda<'info> {
#[account(
    seeds=[b"sender"],
    bump = sender.bump
)]
pub sender: Account<'info, SigningAccount>,
pub receiver: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}

impl<'info> SendSolPda<'info> {    
pub fn send_sol_pda(
    &mut self,
    amount:u64,
) -> Result<()> {       
    let accounts = Transfer {
        from: self.sender.to_account_info(),
        to: self.receiver.to_account_info()
    };
    let seeds = &[
        &b"sender"[..],
        &[self.sender.bump],
    ];

    let signer_seeds = &[&seeds[..]];

    let ctx = CpiContext::new_with_signer(
        self.system_program.to_account_info(),
        accounts,
        signer_seeds
    );

    transfer(ctx, amount)
}
}

#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
      init,
      payer = signer,          
      seeds = [b"sender"], 
      bump,
      space = SigningAccount::LEN
)]
pub new_account: Account<'info, SigningAccount>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}

impl<'info> Initialize<'info> {
pub fn init(
    &mut self, 
    bumps: &InitializeBumps,
    data: u64,
) -> Result<()> {
    
    self.new_account.set_inner(SigningAccount{ 
        data, 
        bump: bumps.new_account
});
    Ok(())

}

}

#[account]
pub struct SigningAccount {
data: u64,
bump: u8,
}

impl SigningAccount {
pub const LEN: usize = 8 + 8 + 1;

}

Notes:

  1. Changed your send_sol function;
  2. Added send_sol_pda function;
  3. Fixed and added the normal cpi way;
  4. Added Pda Cpi;
  5. Also added a set_inner when initing the type SigningAccount accounts.

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.