0

When I am running an anchor test, I am getting the following error, of invalid arguments when calling function transferSolFromPda. This is my anchor program

use anchor_lang::prelude::*;
use anchor_lang::solana_program::system_instruction;
use anchor_lang::solana_program::program::{invoke, invoke_signed};

declare_id!("CCsXFXn6noPhKG4Y3QPAxsFg6RroKKSd8aRajPT3a8dr");

#[program]
mod anchor_test_lamports {
    use super::*;

    pub fn transfer_sol_from_pda(ctx: Context<TransferSol>, amount: u64, bump: u8) -> Result<()> {
        
        let binding = ctx.accounts.sender.key();
        let pda_signer_seeds: &[&[_]] = &[
            b"pda_seed",
            &binding.as_ref(),
            &[bump],
        ];
        
        let ix = system_instruction::transfer(
            &ctx.accounts.pda_account.key(),
            &ctx.accounts.receiver.key(),
            amount,
        );

        invoke_signed(
            &ix,
            &[
                ctx.accounts.pda_account.to_account_info(),
                ctx.accounts.receiver.to_account_info(),
            ],
            &[pda_signer_seeds],
        )?;

        Ok(())
    }
    pub fn transfer_sol_to_pda(ctx: Context<TransferSolToPda>, amount: u64) -> Result<()> {
        let ix = system_instruction::transfer(
            &ctx.accounts.sender.key(),
            &ctx.accounts.pda_account.key(),
            amount,
        );
        invoke(
            &ix,
            &[
                ctx.accounts.sender.to_account_info(),
                ctx.accounts.pda_account.to_account_info(),
            ],
        )?;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct TransferSol<'info> {
    #[account(mut)]
    pub sender: Signer<'info>,
    /// CHECK: This is safe because we are only transferring SOL to this account. No need to deserialize or perform any operations on it.
    #[account(mut)]
    pub pda_account: AccountInfo<'info>,
    /// CHECK: This is safe because we are only transferring SOL to this account. No need to deserialize or perform any operations on it.
    #[account(mut)]
    pub receiver: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct TransferSolToPda<'info> {
    #[account(mut)]
    pub sender: Signer<'info>,
    /// CHECK: This is safe because we are only transferring SOL to this account. No need to deserialize or perform any operations on it.
    #[account(
        init_if_needed,
        payer = sender,
        seeds = [b"pda_seed".as_ref(), sender.key().as_ref()],
        bump,
        space = 50
    )]
    pub pda_account: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

This is my test

import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AnchorTestLamports } from "../target/types/anchor_test_lamports";
import {
  PublicKey, SystemProgram, LAMPORTS_PER_SOL, Keypair, Connection, Signer

} from "@solana/web3.js";

describe("anchor-test-lamports", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.AnchorProvider.env());
  const connection = new Connection("http://localhost:8899", 'recent');

  const program = anchor.workspace.AnchorTestLamports as Program<AnchorTestLamports>;
  let sender;
  let pdaAccount;
  

  it('transfers SOL to PDA', async () => {
    // Generate a new keypair for the test sender account.
    sender = await createUserWithLamports(connection, 10);
    pdaAccount = PublicKey.findProgramAddressSync(
      [Buffer.from("pda_seed"), sender.publicKey.toBuffer()],
      program.programId
    )[0];
    await program.methods
      .transferSolToPda(new anchor.BN(1 * LAMPORTS_PER_SOL))
      .accounts({ pdaAccount: pdaAccount, sender: sender.publicKey, systemProgram: SystemProgram.programId })
      .signers([sender])
      .rpc();
    // Verify the balance of the PDA after the transfer.
    const pdaBalance = await program.provider.connection.getBalance(pdaAccount);
    console.log(`PDA balance: ${pdaBalance / LAMPORTS_PER_SOL} SOL`); 
    // Assert that the PDA received the SOL (this assertion might need adjustment based on the actual balance).
  });

  it('transfer back SOL to PDA', async () => {
    const receiver  = await createUserWithLamports(connection, 10);
    const senderBalance = await program.provider.connection.getBalance(sender.publicKey);

    console.log(`PDA balance: ${senderBalance / LAMPORTS_PER_SOL} SOL`); 

    await program.methods
      .transferSolFromPda(new anchor.BN(0.1 * LAMPORTS_PER_SOL), 255)
      .accounts({ pdaAccount: pdaAccount, sender: sender.publicKey,receiver: receiver.publicKey, systemProgram: SystemProgram.programId })
      .signers([sender])
      .rpc({
        skipPreflight:true
      });
    // Verify the balance of the PDA after the transfer.
    // Assert that the PDA received the SOL (this assertion might need adjustment based on the actual balance).
  });
});


export async function createUserWithLamports(
  connection: Connection,
  lamports: number,
): Promise<Signer> {
  const account = Keypair.generate();
  const signature = await connection.requestAirdrop(
    account.publicKey,
    lamports * LAMPORTS_PER_SOL
  );
  const block = await connection.getLatestBlockhash();
  await connection.confirmTransaction({ ...block, signature });
  return account;
}

1 Answer 1

1

During tranfer_sol_to_pda, you're actually initializing the account on the PDA, which means it's being allocated 50 bytes and assigned to your program. Since it belongs to your program, it does not belong to the system program, and system_instruction::transfer only works on account that are owned by the system program.

Since your program already owns the account, you can actually do something much simpler, and simply deduct the lamports from your PDA and credit them to the destination account:

    pub fn transfer_sol_from_pda(ctx: Context<TransferSol>, amount: u64, bump: u8) -> Result<()> {
        let binding = ctx.accounts.sender.key();
        let pda_signer_seeds: &[&[_]] = &[
            b"pda_seed",
            &binding.as_ref(),
            &[bump],
        ];

        // do a check here to make sure the PDA is properly derived
        
        let pda_account_info = ctx.accounts.pda_account.to_account_info();
        **pda_account_info.lamports.borrow_mut() -= amount;
        let receiver_account_info = ctx.accounts.receiver.to_account_info();
        **receiver_account_info.lamports.borrow_mut() += amount;

        Ok(())
    }

You can read more about the account ownership model at https://solana.com/docs/core/accounts#ownership-and-assignment-to-programs

1
  • Thanks, that solves my problem.
    – Toan Bùi
    Commented Mar 11 at 9:21

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.