use crate::Vault;
use crate::*;
use anchor_lang::prelude::*;
use anchor_spl::token::{Token, TokenAccount};
use solana_farm_sdk::id::zero;
use solana_farm_sdk::program::pda;
use solana_farm_sdk::{program::account, program::protocol::raydium};
use vipers::{assert_keys_eq, invariant, unwrap_int, unwrap_opt, Validate};
#[derive(Accounts)]
pub struct Crank1<'info> {
#[account(mut)]
pub vault: Box<Account<'info, Vault>>,
pub manager: Signer<'info>,
#[account(mut)]
pub pool_lp_custody_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub farm_token_a_reward_custody_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub farm_token_b_reward_custody_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub stake_info_account: UncheckedAccount<'info>,
#[account(mut)]
pub farm_lp_token_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub farm_reward_token_a_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub farm_reward_token_b_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub fees_account_a: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub fees_account_b: Box<Account<'info, TokenAccount>>,
pub spl_token_program: Program<'info, Token>,
pub clock_program: Sysvar<'info, Clock>,
#[account(mut)]
pub farm: UncheckedAccount<'info>,
pub farm_authority: UncheckedAccount<'info>,
pub farm_program: UncheckedAccount<'info>,
}
impl<'info> Crank1<'info> {
fn crank(&mut self) -> Result<()> {
let dual_rewards = self.farm_reward_token_b_account.key() != zero::id();
invariant!((dual_rewards && self.fees_account_b.key() == self.vault.fees_account_b));
check_min_crank_interval(&self.vault)?;
let seed = gen_vault_signer_seeds!(self.vault);
let signer_seeds = &[&seed[..]];
let initial_token_a_reward_balance = self.farm_token_a_reward_custody_account.amount;
let initial_token_b_reward_balance = if dual_rewards {
self.farm_token_b_reward_custody_account.amount
} else {
0
};
let initial_lp_tokens_balance = self.pool_lp_custody_account.amount;
msg!("Harvest rewards");
raydium::stake_with_seeds(
&[
self.vault.to_account_info(),
self.stake_info_account.to_account_info(),
self.pool_lp_custody_account.to_account_info(),
self.farm_token_a_reward_custody_account.to_account_info(),
self.farm_token_b_reward_custody_account.to_account_info(),
self.farm_program.to_account_info(),
self.farm_lp_token_account.to_account_info(),
self.farm_reward_token_a_account.to_account_info(),
self.farm_reward_token_b_account.to_account_info(),
self.clock_program.to_account_info(),
self.spl_token_program.to_account_info(),
self.farm.to_account_info(),
self.farm_authority.to_account_info(),
],
signer_seeds,
0,
)?;
let _ = account::check_tokens_spent(
&self.pool_lp_custody_account.to_account_info(),
initial_lp_tokens_balance,
0,
)?;
let token_a_rewards = account::get_balance_increase(
&self.farm_token_a_reward_custody_account.to_account_info(),
initial_token_a_reward_balance,
)?;
let token_b_rewards = if dual_rewards {
account::get_balance_increase(
&self.farm_token_b_reward_custody_account.to_account_info(),
initial_token_b_reward_balance,
)?
} else {
0
};
msg!(
"Rewards received. token_a_rewards: {}, token_b_rewards: {}",
token_a_rewards,
token_b_rewards
);
let fee = self.vault.fee_millibps;
if fee > fee * MILLIBPS_PER_WHOLE {
msg!("Error: Invalid fee. fee: {}", fee);
return Err(ProgramError::Custom(260).into());
}
let fees_a = unwrap_opt!(
unwrap_int!(token_a_rewards.checked_mul(self.vault.fee_millibps))
.checked_div(MILLIBPS_PER_WHOLE),
"Could not calculate fee a."
);
let fees_b = unwrap_opt!(
unwrap_int!(token_b_rewards.checked_mul(self.vault.fee_millibps))
.checked_div(MILLIBPS_PER_WHOLE),
"Could not calculate fee a."
);
msg!(
"Apply fees. fee: {}, fees_a: {}, fees_b: {}",
fee,
fees_a,
fees_b
);
pda::transfer_tokens_with_seeds(
&self.farm_token_a_reward_custody_account.to_account_info(),
&self.fees_account_a.to_account_info(),
&self.vault.to_account_info(),
signer_seeds,
fees_a,
)?;
if dual_rewards {
pda::transfer_tokens_with_seeds(
&self.farm_token_b_reward_custody_account.to_account_info(),
&self.fees_account_b.to_account_info(),
&self.vault.to_account_info(),
signer_seeds,
fees_b,
)?;
}
msg!("Update Vault stats",);
self.vault.add_rewards(token_a_rewards, token_b_rewards)?;
self.vault.update_crank_time()?;
self.vault.crank_step = 1;
Ok(())
}
}
pub fn handler(ctx: Context<Crank1>) -> Result<()> {
ctx.accounts.crank()
}
impl<'info> Validate<'info> for Crank1<'info> {
fn validate(&self) -> Result<()> {
assert_keys_eq!(self.manager, self.vault.manager);
assert_keys_eq!(
self.pool_lp_custody_account,
self.vault.pool_lp_custody_account
);
assert_keys_eq!(
self.farm_token_a_reward_custody_account,
self.vault.farm_token_a_reward_custody_account
);
assert_keys_eq!(
self.farm_token_b_reward_custody_account,
self.vault.farm_token_b_reward_custody_account
);
assert_keys_eq!(self.farm_lp_token_account, self.vault.farm_lp_token_account);
assert_keys_eq!(
self.farm_reward_token_a_account,
self.vault.farm_reward_token_a_account
);
assert_keys_eq!(
self.farm_reward_token_b_account,
self.vault.farm_reward_token_b_account
);
assert_keys_eq!(self.fees_account_a, self.vault.fees_account_a);
assert_keys_eq!(self.fees_account_b, self.vault.fees_account_b);
assert_keys_eq!(self.farm, self.vault.farm);
assert_keys_eq!(self.farm_authority, self.vault.farm_authority);
assert_keys_eq!(self.farm_program, self.vault.farm_program_id);
Ok(())
}
}