Hi everyone I’m developing a Solana smart contract/Program written in Rust for a lottery application. I’m encountering a borrow checker issue specifically in the claim_prize function. The error indicates that ctx.accounts.lottery is being borrowed mutably and immutably at the same time.
Here’s the relevant code snippet:
pub fn claim_prize(ctx: Context<ClaimPrize>, _lottery_id: u32, _ticket_id: u32) -> Result<()> {
// Immutable borrow for checks
let ticket = &ctx.accounts.ticket;
let ticket_id = ticket.id;
let lottery = &ctx.accounts.lottery;
// Check if the prize has already been claimed
if lottery.claimed {
return Err(LotteryError::AlreadyClaimed.into());
}
// Check if the provided ticket is a valid winner
match lottery.winner_id {
Some(winner_id) => {
if winner_id != ticket_id {
return Err(LotteryError::InvalidWinner.into());
}
}
None => return Err(LotteryError::WinnerNotChosen.into()),
};
// Calculate the prize amount
let prize = lottery
.ticket_price
.checked_mul(lottery.last_ticket_id.into())
.ok_or(LotteryError::InvalidWinner)?;
// Now that we've completed the immutable borrow, we can mutably borrow the lottery account
let mut lottery = &mut ctx.accounts.lottery;
// Update the lottery state
lottery.claimed = true;
// Perform the token transfer
let cpi_accounts = Transfer {
from: ctx.accounts.lottery_token_account.to_account_info(),
to: ctx.accounts.winner_token_account.to_account_info(),
authority: ctx.accounts.lottery.to_account_info(), // No mutable borrow here
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, prize)?;
msg!(
"{} claimed {} lamports from lottery id {} with ticket id {}",
ctx.accounts.authority.key(),
prize,
lottery.id,
ticket_id
);
Ok(())
}
this is the error :
Building...
error[E0502]: cannot borrow `ctx.accounts.lottery` as immutable because it is also borrowed as mutable
--> src/lib.rs:151:20
|
142 | let mut lottery = &mut ctx.accounts.lottery;
| ------------------------- mutable borrow occurs here
...
error[E0502]: cannot borrow `ctx.accounts.lottery` as immutable because it is also borrowed as mutable
--> src/lib.rs:153:20
|
144 | let mut lottery = &mut ctx.accounts.lottery;
| ------------------------- mutable borrow occurs here
...
153 | authority: ctx.accounts.lottery.to_account_info(), // No mutable borrow here
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here
...
163 | lottery.id,
| ------- mutable borrow later used here
warning: `Lottery` (lib) generated 2 warnings
I’ve been trying to fix it, but I haven’t been able to, even with ChatGPT. I feel like I’ve been going in circles
I’m trying to “Build” and “Deploy” the Program in the Solana Blockchain, but it’s not “Building” because of the Mutable and InMutable error.
this is the complete code :
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer};
use solana_program::hash::hash;
use std::convert::TryFrom;
declare_id!("---EXAMPLE-ID_REPLACE_BY_PROGRAM-ID---");
mod constants {
pub const MASTER_SEED: &str = "master";
pub const LOTTERY_SEED: &str = "lottery";
pub const TICKET_SEED: &str = "ticket";
}
mod error {
use anchor_lang::prelude::*;
#[error_code]
pub enum LotteryError {
#[msg("The winner has already been chosen.")]
WinnerAlreadyExists,
#[msg("No tickets have been purchased.")]
NoTickets,
#[msg("The prize has already been claimed.")]
AlreadyClaimed,
#[msg("The winner ID is invalid.")]
InvalidWinner,
#[msg("The winner has not been chosen yet.")]
WinnerNotChosen,
}
}
use constants::*;
use error::LotteryError;
#[program]
pub mod lottery_program {
use super::*;
pub fn init_master(ctx: Context<InitMaster>) -> Result<()> {
Ok(())
}
pub fn create_lottery(ctx: Context<CreateLottery>, ticket_price: u64) -> Result<()> {
let master = &mut ctx.accounts.master;
let lottery = &mut ctx.accounts.lottery;
master.last_id += 1;
lottery.id = master.last_id;
lottery.authority = ctx.accounts.authority.key();
lottery.ticket_price = ticket_price;
msg!("Created lottery: {}", lottery.id);
msg!("Authority: {}", lottery.authority);
msg!("Ticket price: {}", lottery.ticket_price);
Ok(())
}
pub fn buy_ticket(ctx: Context<BuyTicket>, lottery_id: u32) -> Result<()> {
let lottery = &mut ctx.accounts.lottery;
let ticket = &mut ctx.accounts.ticket;
let buyer = &ctx.accounts.buyer;
if lottery.winner_id.is_some() {
return Err(LotteryError::WinnerAlreadyExists.into());
}
let cpi_accounts = Transfer {
from: ctx.accounts.buyer_token_account.to_account_info(),
to: ctx.accounts.lottery_token_account.to_account_info(),
authority: ctx.accounts.buyer.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
token::transfer(cpi_ctx, lottery.ticket_price)?;
lottery.last_ticket_id += 1;
ticket.id = lottery.last_ticket_id;
ticket.lottery_id = lottery_id;
ticket.authority = buyer.key();
msg!("Ticket id: {}", ticket.id);
msg!("Ticket authority: {}", ticket.authority);
Ok(())
}
pub fn pick_winner(ctx: Context<PickWinner>, _lottery_id: u32) -> Result<()> {
let lottery = &mut ctx.accounts.lottery;
if lottery.winner_id.is_some() {
return Err(LotteryError::WinnerAlreadyExists.into());
}
if lottery.last_ticket_id == 0 {
return Err(LotteryError::NoTickets.into());
}
let clock = Clock::get()?;
let pseudo_random_number = ((u64::from_le_bytes(
<[u8; 8]>::try_from(&hash(&clock.unix_timestamp.to_be_bytes()).to_bytes()[..8])
.unwrap(),
) * clock.slot)
% u32::MAX as u64) as u32;
let winner_id = (pseudo_random_number % lottery.last_ticket_id) + 1;
lottery.winner_id = Some(winner_id);
msg!("Winner id: {}", winner_id);
Ok(())
}
pub fn claim_prize(ctx: Context<ClaimPrize>, _lottery_id: u32, _ticket_id: u32) -> Result<()> {
let lottery = &mut ctx.accounts.lottery; // Mutable borrow
let ticket = &ctx.accounts.ticket; // Immutable borrow
if lottery.claimed {
return Err(LotteryError::AlreadyClaimed.into());
}
let ticket_id = ticket.id;
match lottery.winner_id {
Some(winner_id) => {
if winner_id != ticket_id {
return Err(LotteryError::InvalidWinner.into());
}
}
None => return Err(LotteryError::WinnerNotChosen.into()),
}
let prize = lottery
.ticket_price
.checked_mul(lottery.last_ticket_id.into())
.ok_or(LotteryError::InvalidWinner)?;
// Update lottery before creating CPI context
lottery.claimed = true;
// Create CPI context
let cpi_accounts = Transfer {
from: ctx.accounts.lottery_token_account.to_account_info(),
to: ctx.accounts.winner_token_account.to_account_info(),
authority: ctx.accounts.lottery.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
// Call the CPI function
token::transfer(cpi_ctx, prize)?;
msg!(
"{} claimed {} lamports from lottery id {} with ticket id {}",
ctx.accounts.authority.key(),
prize,
lottery.id,
ticket_id
);
Ok(())
}
}
#[derive(Accounts)]
pub struct InitMaster<'info> {
#[account(
init,
payer = payer,
space = 8 + 4,
seeds = [MASTER_SEED.as_bytes()],
bump,
)]
pub master: Account<'info, Master>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct CreateLottery<'info> {
#[account(
init,
payer = authority,
space = 8 + 4 + 32 + 8 + 4 + 1 + 4 + 1,
seeds = [LOTTERY_SEED.as_bytes(), &(master.last_id + 1).to_le_bytes()],
bump,
)]
pub lottery: Account<'info, Lottery>,
#[account(
mut,
seeds = [MASTER_SEED.as_bytes()],
bump,
)]
pub master: Account<'info, Master>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
#[instruction(lottery_id: u32)]
pub struct BuyTicket<'info> {
#[account(
mut,
seeds = [LOTTERY_SEED.as_bytes(), &lottery_id.to_le_bytes()],
bump,
)]
pub lottery: Account<'info, Lottery>,
#[account(
init,
payer = buyer,
space = 8 + 4 + 4 + 32,
seeds = [
TICKET_SEED.as_bytes(),
lottery.key().as_ref(),
&(lottery.last_ticket_id + 1).to_le_bytes()
],
bump,
)]
pub ticket: Account<'info, Ticket>,
#[account(mut)]
pub buyer: Signer<'info>,
#[account(mut, constraint = buyer_token_account.owner == buyer.key())]
pub buyer_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub lottery_token_account: Account<'info, TokenAccount>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
#[instruction(lottery_id: u32)]
pub struct PickWinner<'info> {
#[account(
mut,
seeds = [LOTTERY_SEED.as_bytes(), &lottery_id.to_le_bytes()],
bump,
has_one = authority,
)]
pub lottery: Account<'info, Lottery>,
pub authority: Signer<'info>,
}
#[derive(Accounts)]
#[instruction(lottery_id: u32, ticket_id: u32)]
pub struct ClaimPrize<'info> {
#[account(
mut,
seeds = [LOTTERY_SEED.as_bytes(), &lottery_id.to_le_bytes()],
bump,
has_one = authority,
)]
pub lottery: Account<'info, Lottery>,
#[account(
seeds = [TICKET_SEED.as_bytes(), lottery.key().as_ref(), &ticket_id.to_le_bytes()],
bump,
has_one = authority,
)]
pub ticket: Account<'info, Ticket>,
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut, constraint = lottery_token_account.owner == authority.key())]
pub lottery_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub winner_token_account: Account<'info, TokenAccount>,
pub token_program: Program<'info, Token>,
}
#[account]
pub struct Master {
pub last_id: u32,
}
#[account]
pub struct Lottery {
pub id: u32,
pub authority: Pubkey,
pub ticket_price: u64,
pub last_ticket_id: u32,
pub winner_id: Option<u32>,
pub claimed: bool,
}
#[account]
pub struct Ticket {
pub id: u32,
pub lottery_id: u32,
pub authority: Pubkey,
}
i also have a Error.rs file that look like this :
use anchor_lang::prelude::*;
#[error_code]
pub enum LotteryError {
#[msg("The winner has already been chosen.")]
WinnerAlreadyExists,
#[msg("No tickets have been purchased.")]
NoTickets,
#[msg("The prize has already been claimed.")]
AlreadyClaimed,
#[msg("The winner ID is invalid.")]
InvalidWinner,
#[msg("The winner has not been chosen yet.")]
WinnerNotChosen,
}
and a constants.rs file that look like this :
pub const MASTER_SEED: &str = "master";
pub const LOTTERY_SEED: &str = "lottery";
pub const TICKET_SEED: &str = "ticket";
pub const MEME_COIN_MINT: &str = "Example-Contract-Adress-MEMEcoin"; // replace Address