1

I know this question is repeated again, but I look over all the StackExchange to solve the CPI problem, but none is working for me.

I created a smart contract in which the user will toss a coin and x amount of sol will be transferred to the smart contract's well account, probably, but when I call the distribute_rewards function, I'm getting an error of "Cross-program invocation with an unauthorised signer or writable account," and I don't know what's the problem and have been stuck on this problem for two days. I'm testing it out in the Solana playground, and if I make the well as signer, then I'm getting an error of Testing 'initialise'...❌ Test 'initialise' failed:  Reason: Error processing Instruction 2.

Code:

use anchor_lang::prelude::*;
use solana_program::sysvar::clock::Clock;
use anchor_lang::system_program::{self, Transfer};


declare_id!("7Z3D7VoA53M4TS1UbgwWyT8AuTRSb9LYziNhaNNXzdGB");

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

    pub fn initialize(ctx: Context<Initialize>, initial_toss_price: u64) -> Result<()> {
        let well = &mut ctx.accounts.well;
        well.owner = *ctx.accounts.user.key;
        well.total_sol_collected = 0;
        well.toss_price = initial_toss_price;
        well.last_distribution_time = Clock::get()?.unix_timestamp;
        Ok(())
    } 

    pub fn toss_coin(ctx: Context<TossCoin>, amount: u64) -> Result<()> {

        // Ensure the amount sent is sufficient
     let well_info = ctx.accounts.well.to_account_info(); // Create an immutable reference to the well account

     let well = &mut ctx.accounts.well; // Create a mutable reference

     require!(amount >= well.toss_price, WishingWellError::InsufficientFunds);

     // Transfer SOL from the user to the well account
     let cpi_accounts = anchor_lang::system_program::Transfer {
        from: ctx.accounts.user.to_account_info(),
        to: well_info, // Use the immutable reference here
     };

     let cpi_program = ctx.accounts.system_program.to_account_info();
     let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
     anchor_lang::system_program::transfer(cpi_context, amount)?;

      // Now you can safely use the mutable reference to well
     well.total_sol_collected += amount;

     //@Dev Storing the time and user adrress in participant vector
      let participant = Participant {
            key: ctx.accounts.user.key(),
            timestamp: Clock::get()?.unix_timestamp,
        };
        well.participants.push(participant);

     //  well.participants.push(ctx.accounts.user.key());

        Ok(())
    }

    pub fn distribute_rewards(ctx: Context<DistributeRewards>) -> Result<()> {
        let mut fees;
        let first_prize_u64:u64;
         let second_prize_u64:u64;
        let third_prize_u64:u64;
        


        {
        let well = &mut ctx.accounts.well;
        
        let current_time = Clock::get()?.unix_timestamp;
        require!(
            current_time >= well.last_distribution_time + (3 * 60),
            WishingWellError::DistributionTimeError
        );

        require!(well.participants.len() >= 3, WishingWellError::NotEnoughParticipants);

           // Calculate fees first
        fees = (well.total_sol_collected * 35) / 1000; // 3.5% fees
        well.total_sol_collected -= fees;


        // Use fixed-point arithmetic for more precise calculations
        let total_sol_scaled = well.total_sol_collected * 10000000;
        let first_prize = total_sol_scaled * 50 / 100;
        let second_prize = total_sol_scaled * 30 / 100;
        let third_prize = total_sol_scaled * 20 / 100;

        // Convert back to u64
         first_prize_u64 = first_prize / 10000000;
         second_prize_u64 = second_prize / 10000000;
         third_prize_u64 = third_prize / 10000000;

        // Ensure total rewards equal total collected minus fees
        let total_rewards = first_prize_u64 + second_prize_u64 + third_prize_u64;
        require!(
            total_rewards <= well.total_sol_collected,
            WishingWellError::RewardCalculationError
        );


        // Calculate remaining sol after fees and rewards
        let remaining_sol = well.total_sol_collected - total_rewards;
        let fees = (remaining_sol * 35) / 1000; // 3.5% fees

        // well.total_sol_collected = remaining_sol - fees;
        well.participants.clear();
        well.last_distribution_time = current_time;
        }
        let well_account_info = ctx.accounts.well.to_account_info();


        {
            // Transfer fees to the owner's account using CPI
            let cpi_accounts_fee = Transfer {
                from: well_account_info.clone(),
                to: ctx.accounts.owner.to_account_info(),
            };
            let cpi_context_fee = CpiContext::new(ctx.accounts.system_program.to_account_info(), cpi_accounts_fee);
            system_program::transfer(cpi_context_fee, fees)?;
        }
        let cpi_program = ctx.accounts.system_program.to_account_info();

        // {
        //     // Transfer rewards to each winner using CPI

        //  // First winner transfer
        //     let cpi_accounts_first = Transfer {
        //     from: well_account_info.clone(),
        //     to: ctx.accounts.first_winner.to_account_info(),
        //     };
        //  let cpi_context_first = CpiContext::new(cpi_program.clone(), cpi_accounts_first);
        //      system_program::transfer(cpi_context_first, first_prize_u64)?;

        //     // Log first winner
        //  ctx.accounts.well.winners.push(Winner {
        //     key: ctx.accounts.first_winner.key(),
        //   amount: first_prize_u64,
        //  });

        // }
        // {
        //    // Second winner transfer
        //  let cpi_accounts_second = Transfer {
        //    from: well_account_info.clone(),
        // to: ctx.accounts.second_winner.to_account_info(),
        // };
        //  let cpi_context_second = CpiContext::new(cpi_program.clone(), cpi_accounts_second);
        //  system_program::transfer(cpi_context_second, second_prize_u64)?;

        //  // Log second winner
        //      ctx.accounts.well.winners.push(Winner {
        //       key: ctx.accounts.second_winner.key(),
        //         amount: second_prize_u64,
        //  });

        // }
        // {
        //     // Third winner transfer
        //  let cpi_accounts_third = Transfer {
        //  from: well_account_info.clone(),
        //   to: ctx.accounts.third_winner.to_account_info(),
        //  };
        //  let cpi_context_third = CpiContext::new(cpi_program, cpi_accounts_third);
        //  system_program::transfer(cpi_context_third, third_prize_u64)?;

        //  // Log third winner
        //  ctx.accounts.well.winners.push(Winner {
        //      key: ctx.accounts.third_winner.key(),
        //     amount: third_prize_u64,
        //   });
        // }


        Ok(())
    }
    

    pub fn change_toss_price(ctx: Context<ChangeTossPrice>, new_price: u64) -> Result<()> {
        let well = &mut ctx.accounts.well;
        well.toss_price = new_price;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user, space = 8 + 32 + 8 + 8 + 8 + 32 * 100)]
    pub well: Account<'info, WishingWell>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct TossCoin<'info> {
    #[account(mut)]
    pub well: Account<'info, WishingWell>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct DistributeRewards<'info> {
    #[account(mut)]
    pub well: Account<'info, WishingWell>,
    #[account(mut)]
    pub first_winner: AccountInfo<'info>,
    #[account(mut)]
    pub second_winner: AccountInfo<'info>,
    #[account(mut)]
    pub third_winner: AccountInfo<'info>,
    #[account(mut)]
    pub owner: Signer<'info>, // Add the owner field here
     /// CHECK: Safe because this is the system program
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct ChangeTossPrice<'info> {
    #[account(mut, has_one = owner)]
    pub well: Account<'info, WishingWell>,
    pub owner: Signer<'info>,
}

#[account]
pub struct WishingWell {
    pub owner: Pubkey,
    pub total_sol_collected: u64,
    pub toss_price: u64,
    pub last_distribution_time: i64,
    // pub participants: Vec<Pubkey>, 
    pub participants: Vec<Participant>,
    pub winners: Vec<Winner>, // New vector for storing winners
}

#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct Participant {
    pub key: Pubkey,
    pub timestamp: i64,
}

#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub struct Winner {
    pub key: Pubkey,
    pub amount: u64,
}


#[error_code]
pub enum WishingWellError {
    #[msg("Insufficient funds sent.")]
    InsufficientFunds,
    #[msg("Distribution can only happen every 3 days.")]
    DistributionTimeError,
    #[msg("Not enough participants for a draw.")]
    NotEnoughParticipants,
    #[msg("Reward calculation error.")]
    RewardCalculationError,
} 

2
  • 1
    could you share the full error log?
    – Jimii
    Commented Nov 4 at 9:19
  • @Jimii I was using Solana playground and i can only see the error of "Cross-program invocation with unauthorized signer or writable account" when i call the Distribute rewards function.
    – Nebula
    Commented Nov 6 at 9:40

1 Answer 1

2

I guess the problem is that your well account is not a signer. If you want to take sol from that account it needs to be a signer of the transaction.

#[derive(Accounts)]
pub struct DistributeRewards<'info> {
    #[account(mut)]
    pub well: Signer<'info, WishingWell>,
    #[account(mut)]
    pub first_winner: AccountInfo<'info>,
    #[account(mut)]
    pub second_winner: AccountInfo<'info>,
    #[account(mut)]
    pub third_winner: AccountInfo<'info>,
    #[account(mut)]
    pub owner: Signer<'info>, // Add the owner field here
     /// CHECK: Safe because this is the system program
    pub system_program: Program<'info, System>,
}

Another approach you can take is make the Well account a PDA:

Here is a tutorial showing how you can use a PDA as an account that saves and pays out SOL:

https://beta.solpg.io/tutorials/tiny-adventure-two

2
  • For well account, If I make it signer and chose owner and well then it gives error of signer verification failed and I make the owner and signer again in Solana Playground then again error as at a time only once can sign the transaction.
    – Nebula
    Commented Nov 6 at 9:42
  • You can sign with both accounts. The signer field is an array. For further details i would need to see the client code. Maybe you can paste it in your question
    – Jonas H.
    Commented Nov 6 at 22:18

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.