pyra-instructions 0.4.1

Instruction builders for the Pyra protocol on Solana
Documentation
use anchor_lang::{InstructionData, ToAccountMetas};
use solana_program::{instruction::Instruction, pubkey::Pubkey};

use crate::constants::{
    INSTRUCTIONS_SYSVAR_ID, KAMINO_FARMS_PROGRAM_ID, KAMINO_LENDING_PROGRAM_ID, SYSTEM_PROGRAM_ID,
};

pub struct SpendKaminoParams {
    pub owner: Pubkey,
    pub admin: Pubkey,
    pub spend_fee_destination: Pubkey,
    pub liquidity_mint: Pubkey,
    pub liquidity_token_program: Pubkey,
    pub kamino_market: Pubkey,
    pub kamino_reserve: Pubkey,
    pub spend_amount_base_units: u64,
    pub fee_amount_base_units: u64,
}

pub fn spend_kamino(params: &SpendKaminoParams) -> Instruction {
    let vault = pyra_accounts::get_vault(&params.owner);
    let vault_liquidity_ata = pyra_accounts::get_associated_token_address(
        &vault,
        &params.liquidity_mint,
        &params.liquidity_token_program,
    );
    let spend_hold = pyra_accounts::get_spend_hold();
    let spend_hold_liquidity_ata = pyra_accounts::get_associated_token_address(
        &spend_hold,
        &params.liquidity_mint,
        &params.liquidity_token_program,
    );
    let spend_fee_destination_ata = pyra_accounts::get_associated_token_address(
        &params.spend_fee_destination,
        &params.liquidity_mint,
        &params.liquidity_token_program,
    );
    let kamino_obligation =
        pyra_accounts::get_kamino_obligation(&params.owner, &params.kamino_market);
    let kamino_market_authority =
        pyra_accounts::get_kamino_lending_market_authority(&params.kamino_market);
    let kamino_reserve_liquidity_supply =
        pyra_accounts::get_kamino_reserve_liquidity_supply(&params.kamino_reserve);
    let kamino_reserve_collateral_mint =
        pyra_accounts::get_kamino_reserve_collateral_mint(&params.kamino_reserve);
    let kamino_reserve_collateral_supply =
        pyra_accounts::get_kamino_reserve_collateral_supply(&params.kamino_reserve);

    let accounts = crate::pyra_program::client::accounts::SpendKamino {
        vault,
        vault_liquidity_ata,
        admin: params.admin,
        spend_hold,
        spend_hold_liquidity_ata,
        spend_fee_destination: params.spend_fee_destination,
        spend_fee_destination_ata,
        liquidity_mint: params.liquidity_mint,
        liquidity_token_program: params.liquidity_token_program,
        associated_token_program: pyra_accounts::ASSOCIATED_TOKEN_PROGRAM_ID,
        kamino_program: KAMINO_LENDING_PROGRAM_ID,
        kamino_obligation,
        kamino_market: params.kamino_market,
        kamino_market_authority,
        kamino_reserve: params.kamino_reserve,
        kamino_reserve_liquidity_supply,
        kamino_reserve_collateral_mint,
        kamino_reserve_collateral_supply,
        // All Kamino collateral tokens use spl-token (not Token-2022)
        collateral_token_program: pyra_tokens::TOKEN_PROGRAM_ID,
        instructions_sysvar_account: INSTRUCTIONS_SYSVAR_ID,
        kamino_farms_program: KAMINO_FARMS_PROGRAM_ID,
        system_program: SYSTEM_PROGRAM_ID,
    }
    .to_account_metas(None);

    let data = crate::pyra_program::client::args::SpendKamino {
        spend_amount_base_units: params.spend_amount_base_units,
        fee_amount_base_units: params.fee_amount_base_units,
    }
    .data();

    Instruction {
        program_id: pyra_accounts::PYRA_PROGRAM_ID,
        accounts,
        data,
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;
    use solana_pubkey::pubkey;

    #[test]
    fn test_spend_kamino_instruction() {
        let owner = pubkey!("d4A2prbA2whesmvHaL88BH6Ewn5N4bTSjm4GiKy2eSi");
        let admin = pubkey!("7u3HeHxYDLhnCoErrtycNokbQYbWGzLs6JSDqGAv5PfF");
        let market = pubkey!("Gnt27xtC473ZT2Mw5u8wZ68Z3gULkSTb5DuxJy7eJotD");
        let reserve = pubkey!("AdtRGGhmqvom3Fk5H2LTnGMfcXmUQdhSz8aPJGfhFqHj");
        let mint = pubkey!("DUcTi3rDyS5QEmZ4BNRBejtArmDCWaPYGfN44vBJXKL5");
        let token_program = pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
        let fee_dest = pubkey!("3NJYftD5sjVfxFkKFgU7bbtBBhBsBYAg1CEhJkJ4iY3H");

        let ix = spend_kamino(&SpendKaminoParams {
            owner,
            admin,
            spend_fee_destination: fee_dest,
            liquidity_mint: mint,
            liquidity_token_program: token_program,
            kamino_market: market,
            kamino_reserve: reserve,
            spend_amount_base_units: 1_000_000,
            fee_amount_base_units: 10_000,
        });

        assert_eq!(ix.program_id, pyra_accounts::PYRA_PROGRAM_ID);
        // 22 accounts
        assert_eq!(ix.accounts.len(), 22);
        // vault is first and writable
        assert_eq!(ix.accounts[0].pubkey, pyra_accounts::get_vault(&owner));
        assert!(ix.accounts[0].is_writable);
        // admin is signer + writable
        assert_eq!(ix.accounts[2].pubkey, admin);
        assert!(ix.accounts[2].is_signer);
        assert!(ix.accounts[2].is_writable);
        // data should not be empty
        assert!(!ix.data.is_empty());
    }
}