mod program_test; use { mpl_token_metadata::accounts::{MasterEdition, Metadata}, program_test::StablebondProgramTest, solana_program_test::{tokio, ProgramTestContext}, solana_sdk::{ instruction::InstructionError, pubkey::Pubkey, signature::{Keypair, Signer}, transaction::{Transaction, TransactionError}, }, spl_associated_token_account::get_associated_token_address, stablebond_sdk::{ accounts::Issuance, errors::StablebondError, find_bond_pda, find_config_pda, find_delegate_pda, find_issuance_pda, find_payment_feed_pda, find_payment_pda, find_payout_pda, instructions::{ InitializeBond, InitializeBondInstructionArgs, InitializeConfig, InitializeConfigInstructionArgs, InitializeDelegate, InitializeIssuance, InitializeIssuanceInstructionArgs, }, types::{OracleSetup, PaymentFeedType}, }, }; struct ExecuteTestContext { context: ProgramTestContext, admin: Keypair, bond_mint: Keypair, usdc_mint: Pubkey, } 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 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_bond_ix_args = InitializeBondInstructionArgs { name: "Test Mint".to_string(), symbol: "TM".to_string(), uri: "https://test.com".to_string(), payment_feed_type: PaymentFeedType::UsdcUsd, 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 ixs = &[ initialize_config_ix, initialize_delegate_ix, initialize_bond_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, admin, bond_mint, usdc_mint, } } #[tokio::test] async fn initialize_issuance() { let mut et = setup().await; let current_time = StablebondProgramTest::get_current_time(); let one_day_from_now: i64 = current_time + 60 * 60 * 24; 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_day_from_now, length_in_seconds: two_days_from_now, }; let issuance_number = 1; let bond_pda = find_bond_pda(et.bond_mint.pubkey()).0; let upcoming_issuance_pda = find_issuance_pda(bond_pda, issuance_number).0; let payment_pda = find_payment_pda(upcoming_issuance_pda).0; let payout_pda = find_payout_pda(upcoming_issuance_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: upcoming_issuance_pda, payment_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).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: None, } .instruction(initialize_issuance_ix_args); let recent_blockhash = et.context.last_blockhash; let transaction = Transaction::new_signed_with_payer( &[initialize_issuance_ix], Some(&et.admin.pubkey()), &[&et.admin], recent_blockhash, ); assert!(et .context .banks_client .process_transaction(transaction) .await .is_ok()); // validate issuance values after starting let account = et .context .banks_client .get_account(upcoming_issuance_pda) .await .unwrap() .unwrap(); let issuance = Issuance::from_bytes(&account.data).unwrap(); // validate issuances are equal assert_eq!( issuance.status, stablebond_sdk::types::IssuanceStatus::Upcoming, ); assert_eq!(issuance.parent_bond, bond_pda); assert_eq!(issuance.interest_rate_bps, 11_00); assert_eq!(issuance.liquidity, 100_000_000); assert_eq!(issuance.estimated_start_datetime, one_day_from_now); assert_eq!(issuance.length_in_seconds, two_days_from_now); assert_eq!(issuance.actual_start_datetime, 0); } #[tokio::test] #[allow(deprecated)] async fn fail_initializing_issuance_if_prev_hasnt_started() { let mut et = setup().await; let current_time = StablebondProgramTest::get_current_time(); let one_day_from_now: i64 = current_time + 60 * 60 * 24; 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_day_from_now, length_in_seconds: two_days_from_now, }; let issuance_number = 1; let bond_pda = find_bond_pda(et.bond_mint.pubkey()).0; let upcoming_issuance_pda = find_issuance_pda(bond_pda, issuance_number).0; let payment_pda = find_payment_pda(upcoming_issuance_pda).0; let payout_pda = find_payout_pda(upcoming_issuance_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, payment_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).0, upcoming_issuance_account: upcoming_issuance_pda, payment_account: payment_pda, payment_mint_account: et.usdc_mint, 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: None, } .instruction(initialize_issuance_ix_args); let recent_blockhash = et.context.last_blockhash; let transaction = Transaction::new_signed_with_payer( &[initialize_issuance_ix], Some(&et.admin.pubkey()), &[&et.admin], recent_blockhash, ); assert!(et .context .banks_client .process_transaction(transaction) .await .is_ok()); let initialize_issuance_ix_args = InitializeIssuanceInstructionArgs { liquidity: 100_000_000, interest_rate_bps: 11_00, estimated_start_datetime: one_day_from_now, length_in_seconds: two_days_from_now, }; let issuance_number = 2; let bond_pda = find_bond_pda(et.bond_mint.pubkey()).0; let upcoming_issuance_pda = find_issuance_pda(bond_pda, issuance_number).0; let payment_pda = find_payment_pda(upcoming_issuance_pda).0; let payout_pda = find_payout_pda(upcoming_issuance_pda).0; let current_issuance_pda = find_issuance_pda(bond_pda, 1).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: upcoming_issuance_pda, payment_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).0, payment_account: payment_pda, payment_mint_account: et.usdc_mint, 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(current_issuance_pda), } .instruction(initialize_issuance_ix_args); let recent_blockhash = et.context.last_blockhash; let transaction = Transaction::new_signed_with_payer( &[initialize_issuance_ix], Some(&et.admin.pubkey()), &[&et.admin], recent_blockhash, ); let error = et .context .banks_client .process_transaction(transaction) .await .err() .unwrap() .unwrap(); match error { TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { let program_error = StablebondError::InvalidIssuance as u32; assert_eq!(error_index, program_error + 6000); } _ => { panic!( "Wrong error occurs while testing fail_initializing_issuance_if_prev_hasnt_started" ); } } } #[tokio::test] #[allow(deprecated)] async fn fail_initializing_issuance_invalid_interest_rate_101() { let mut et = setup().await; let current_time = StablebondProgramTest::get_current_time(); let one_day_from_now: i64 = current_time + 60 * 60 * 24; let two_days_from_now: i64 = 60 * 60 * 24 * 2; let initialize_issuance_ix_args = InitializeIssuanceInstructionArgs { liquidity: 100_000_000, interest_rate_bps: 10_100, estimated_start_datetime: one_day_from_now, length_in_seconds: two_days_from_now, }; let issuance_number = 1; let bond_pda = find_bond_pda(et.bond_mint.pubkey()).0; let upcoming_issuance_pda = find_issuance_pda(bond_pda, issuance_number).0; let payment_pda = find_payment_pda(upcoming_issuance_pda).0; let payout_pda = find_payout_pda(upcoming_issuance_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: upcoming_issuance_pda, payment_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).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: None, } .instruction(initialize_issuance_ix_args); let recent_blockhash = et.context.last_blockhash; let transaction = Transaction::new_signed_with_payer( &[initialize_issuance_ix], Some(&et.admin.pubkey()), &[&et.admin], recent_blockhash, ); let error = et .context .banks_client .process_transaction(transaction) .await .err() .unwrap() .unwrap(); match error { TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { let program_error = StablebondError::InvalidInterestRate as u32; assert_eq!(error_index, program_error + 6000); } _ => { panic!("Wrong error occurs while testing invalid interest rate"); } } } #[tokio::test] #[allow(deprecated)] async fn fail_initializing_issuance_invalid_interest_rate_negativ() { let mut et = setup().await; let current_time = StablebondProgramTest::get_current_time(); let one_day_from_now: i64 = current_time + 60 * 60 * 24; let two_days_from_now: i64 = 60 * 60 * 24 * 2; let initialize_issuance_ix_args = InitializeIssuanceInstructionArgs { liquidity: 100_000_000, interest_rate_bps: -1, estimated_start_datetime: one_day_from_now, length_in_seconds: two_days_from_now, }; let issuance_number = 1; let bond_pda = find_bond_pda(et.bond_mint.pubkey()).0; let upcoming_issuance_pda = find_issuance_pda(bond_pda, issuance_number).0; let payment_pda = find_payment_pda(upcoming_issuance_pda).0; let payout_pda = find_payout_pda(upcoming_issuance_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: upcoming_issuance_pda, payment_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).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: None, } .instruction(initialize_issuance_ix_args); let recent_blockhash = et.context.last_blockhash; let transaction = Transaction::new_signed_with_payer( &[initialize_issuance_ix], Some(&et.admin.pubkey()), &[&et.admin], recent_blockhash, ); let error = et .context .banks_client .process_transaction(transaction) .await .err() .unwrap() .unwrap(); match error { TransactionError::InstructionError(_, InstructionError::Custom(error_index)) => { let program_error = StablebondError::InvalidInterestRate as u32; assert_eq!(error_index, program_error + 6000); } _ => { panic!("Wrong error occurs while testing invalid interest rate"); } } } #[tokio::test] async fn initialize_issuance_interest_rate_0() { let mut et = setup().await; let current_time = StablebondProgramTest::get_current_time(); let one_day_from_now: i64 = current_time + 60 * 60 * 24; 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_day_from_now, length_in_seconds: two_days_from_now, }; let issuance_number = 1; let bond_pda = find_bond_pda(et.bond_mint.pubkey()).0; let upcoming_issuance_pda = find_issuance_pda(bond_pda, issuance_number).0; let payment_pda = find_payment_pda(upcoming_issuance_pda).0; let payout_pda = find_payout_pda(upcoming_issuance_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: upcoming_issuance_pda, payment_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).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: None, } .instruction(initialize_issuance_ix_args); let recent_blockhash = et.context.last_blockhash; let transaction = Transaction::new_signed_with_payer( &[initialize_issuance_ix], Some(&et.admin.pubkey()), &[&et.admin], recent_blockhash, ); assert!(et .context .banks_client .process_transaction(transaction) .await .is_ok()); } #[tokio::test] async fn initialize_issuance_interest_rate_liquidity_max() { let mut et = setup().await; let current_time = StablebondProgramTest::get_current_time(); let one_day_from_now: i64 = current_time + 60 * 60 * 24; let two_days_from_now: i64 = 60 * 60 * 24 * 2; let initialize_issuance_ix_args = InitializeIssuanceInstructionArgs { liquidity: u64::MAX, interest_rate_bps: 00_00, estimated_start_datetime: one_day_from_now, length_in_seconds: two_days_from_now, }; let issuance_number = 1; let bond_pda = find_bond_pda(et.bond_mint.pubkey()).0; let upcoming_issuance_pda = find_issuance_pda(bond_pda, issuance_number).0; let payment_pda = find_payment_pda(upcoming_issuance_pda).0; let payout_pda = find_payout_pda(upcoming_issuance_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: upcoming_issuance_pda, payment_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).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: None, } .instruction(initialize_issuance_ix_args); let recent_blockhash = et.context.last_blockhash; let transaction = Transaction::new_signed_with_payer( &[initialize_issuance_ix], Some(&et.admin.pubkey()), &[&et.admin], recent_blockhash, ); assert!(et .context .banks_client .process_transaction(transaction) .await .is_ok()); } #[tokio::test] async fn initialize_issuance_interest_rate_liquidity_0() { let mut et = setup().await; let current_time = StablebondProgramTest::get_current_time(); let one_day_from_now: i64 = current_time + 60 * 60 * 24; let two_days_from_now: i64 = 60 * 60 * 24 * 2; let initialize_issuance_ix_args = InitializeIssuanceInstructionArgs { liquidity: 0, interest_rate_bps: 00_00, estimated_start_datetime: one_day_from_now, length_in_seconds: two_days_from_now, }; let issuance_number = 1; let bond_pda = find_bond_pda(et.bond_mint.pubkey()).0; let upcoming_issuance_pda = find_issuance_pda(bond_pda, issuance_number).0; let payment_pda = find_payment_pda(upcoming_issuance_pda).0; let payout_pda = find_payout_pda(upcoming_issuance_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: upcoming_issuance_pda, payment_feed_account: find_payment_feed_pda(PaymentFeedType::UsdcUsd).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: None, } .instruction(initialize_issuance_ix_args); let recent_blockhash = et.context.last_blockhash; let transaction = Transaction::new_signed_with_payer( &[initialize_issuance_ix], Some(&et.admin.pubkey()), &[&et.admin], recent_blockhash, ); assert!(et .context .banks_client .process_transaction(transaction) .await .is_ok()); }