use crate::Vault;
use crate::*;
use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, Token, TokenAccount};
use solana_farm_sdk::id::zero;
use solana_farm_sdk::{program::account, program::protocol::raydium};
use vipers::{assert_keys_eq, Validate};
#[derive(Accounts)]
pub struct Crank3<'info> {
#[account(mut)]
pub vault: Box<Account<'info, Vault>>,
pub manager: Signer<'info>,
#[account(mut)]
pub pool_token_a_custody_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub pool_token_b_custody_account: Box<Account<'info, TokenAccount>>,
#[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 pool_program: UncheckedAccount<'info>,
#[account(mut)]
pub pool_coin_token_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub pool_pc_token_account: Box<Account<'info, TokenAccount>>,
pub spl_token_program: Program<'info, Token>,
#[account(mut)]
pub amm: Box<Account<'info, AmmInfoV4>>,
#[account(mut)]
pub amm_authority: UncheckedAccount<'info>,
#[account(mut)]
pub amm_open_orders: UncheckedAccount<'info>,
#[account(mut)]
pub amm_target: UncheckedAccount<'info>,
#[account(mut)]
pub serum_market: UncheckedAccount<'info>,
#[account(mut)]
pub lp_mint: Account<'info, Mint>,
pub clock_program: Sysvar<'info, Clock>,
#[account(mut)]
pub farm: UncheckedAccount<'info>,
pub farm_authority: UncheckedAccount<'info>,
pub farm_program: UncheckedAccount<'info>,
}
impl<'info> Crank3<'info> {
fn crank3(&mut self) -> Result<()> {
check_min_crank_interval(&self.vault)?;
self.vault.update_crank_time()?;
self.vault.crank_step = 3;
let token_a_balance =
account::get_token_balance(&self.pool_token_a_custody_account.to_account_info())?;
let token_b_balance =
account::get_token_balance(&self.pool_token_b_custody_account.to_account_info())?;
let lp_token_balance =
account::get_token_balance(&self.pool_lp_custody_account.to_account_info())?;
msg!(
"Read balances. token_a_balance: {}, token_b_balance: {}",
token_a_balance,
token_b_balance
);
if token_a_balance < 10 || token_b_balance < 10 {
msg!("Nothing to do: Not enough tokens to compound");
return Ok(());
}
let (pool_coin_balance, pool_pc_balance) = raydium::get_pool_token_balances(
&self.pool_coin_token_account.to_account_info(),
&self.pool_pc_token_account.to_account_info(),
&self.amm_open_orders.to_account_info(),
&self.amm.to_account_info(),
)?;
let pool_ratio = if pool_coin_balance != 0 {
pool_pc_balance as f64 / pool_coin_balance as f64
} else {
0.0
};
let custody_ratio = account::get_token_pair_ratio(
&self.pool_token_a_custody_account.to_account_info(),
&self.pool_token_b_custody_account.to_account_info(),
)?;
msg!(
"Compute pool ratios. custody_ratio: {}, pool_ratio: {}",
custody_ratio,
pool_ratio
);
if custody_ratio == 0.0 || pool_ratio == 0.0 {
msg!("Pool ratio is zero");
return Ok(());
}
if (custody_ratio - pool_ratio).abs() * 100.0 / pool_ratio > 10.0 {
msg!("Unbalanced tokens, run Crank2 first");
return Ok(());
}
let seed = gen_vault_signer_seeds!(self.vault);
let signer_seeds = &[&seed[..]];
let (max_token_a_deposit_amount, max_token_b_deposit_amount) =
if custody_ratio >= pool_ratio {
raydium::get_pool_deposit_amounts(
&self.pool_coin_token_account.to_account_info(),
&self.pool_pc_token_account.to_account_info(),
&self.amm_open_orders.to_account_info(),
&self.amm.to_account_info(),
token_a_balance,
0,
)?
} else {
raydium::get_pool_deposit_amounts(
&self.pool_coin_token_account.to_account_info(),
&self.pool_pc_token_account.to_account_info(),
&self.amm_open_orders.to_account_info(),
&self.amm.to_account_info(),
0,
token_b_balance,
)?
};
let (max_token_a_deposit_amount, max_token_b_deposit_amount) =
if max_token_b_deposit_amount > token_b_balance {
raydium::get_pool_deposit_amounts(
&self.pool_coin_token_account.to_account_info(),
&self.pool_pc_token_account.to_account_info(),
&self.amm_open_orders.to_account_info(),
&self.amm.to_account_info(),
0,
token_b_balance,
)?
} else if max_token_a_deposit_amount > token_a_balance {
raydium::get_pool_deposit_amounts(
&self.pool_coin_token_account.to_account_info(),
&self.pool_pc_token_account.to_account_info(),
&self.amm_open_orders.to_account_info(),
&self.amm.to_account_info(),
token_a_balance,
0,
)?
} else {
(max_token_a_deposit_amount, max_token_b_deposit_amount)
};
msg!("Deposit tokens into the pool. max_token_a_deposit_amount: {}, max_token_b_deposit_amount: {}",
max_token_a_deposit_amount,
max_token_b_deposit_amount);
if max_token_a_deposit_amount == 0
|| max_token_b_deposit_amount == 0
|| raydium::estimate_lp_tokens_amount(
&self.lp_mint.to_account_info(),
max_token_a_deposit_amount,
max_token_b_deposit_amount,
pool_coin_balance,
pool_pc_balance,
)? < 2
{
msg!("Nothing to do: Tokens balance is not large enough");
return Ok(());
}
raydium::add_liquidity_with_seeds(
&[
self.vault.to_account_info(),
self.pool_token_a_custody_account.to_account_info(),
self.pool_token_b_custody_account.to_account_info(),
self.pool_lp_custody_account.to_account_info(),
self.pool_program.to_account_info(),
self.pool_coin_token_account.to_account_info(),
self.pool_pc_token_account.to_account_info(),
self.lp_mint.to_account_info(),
self.spl_token_program.to_account_info(),
self.amm.to_account_info(),
self.amm_authority.to_account_info(),
self.amm_open_orders.to_account_info(),
self.amm_target.to_account_info(),
self.serum_market.to_account_info(),
],
signer_seeds,
max_token_a_deposit_amount,
max_token_b_deposit_amount,
)?;
let tokens_a_spent = account::check_tokens_spent(
&self.pool_token_a_custody_account.to_account_info(),
token_a_balance,
max_token_a_deposit_amount,
)?;
let tokens_b_spent = account::check_tokens_spent(
&self.pool_token_b_custody_account.to_account_info(),
token_b_balance,
max_token_b_deposit_amount,
)?;
let dual_rewards = self.farm_reward_token_b_account.key() != zero::id();
let lp_tokens_received = account::check_tokens_received(
&self.pool_lp_custody_account.to_account_info(),
lp_token_balance,
1,
)?;
msg!(
"Stake LP tokens. tokens_a_spent: {}, tokens_b_spent: {}, lp_tokens_received: {}",
tokens_a_spent,
tokens_b_spent,
lp_tokens_received
);
let token_a_reward_balance = self.farm_token_a_reward_custody_account.amount;
let token_b_reward_balance = if dual_rewards {
self.farm_token_b_reward_custody_account.amount
} else {
0
};
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,
lp_tokens_received,
)?;
if lp_token_balance
!= account::get_token_balance(&self.pool_lp_custody_account.to_account_info())?
{
msg!("Error: Stake instruction didn't result in expected amount of LP tokens spent");
return Err(ProgramError::Custom(165).into());
}
let token_a_rewards = account::get_balance_increase(
&self.farm_token_a_reward_custody_account.to_account_info(),
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(),
token_b_reward_balance,
)?
} else {
0
};
msg!(
"Update Vault stats. token_a_rewards: {}, token_b_rewards: {}",
token_a_rewards,
token_b_rewards
);
self.vault.add_rewards(token_a_rewards, token_b_rewards)?;
self.vault.add_liquidity(tokens_a_spent, tokens_b_spent)?;
Ok(())
}
}
pub fn handler(ctx: Context<Crank3>) -> Result<()> {
ctx.accounts.crank3()
}
impl<'info> Validate<'info> for Crank3<'info> {
fn validate(&self) -> Result<()> {
assert_keys_eq!(self.manager, self.vault.manager);
assert_keys_eq!(
self.pool_token_a_custody_account,
self.vault.pool_token_a_custody_account
);
assert_keys_eq!(
self.pool_token_b_custody_account,
self.vault.pool_token_b_custody_account
);
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.pool_program, self.vault.pool_program_id);
assert_keys_eq!(
self.pool_coin_token_account,
self.vault.pool_coin_token_account
);
assert_keys_eq!(self.pool_pc_token_account, self.vault.pool_pc_token_account);
assert_keys_eq!(self.vault.amm, self.amm);
assert_keys_eq!(self.amm_authority, self.vault.amm_authority);
assert_keys_eq!(self.amm_open_orders, self.vault.amm_open_orders);
assert_keys_eq!(self.amm_target, self.vault.amm_target);
assert_keys_eq!(self.serum_market, self.vault.serum_market);
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(())
}
}