1

sorry if this is a more rusty question.

As the title suggests, how can I pass the context struct into multiple helper functions?

What I'd like to do is something like this:

impl<'info> BuyIn<'info> {
    pub fn handler(ctx: Context<BuyIn>) -> Result<()> {
        
        let tuple_vec = config!(
            "ZERO_LEVEL",
            "ZERO_MULTIPLIER",
            "ONE_LEVEL",
            "ONE_MULTIPLIER",
            "TWO_LEVEL",
            "TWO_MULTIPLIER",
            "THREE_LEVEL",
            "THREE_MULTIPLIER",
            "FOUR_LEVEL",
            "FOUR_MULTIPLIER"
        );
        msg!("{:#?}", tuple_vec);

        for x in tuple_vec {
            if x.0 == ctx.accounts.game.level {
                let (amount, fee_amount) = calculate_transfers(x.1);
                transfer_to_vault(&ctx, amount)?;
                transfer_to_fee_collector(&ctx, fee_amount)?;
                update_vault_state(&ctx, amount)?;
                update_game_state(ctx)?;
                break;

            }
        }

        Ok(())

    }
}

And here is what my helpers look like:

pub fn calculate_transfers(multiplier: f32) -> (u64, u64) {
    let amount = LAMPORTS_PER_SOL as f32 / multiplier;
    let fee_amount = (amount * FEE_PERCENTAGE) as u64;
    let amount = amount as u64 - fee_amount;
    (amount, fee_amount)
}

pub fn transfer_to_fee_collector(
    ctx: &Context<BuyIn>,
    amount: u64,
) -> Result<()> {

    let cpi_context = CpiContext::new(
        ctx.accounts.system_program.to_account_info(), 
        system_program::Transfer {
            from: ctx.accounts.player.to_account_info(),
            to: ctx.accounts.fee_collector.to_account_info(),
        });
    system_program::transfer(cpi_context, amount)?;

    Ok(())
}

pub fn transfer_to_vault(
    ctx: &Context<BuyIn>,
    amount: u64,
) -> Result<()> {

    let cpi_context = CpiContext::new(
        ctx.accounts.system_program.to_account_info(), 
        system_program::Transfer {
            from: ctx.accounts.player.to_account_info(),
            to: ctx.accounts.vault.to_account_info(),
        });
    system_program::transfer(cpi_context, amount)?;

    Ok(())
}

pub fn update_game_state(ctx: Context<BuyIn>) -> Result<()> {
    let player = &mut ctx.accounts.player;
    let game = &mut ctx.accounts.game;

    game.leaderboard.push(
        Rank {
            player: Some(player.key()),
            weight: None,
        }
    );

    Ok(())
}

pub fn update_vault_states(ctx: &Context<BuyIn>) -> Result<()> {
    let vault = &mut ctx.accounts.vault;                   // error here

    Ok(())
}

And the error I recieve is the following: rustc: cannot borrow 'ctx.accounts.vault' as mutable, as it is behind a '&' reference 'ctx' is a '&' reference, so the data it refers to cannot be borrowed

Ive tried getting all the accounts before the for loop and then passing those as parameters to the helper functions, also changed parameters to include &mut type, but this results in an error in other functions. Specifically transfer_to_vault. I get a trait bound not satisfied when getting/passing in the account_info() into the Transfer struct.

Thanks!

edit: Trying another method but I'm unsure if it will work. Im unable to clone ctx, but I can pass &ctx to the helper functions and clone the relative accounts there into new variables.

My confusion with this is if I clone into new variable, will the subsequent logic/operations not actually be reflected on chain?

        for x in tuple_vec {
            if x.0 == ctx.accounts.game.level {
                let (amount, fee_amount) = calculate_transfers(x.1);
                transfer_to_vault(&ctx, amount)?;
                transfer_to_fee_collector(&ctx, fee_amount)?;
                update_game_state(&ctx)?;
                update_vault_state(&ctx, amount)?;
            
                break;
            }
        }

And here are the new helpers:

pub fn calculate_transfers(multiplier: f32) -> (u64, u64) {
    let amount = LAMPORTS_PER_SOL as f32 / multiplier;
    let fee_amount = (amount * FEE_PERCENTAGE) as u64;
    let amount = amount as u64 - fee_amount;
    (amount, fee_amount)
}

pub fn transfer_to_vault(
    ctx: &Context<BuyIn>,
    amount: u64,
) -> Result<()> {

    let cpi_context = CpiContext::new(
        ctx.accounts.system_program.to_account_info(), 
        system_program::Transfer {
            from: ctx.accounts.player.to_account_info(),
            to: ctx.accounts.vault.to_account_info(),
        });
    system_program::transfer(cpi_context, amount)?;

    Ok(())
}

pub fn transfer_to_fee_collector(
    ctx: &Context<BuyIn>,
    amount: u64,
) -> Result<()> {

    let cpi_context = CpiContext::new(
        ctx.accounts.system_program.to_account_info(), 
        system_program::Transfer {
            from: ctx.accounts.player.to_account_info(),
            to: ctx.accounts.fee_collector.to_account_info(),
        });
    system_program::transfer(cpi_context, amount)?;

    Ok(())
}


pub fn update_game_state(
    ctx: &Context<BuyIn>,
) -> Result<()> {
    let game = &mut ctx.accounts.game.clone();

    game.leaderboard.push(
        Rank { 
            player: Some(ctx.accounts.player.key()), 
            weight: None 
        }
    );

    Ok(())
}

pub fn update_vault_state(
    ctx: &Context<BuyIn>, 
    amount: u64
) -> Result<()> {
    let mut vault = ctx.accounts.vault.clone();
    vault.pot += amount;

    Ok(())
}

3
  • can you pass in ctx.clone() into your helper functions instead?
    – Ripleys 0x
    Jan 6, 2023 at 22:37
  • Unfortunately, Im unable to call clone() on ctx itself, nor can I the accounts field of ctx. However I can clone the accounts themselves! But Im unsure if my implementation will work. I can pass in &ctx to my helpers, and then I can clone the relevant account and then do the operations. But thinking about this, if Im cloning the account into a new variable, will it not be writing to the relevant account that just got cloned? I edited in what we're discussing in the post ser
    – TLS
    Jan 6, 2023 at 23:39
  • I edited in what we're discussing in the post ser
    – TLS
    Jan 6, 2023 at 23:45

2 Answers 2

2

You can change your code to use mutable references where needed rather than immutable references, which will then let you mutate.

So when you see:

pub fn update_game_state(ctx: Context<BuyIn>) -> Result<()> {
    let player = &mut ctx.accounts.player;
    let game = &mut ctx.accounts.game;

You should do:

pub fn update_game_state(ctx: &mut Context<BuyIn>) -> Result<()> {
    let player = &mut ctx.accounts.player;
    let game = &mut ctx.accounts.game;
1

Try this:

impl<'info> BuyIn<'info> {
    pub fn transfer_to_vault(&self, amount: u64) -> Result<()> {
        let cpi_context = CpiContext::new(
            ctx.accounts.system_program.to_account_info(), 
            system_program::Transfer {
                from: self.player.to_account_info(),
                to: self.vault.to_account_info(),
            });
        system_program::transfer(cpi_context, amount)
    }
}

and then in your instruction:

    ctx.accounts.transfer_to_vault(amount)?;

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.