strategy-vaults 0.0.1

Farm Vaults
Documentation
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>>,
    /// Manager of the 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>>,

    // For Farming Metadata.
    #[account(mut)]
    /// CHECK: Raydium verifies
    pub stake_info_account: UncheckedAccount<'info>,

    // The following are farm reward vaults.
    // They are owned by raydium not delta.
    #[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>>,

    // SPL Token Program
    pub spl_token_program: Program<'info, Token>,

    // Clock Program
    pub clock_program: Sysvar<'info, Clock>,

    // The Raydium Farm Account
    #[account(mut)]
    /// CHECK: Raydium will verify
    pub farm: UncheckedAccount<'info>,

    /// Raydium Farm Metadata
    /// The following are accounts from the AMM that need to be passed in
    /// CHECK: Raydium will verify
    pub farm_authority: UncheckedAccount<'info>,

    // Raydium Farm Program.
    // Make sure this is the same as in the vault.
    /// CHECK: Raydium will verify
    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(),
                // first the farm lp and reward custody accounts - all should be owned by the vault.
                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(),
                // then comes the farm lp and reward accounts.
                // these live in the farm and are owned by raydium.
                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(),
                // solana information
                self.clock_program.to_account_info(),
                self.spl_token_program.to_account_info(),
                // farm information.
                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,
        )?;

        // calculate rewards
        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
        );
        // take fees
        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
        );
        // Paying fees to protocol.
        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,
            )?;
        }

        // update Vault stats
        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<()> {
        // make sure that only the vault manager can call this crank.
        assert_keys_eq!(self.manager, self.vault.manager);
        // make sure the custody accounts are the same as those in the vault.
        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
        );

        // TODO - not specifying stake info account because it can be different on devnet and on mainnet.

        // Farm Vaults
        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
        );

        // make sure the stake info account is in the vault.
        assert_keys_eq!(self.fees_account_a, self.vault.fees_account_a);
        assert_keys_eq!(self.fees_account_b, self.vault.fees_account_b);

        // make sure farm metadata is same as specified in vault
        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(())
    }
}