mod program_test; use { bincode::deserialize, mpl_token_metadata::accounts::{MasterEdition, Metadata}, program_test::StablebondProgramTest, solana_program_test::{tokio, ProgramTestContext}, solana_sdk::{ clock::{Clock, UnixTimestamp}, compute_budget::ComputeBudgetInstruction, pubkey::Pubkey, signature::{Keypair, Signer}, sysvar, transaction::Transaction, }, spl_associated_token_account::{ get_associated_token_address, get_associated_token_address_with_program_id, }, stablebond_sdk::{ find_bond_pda, find_config_pda, find_delegate_pda, find_issuance_pda, find_payment_feed_pda, find_payment_pda, find_payout_pda, find_purchase_order_pda, instructions::{ CreatePurchaseOrder, CreatePurchaseOrderInstructionArgs, InitializeBond, InitializeBondInstructionArgs, InitializeConfig, InitializeConfigInstructionArgs, InitializeDelegate, InitializeIssuance, InitializeIssuanceInstructionArgs, InitializePaymentFeed, InitializePaymentFeedInstructionArgs, PayoutIssuance, RedeemPurchaseOrder, StartIssuance, }, types::{OracleSetup, PaymentFeedType}, }, std::{ borrow::Borrow, time::{SystemTime, UNIX_EPOCH}, }, }; struct ExecuteTestContext { context: ProgramTestContext, user: Keypair, bond_mint: Keypair, usdc_mint: Pubkey, payment_base_price_feed_account: Pubkey, payment_quote_price_feed_account: Option, nft_collection_mint: Pubkey, admin: Keypair, } impl ExecuteTestContext { #[allow(dead_code)] pub fn get_current_time() -> i64 { let now = SystemTime::now(); now.duration_since(UNIX_EPOCH).unwrap().as_secs() as i64 } #[allow(dead_code)] pub async fn get_bincode_account( &mut self, address: &Pubkey, ) -> T { self.context .banks_client .get_account(*address) .await .unwrap() .map(|a| deserialize::(&a.data.borrow()).unwrap()) .expect(format!("GET-TEST-ACCOUNT-ERROR: Account {}", address).as_str()) } #[allow(dead_code)] pub async fn get_clock(&mut self) -> Clock { self.get_bincode_account::(&sysvar::clock::id()) .await } #[allow(dead_code)] pub async fn advance_clock_by_slots(&mut self, slots: u64) { let mut clock: Clock = self.get_clock().await; println!("clock slot before: {}", clock.slot); self.context.warp_to_slot(clock.slot + slots).unwrap(); clock = self.get_clock().await; println!("clock slot after: {}", clock.slot); } #[allow(dead_code)] pub async fn advance_clock_past_timestamp(&mut self, unix_timestamp: UnixTimestamp) { let mut clock: Clock = self.get_clock().await; let mut n = 1; while clock.unix_timestamp <= unix_timestamp { // Since the exact time is not deterministic keep wrapping by arbitrary 400 // slots until we pass the requested timestamp self.context.warp_to_slot(clock.slot + 400).unwrap(); n = n + 1; clock = self.get_clock().await; } } } async fn setup() -> ExecuteTestContext { let pt = StablebondProgramTest::start_new().await; let usdc_mint = pt.mints[0].payment_mint; let bond_mint = pt.bond_mints[0].insecure_clone(); let admin = pt.admin; let user = pt.users[0].insecure_clone(); let payment_base_price_feed_account = pt.stub_payment_feed.base_price_feed; let payment_quote_price_feed_account = None; let nft_collection_mint = pt.nft_collection_mint; let initialize_config_ix_args = InitializeConfigInstructionArgs { oracle: OracleSetup::Stub, usdc_mxn_payment_feed: pt.usdc_mxn_payment_feed.clone(), usdc_usd_payment_feed: pt.usdc_usd_payment_feed, }; let initialize_config_ix = InitializeConfig { admin_wallet: admin.pubkey(), config_account: find_config_pda().0, config_token_account: get_associated_token_address( &find_config_pda().0, &nft_collection_mint.pubkey(), ), mint_account: nft_collection_mint.pubkey(), payment_mint_account: pt.usdc_mxn_payment_feed.payment_mint, metadata_account: Metadata::find_pda(&nft_collection_mint.pubkey()).0, master_edition_account: MasterEdition::find_pda(&nft_collection_mint.pubkey()).0, usdc_mxn_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcMxn).0, usdc_usd_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).0, token_program: spl_token::id(), associated_token_program: spl_associated_token_account::id(), metadata_program: mpl_token_metadata::ID, system_program: solana_program::system_program::id(), } .instruction(initialize_config_ix_args); let initialize_delegate_ix = InitializeDelegate { config_account: find_config_pda().0, admin_wallet: admin.pubkey(), delegate_wallet: admin.pubkey(), delegate_account: find_delegate_pda(admin.pubkey()).0, system_program: solana_program::system_program::id(), } .instruction(); let initialize_payment_feed_args = InitializePaymentFeedInstructionArgs { payment_feed_info: pt.stub_payment_feed, }; let initialize_payment_feed_ix = InitializePaymentFeed { delegate_wallet: admin.pubkey(), delegate_account: find_delegate_pda(admin.pubkey()).0, payment_feed_account: find_payment_feed_pda(PaymentFeedType::Stub).0, payment_mint_account: usdc_mint, system_program: solana_program::system_program::id(), } .instruction(initialize_payment_feed_args); let initialize_bond_ix_args = InitializeBondInstructionArgs { name: "Test Mint".to_string(), symbol: "TM".to_string(), uri: "https://test.com".to_string(), payment_feed_type: PaymentFeedType::Stub, cutoff_in_seconds: 60 * 60 * 24, }; let initialize_bond_ix = InitializeBond { delegate_account: find_delegate_pda(admin.pubkey()).0, delegate_wallet: admin.pubkey(), bond_account: find_bond_pda(bond_mint.pubkey()).0, mint_account: bond_mint.pubkey(), metadata_account: Metadata::find_pda(&bond_mint.pubkey()).0, token2022_program: spl_token_2022::id(), metadata_program: mpl_token_metadata::ID, sysvar_instructions: solana_program::sysvar::instructions::id(), system_program: solana_program::system_program::id(), } .instruction(initialize_bond_ix_args); let current_time = StablebondProgramTest::get_current_time(); let one_minute_ago: i64 = current_time - 60; let two_days_from_now: i64 = 60 * 60 * 24 * 2; let initialize_issuance_ix_args = InitializeIssuanceInstructionArgs { liquidity: 100_000_000, interest_rate_bps: 11_00, estimated_start_datetime: one_minute_ago, length_in_seconds: two_days_from_now, }; let issuance_number = 1; let bond_pda = find_bond_pda(bond_mint.pubkey()).0; let issuance_pda = find_issuance_pda(bond_pda, issuance_number).0; let payment_pda = find_payment_pda(issuance_pda).0; let payout_pda = find_payout_pda(issuance_pda).0; let initialize_issuance_ix = InitializeIssuance { delegate_wallet: admin.pubkey(), delegate_account: find_delegate_pda(admin.pubkey()).0, bond_account: find_bond_pda(bond_mint.pubkey()).0, upcoming_issuance_account: issuance_pda, payment_feed_account: find_payment_feed_pda(PaymentFeedType::Stub).0, payment_mint_account: usdc_mint, payment_account: payment_pda, payment_token_account: get_associated_token_address(&payment_pda, &usdc_mint), payout_account: payout_pda, payout_token_account: get_associated_token_address(&payout_pda, &usdc_mint), associated_token_program: spl_associated_token_account::id(), token_program: spl_token::id(), system_program: solana_program::system_program::id(), current_issuance_account: None, } .instruction(initialize_issuance_ix_args); let start_issuance_ix = StartIssuance { delegate_account: find_delegate_pda(admin.pubkey()).0, delegate_wallet: admin.pubkey(), bond_account: find_bond_pda(bond_mint.pubkey()).0, issuance_account: find_issuance_pda(find_bond_pda(bond_mint.pubkey()).0, 1).0, mint_account: bond_mint.pubkey(), token2022_program: spl_token_2022::id(), system_program: solana_program::system_program::id(), } .instruction(); let ixs = &[ initialize_config_ix, initialize_delegate_ix, initialize_payment_feed_ix, initialize_bond_ix, initialize_issuance_ix, start_issuance_ix, ]; let mut context = pt.context; let transaction = Transaction::new_signed_with_payer( ixs, Some(&admin.pubkey()), &[&admin, &nft_collection_mint, &bond_mint], context.last_blockhash, ); context .banks_client .process_transaction(transaction) .await .unwrap(); ExecuteTestContext { context, user, bond_mint, usdc_mint, payment_base_price_feed_account, payment_quote_price_feed_account, nft_collection_mint: nft_collection_mint.pubkey(), admin, } } #[tokio::test] async fn redeem_purchase_order() { let mut et = setup().await; let recent_blockhash = et.context.last_blockhash; let payment_amount = 1_000_000; let create_purchase_ix_args = CreatePurchaseOrderInstructionArgs { amount: payment_amount, }; let bond_account = find_bond_pda(et.bond_mint.pubkey()).0; let issuance_account = find_issuance_pda(bond_account, 1).0; let payment_account = find_payment_pda(issuance_account).0; let payment_token_account = get_associated_token_address(&payment_account, &et.usdc_mint); // let user_token_account = get_associated_token_address_with_program_id( // &et.user.pubkey(), // &et.bond_mint.pubkey(), // &spl_token_2022::id(), // ); let user_payment_token_account = get_associated_token_address(&et.user.pubkey(), &et.usdc_mint); let nft_mint = Keypair::new(); let user_nft_token_account = get_associated_token_address(&et.user.pubkey(), &nft_mint.pubkey()); let purchase_order_vault_account = find_purchase_order_pda(nft_mint.pubkey()).0; let create_purchase_order_ix = CreatePurchaseOrder { user_wallet: et.user.pubkey(), config_account: find_config_pda().0, bond_account, issuance_account, payment_account, payment_token_account, user_payment_token_account, user_nft_token_account, nft_mint_account: nft_mint.pubkey(), nft_metadata_account: Metadata::find_pda(&nft_mint.pubkey()).0, nft_master_edition_account: MasterEdition::find_pda(&nft_mint.pubkey()).0, nft_collection_mint: et.nft_collection_mint, nft_collection_metadata_account: Metadata::find_pda(&et.nft_collection_mint).0, nft_collection_master_edition_account: MasterEdition::find_pda(&et.nft_collection_mint).0, purchase_order_vault_account, payment_mint_account: et.usdc_mint, payment_feed_account: find_payment_feed_pda(PaymentFeedType::Stub).0, payment_base_price_feed_account: et.payment_base_price_feed_account, payment_quote_price_feed_account: et.payment_quote_price_feed_account, associated_token_program: spl_associated_token_account::id(), token_program: spl_token::id(), token2022_program: spl_token_2022::id(), sysvar_instructions: solana_program::sysvar::instructions::id(), metadata_program: mpl_token_metadata::ID, system_program: solana_program::system_program::id(), } .instruction(create_purchase_ix_args); let transaction = Transaction::new_signed_with_payer( &[create_purchase_order_ix], Some(&et.user.pubkey()), &[&et.user, &nft_mint], recent_blockhash, ); assert!(et .context .banks_client .process_transaction(transaction) .await .is_ok()); let current_time = et.get_clock().await.unix_timestamp; let two_days_from_now: i64 = 60 * 60 * 24 * 2; et.advance_clock_past_timestamp(current_time + two_days_from_now + 1) .await; let delegate_wallet = et.admin.pubkey(); let delegate_account = find_delegate_pda(delegate_wallet).0; let payout_account = find_payout_pda(issuance_account).0; let payout_token_account = get_associated_token_address(&payout_account, &et.usdc_mint); let payment_mint_account = et.usdc_mint; let payout_issuance_ix = PayoutIssuance { delegate_wallet, delegate_wallet_token_account: get_associated_token_address( &delegate_wallet, &et.usdc_mint, ), delegate_account, bond_account, issuance_account, mint_account: et.bond_mint.pubkey(), payment_mint_account, payout_account, payout_token_account, payment_feed_account: find_payment_feed_pda(PaymentFeedType::Stub).0, payment_base_price_feed_account: et.payment_base_price_feed_account, payment_quote_price_feed_account: et.payment_quote_price_feed_account, token_program: spl_token::id(), system_program: solana_program::system_program::id(), } .instruction(); let current_time = et.get_clock().await.unix_timestamp; let one_minute_ago: i64 = current_time - 60; let two_days_from_now: i64 = 60 * 60 * 24 * 2; let initialize_issuance_ix_args = InitializeIssuanceInstructionArgs { liquidity: 100_000_000, interest_rate_bps: 11_00, estimated_start_datetime: one_minute_ago, length_in_seconds: two_days_from_now, }; let bond_pda = find_bond_pda(et.bond_mint.pubkey()).0; let issuance_two_pda = find_issuance_pda(bond_pda, 2).0; let payment_pda = find_payment_pda(issuance_two_pda).0; let payout_pda = find_payout_pda(issuance_two_pda).0; let initialize_issuance_ix = InitializeIssuance { delegate_wallet: et.admin.pubkey(), delegate_account: find_delegate_pda(et.admin.pubkey()).0, bond_account: find_bond_pda(et.bond_mint.pubkey()).0, upcoming_issuance_account: issuance_two_pda, payment_feed_account: find_payment_feed_pda(PaymentFeedType::Stub).0, payment_mint_account: et.usdc_mint, payment_account: payment_pda, payment_token_account: get_associated_token_address(&payment_pda, &et.usdc_mint), payout_account: payout_pda, payout_token_account: get_associated_token_address(&payout_pda, &et.usdc_mint), associated_token_program: spl_associated_token_account::id(), token_program: spl_token::id(), system_program: solana_program::system_program::id(), current_issuance_account: Some(find_issuance_pda(bond_pda, 1).0), } .instruction(initialize_issuance_ix_args); let start_issuance_ix = StartIssuance { delegate_account: find_delegate_pda(et.admin.pubkey()).0, delegate_wallet: et.admin.pubkey(), bond_account: find_bond_pda(et.bond_mint.pubkey()).0, issuance_account: issuance_two_pda, mint_account: et.bond_mint.pubkey(), token2022_program: spl_token_2022::id(), system_program: solana_program::system_program::id(), } .instruction(); let redeem_purchase_order_ix = RedeemPurchaseOrder { user_wallet: et.user.pubkey(), bond_account, mint_account: et.bond_mint.pubkey(), issuance_account: issuance_two_pda, purchase_order_vault_account, user_token_account: get_associated_token_address_with_program_id( &et.user.pubkey(), &et.bond_mint.pubkey(), &spl_token_2022::id(), ), user_nft_token_account, nft_mint_account: nft_mint.pubkey(), nft_metadata_account: Metadata::find_pda(&nft_mint.pubkey()).0, nft_master_edition_account: MasterEdition::find_pda(&nft_mint.pubkey()).0, nft_collection_metadata_account: Metadata::find_pda(&et.nft_collection_mint).0, associated_token_program: spl_associated_token_account::id(), token_program: spl_token::id(), token2022_program: spl_token_2022::id(), metadata_program: mpl_token_metadata::ID, system_program: solana_program::system_program::id(), } .instruction(); let transaction = Transaction::new_signed_with_payer( &[ ComputeBudgetInstruction::set_compute_unit_limit(300_000), initialize_issuance_ix, start_issuance_ix, payout_issuance_ix, redeem_purchase_order_ix, ], Some(&et.user.pubkey()), &[&et.admin, &et.user], et.context.last_blockhash, ); assert!(et .context .banks_client .process_transaction(transaction) .await .is_ok()); }