Calling solana (anchor) program . No Errors, But lamports not transferring between accounts as requested

1.1k Views Asked by At

My code runs fine when using bump + seeds. All tests are passing. But Please see below the code of a Crowdfunding campaign, created using Anchor. The problem is that I have to use 'invoke' inside my donate function in order to transfer funds from the donator(provider.wallet.publicKey) to the donator's PDA first and then from the donator PDA to another PDA using my executing program so that the donation can be complete.

I mean isn't there a better way to do this using Anchor? Doesn't Anchor support transferring of lamports at the time of making an rpc call from authority(signer) to its PDA, I mean at the start, when using the init attribute inside #[derive(Accounts)]?

 #[derive(Accounts)]
    #[instruction(donator_program_account_bump : u8)]
    pub struct InitializeDonation<'info> {
        #[account(init, 
            seeds = [b"shutUpAndDance!".as_ref(), authority.key().as_ref()], 
            bump = donator_program_account_bump, 
            payer = authority, 
            space = 50
            amount = ? )]
            pub donator_program_account : Account<'info, Donation>,

            #[account(mut)]
            pub authority : Signer<'info>,
            pub system_program : Program<'info, System>,
            
    }

Something like 'amount' cannot be passed somewhere in instruction? Or some other way?

I have read through the anchor docs, and nowhere I can find such info. Now the Funny part: The lamports are not getting transferred as requested, which means there is a logic error, if not validation error. Below is the message from the Terminal:

Got ProgramDerivedWritingAccountAddress: bump: 254, pubkey: 4xgaJXeBXVxLwY9uvELmS3yhBgLVGBbXs3wJmjYSihXB
Got ProgramDerivedDonatorProgramAccountAddress: bump: 255, pubkey: 5JqnV2bhUE8MPWs8jJBavc57G86ruZRzzhfKGKNbgt3V
Balance of CampaignWritingAccount before Donation :  63530880
Balance of Donator before Donation :  499999975882047550
Balance of DonatorProgramAccount before Donation :  1238880
 Your Donation transaction signature is :  5WtEEXAUjXMeULwbQ1zKy4NS89wtoDXDafK9NGpYhpG4ysPQeFaiBghD9qwSyzkNtMTpfV1TbZtX6qbNGW3BgFBQ
Balance of CampaignWritingAccount after Donation :  63530880
Balance of Donator post Donation :  499999975882042560
Balance of DonatorProgramAccount post Donation :  1238880
    ✔ Can Make a Donation (585ms)
Got ProgramDerivedWritingAccountAddress: bump: 254, pubkey: 4xgaJXeBXVxLwY9uvELmS3yhBgLVGBbXs3wJmjYSihXB
Balance of Campaign before Withdrawal:  1164769760
Your Withdrawal transaction signature TZBhJ8Z51DyENL343gY5EizV6rjbARdkjKavdKjHjDP1kB8Yio8vc44ZFFT99qvHngr24neSmFqZugUTTuDsSud
Balance of Campaign after Withdrawal:  1164769760
    ✔ Can Make a Withdrawal (1063ms)


  5 passing (4s)

Check out the full code below :

lib.rs


use anchor_lang::prelude::*;
use anchor_lang::solana_program::program::{invoke /* , invoke_signed */ };
use anchor_lang::solana_program::system_instruction;
use anchor_lang::solana_program::program_error::ProgramError;
use anchor_lang::solana_program::pubkey::Pubkey;

declare_id!("Br3pwYVUCP8iafhtoqRSFYjZ4QsreqaZffVT6GtaoiUR");


#[program]
pub mod solanacrowdfundingproject {
    use super::*;
    

    // `seeds` and `bump` tell us that our `writing_account` is a PDA that can be derived from their respective values

    pub fn initialize_campaign(ctx: Context<InitializeCampaign>, writing_account_bump: u8) -> ProgramResult {
        let writing_account = &mut ctx.accounts.writing_account;
        let authority = &mut ctx.accounts.authority;
         
        writing_account.bump = writing_account_bump;
        writing_account.count = 0;
        writing_account.authority = *authority.key; 
        writing_account.campaign_details = Vec::new();
        writing_account.withdraw_request = Vec::new();
        writing_account.donation_received = Vec::new();

        
        Ok(())
    }

    pub fn initialize_donation(ctx: Context<InitializeDonation>, donator_program_account_bump : u8) -> ProgramResult {
        let donator_program_account = &mut ctx.accounts.donator_program_account;
        let authority = &mut ctx.accounts.authority;

        donator_program_account.bump = donator_program_account_bump;
        donator_program_account.authority = *authority.key; 

        Ok(())

    }

    pub fn create_campaign
    (
        ctx : Context<CreateCampaign>, 
        name : String, 
        description : String, 
        image_link: String,
        writing_account_bump : u8
        
        
    ) 
        -> ProgramResult {
            
        let writing_account = &mut ctx.accounts.writing_account;
        let authority = &mut ctx.accounts.authority;
        
             
        let (pda, bump ) = Pubkey::find_program_address(
        &[b"upOnlyCrypto!", &*authority.key().as_ref()], &self::ID
        );

        if pda != writing_account.key()  {                         // Confirm if passed in PDA address is the same
            return Err(ProgramError::Custom(1))
        };

        if bump != writing_account_bump {
            return Err(ProgramError::Custom(2))
        };

        if name.len()  > 30 || description.len() > 50 {
            return Err(ErrorCode::NameOrDescriptionTooLong.into())
        }


        let campaign_data = CampaignDetails {
            admin : *authority.key,
            name : name.to_string(),
            description : description.to_string(),
            image_link : image_link.to_string(),
               
        };

        writing_account.count += 1;
        writing_account.campaign_details.push(campaign_data);

        
        Ok(())
    }



    
    pub fn withdraw
    (
        ctx : Context<Withdraw>, 
        amount : u64,
        writing_account_bump : u8
        

    ) -> ProgramResult {
       
        let writing_account = &mut ctx.accounts.writing_account;
        let authority = &mut ctx.accounts.authority;

        let (pda, bump ) = Pubkey::find_program_address(
            &[b"upOnlyCrypto!", &*authority.key().as_ref()], &self::ID
            );
    
            if pda != writing_account.key()  {                         // Confirm if passed in PDA address is the same
                return Err(ProgramError::Custom(1))
            };
    
            if bump != writing_account_bump {
                return Err(ProgramError::Custom(2))
            };
      
         **writing_account.to_account_info().try_borrow_mut_lamports()? -= amount;
         **authority.to_account_info().try_borrow_mut_lamports()? += amount;

        let withdraw_data = WithdrawRequest {
            amount_withdrawn : amount,
            admin : *authority.to_account_info().key,
        };

        writing_account.withdraw_request.push(withdraw_data);
         
        Ok(())

    }

    


    pub fn donate
    (
        ctx : Context<Donate>, 
        amount : u64, 
        donator_program_account_bump : u8


    ) -> ProgramResult {
 
    let writing_account = &mut ctx.accounts.writing_account;
    let donator_program_account = &mut ctx.accounts.donator_program_account;
    let authority = &mut ctx.accounts.authority;

    let (pda, bump ) = Pubkey::find_program_address(
    &[b"shutUpAndDance!", &*authority.key().as_ref()], &self::ID
    );

    if pda != donator_program_account.key()  {                         // Confirm if passed in PDA address is the same
        return Err(ProgramError::Custom(1))
    };

    if bump != donator_program_account_bump {
        return Err(ProgramError::Custom(2))
    };

   let transfer_ix = system_instruction::transfer(
        &authority.to_account_info().key(),
        &donator_program_account.to_account_info().key(),
        amount,
   );

   invoke(
       &transfer_ix, 
       &[
           authority.to_account_info(),
           donator_program_account.to_account_info(),
        ],
    )?;

     
    **writing_account.to_account_info().try_borrow_mut_lamports()? += **donator_program_account.to_account_info().lamports.borrow();
    **donator_program_account.to_account_info().try_borrow_mut_lamports()? = 0;
    

    let donation = DonationMade {
        amount_donated : amount,
    };

    writing_account.donation_received.push(donation);
   
    Ok(())

    }


    #[derive(Accounts)]
    #[instruction(writing_account_bump : u8)]
    pub struct InitializeCampaign<'info> {
        #[account(init, 
            seeds = [b"upOnlyCrypto!".as_ref(), authority.key().as_ref()], 
            bump = writing_account_bump, 
            payer = authority, 
            space = 9000)]
            pub writing_account : Account<'info, CampaignState>,

            #[account(mut)]
            pub authority : Signer<'info>,
            pub system_program : Program<'info, System>,
            
    }
    

    #[derive(Accounts)]
    #[instruction(donator_program_account_bump : u8)]
    pub struct InitializeDonation<'info> {
        #[account(init, 
            seeds = [b"shutUpAndDance!".as_ref(), authority.key().as_ref()], 
            bump = donator_program_account_bump, 
            payer = authority, 
            space = 50)]
            pub donator_program_account : Account<'info, Donation>,

            #[account(mut)]
            pub authority : Signer<'info>,
            pub system_program : Program<'info, System>,
            
    }

    
    #[derive(Accounts)] 
    pub struct CreateCampaign<'info> {
       #[account(mut, has_one = authority)]
        pub writing_account  : Account<'info, CampaignState>,
        #[account(mut)]
        pub authority : Signer<'info>,      
             
    }

   
    #[derive(Accounts)] 
    pub struct Withdraw<'info> {
        #[account(mut, has_one = authority)]
        pub writing_account : Account<'info, CampaignState>,
        #[account(mut)]
        pub authority : Signer<'info>,  
          
    }

   
    #[derive(Accounts)]
    pub struct Donate<'info> {
        #[account(mut, has_one = authority)]
        pub donator_program_account : Account<'info, Donation>,
        #[account(mut)]
        pub writing_account : Account<'info, CampaignState>,
        #[account(mut)]
        pub authority : Signer<'info>,
        pub system_program : Program<'info, System>,
    }


    #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
    pub struct CampaignDetails  {
        pub admin: Pubkey,
        pub name: String,
        pub description: String,
        pub image_link: String,       
             
    }

    #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
    pub struct WithdrawRequest {
        pub amount_withdrawn : u64,
        pub admin : Pubkey,        
    }

    #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
    pub struct DonationMade {
        pub amount_donated: u64,
           
    }


    #[account]
    pub struct CampaignState {    
       pub campaign_details : Vec<CampaignDetails>,
       pub bump : u8,
       pub count : u8,
       pub authority: Pubkey,  
       pub withdraw_request : Vec<WithdrawRequest>, 
       pub donation_received : Vec<DonationMade>,        
    }

    #[account]
    pub struct Donation {
        
        pub bump : u8,
        pub authority : Pubkey,
    }
   
    #[error]
    pub enum ErrorCode {    
       
        #[msg("Name cannot be more than 30 charecters and Description cannot be more than 50 charecters")]
            NameOrDescriptionTooLong,
    }



}


Also, check tests/solanacrowdfundingproject.js below :

const assert = require('assert');
const anchor = require('@project-serum/anchor');
const { PublicKey, Connection } = require("@solana/web3.js");
const cluster = "http://localhost:8899";
//const cluster = "https://api.devnet.solana.com";
const connection = new Connection(cluster, "confirmed");
const { SystemProgram /*, Keypair, SYSVAR_RENT_PUBKEY*/ } = anchor.web3;
const { Buffer } = require('buffer');



// Specify provider environment. 
const provider = anchor.Provider.env();
//Set provider.
anchor.setProvider(provider);
//Specify the workspace 
const program = anchor.workspace.Solanacrowdfundingproject;
//const programID = await connection.programID(program);
const programID = new PublicKey("Br3pwYVUCP8iafhtoqRSFYjZ4QsreqaZffVT6GtaoiUR");




describe('Solanacrowdfundingproject', () => {

  console.log(" Starting tests...");
  try {
    it('gets the Campaign Writing Account initialized', async () => {
      const { writingAccount, bump } = await getProgramDerivedCampaignWritingAccountAddress();
      

      let tx = await program.rpc.initializeCampaign(new anchor.BN(bump),  {
        accounts: {
          writingAccount: writingAccount,
          authority: provider.wallet.publicKey,
          systemProgram: SystemProgram.programId,

        },

      });
      //Console.log the Transaction signature of the Initialization procedure. 
      console.log("Campaign Writing Account Initialization signature : ", tx);

      //Console logs
      const account = await program.account.campaignState.fetch(writingAccount);
      console.log(" Created A New Campaign Writing Account : ", account);
      console.log(" Writing Account's Campaign count is :", account.count);

      //Asserts
      assert.equal(account.authority.toBase58(), provider.wallet.publicKey.toBase58());
      //assert.ok(account.count.eq(new anchor.BN(0)));
      //console.log(' Account Authority pubKey : ', account.authority.toBase58());
      
    });
  } catch (error) {
    console.log(error);
  }


  try {
    it('gets the Donator Account initialized', async () => {
      const { donatorProgramAccount, bump } = await getProgramDerivedDonatorProgramAccountAddress();
      
      let tx = await program.rpc.initializeDonation(new anchor.BN(bump),  {
        accounts: {
          authority: provider.wallet.publicKey,
          donatorProgramAccount : donatorProgramAccount,
          systemProgram: SystemProgram.programId,

        },

      });
      //Console.log the Transaction signature of the Initialization procedure. 
      console.log("Donation Account Initialization signature : ", tx);

      //Console.log the accounts created:
      const account = await program.account.donation.fetch(donatorProgramAccount);
      console.log(" Created a New Donator Program Account : ", account);
      
      //Asserts
      assert.equal(account.authority.toBase58(), provider.wallet.publicKey.toBase58());
      
    });
  } catch (error) {
    console.log(error);
  }


  try {
    it('Creates a campaign', async () => {

      const { writingAccount, bump } = await getProgramDerivedCampaignWritingAccountAddress();

      //Lets invocate the createCampaign function using provider.wallet.publicKey
      let tx = await program.rpc.createCampaign("Suveett", "Blockchain Speaker", "Enter a fancy giflink for Campaign", new anchor.BN(bump),
        {
          accounts: {
            writingAccount: writingAccount,
            authority: provider.wallet.publicKey,

          },
          
        });
      
      //Console.log the Transaction signature of the Initialization procedure. 
      console.log("Your CreateCampaign transaction signature", tx);
      //Console Logs
      let account = await program.account.campaignState.fetch(writingAccount);
      console.log("Writing Account after Campaign Creation :", account);
      //console.log("This Writing account's address is : ", account.key().toBase58());
      //console.log("This writing Account's owner is the Executing Program : ", account.owner().toBase58());
      console.log("This Writing account's admin is : ", account.campaignDetails[0].admin.toBase58());
      console.log("This Writing account's Campaign Details contains `name` :", account.campaignDetails[0].name);
      console.log("This Writing account's Campaign Details contains `description` :", account.campaignDetails[0].description);
      //Asserts
      //assert.ok(account.count.eq(new anchor.BN(1)));

    });

  } catch (error) {
    console.log(error);
  }



 
  try {
    it('Can Make a Donation', async () => {

      const { writingAccount } = await getProgramDerivedCampaignWritingAccountAddress();
      const { donatorProgramAccount, bump } = await getProgramDerivedDonatorProgramAccountAddress();


      let balanceOfCampaignAccountPreDonation = await connection.getBalance(writingAccount);
      console.log("Balance of CampaignWritingAccount before Donation : ", balanceOfCampaignAccountPreDonation);

      let balanceOfDonatorPreDonation = await connection.getBalance(provider.wallet.publicKey);
      console.log("Balance of Donator before Donation : ", balanceOfDonatorPreDonation);

      let balanceOfDonatorProgramAccountPreDonation = await connection.getBalance(donatorProgramAccount);
      console.log("Balance of DonatorProgramAccount before Donation : ", balanceOfDonatorProgramAccountPreDonation);
     
        let donateTx = await program.rpc.donate(new anchor.BN(100000000), new anchor.BN(bump),
        {
          accounts: {
            writingAccount: writingAccount,
            donatorProgramAccount: donatorProgramAccount,
            authority: provider.wallet.publicKey,
            systemProgram: SystemProgram.programId,   

          },
         
        });

      //Console Logs
      //Console.log the Transaction signature of the Donation procedure. 
      
      console.log(" Your Donation transaction signature is : ", donateTx);

      let balanceOfCampaignAccountPostDonation = await connection.getBalance(writingAccount);
      console.log("Balance of CampaignWritingAccount after Donation : ", balanceOfCampaignAccountPostDonation);

      let balanceOfDonatorPostDonation = await connection.getBalance(provider.wallet.publicKey);
      console.log("Balance of Donator post Donation : ", balanceOfDonatorPostDonation);

      let balanceOfDonatorProgramAccountPostDonation = await connection.getBalance(donatorProgramAccount);
      console.log("Balance of DonatorProgramAccount post Donation : ", balanceOfDonatorProgramAccountPostDonation);


    });
  } catch (error) {
    console.log(error);
  }



  try {
    it('Can Make a Withdrawal', async () => {
      const { writingAccount , bump } = await getProgramDerivedCampaignWritingAccountAddress();
      const signature = await connection.requestAirdrop(writingAccount, 1000000000);
      await connection.confirmTransaction(signature);
      let balanceOfCampaignAccountPreWithdrawal = await connection.getBalance(writingAccount);
      console.log("Balance of Campaign before Withdrawal: ", balanceOfCampaignAccountPreWithdrawal);

      let withdrawTx = await program.rpc.withdraw(new anchor.BN(500000000), new anchor.BN(bump),
        {
          accounts: {
            writingAccount: writingAccount,
            authority: provider.wallet.publicKey,

          }

        });
      //Console Logs
      //Console.log the Transaction signature of the Withdrawal procedure.
      console.log("Your Withdrawal transaction signature", withdrawTx);
      let balanceOfCampaignAccountPostWithdrawal = await connection.getBalance(writingAccount);
      console.log("Balance of Campaign after Withdrawal: ", balanceOfCampaignAccountPostWithdrawal);


    });
  } catch (error) {
    console.log(error);
  }


});



async function getProgramDerivedCampaignWritingAccountAddress() {
  const [writingAccount, bump] = await PublicKey.findProgramAddress(
    [Buffer.from('upOnlyCrypto!'), provider.wallet.publicKey.toBuffer()],
    programID
  );

  console.log(`Got ProgramDerivedWritingAccountAddress: bump: ${bump}, pubkey: ${writingAccount.toBase58()}`);
  return { writingAccount, bump };

};


async function getProgramDerivedDonatorProgramAccountAddress() {
  const [donatorProgramAccount, bump] = await PublicKey.findProgramAddress(
    [Buffer.from(anchor.utils.bytes.utf8.encode('shutUpAndDance!')) , provider.wallet.publicKey.toBuffer() ],
    programID
  );

  console.log(`Got ProgramDerivedDonatorProgramAccountAddress: bump: ${bump}, pubkey: ${donatorProgramAccount.toBase58()}`);
  return { donatorProgramAccount, bump };

};

Kindly please also suggest a better way to write this code (in case any)??

Thanks and Regards

1

There are 1 best solutions below

0
On

You should probably wait until the transactions you sent are confirmed. You could do something like

let txSignature = await program.rpc.initializeDonation(new anchor.BN(bump),  {
        accounts: {
          authority: provider.wallet.publicKey,
          donatorProgramAccount : donatorProgramAccount,
          systemProgram: SystemProgram.programId,

        },

      });
connection.confirm(txSignature, "confirmed");

I believe it was "confirm", might be a very similar keyword.

Also make sure that all TokenAccounts (and any accounts that receive and send SOL) are mutable.