use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, Token, TokenAccount};
use solana_farm_sdk::{program::account, program::protocol::raydium};
use vipers::{assert_keys_eq, invariant, Validate};
use crate::{AmmInfoV4, UserInfo, Vault, VaultAddLiquidityEvent};
#[derive(Accounts)]
pub struct AddLiquidity<'info> {
#[account(mut)]
pub vault: Box<Account<'info, Vault>>,
pub user_account: Signer<'info>,
#[account(mut)]
pub user_info_account: Box<Account<'info, UserInfo>>,
#[account(mut)]
pub token_a_funding_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub token_b_funding_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub lp_funding_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub pool_coin_token_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub pool_pc_token_account: Box<Account<'info, TokenAccount>>,
#[account(mut)]
pub amm: Box<Account<'info, AmmInfoV4>>,
#[account(mut)]
pub lp_mint: Account<'info, Mint>,
#[account(mut)]
pub amm_authority: UncheckedAccount<'info>,
#[account(mut)]
pub amm_target: UncheckedAccount<'info>,
#[account(mut)]
pub serum_market: UncheckedAccount<'info>,
#[account(mut)]
pub amm_open_orders: UncheckedAccount<'info>,
#[account(mut)]
pub pool_lp_custody_account: Box<Account<'info, TokenAccount>>,
pub pool_program: UncheckedAccount<'info>,
pub spl_token_program: Program<'info, Token>,
pub clock_program: Sysvar<'info, Clock>,
}
impl<'info> AddLiquidity<'info> {
fn add_liquidity(
&mut self,
max_token_a_deposit_amount: u64,
max_token_b_deposit_amount: u64,
) -> Result<()> {
let initial_token_a_user_balance =
account::get_token_balance(&self.token_a_funding_account.to_account_info())?;
let initial_token_b_user_balance =
account::get_token_balance(&self.token_b_funding_account.to_account_info())?;
let initial_lp_user_balance =
account::get_token_balance(&self.lp_funding_account.to_account_info())?;
let (max_token_a_deposit_amount, max_token_b_deposit_amount) =
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(),
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);
invariant!(max_token_a_deposit_amount > 0 && max_token_b_deposit_amount > 0,);
msg!("Fetched user balances.");
invariant!(self.vault.deposits_allowed);
raydium::add_liquidity(
&[
self.user_account.to_account_info(),
self.token_a_funding_account.to_account_info(),
self.token_b_funding_account.to_account_info(),
self.lp_funding_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(),
],
max_token_a_deposit_amount,
max_token_b_deposit_amount,
)?;
msg!("Finished add liquidity.");
let tokens_a_spent = account::check_tokens_spent(
&self.token_a_funding_account.to_account_info(),
initial_token_a_user_balance,
max_token_a_deposit_amount,
)?;
let tokens_b_spent = account::check_tokens_spent(
&self.token_b_funding_account.to_account_info(),
initial_token_b_user_balance,
max_token_b_deposit_amount,
)?;
let lp_tokens_received = account::check_tokens_received(
&self.lp_funding_account.to_account_info(),
initial_lp_user_balance,
1,
)?;
msg!(
"Transfer LP tokens from user. tokens_a_spent: {}, tokens_b_spent: {}, lp_tokens_received: {}",
tokens_a_spent,
tokens_b_spent,
lp_tokens_received
);
self.user_info_account
.add_liquidity(tokens_a_spent, tokens_b_spent)?;
self.vault.add_liquidity(tokens_a_spent, tokens_b_spent)?;
msg!("Completed add liquidity.");
Ok(())
}
}
pub fn handler(ctx: Context<AddLiquidity>, token_a_amount: u64, token_b_amount: u64) -> Result<()> {
ctx.accounts.add_liquidity(token_a_amount, token_b_amount)?;
emit!(VaultAddLiquidityEvent {
vault: ctx.accounts.vault.key(),
token_a_amount,
token_b_amount,
});
Ok(())
}
impl<'info> Validate<'info> for AddLiquidity<'info> {
fn validate(&self) -> Result<()> {
assert_keys_eq!(self.user_info_account.vault, self.vault);
assert_keys_eq!(self.token_a_funding_account.owner, self.user_account,);
assert_keys_eq!(self.token_b_funding_account.owner, self.user_account,);
assert_keys_eq!(self.lp_funding_account.owner, self.user_account,);
assert_keys_eq!(
self.token_a_funding_account.mint,
self.vault.pool_token_a_mint
);
assert_keys_eq!(
self.token_b_funding_account.mint,
self.vault.pool_token_b_mint
);
assert_keys_eq!(self.lp_funding_account.mint, self.vault.pool_lp_token_mint);
assert_keys_eq!(self.pool_coin_token_account, self.amm.token_coin);
assert_keys_eq!(self.pool_pc_token_account, self.amm.token_pc);
assert_keys_eq!(self.lp_mint, self.vault.pool_lp_token_mint);
assert_keys_eq!(self.amm_target, self.vault.amm_target);
assert_keys_eq!(self.serum_market, self.vault.serum_market);
assert_keys_eq!(self.amm_open_orders, self.vault.amm_open_orders);
assert_keys_eq!(
self.pool_lp_custody_account,
self.vault.pool_lp_custody_account
);
assert_keys_eq!(self.pool_program, self.vault.pool_program_id);
assert_keys_eq!(self.vault.amm, self.amm);
Ok(())
}
}