#![allow(dead_code)] use assert_matches::*; use solana_program::{program_option::COption, program_pack::Pack, pubkey::Pubkey}; use solana_program_test::*; use solana_sdk::{ account::Account, signature::{read_keypair_file, Keypair, Signer}, system_instruction::create_account, transaction::{Transaction, TransactionError}, }; use spl_token::{ instruction::approve, state::{Account as Token, AccountState, Mint}, }; use spl_token_lending::{ instruction::{ borrow_reserve_liquidity, deposit_reserve_liquidity, init_lending_market, init_obligation, init_reserve, liquidate_obligation, BorrowAmountType, }, math::{Decimal, Rate, TryAdd, TryMul}, processor::process_instruction, state::{ LendingMarket, NewReserveParams, Obligation, Reserve, ReserveCollateral, ReserveConfig, ReserveFees, ReserveLiquidity, INITIAL_COLLATERAL_RATIO, PROGRAM_VERSION, }, }; use std::str::FromStr; pub mod genesis; use genesis::GenesisAccounts; pub const TEST_RESERVE_CONFIG: ReserveConfig = ReserveConfig { optimal_utilization_rate: 80, loan_to_value_ratio: 50, liquidation_bonus: 5, liquidation_threshold: 55, min_borrow_rate: 0, optimal_borrow_rate: 4, max_borrow_rate: 30, fees: ReserveFees { borrow_fee_wad: 100_000_000_000, /// 0.00001% (Aave borrow fee) host_fee_percentage: 20, }, }; pub const USDC_MINT: &str = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; pub const SRM_MINT: &str = "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt"; pub const SOL_USDC_MARKET: &str = "9wFFyRfZBsuAha4YcuxcXLKwMxJR43S7fPfQLusDBzvT"; pub const SOL_USDC_BIDS: &str = "14ivtgssEBoBjuZJtSAPKYgpUK7DmnSwuPMqJoVTSgKJ"; pub const SOL_USDC_ASKS: &str = "CEQdAFKdycHugujQg9k2wbmxjcpdYZyVLfV9WerTnafJ"; pub const SRM_USDC_MARKET: &str = "ByRys5tuUWDgL73G8JBAEfkdFf8JWBzPBDHsBVQ5vbQA"; pub const SRM_USDC_BIDS: &str = "AuL9JzRJ55MdqzubK4EutJgAumtkuFcRVuPUvTX39pN8"; pub const SRM_USDC_ASKS: &str = "8Lx9U9wdE3afdqih1mCAXy3unJDfzSaXFqAvoLMjhwoD"; #[allow(non_camel_case_types)] pub enum TestDexMarketPair { SRM_USDC, SOL_USDC, } pub struct LendingTest { pub sol_usdc_dex_market: TestDexMarket, pub srm_usdc_dex_market: TestDexMarket, pub usdc_mint: TestQuoteMint, pub srm_mint: TestQuoteMint, } pub fn setup_test() -> (ProgramTest, LendingTest) { let mut test = ProgramTest::new( "spl_token_lending", spl_token_lending::id(), processor!(process_instruction), ); let usdc_mint = add_usdc_mint(&mut test); let srm_mint = add_srm_mint(&mut test); let sol_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SOL_USDC); let srm_usdc_dex_market = TestDexMarket::setup(&mut test, TestDexMarketPair::SRM_USDC); ( test, LendingTest { sol_usdc_dex_market, srm_usdc_dex_market, usdc_mint, srm_mint, }, ) } trait AddPacked { fn add_packable_account( &mut self, pubkey: Pubkey, amount: u64, data: &T, owner: &Pubkey, ); } impl AddPacked for ProgramTest { fn add_packable_account( &mut self, pubkey: Pubkey, amount: u64, data: &T, owner: &Pubkey, ) { let mut account = Account::new(amount, T::get_packed_len(), owner); data.pack_into_slice(&mut account.data); self.add_account(pubkey, account); } } pub fn add_lending_market(test: &mut ProgramTest, quote_token_mint: Pubkey) -> TestLendingMarket { let pubkey = Pubkey::new_unique(); let (authority, bump_seed) = Pubkey::find_program_address(&[pubkey.as_ref()], &spl_token_lending::id()); let owner = read_keypair_file("tests/fixtures/lending_market_owner.json").unwrap(); test.add_packable_account( pubkey, u32::MAX as u64, &LendingMarket { version: PROGRAM_VERSION, bump_seed, owner: owner.pubkey(), quote_token_mint, token_program_id: spl_token::id(), }, &spl_token_lending::id(), ); TestLendingMarket { pubkey, owner, authority, quote_token_mint, } } pub struct AddObligationArgs<'a> { pub borrow_reserve: &'a TestReserve, pub collateral_reserve: &'a TestReserve, pub collateral_amount: u64, pub borrowed_liquidity_wads: Decimal, } pub fn add_obligation( test: &mut ProgramTest, user_accounts_owner: &Keypair, lending_market: &TestLendingMarket, args: AddObligationArgs, ) -> TestObligation { let AddObligationArgs { borrow_reserve, collateral_reserve, collateral_amount, borrowed_liquidity_wads, } = args; let token_mint_pubkey = Pubkey::new_unique(); test.add_packable_account( token_mint_pubkey, u32::MAX as u64, &Mint { is_initialized: true, decimals: collateral_reserve.liquidity_mint_decimals, mint_authority: COption::Some(lending_market.authority), supply: collateral_amount, ..Mint::default() }, &spl_token::id(), ); let token_account_pubkey = Pubkey::new_unique(); test.add_packable_account( token_account_pubkey, u32::MAX as u64, &Token { mint: token_mint_pubkey, owner: user_accounts_owner.pubkey(), state: AccountState::Initialized, amount: collateral_amount, ..Token::default() }, &spl_token::id(), ); let obligation_keypair = Keypair::new(); let obligation_pubkey = obligation_keypair.pubkey(); test.add_packable_account( obligation_pubkey, u32::MAX as u64, &Obligation { version: PROGRAM_VERSION, deposited_collateral_tokens: collateral_amount, collateral_reserve: collateral_reserve.pubkey, cumulative_borrow_rate_wads: Decimal::one(), borrowed_liquidity_wads, borrow_reserve: borrow_reserve.pubkey, token_mint: token_mint_pubkey, }, &spl_token_lending::id(), ); TestObligation { pubkey: obligation_pubkey, token_mint: token_mint_pubkey, token_account: token_account_pubkey, borrow_reserve: borrow_reserve.pubkey, collateral_reserve: collateral_reserve.pubkey, } } #[derive(Default)] pub struct AddReserveArgs { pub name: String, pub slots_elapsed: u64, pub config: ReserveConfig, pub liquidity_amount: u64, pub liquidity_mint_pubkey: Pubkey, pub liquidity_mint_decimals: u8, pub user_liquidity_amount: u64, pub borrow_amount: u64, pub initial_borrow_rate: u8, pub collateral_amount: u64, pub fees_amount: u64, pub dex_market_pubkey: Option, } pub fn add_reserve( test: &mut ProgramTest, user_accounts_owner: &Keypair, lending_market: &TestLendingMarket, args: AddReserveArgs, ) -> TestReserve { let AddReserveArgs { name, slots_elapsed, config, liquidity_amount, liquidity_mint_pubkey, liquidity_mint_decimals, user_liquidity_amount, borrow_amount, initial_borrow_rate, collateral_amount, fees_amount, dex_market_pubkey, } = args; let is_native = if liquidity_mint_pubkey == spl_token::native_mint::id() { COption::Some(1) } else { COption::None }; let collateral_mint_pubkey = Pubkey::new_unique(); test.add_packable_account( collateral_mint_pubkey, u32::MAX as u64, &Mint { is_initialized: true, decimals: liquidity_mint_decimals, mint_authority: COption::Some(lending_market.authority), supply: collateral_amount, ..Mint::default() }, &spl_token::id(), ); let collateral_supply_pubkey = Pubkey::new_unique(); test.add_packable_account( collateral_supply_pubkey, u32::MAX as u64, &Token { mint: collateral_mint_pubkey, owner: lending_market.authority, amount: collateral_amount, state: AccountState::Initialized, ..Token::default() }, &spl_token::id(), ); let collateral_fees_receiver_pubkey = Pubkey::new_unique(); test.add_packable_account( collateral_fees_receiver_pubkey, u32::MAX as u64, &Token { mint: collateral_mint_pubkey, owner: lending_market.owner.pubkey(), amount: fees_amount, state: AccountState::Initialized, ..Token::default() }, &spl_token::id(), ); let collateral_host_pubkey = Pubkey::new_unique(); test.add_packable_account( collateral_host_pubkey, u32::MAX as u64, &Token { mint: collateral_mint_pubkey, owner: user_accounts_owner.pubkey(), amount: fees_amount, state: AccountState::Initialized, ..Token::default() }, &spl_token::id(), ); let amount = if let COption::Some(rent_reserve) = is_native { liquidity_amount + rent_reserve } else { u32::MAX as u64 }; let liquidity_supply_pubkey = Pubkey::new_unique(); test.add_packable_account( liquidity_supply_pubkey, amount, &Token { mint: liquidity_mint_pubkey, owner: lending_market.authority, amount: liquidity_amount, state: AccountState::Initialized, is_native, ..Token::default() }, &spl_token::id(), ); let reserve_keypair = Keypair::new(); let reserve_pubkey = reserve_keypair.pubkey(); let reserve_liquidity = ReserveLiquidity::new( liquidity_mint_pubkey, liquidity_mint_decimals, liquidity_supply_pubkey, ); let reserve_collateral = ReserveCollateral::new( collateral_mint_pubkey, collateral_supply_pubkey, collateral_fees_receiver_pubkey, ); let mut reserve = Reserve::new(NewReserveParams { // intentionally wrapped to simulate elapsed slots current_slot: 1u64.wrapping_sub(slots_elapsed), lending_market: lending_market.pubkey, dex_market: dex_market_pubkey.into(), liquidity: reserve_liquidity, collateral: reserve_collateral, config, }); reserve.deposit_liquidity(liquidity_amount).unwrap(); reserve.liquidity.borrow(borrow_amount).unwrap(); let borrow_rate_multiplier = Rate::one() .try_add(Rate::from_percent(initial_borrow_rate)) .unwrap(); reserve.cumulative_borrow_rate_wads = Decimal::one().try_mul(borrow_rate_multiplier).unwrap(); test.add_packable_account( reserve_pubkey, u32::MAX as u64, &reserve, &spl_token_lending::id(), ); let amount = if let COption::Some(rent_reserve) = is_native { user_liquidity_amount + rent_reserve } else { u32::MAX as u64 }; let user_liquidity_pubkey = Pubkey::new_unique(); test.add_packable_account( user_liquidity_pubkey, amount, &Token { mint: liquidity_mint_pubkey, owner: user_accounts_owner.pubkey(), amount: user_liquidity_amount, state: AccountState::Initialized, is_native, ..Token::default() }, &spl_token::id(), ); let user_collateral_pubkey = Pubkey::new_unique(); test.add_packable_account( user_collateral_pubkey, u32::MAX as u64, &Token { mint: collateral_mint_pubkey, owner: user_accounts_owner.pubkey(), amount: liquidity_amount * INITIAL_COLLATERAL_RATIO, state: AccountState::Initialized, ..Token::default() }, &spl_token::id(), ); TestReserve { name, pubkey: reserve_pubkey, lending_market: lending_market.pubkey, config, liquidity_mint: liquidity_mint_pubkey, liquidity_mint_decimals, liquidity_supply: liquidity_supply_pubkey, collateral_mint: collateral_mint_pubkey, collateral_supply: collateral_supply_pubkey, collateral_fees_receiver: collateral_fees_receiver_pubkey, collateral_host: collateral_host_pubkey, user_liquidity_account: user_liquidity_pubkey, user_collateral_account: user_collateral_pubkey, dex_market: dex_market_pubkey, } } pub struct TestLendingMarket { pub pubkey: Pubkey, pub owner: Keypair, pub authority: Pubkey, pub quote_token_mint: Pubkey, } pub struct BorrowArgs<'a> { pub deposit_reserve: &'a TestReserve, pub borrow_reserve: &'a TestReserve, pub borrow_amount_type: BorrowAmountType, pub amount: u64, pub dex_market: &'a TestDexMarket, pub user_accounts_owner: &'a Keypair, pub obligation: &'a TestObligation, } pub struct LiquidateArgs<'a> { pub repay_reserve: &'a TestReserve, pub withdraw_reserve: &'a TestReserve, pub obligation: &'a TestObligation, pub amount: u64, pub dex_market: &'a TestDexMarket, pub user_accounts_owner: &'a Keypair, } impl TestLendingMarket { pub async fn init( banks_client: &mut BanksClient, quote_token_mint: Pubkey, payer: &Keypair, ) -> Self { let owner = read_keypair_file("tests/fixtures/lending_market_owner.json").unwrap(); let keypair = read_keypair_file("tests/fixtures/lending_market.json").unwrap(); let pubkey = keypair.pubkey(); let (authority_pubkey, _bump_seed) = Pubkey::find_program_address(&[&pubkey.to_bytes()[..32]], &spl_token_lending::id()); let rent = banks_client.get_rent().await.unwrap(); let mut transaction = Transaction::new_with_payer( &[ create_account( &payer.pubkey(), &pubkey, rent.minimum_balance(LendingMarket::LEN), LendingMarket::LEN as u64, &spl_token_lending::id(), ), init_lending_market( spl_token_lending::id(), pubkey, owner.pubkey(), quote_token_mint, ), ], Some(&payer.pubkey()), ); let recent_blockhash = banks_client.get_recent_blockhash().await.unwrap(); transaction.sign(&[&payer, &keypair], recent_blockhash); assert_matches!(banks_client.process_transaction(transaction).await, Ok(())); TestLendingMarket { owner, pubkey, authority: authority_pubkey, quote_token_mint, } } pub async fn deposit( &self, banks_client: &mut BanksClient, user_accounts_owner: &Keypair, payer: &Keypair, reserve: &TestReserve, amount: u64, ) { let user_transfer_authority = Keypair::new(); let mut transaction = Transaction::new_with_payer( &[ approve( &spl_token::id(), &reserve.user_liquidity_account, &user_transfer_authority.pubkey(), &user_accounts_owner.pubkey(), &[], amount, ) .unwrap(), deposit_reserve_liquidity( spl_token_lending::id(), amount, reserve.user_liquidity_account, reserve.user_collateral_account, reserve.pubkey, reserve.liquidity_supply, reserve.collateral_mint, self.pubkey, self.authority, user_transfer_authority.pubkey(), ), ], Some(&payer.pubkey()), ); let recent_blockhash = banks_client.get_recent_blockhash().await.unwrap(); transaction.sign( &[payer, user_accounts_owner, &user_transfer_authority], recent_blockhash, ); assert_matches!(banks_client.process_transaction(transaction).await, Ok(())); } pub async fn liquidate( &self, banks_client: &mut BanksClient, payer: &Keypair, args: LiquidateArgs<'_>, ) { let LiquidateArgs { repay_reserve, withdraw_reserve, obligation, amount, dex_market, user_accounts_owner, } = args; let dex_market_orders_pubkey = if repay_reserve.dex_market.is_none() { dex_market.asks_pubkey } else { dex_market.bids_pubkey }; let memory_keypair = Keypair::new(); let user_transfer_authority = Keypair::new(); let mut transaction = Transaction::new_with_payer( &[ create_account( &payer.pubkey(), &memory_keypair.pubkey(), 0, 65548, &spl_token_lending::id(), ), approve( &spl_token::id(), &repay_reserve.user_liquidity_account, &user_transfer_authority.pubkey(), &user_accounts_owner.pubkey(), &[], amount, ) .unwrap(), liquidate_obligation( spl_token_lending::id(), amount, repay_reserve.user_liquidity_account, withdraw_reserve.user_collateral_account, repay_reserve.pubkey, repay_reserve.liquidity_supply, withdraw_reserve.pubkey, withdraw_reserve.collateral_supply, obligation.pubkey, self.pubkey, self.authority, user_transfer_authority.pubkey(), dex_market.pubkey, dex_market_orders_pubkey, memory_keypair.pubkey(), ), ], Some(&payer.pubkey()), ); let recent_blockhash = banks_client.get_recent_blockhash().await.unwrap(); transaction.sign( &[ &payer, &memory_keypair, &user_accounts_owner, &user_transfer_authority, ], recent_blockhash, ); assert!(banks_client.process_transaction(transaction).await.is_ok()); } pub async fn borrow( &self, banks_client: &mut BanksClient, payer: &Keypair, args: BorrowArgs<'_>, ) { let memory_keypair = Keypair::new(); let user_transfer_authority = Keypair::new(); let BorrowArgs { borrow_reserve, deposit_reserve, borrow_amount_type, amount, dex_market, user_accounts_owner, obligation, } = args; let dex_market_orders_pubkey = if deposit_reserve.dex_market.is_none() { dex_market.asks_pubkey } else { dex_market.bids_pubkey }; let approve_amount = if borrow_amount_type == BorrowAmountType::CollateralDepositAmount { amount } else { get_token_balance(banks_client, deposit_reserve.user_collateral_account).await }; let mut transaction = Transaction::new_with_payer( &[ approve( &spl_token::id(), &deposit_reserve.user_collateral_account, &user_transfer_authority.pubkey(), &user_accounts_owner.pubkey(), &[], approve_amount, ) .unwrap(), create_account( &payer.pubkey(), &memory_keypair.pubkey(), 0, 65548, &spl_token_lending::id(), ), borrow_reserve_liquidity( spl_token_lending::id(), amount, borrow_amount_type, deposit_reserve.user_collateral_account, borrow_reserve.user_liquidity_account, deposit_reserve.pubkey, deposit_reserve.collateral_supply, deposit_reserve.collateral_fees_receiver, borrow_reserve.pubkey, borrow_reserve.liquidity_supply, self.pubkey, self.authority, user_transfer_authority.pubkey(), obligation.pubkey, obligation.token_mint, obligation.token_account, dex_market.pubkey, dex_market_orders_pubkey, memory_keypair.pubkey(), Some(deposit_reserve.collateral_host), ), ], Some(&payer.pubkey()), ); let recent_blockhash = banks_client.get_recent_blockhash().await.unwrap(); transaction.sign( &vec![ payer, user_accounts_owner, &memory_keypair, &user_transfer_authority, ], recent_blockhash, ); assert_matches!(banks_client.process_transaction(transaction).await, Ok(())); } pub async fn get_state(&self, banks_client: &mut BanksClient) -> LendingMarket { let lending_market_account: Account = banks_client .get_account(self.pubkey) .await .unwrap() .unwrap(); LendingMarket::unpack(&lending_market_account.data[..]).unwrap() } pub async fn add_to_genesis( &self, banks_client: &mut BanksClient, genesis_accounts: &mut GenesisAccounts, ) { println!("lending_market: {}", self.pubkey); genesis_accounts .fetch_and_insert(banks_client, self.pubkey) .await; } } #[derive(Debug)] pub struct TestReserve { pub name: String, pub pubkey: Pubkey, pub lending_market: Pubkey, pub config: ReserveConfig, pub liquidity_mint: Pubkey, pub liquidity_mint_decimals: u8, pub liquidity_supply: Pubkey, pub collateral_mint: Pubkey, pub collateral_supply: Pubkey, pub collateral_fees_receiver: Pubkey, pub collateral_host: Pubkey, pub user_liquidity_account: Pubkey, pub user_collateral_account: Pubkey, pub dex_market: Option, } impl TestReserve { #[allow(clippy::too_many_arguments)] pub async fn init( name: String, banks_client: &mut BanksClient, lending_market: &TestLendingMarket, reserve_amount: u64, config: ReserveConfig, liquidity_mint_pubkey: Pubkey, user_liquidity_account: Pubkey, payer: &Keypair, user_accounts_owner: &Keypair, dex_market: &TestDexMarket, ) -> Result { let reserve_keypair = Keypair::new(); let reserve_pubkey = reserve_keypair.pubkey(); let collateral_mint_keypair = Keypair::new(); let collateral_supply_keypair = Keypair::new(); let collateral_fees_receiver_keypair = Keypair::new(); let collateral_host_keypair = Keypair::new(); let liquidity_supply_keypair = Keypair::new(); let user_collateral_token_keypair = Keypair::new(); let user_transfer_authority_keypair = Keypair::new(); let dex_market_pubkey = if liquidity_mint_pubkey != lending_market.quote_token_mint { Some(dex_market.pubkey) } else { None }; let liquidity_mint_account = banks_client .get_account(liquidity_mint_pubkey) .await .unwrap() .unwrap(); let liquidity_mint = Mint::unpack(&liquidity_mint_account.data[..]).unwrap(); let rent = banks_client.get_rent().await.unwrap(); let mut transaction = Transaction::new_with_payer( &[ approve( &spl_token::id(), &user_liquidity_account, &user_transfer_authority_keypair.pubkey(), &user_accounts_owner.pubkey(), &[], reserve_amount, ) .unwrap(), create_account( &payer.pubkey(), &collateral_mint_keypair.pubkey(), rent.minimum_balance(Mint::LEN), Mint::LEN as u64, &spl_token::id(), ), create_account( &payer.pubkey(), &collateral_supply_keypair.pubkey(), rent.minimum_balance(Token::LEN), Token::LEN as u64, &spl_token::id(), ), create_account( &payer.pubkey(), &collateral_fees_receiver_keypair.pubkey(), rent.minimum_balance(Token::LEN), Token::LEN as u64, &spl_token::id(), ), create_account( &payer.pubkey(), &collateral_host_keypair.pubkey(), rent.minimum_balance(Token::LEN), Token::LEN as u64, &spl_token::id(), ), create_account( &payer.pubkey(), &liquidity_supply_keypair.pubkey(), rent.minimum_balance(Token::LEN), Token::LEN as u64, &spl_token::id(), ), create_account( &payer.pubkey(), &user_collateral_token_keypair.pubkey(), rent.minimum_balance(Token::LEN), Token::LEN as u64, &spl_token::id(), ), create_account( &payer.pubkey(), &reserve_pubkey, rent.minimum_balance(Reserve::LEN), Reserve::LEN as u64, &spl_token_lending::id(), ), init_reserve( spl_token_lending::id(), reserve_amount, config, user_liquidity_account, user_collateral_token_keypair.pubkey(), reserve_pubkey, liquidity_mint_pubkey, liquidity_supply_keypair.pubkey(), collateral_mint_keypair.pubkey(), collateral_supply_keypair.pubkey(), collateral_fees_receiver_keypair.pubkey(), lending_market.pubkey, lending_market.owner.pubkey(), user_transfer_authority_keypair.pubkey(), dex_market_pubkey, ), ], Some(&payer.pubkey()), ); let recent_blockhash = banks_client.get_recent_blockhash().await.unwrap(); transaction.sign( &vec![ payer, user_accounts_owner, &reserve_keypair, &lending_market.owner, &collateral_mint_keypair, &collateral_supply_keypair, &collateral_fees_receiver_keypair, &collateral_host_keypair, &liquidity_supply_keypair, &user_collateral_token_keypair, &user_transfer_authority_keypair, ], recent_blockhash, ); banks_client .process_transaction(transaction) .await .map(|_| Self { name, pubkey: reserve_pubkey, lending_market: lending_market.pubkey, config, liquidity_mint: liquidity_mint_pubkey, liquidity_mint_decimals: liquidity_mint.decimals, liquidity_supply: liquidity_supply_keypair.pubkey(), collateral_mint: collateral_mint_keypair.pubkey(), collateral_supply: collateral_supply_keypair.pubkey(), collateral_fees_receiver: collateral_fees_receiver_keypair.pubkey(), collateral_host: collateral_host_keypair.pubkey(), user_liquidity_account, user_collateral_account: user_collateral_token_keypair.pubkey(), dex_market: dex_market_pubkey, }) .map_err(|e| e.unwrap()) } pub async fn add_to_genesis( &self, banks_client: &mut BanksClient, genesis_accounts: &mut GenesisAccounts, ) { println!("{}_reserve: {}", self.name, self.pubkey); genesis_accounts .fetch_and_insert(banks_client, self.pubkey) .await; println!("{}_collateral_mint: {}", self.name, self.collateral_mint); genesis_accounts .fetch_and_insert(banks_client, self.collateral_mint) .await; println!( "{}_collateral_supply: {}", self.name, self.collateral_supply ); genesis_accounts .fetch_and_insert(banks_client, self.collateral_fees_receiver) .await; println!( "{}_collateral_fees_receiver: {}", self.name, self.collateral_fees_receiver ); genesis_accounts .fetch_and_insert(banks_client, self.collateral_supply) .await; if &self.name != "sol" { println!("{}_liquidity_mint: {}", self.name, self.liquidity_mint); genesis_accounts .fetch_and_insert(banks_client, self.liquidity_mint) .await; } println!("{}_liquidity_supply: {}", self.name, self.liquidity_supply); genesis_accounts .fetch_and_insert(banks_client, self.liquidity_supply) .await; println!( "{}_user_collateral: {}", self.name, self.user_collateral_account ); genesis_accounts .fetch_and_insert(banks_client, self.user_collateral_account) .await; println!( "{}_user_liquidity: {}", self.name, self.user_liquidity_account ); genesis_accounts .fetch_and_insert(banks_client, self.user_liquidity_account) .await; } pub async fn get_state(&self, banks_client: &mut BanksClient) -> Reserve { let reserve_account: Account = banks_client .get_account(self.pubkey) .await .unwrap() .unwrap(); Reserve::unpack(&reserve_account.data[..]).unwrap() } pub async fn validate_state(&self, banks_client: &mut BanksClient) { let reserve = self.get_state(banks_client).await; assert!(reserve.last_update_slot > 0); assert_eq!(PROGRAM_VERSION, reserve.version); assert_eq!(self.lending_market, reserve.lending_market); assert_eq!(self.liquidity_mint, reserve.liquidity.mint_pubkey); assert_eq!(self.liquidity_supply, reserve.liquidity.supply_pubkey); assert_eq!(self.collateral_mint, reserve.collateral.mint_pubkey); assert_eq!(self.collateral_supply, reserve.collateral.supply_pubkey); assert_eq!(self.config, reserve.config); let dex_market_coption = if let Some(dex_market_pubkey) = self.dex_market { COption::Some(dex_market_pubkey) } else { COption::None }; assert_eq!(dex_market_coption, reserve.dex_market); assert_eq!(reserve.cumulative_borrow_rate_wads, Decimal::one()); assert_eq!(reserve.liquidity.borrowed_amount_wads, Decimal::zero()); assert!(reserve.liquidity.available_amount > 0); assert!(reserve.collateral.mint_total_supply > 0); } } #[derive(Debug)] pub struct TestObligation { pub pubkey: Pubkey, pub token_mint: Pubkey, pub token_account: Pubkey, pub collateral_reserve: Pubkey, pub borrow_reserve: Pubkey, } impl TestObligation { #[allow(clippy::too_many_arguments)] pub async fn init( banks_client: &mut BanksClient, lending_market: &TestLendingMarket, deposit_reserve: &TestReserve, borrow_reserve: &TestReserve, payer: &Keypair, user_accounts_owner: &Keypair, ) -> Result { let obligation_keypair = Keypair::new(); let obligation_token_mint_keypair = Keypair::new(); let obligation_token_account_keypair = Keypair::new(); let obligation = TestObligation { pubkey: obligation_keypair.pubkey(), token_mint: obligation_token_mint_keypair.pubkey(), token_account: obligation_token_account_keypair.pubkey(), collateral_reserve: deposit_reserve.pubkey, borrow_reserve: borrow_reserve.pubkey, }; let rent = banks_client.get_rent().await.unwrap(); let mut transaction = Transaction::new_with_payer( &[ create_account( &payer.pubkey(), &obligation_token_mint_keypair.pubkey(), rent.minimum_balance(Mint::LEN), Mint::LEN as u64, &spl_token::id(), ), create_account( &payer.pubkey(), &obligation_token_account_keypair.pubkey(), rent.minimum_balance(Token::LEN), Token::LEN as u64, &spl_token::id(), ), create_account( &payer.pubkey(), &obligation_keypair.pubkey(), rent.minimum_balance(Obligation::LEN), Obligation::LEN as u64, &spl_token_lending::id(), ), init_obligation( spl_token_lending::id(), deposit_reserve.pubkey, borrow_reserve.pubkey, lending_market.pubkey, obligation.pubkey, obligation.token_mint, obligation.token_account, user_accounts_owner.pubkey(), ), ], Some(&payer.pubkey()), ); let recent_blockhash = banks_client.get_recent_blockhash().await.unwrap(); transaction.sign( &vec![ payer, &obligation_keypair, &obligation_token_account_keypair, &obligation_token_mint_keypair, ], recent_blockhash, ); banks_client .process_transaction(transaction) .await .map_err(|e| e.unwrap())?; Ok(obligation) } pub async fn get_state(&self, banks_client: &mut BanksClient) -> Obligation { let obligation_account: Account = banks_client .get_account(self.pubkey) .await .unwrap() .unwrap(); Obligation::unpack(&obligation_account.data[..]).unwrap() } pub async fn validate_state(&self, banks_client: &mut BanksClient) { let obligation = self.get_state(banks_client).await; assert_eq!(obligation.version, PROGRAM_VERSION); assert_eq!(obligation.collateral_reserve, self.collateral_reserve); assert!(obligation.cumulative_borrow_rate_wads >= Decimal::one()); assert_eq!(obligation.borrow_reserve, self.borrow_reserve); assert_eq!(obligation.token_mint, self.token_mint); } } pub struct TestQuoteMint { pub pubkey: Pubkey, pub authority: Keypair, pub decimals: u8, } pub fn add_usdc_mint(test: &mut ProgramTest) -> TestQuoteMint { let authority = Keypair::new(); let pubkey = Pubkey::from_str(USDC_MINT).unwrap(); let decimals = 6; test.add_packable_account( pubkey, u32::MAX as u64, &Mint { is_initialized: true, mint_authority: COption::Some(authority.pubkey()), decimals, ..Mint::default() }, &spl_token::id(), ); TestQuoteMint { pubkey, authority, decimals, } } pub fn add_srm_mint(test: &mut ProgramTest) -> TestQuoteMint { let authority = Keypair::new(); let pubkey = Pubkey::from_str(SRM_MINT).unwrap(); let decimals = 6; test.add_packable_account( pubkey, u32::MAX as u64, &Mint { is_initialized: true, mint_authority: COption::Some(authority.pubkey()), decimals, ..Mint::default() }, &spl_token::id(), ); TestQuoteMint { pubkey, authority, decimals, } } pub struct TestDexMarket { pub name: String, pub pubkey: Pubkey, pub bids_pubkey: Pubkey, pub asks_pubkey: Pubkey, } impl TestDexMarket { pub fn setup(test: &mut ProgramTest, market_pair: TestDexMarketPair) -> TestDexMarket { let (name, pubkey, bids_pubkey, asks_pubkey) = match market_pair { TestDexMarketPair::SOL_USDC => { ("sol_usdc", SOL_USDC_MARKET, SOL_USDC_BIDS, SOL_USDC_ASKS) } TestDexMarketPair::SRM_USDC => { ("srm_usdc", SRM_USDC_MARKET, SRM_USDC_BIDS, SRM_USDC_ASKS) } }; let pubkey = Pubkey::from_str(pubkey).unwrap(); let bids_pubkey = Pubkey::from_str(bids_pubkey).unwrap(); let asks_pubkey = Pubkey::from_str(asks_pubkey).unwrap(); test.add_account_with_file_data( pubkey, u32::MAX as u64, Pubkey::new(&[0; 32]), &format!("{}_dex_market.bin", name), ); test.add_account_with_file_data( bids_pubkey, u32::MAX as u64, Pubkey::new(&[0; 32]), &format!("{}_dex_market_bids.bin", name), ); test.add_account_with_file_data( asks_pubkey, u32::MAX as u64, Pubkey::new(&[0; 32]), &format!("{}_dex_market_asks.bin", name), ); Self { name: name.to_string(), pubkey, bids_pubkey, asks_pubkey, } } pub async fn add_to_genesis( &self, banks_client: &mut BanksClient, genesis_accounts: &mut GenesisAccounts, ) { println!("{}_dex_market: {}", self.name, self.pubkey); genesis_accounts .fetch_and_insert(banks_client, self.pubkey) .await; println!("{}_dex_market_bids: {}", self.name, self.bids_pubkey); genesis_accounts .fetch_and_insert(banks_client, self.bids_pubkey) .await; println!("{}_dex_market_asks: {}", self.name, self.asks_pubkey); genesis_accounts .fetch_and_insert(banks_client, self.asks_pubkey) .await; } } pub async fn create_and_mint_to_token_account( banks_client: &mut BanksClient, mint_pubkey: Pubkey, mint_authority: Option<&Keypair>, payer: &Keypair, authority: Pubkey, amount: u64, ) -> Pubkey { if let Some(mint_authority) = mint_authority { let account_pubkey = create_token_account(banks_client, mint_pubkey, &payer, Some(authority), None).await; mint_to( banks_client, mint_pubkey, &payer, account_pubkey, mint_authority, amount, ) .await; account_pubkey } else { create_token_account( banks_client, mint_pubkey, &payer, Some(authority), Some(amount), ) .await } } pub async fn create_token_account( banks_client: &mut BanksClient, mint_pubkey: Pubkey, payer: &Keypair, authority: Option, native_amount: Option, ) -> Pubkey { let token_keypair = Keypair::new(); let token_pubkey = token_keypair.pubkey(); let authority_pubkey = authority.unwrap_or_else(|| payer.pubkey()); let rent = banks_client.get_rent().await.unwrap(); let lamports = rent.minimum_balance(Token::LEN) + native_amount.unwrap_or_default(); let mut transaction = Transaction::new_with_payer( &[ create_account( &payer.pubkey(), &token_pubkey, lamports, Token::LEN as u64, &spl_token::id(), ), spl_token::instruction::initialize_account( &spl_token::id(), &token_pubkey, &mint_pubkey, &authority_pubkey, ) .unwrap(), ], Some(&payer.pubkey()), ); let recent_blockhash = banks_client.get_recent_blockhash().await.unwrap(); transaction.sign(&[&payer, &token_keypair], recent_blockhash); assert_matches!(banks_client.process_transaction(transaction).await, Ok(())); token_pubkey } pub async fn mint_to( banks_client: &mut BanksClient, mint_pubkey: Pubkey, payer: &Keypair, account_pubkey: Pubkey, authority: &Keypair, amount: u64, ) { let mut transaction = Transaction::new_with_payer( &[spl_token::instruction::mint_to( &spl_token::id(), &mint_pubkey, &account_pubkey, &authority.pubkey(), &[], amount, ) .unwrap()], Some(&payer.pubkey()), ); let recent_blockhash = banks_client.get_recent_blockhash().await.unwrap(); transaction.sign(&[payer, authority], recent_blockhash); assert_matches!(banks_client.process_transaction(transaction).await, Ok(())); } pub async fn get_token_balance(banks_client: &mut BanksClient, pubkey: Pubkey) -> u64 { let token: Account = banks_client.get_account(pubkey).await.unwrap().unwrap(); spl_token::state::Account::unpack(&token.data[..]) .unwrap() .amount }