From 5c3b2c118949e6264388bc58876997d49f64a382 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Mon, 1 Aug 2022 16:55:17 +0200 Subject: [PATCH] mc/kill flash loan 1 & 2 and rename flash loan 3 to flash loan (#131) * remove flash loan 1 & 2 Signed-off-by: microwavedcola1 * rename flash loan 3 to flash loan * fix test Signed-off-by: microwavedcola1 --- .../mango-v4/src/instructions/flash_loan.rs | 658 +++++++++--------- .../mango-v4/src/instructions/flash_loan2.rs | 328 --------- .../mango-v4/src/instructions/flash_loan3.rs | 361 ---------- programs/mango-v4/src/instructions/mod.rs | 4 - programs/mango-v4/src/lib.rs | 35 +- .../tests/program_test/mango_client.rs | 215 +----- programs/mango-v4/tests/test_margin_trade.rs | 590 +--------------- ts/client/src/client.ts | 187 +---- ts/client/src/mango_v4.ts | 542 ++++++--------- .../{mb-flash-loan-3.ts => mb-flash-loan.ts} | 8 +- 10 files changed, 556 insertions(+), 2372 deletions(-) delete mode 100644 programs/mango-v4/src/instructions/flash_loan2.rs delete mode 100644 programs/mango-v4/src/instructions/flash_loan3.rs rename ts/client/src/scripts/{mb-flash-loan-3.ts => mb-flash-loan.ts} (98%) diff --git a/programs/mango-v4/src/instructions/flash_loan.rs b/programs/mango-v4/src/instructions/flash_loan.rs index 255e9211a..5feec92a1 100644 --- a/programs/mango-v4/src/instructions/flash_loan.rs +++ b/programs/mango-v4/src/instructions/flash_loan.rs @@ -1,397 +1,361 @@ use crate::accounts_zerocopy::*; -use crate::error::MangoError; -use crate::logs::{MarginTradeLog, TokenBalanceLog}; +use crate::error::*; +use crate::group_seeds; +use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog}; +use crate::state::MangoAccount; use crate::state::{ - compute_health, new_fixed_order_account_retriever, AccountLoaderDynamic, AccountRetriever, - Bank, Group, HealthType, MangoAccount, MangoAccountRefMut, + compute_health, compute_health_from_fixed_accounts, new_fixed_order_account_retriever, + AccountLoaderDynamic, AccountRetriever, Bank, Group, HealthType, TokenIndex, }; -use crate::{group_seeds, Mango}; +use crate::util::checked_math as cm; use anchor_lang::prelude::*; +use anchor_lang::solana_program::sysvar::instructions as tx_instructions; use anchor_spl::token::{self, Token, TokenAccount}; use fixed::types::I80F48; -use solana_program::instruction::Instruction; -use std::cell::Ref; -use std::collections::HashMap; -/// The flash loan instruction +/// Sets up mango vaults for flash loan /// -/// In addition to these accounts, there must be a sequence of remaining_accounts: -/// 1. health_accounts: accounts needed for health checking -/// 2. per cpi -/// 2.a. target_program_id: the target program account -/// 2.b. target_accounts: the accounts to pass to the target program -/// -/// Every vault address listed in 3. must also have the matching bank and oracle appear in 1. -/// -/// Every vault that is to be withdrawn from must appear in the `withdraws` instruction argument. -/// The corresponding bank may be used as an authority for vault withdrawals. +/// In addition to these accounts, there must be remaining_accounts: +/// 1. N banks (writable) +/// 2. N vaults (writable), matching the banks +/// 3. N token accounts (writable), in the same order as the vaults, +/// the loaned funds are transfered into these #[derive(Accounts)] -pub struct FlashLoan<'info> { +pub struct FlashLoanBegin<'info> { pub group: AccountLoader<'info, Group>, + pub token_program: Program<'info, Token>, - #[account(mut, has_one = group, has_one = owner)] + /// Instructions Sysvar for instruction introspection + #[account(address = tx_instructions::ID)] + pub instructions: UncheckedAccount<'info>, +} + +/// Finalizes a flash loan +/// +/// In addition to these accounts, there must be remaining_accounts: +/// 1. health accounts, and every bank that also appeared in FlashLoanBegin must be writable +/// 2. N vaults (writable), matching what was in FlashLoanBegin +/// 3. N token accounts (writable), matching what was in FlashLoanBegin; +/// the `owner` must have authority to transfer tokens out of them +#[derive(Accounts)] +pub struct FlashLoanEnd<'info> { + #[account(mut, has_one = owner)] pub account: AccountLoaderDynamic<'info, MangoAccount>, pub owner: Signer<'info>, + pub token_program: Program<'info, Token>, } -#[derive(Debug)] -struct AllowedVault { - /// index of the vault in cpi_ais - vault_cpi_ai_index: usize, - /// index of the bank in health_ais - bank_health_ai_index: usize, - /// raw index into account.tokens - raw_token_index: usize, - /// vault amount before cpi - pre_amount: u64, - /// requested withdraw amount - withdraw_amount: u64, - /// amount of withdraw request that is a loan - loan_amount: I80F48, -} - -#[derive(AnchorDeserialize, AnchorSerialize, Clone, Copy, Debug)] -pub struct FlashLoanWithdraw { - /// Account index of the vault to withdraw from in the target_accounts section. - /// Index is counted after health accounts. - pub index: u8, - /// Requested withdraw amount. - pub amount: u64, -} - -#[derive(AnchorDeserialize, AnchorSerialize, Debug)] -pub struct CpiData { - pub account_start: u8, - pub data: Vec, -} - -/// - `withdraws` is a list of FlashLoanWithdraw requests. -/// - `cpi_datas` is a list of bytes per cpi to call the target_program_id with. -/// - `cpi_account_starts` is a list of index into the remaining accounts per cpi to call the target_program_id with. -pub fn flash_loan<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan<'info>>, - withdraws: Vec, - cpi_datas: Vec, +/// The `loan_amounts` argument lists the amount to be loaned from each bank/vault and +/// the order matches the order of bank accounts. +pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>( + ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanBegin<'info>>, + loan_amounts: Vec, ) -> Result<()> { - require!(!cpi_datas.is_empty(), MangoError::SomeError); - let num_of_cpis = cpi_datas.len(); - let num_health_accounts = cpi_datas.get(0).unwrap().account_start as usize; + let num_loans = loan_amounts.len(); + require_eq!(ctx.remaining_accounts.len(), 3 * num_loans); + let banks = &ctx.remaining_accounts[..num_loans]; + let vaults = &ctx.remaining_accounts[num_loans..2 * num_loans]; + let token_accounts = &ctx.remaining_accounts[2 * num_loans..]; let group = ctx.accounts.group.load()?; + let group_seeds = group_seeds!(group); + let seeds = [&group_seeds[..]]; + + // Check that the banks and vaults correspond + for (((bank_ai, vault_ai), token_account_ai), amount) in banks + .iter() + .zip(vaults.iter()) + .zip(token_accounts.iter()) + .zip(loan_amounts.iter()) + { + let mut bank = bank_ai.load_mut::()?; + require_keys_eq!(bank.group, ctx.accounts.group.key()); + require_keys_eq!(bank.vault, *vault_ai.key); + + let token_account = Account::::try_from(token_account_ai)?; + + bank.flash_loan_approved_amount = *amount; + bank.flash_loan_vault_initial = token_account.amount; + + // Transfer the loaned funds + if *amount > 0 { + // Provide a readable error message in case the vault doesn't have enough tokens + if token_account.amount < *amount { + return err!(MangoError::InsufficentBankVaultFunds).with_context(|| { + format!( + "bank vault {} does not have enough tokens, need {} but have {}", + vault_ai.key, amount, token_account.amount + ) + }); + } + + let transfer_ctx = CpiContext::new( + ctx.accounts.token_program.to_account_info(), + token::Transfer { + from: vault_ai.clone(), + to: token_account_ai.clone(), + authority: ctx.accounts.group.to_account_info(), + }, + ) + .with_signer(&seeds); + token::transfer(transfer_ctx, *amount)?; + } + } + + // Check if the other instructions in the transactions are compatible + { + let ixs = ctx.accounts.instructions.as_ref(); + let current_index = tx_instructions::load_current_index_checked(ixs)? as usize; + + // Forbid FlashLoanBegin to be called from CPI (it does not have to be the first instruction) + let current_ix = tx_instructions::load_instruction_at_checked(current_index, ixs)?; + require_msg!( + current_ix.program_id == *ctx.program_id, + "FlashLoanBegin must be a top-level instruction" + ); + + // The only other mango instruction that must appear before the end of the tx is + // the FlashLoanEnd instruction. No other mango instructions are allowed. + let mut index = current_index + 1; + let mut found_end = false; + loop { + let ix = match tx_instructions::load_instruction_at_checked(index, ixs) { + Ok(ix) => ix, + Err(ProgramError::InvalidArgument) => break, // past the last instruction + Err(e) => return Err(e.into()), + }; + + // Check that the mango program key is not used + if ix.program_id == crate::id() { + // must be the last mango ix -- this could possibly be relaxed, but right now + // we need to guard against multiple FlashLoanEnds + require_msg!( + !found_end, + "the transaction must not contain a Mango instruction after FlashLoanEnd" + ); + found_end = true; + + // must be the FlashLoanEnd instruction + require!( + ix.data[0..8] == [178, 170, 2, 78, 240, 23, 190, 178], + MangoError::SomeError + ); + + // check that the same vaults are passed + let begin_accounts = &ctx.remaining_accounts[num_loans..]; + let end_accounts = &ix.accounts[ix.accounts.len() - 2 * num_loans..]; + for (begin_account, end_account) in begin_accounts.iter().zip(end_accounts.iter()) { + require_msg!(*begin_account.key == end_account.pubkey, "the trailing accounts passed to FlashLoanBegin and End must match, found {} on begin and {} on end", begin_account.key, end_account.pubkey); + } + } else { + // ensure no one can cpi into mango either + for meta in ix.accounts.iter() { + require_msg!(meta.pubkey != crate::id(), "instructions between FlashLoanBegin and End may not use the Mango program account"); + } + } + + index += 1; + } + require_msg!( + found_end, + "found no FlashLoanEnd instruction in transaction" + ); + } + + Ok(()) +} + +struct TokenVaultChange { + token_index: TokenIndex, + bank_index: usize, + raw_token_index: usize, + amount: I80F48, +} + +pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( + ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanEnd<'info>>, +) -> Result<()> { let mut account = ctx.accounts.account.load_mut()?; + require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt); - // Go over the banks passed as health accounts and: - // - Ensure that all banks that are passed in have activated positions. - // This is necessary because maybe the user wants to margin trade on a token - // that the account hasn't used before. - // - Collect the addresses of all banks to potentially sign for in cpi_ais. - // - Collect the addresses of all bank vaults. - // Note: This depends on the particular health account ordering. - let health_ais = &ctx.remaining_accounts[0..num_health_accounts]; - let mut allowed_banks = HashMap::<&Pubkey, Ref>::new(); - // vault pubkey -> (bank_account_index, raw_token_index) - let mut allowed_vaults = HashMap::::new(); - for (i, ai) in health_ais.iter().enumerate() { - match ai.load::() { - Ok(bank) => { - require!(bank.group == account.fixed.group, MangoError::SomeError); - let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?; - allowed_vaults.insert(bank.vault, (i, raw_token_index)); - allowed_banks.insert(ai.key, bank); + // Find index at which vaults start + let vaults_index = ctx + .remaining_accounts + .iter() + .position(|ai| { + let maybe_token_account = Account::::try_from(ai); + if maybe_token_account.is_err() { + return false; } - Err(Error::AnchorError(error)) - if error.error_code_number == ErrorCode::AccountDiscriminatorMismatch as u32 - || error.error_code_number == ErrorCode::AccountOwnedByWrongProgram as u32 => - { - break; - } - Err(error) => return Err(error), + + maybe_token_account.unwrap().owner == account.fixed.group + }) + .ok_or_else(|| error_msg!("expected at least one vault token account to be passed"))?; + let vaults_len = (ctx.remaining_accounts.len() - vaults_index) / 2; + require_eq!(ctx.remaining_accounts.len(), vaults_index + 2 * vaults_len); + + // First initialize to the remaining delegated amount + let health_ais = &ctx.remaining_accounts[..vaults_index]; + let vaults = &ctx.remaining_accounts[vaults_index..vaults_index + vaults_len]; + let token_accounts = &ctx.remaining_accounts[vaults_index + vaults_len..]; + let mut vaults_with_banks = vec![false; vaults.len()]; + + // Loop over the banks, finding matching vaults + // TODO: must be moved into health.rs, because it assumes something about the health accounts structure + let mut changes = vec![]; + for (i, bank_ai) in health_ais.iter().enumerate() { + // iterate until the first non-bank + let bank = match bank_ai.load::() { + Ok(b) => b, + Err(_) => break, }; + + // find a vault -- if there's none, skip + let (vault_index, vault_ai) = match vaults + .iter() + .enumerate() + .find(|(_, vault_ai)| vault_ai.key == &bank.vault) + { + Some(v) => v, + None => continue, + }; + + vaults_with_banks[vault_index] = true; + let token_account_ai = &token_accounts[vault_index]; + let token_account = Account::::try_from(&token_account_ai)?; + + // Ensure this bank/vault combination was mentioned in the Begin instruction: + // The Begin instruction only checks that End ends with the same vault accounts - + // but there could be an extra vault account in End, or a different bank could be + // used for the same vault. + require_neq!(bank.flash_loan_vault_initial, u64::MAX); + + // Create the token position now, so we can compute the pre-health with fixed order health accounts + let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?; + + // Transfer any excess over the inital balance of the token account back + // into the vault. Compute the total change in the vault balance. + let mut change = -I80F48::from(bank.flash_loan_approved_amount); + if token_account.amount > bank.flash_loan_vault_initial { + let transfer_ctx = CpiContext::new( + ctx.accounts.token_program.to_account_info(), + token::Transfer { + from: token_account_ai.clone(), + to: vault_ai.clone(), + authority: ctx.accounts.owner.to_account_info(), + }, + ); + let repay = token_account.amount - bank.flash_loan_vault_initial; + token::transfer(transfer_ctx, repay)?; + + let repay = I80F48::from(repay); + change = cm!(change + repay); + } + + changes.push(TokenVaultChange { + token_index: bank.token_index, + bank_index: i, + raw_token_index, + amount: change, + }); + } + + // all vaults must have had matching banks + for (i, has_bank) in vaults_with_banks.iter().enumerate() { + require_msg!( + has_bank, + "missing bank for vault index {}, address {}", + i, + vaults[i].key + ); } // Check pre-cpi health // NOTE: This health check isn't strictly necessary. It will be, later, when // we want to have reduce_only or be able to move an account out of bankruptcy. - { - let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?; - let pre_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?; - require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive); - msg!("pre_cpi_health {:?}", pre_cpi_health); - } - - let all_cpi_ais = &ctx.remaining_accounts[num_health_accounts..]; - let mut all_cpi_ams = all_cpi_ais - .iter() - .flat_map(|item| item.to_account_metas(None)) - .collect::>(); - require!( - all_cpi_ais.len() == all_cpi_ams.len(), - MangoError::SomeError - ); - - // Check that each group-owned token account is the vault of one of the allowed banks, - // and track its balance. - let mut used_vaults = all_cpi_ais - .iter() - .enumerate() - .filter_map(|(i, ai)| { - if ai.owner != &TokenAccount::owner() { - return None; - } - - // Skip mints and other accounts that may be owned by the spl_token program - let maybe_token_account = Account::::try_from(ai); - if maybe_token_account.is_err() { - return None; - } - - let token_account = maybe_token_account.unwrap(); - if token_account.owner != ctx.accounts.group.key() { - return None; - } - - // Every group-owned token account must be a vault of one of the banks. - if let Some(&(bank_index, raw_token_index)) = allowed_vaults.get(&ai.key) { - return Some(Ok(( - ai.key, - AllowedVault { - vault_cpi_ai_index: i, - bank_health_ai_index: bank_index, - raw_token_index, - pre_amount: token_account.amount, - // these two are updated later - withdraw_amount: 0, - loan_amount: I80F48::ZERO, - }, - ))); - } - - // This is to protect users, because if their cpi program sends deposits to a vault - // and they forgot to pass in the bank for the vault, their account would not be credited. - Some(Err(error!(MangoError::SomeError))) - }) - .collect::>>()?; - - // Store the indexed value before the margin trade for logging purposes - let mut pre_indexed_positions = Vec::new(); - for (_, info) in used_vaults.iter() { - let position = account.token_get_raw(info.raw_token_index); - pre_indexed_positions.push(position.indexed_position.to_bits()); - } - - // Find banks for used vaults in cpi_ais and collect signer seeds for them. - // Also update withdraw_amount and loan_amount. - let mut bank_signer_data = Vec::with_capacity(used_vaults.len()); - for (ai, am) in all_cpi_ais.iter().zip(all_cpi_ams.iter_mut()) { - if ai.owner != &Mango::id() { - continue; - } - if let Some(bank) = allowed_banks.get(ai.key) { - if let Some(vault_info) = used_vaults.get_mut(&bank.vault) { - let withdraw_amount = withdraws - .iter() - .find_map(|&withdraw| { - (withdraw.index as usize == vault_info.vault_cpi_ai_index) - .then(|| withdraw.amount) - }) - // Even if we don't withdraw from a vault we still need to track it: - // Possibly the invoked program will deposit funds into it. - .unwrap_or(0); - require!( - withdraw_amount <= vault_info.pre_amount, - MangoError::SomeError - ); - vault_info.withdraw_amount = withdraw_amount; - - // if there are withdraws: figure out loan amount, mark as signer - if withdraw_amount > 0 { - let token_account = account.token_get_mut_raw(vault_info.raw_token_index); - let native_position = token_account.native(&bank); - vault_info.loan_amount = if native_position > 0 { - (I80F48::from(withdraw_amount) - native_position).max(I80F48::ZERO) - } else { - I80F48::from(withdraw_amount) - }; - - am.is_signer = true; - // this is the data we'll need later to build the PDA account signer seeds - bank_signer_data.push(( - bank.token_index.to_le_bytes(), - bank.bank_num.to_le_bytes(), - [bank.bump], - )); - } - } - } - } - - // Approve bank delegates for withdrawals - let group_seeds = group_seeds!(group); - let seeds = [&group_seeds[..]]; - for (_, vault_info) in used_vaults.iter() { - if vault_info.withdraw_amount > 0 { - let approve_ctx = CpiContext::new( - ctx.accounts.token_program.to_account_info(), - token::Approve { - to: all_cpi_ais[vault_info.vault_cpi_ai_index].clone(), - delegate: health_ais[vault_info.bank_health_ai_index].clone(), - authority: ctx.accounts.group.to_account_info(), - }, - ) - .with_signer(&seeds); - token::approve(approve_ctx, vault_info.withdraw_amount)?; - } - } - - msg!("withdraws {:#?}", withdraws); - msg!("cpi_datas {:#?}", cpi_datas); - msg!("allowed_vaults {:#?}", allowed_vaults); - msg!("used_vaults {:#?}", used_vaults); - - // get rid of Ref<> to avoid limiting the cpi call - drop(allowed_banks); - drop(group); - drop(account); - - // prepare signer seeds and invoke cpi - let group_key = ctx.accounts.group.key(); - let signers = bank_signer_data - .iter() - .map(|(token_index, bank_num, bump)| { - [ - group_key.as_ref(), - b"Bank".as_ref(), - &token_index[..], - &bank_num[..], - &bump[..], - ] - }) - .collect::>(); - let signers_ref = signers.iter().map(|v| &v[..]).collect::>(); - for (cpi_index, cpi_data) in cpi_datas.iter().enumerate() { - let cpi_account_start = cpi_data.account_start as usize; - let cpi_program_id = *ctx.remaining_accounts[cpi_account_start].key; - require_keys_neq!(cpi_program_id, crate::id(), MangoError::SomeError); - - let all_cpi_ais_end_index = if cpi_index == num_of_cpis - 1 { - all_cpi_ams.len() - } else { - cpi_datas[cpi_index + 1].account_start as usize - num_health_accounts - }; - - let all_cpi_ais_start_index = cpi_account_start - num_health_accounts + 1; - - let cpi_ais = &all_cpi_ais[all_cpi_ais_start_index..all_cpi_ais_end_index]; - let cpi_ams = &all_cpi_ams[all_cpi_ais_start_index..all_cpi_ais_end_index]; - let cpi_ix = Instruction { - program_id: cpi_program_id, - // todo future: optimise out these to_vecs - data: cpi_data.data.to_vec(), - accounts: cpi_ams.to_vec(), - }; - solana_program::program::invoke_signed(&cpi_ix, &cpi_ais, &signers_ref)?; - } - - // Revoke delegates for vaults - let group = ctx.accounts.group.load()?; - let group_seeds = group_seeds!(group); - for (_, vault_info) in used_vaults.iter() { - if vault_info.withdraw_amount > 0 { - let ix = token::spl_token::instruction::revoke( - &token::spl_token::ID, - all_cpi_ais[vault_info.vault_cpi_ai_index].key, - &ctx.accounts.group.key(), - &[], - )?; - solana_program::program::invoke_signed( - &ix, - &[ - all_cpi_ais[vault_info.vault_cpi_ai_index].clone(), - ctx.accounts.group.to_account_info(), - ], - &[group_seeds], - )?; - } - } - - // Track vault changes and apply them to the user's token positions - let mut account = ctx.accounts.account.load_mut()?; - let inactive_tokens = adjust_for_post_cpi_vault_amounts( - health_ais, - all_cpi_ais, - &used_vaults, - &mut account.borrow_mut(), - )?; - - // Check post-cpi health let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?; - let post_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?; - require!(post_cpi_health >= 0, MangoError::HealthMustBePositive); - msg!("post_cpi_health {:?}", post_cpi_health); + let pre_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?; + require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive); + msg!("pre_cpi_health {:?}", pre_cpi_health); - // Token balances logging - let mut token_indexes = Vec::with_capacity(used_vaults.len()); - let mut post_indexed_positions = Vec::with_capacity(used_vaults.len()); - for (_, info) in used_vaults.iter() { - let position = account.token_get_raw(info.raw_token_index); - post_indexed_positions.push(position.indexed_position.to_bits()); - token_indexes.push(position.token_index as u16); - - let (bank, oracle_price) = retriever.bank_and_oracle( - &ctx.accounts.group.key(), - info.bank_health_ai_index, - position.token_index, + // Prices for logging + let mut prices = vec![]; + for change in &changes { + let (_, oracle_price) = retriever.bank_and_oracle( + &account.fixed.group, + change.bank_index, + change.token_index, )?; + prices.push(oracle_price); + } + // Drop retriever as mut bank below uses health_ais + drop(retriever); + + // Apply the vault diffs to the bank positions + let mut deactivated_token_positions = vec![]; + let mut token_loan_details = Vec::with_capacity(changes.len()); + for (change, price) in changes.iter().zip(prices.iter()) { + let mut bank = health_ais[change.bank_index].load_mut::()?; + let position = account.token_get_mut_raw(change.raw_token_index); + let native = position.native(&bank); + let approved_amount = I80F48::from(bank.flash_loan_approved_amount); + + let loan = if native.is_positive() { + cm!(approved_amount - native).max(I80F48::ZERO) + } else { + approved_amount + }; + + let loan_origination_fee = cm!(loan * bank.loan_origination_fee_rate); + bank.collected_fees_native = cm!(bank.collected_fees_native + loan_origination_fee); + + let is_active = + bank.change_without_fee(position, cm!(change.amount - loan_origination_fee))?; + if !is_active { + deactivated_token_positions.push(change.raw_token_index); + } + + bank.flash_loan_approved_amount = 0; + bank.flash_loan_vault_initial = u64::MAX; + + token_loan_details.push(FlashLoanTokenDetail { + token_index: position.token_index, + change_amount: change.amount.to_bits(), + loan: loan.to_bits(), + loan_origination_fee: loan_origination_fee.to_bits(), + deposit_index: bank.deposit_index.to_bits(), + borrow_index: bank.borrow_index.to_bits(), + price: price.to_bits(), + }); + emit!(TokenBalanceLog { mango_account: ctx.accounts.account.key(), token_index: bank.token_index as u16, indexed_position: position.indexed_position.to_bits(), deposit_index: bank.deposit_index.to_bits(), borrow_index: bank.borrow_index.to_bits(), - price: oracle_price.to_bits(), + price: price.to_bits(), }); } - emit!(MarginTradeLog { + emit!(FlashLoanLog { mango_account: ctx.accounts.account.key(), - token_indexes, - pre_indexed_positions, - post_indexed_positions, + token_loan_details }); - // Deactivate inactive token accounts at the end - for raw_token_index in inactive_tokens { + // Check post-cpi health + let post_cpi_health = + compute_health_from_fixed_accounts(&account.borrow(), HealthType::Init, health_ais)?; + require!(post_cpi_health >= 0, MangoError::HealthMustBePositive); + msg!("post_cpi_health {:?}", post_cpi_health); + + // Deactivate inactive token accounts after health check + for raw_token_index in deactivated_token_positions { account.token_deactivate(raw_token_index); } Ok(()) } - -fn adjust_for_post_cpi_vault_amounts( - health_ais: &[AccountInfo], - cpi_ais: &[AccountInfo], - used_vaults: &HashMap<&Pubkey, AllowedVault>, - account: &mut MangoAccountRefMut, -) -> Result> { - let mut inactive_token_raw_indexes = Vec::with_capacity(used_vaults.len()); - for (_, info) in used_vaults.iter() { - let vault = Account::::try_from(&cpi_ais[info.vault_cpi_ai_index]).unwrap(); - let mut bank = health_ais[info.bank_health_ai_index].load_mut::()?; - let position = account.token_get_mut_raw(info.raw_token_index); - - let loan_origination_fee = info.loan_amount * bank.loan_origination_fee_rate; - bank.collected_fees_native += loan_origination_fee; - - let is_active = bank.change_without_fee( - position, - I80F48::from(vault.amount) - I80F48::from(info.pre_amount) - loan_origination_fee, - )?; - if !is_active { - inactive_token_raw_indexes.push(info.raw_token_index); - } - } - Ok(inactive_token_raw_indexes) -} diff --git a/programs/mango-v4/src/instructions/flash_loan2.rs b/programs/mango-v4/src/instructions/flash_loan2.rs deleted file mode 100644 index 0171deac2..000000000 --- a/programs/mango-v4/src/instructions/flash_loan2.rs +++ /dev/null @@ -1,328 +0,0 @@ -use crate::accounts_zerocopy::*; -use crate::error::MangoError; -use crate::group_seeds; -use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog}; -use crate::state::{ - compute_health, compute_health_from_fixed_accounts, new_fixed_order_account_retriever, - AccountLoaderDynamic, AccountRetriever, Bank, Group, HealthType, MangoAccount, TokenIndex, -}; -use crate::util::checked_math as cm; -use anchor_lang::prelude::*; -use anchor_lang::solana_program::sysvar::instructions as tx_instructions; -use anchor_spl::token::{self, Token, TokenAccount}; -use fixed::types::I80F48; - -/// Sets up mango vaults for flash loan -/// -/// In addition to these accounts, there must be a sequence of remaining_accounts: -/// 1. N banks -/// 2. N vaults, matching the banks -#[derive(Accounts)] -pub struct FlashLoan2Begin<'info> { - pub group: AccountLoader<'info, Group>, - pub temporary_vault_authority: Signer<'info>, - pub token_program: Program<'info, Token>, - - #[account(address = tx_instructions::ID)] - pub instructions: UncheckedAccount<'info>, -} - -/// Finalizes a flash loan -/// -/// In addition to these accounts, there must be a sequence of remaining_accounts: -/// 1. health accounts -/// 2. N vaults, matching what was in FlashLoan2Begin -#[derive(Accounts)] -pub struct FlashLoan2End<'info> { - pub group: AccountLoader<'info, Group>, - #[account(mut, has_one = group, has_one = owner)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, - pub owner: Signer<'info>, - - pub token_program: Program<'info, Token>, -} - -pub fn flash_loan2_begin<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan2Begin<'info>>, - loan_amounts: Vec, -) -> Result<()> { - let num_loans = loan_amounts.len(); - require_eq!( - ctx.remaining_accounts.len(), - 2 * num_loans, - MangoError::SomeError - ); - let banks = &ctx.remaining_accounts[..num_loans]; - let vaults = &ctx.remaining_accounts[num_loans..]; - - let group = ctx.accounts.group.load()?; - let group_seeds = group_seeds!(group); - let seeds = [&group_seeds[..]]; - - // Check that the banks and vaults correspond - for ((bank_ai, vault_ai), amount) in banks.iter().zip(vaults.iter()).zip(loan_amounts.iter()) { - let mut bank = bank_ai.load_mut::()?; - require_keys_eq!(bank.group, ctx.accounts.group.key()); - require_keys_eq!(bank.vault, *vault_ai.key); - - let token_account = Account::::try_from(vault_ai)?; - - bank.flash_loan_approved_amount = *amount; - bank.flash_loan_vault_initial = token_account.amount; - - // Approve the withdraw - if *amount > 0 { - let approve_ctx = CpiContext::new( - ctx.accounts.token_program.to_account_info(), - token::Approve { - to: vault_ai.clone(), - delegate: ctx.accounts.temporary_vault_authority.to_account_info(), - authority: ctx.accounts.group.to_account_info(), - }, - ) - .with_signer(&seeds); - token::approve(approve_ctx, *amount)?; - } - } - - // Check if the other instructions in the transactions are compatible - { - let ixs = ctx.accounts.instructions.as_ref(); - let current_index = tx_instructions::load_current_index_checked(ixs)? as usize; - - // Forbid FlashLoan2Begin to be called from CPI (it does not have to be the first instruction) - let current_ix = tx_instructions::load_instruction_at_checked(current_index, ixs)?; - require_keys_eq!( - current_ix.program_id, - *ctx.program_id, - MangoError::SomeError - ); - - // The only other mango instruction that must appear before the end of the tx is - // the FlashLoan2End instruction. No other mango instructions are allowed. - let mut index = current_index + 1; - let mut found_end = false; - loop { - let ix = match tx_instructions::load_instruction_at_checked(index, ixs) { - Ok(ix) => ix, - Err(ProgramError::InvalidArgument) => break, // past the last instruction - Err(e) => Err(e)?, - }; - - // Check that the mango program key is not used - if ix.program_id == crate::id() { - // must be the last mango ix -- this could possibly be relaxed, but right now - // we need to guard against multiple FlashLoanEnds - require!(!found_end, MangoError::SomeError); - found_end = true; - - // must be the FlashLoan2End instruction - require!( - &ix.data[0..8] == &[187, 107, 239, 212, 18, 21, 145, 171], - MangoError::SomeError - ); - - // check that the same vaults are passed - let end_vaults = &ix.accounts[ix.accounts.len() - num_loans..]; - for (start_vault, end_vault) in vaults.iter().zip(end_vaults.iter()) { - require_keys_eq!(*start_vault.key, end_vault.pubkey); - } - } else { - // ensure no one can cpi into mango either - for meta in ix.accounts.iter() { - require_keys_neq!(meta.pubkey, crate::id()); - } - } - - index += 1; - } - require!(found_end, MangoError::SomeError); - } - - Ok(()) -} - -struct TokenVaultChange { - bank_index: usize, - raw_token_index: usize, - amount: I80F48, -} - -// Remaining accounts: -// 1. health -// 2. vaults (must be same as FlashLoanStart) -pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan2End<'info>>, -) -> Result<()> { - let group = ctx.accounts.group.load()?; - let group_seeds = group_seeds!(group); - - let mut account = ctx.accounts.account.load_mut()?; - - require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt); - // Find index at which vaults start - let vaults_index = ctx - .remaining_accounts - .iter() - .position(|ai| { - let maybe_token_account = Account::::try_from(ai); - if maybe_token_account.is_err() { - return false; - } - - maybe_token_account.unwrap().owner == account.fixed.group - }) - .ok_or_else(|| error!(MangoError::SomeError))?; - - // First initialize to the remaining delegated amount - let health_ais = &ctx.remaining_accounts[..vaults_index]; - let vaults = &ctx.remaining_accounts[vaults_index..]; - let mut vaults_with_banks = vec![false; vaults.len()]; - - // Loop over the banks, finding matching vaults - // TODO: must be moved into health.rs, because it assumes something about the health accounts structure - let mut changes = vec![]; - for (i, bank_ai) in health_ais.iter().enumerate() { - // iterate until the first non-bank - let bank = match bank_ai.load::() { - Ok(b) => b, - Err(_) => break, - }; - - // find a vault -- if there's none, skip - let (vault_index, vault_ai) = match vaults - .iter() - .enumerate() - .find(|(_, vault_ai)| vault_ai.key == &bank.vault) - { - Some(v) => v, - None => continue, - }; - - let vault = Account::::try_from(vault_ai)?; - vaults_with_banks[vault_index] = true; - - // Ensure this bank/vault combination was mentioned in the Begin instruction: - // The Begin instruction only checks that End ends with the same vault accounts - - // but there could be an extra vault account in End, or a different bank could be - // used for the same vault. - require_neq!(bank.flash_loan_vault_initial, u64::MAX); - - // Create the token position now, so we can compute the pre-health with fixed order health accounts - let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?; - - // Revoke delegation - let ix = token::spl_token::instruction::revoke( - &token::spl_token::ID, - vault_ai.key, - &ctx.accounts.group.key(), - &[], - )?; - solana_program::program::invoke_signed( - &ix, - &[vault_ai.clone(), ctx.accounts.group.to_account_info()], - &[group_seeds], - )?; - - // Track vault difference - let new_amount = I80F48::from(vault.amount); - let old_amount = I80F48::from(bank.flash_loan_vault_initial); - let change = cm!(new_amount - old_amount); - - changes.push(TokenVaultChange { - bank_index: i, - raw_token_index, - amount: change, - }); - } - - // all vaults must have had matching banks - require!(vaults_with_banks.iter().all(|&b| b), MangoError::SomeError); - - // Check pre-cpi health - // NOTE: This health check isn't strictly necessary. It will be, later, when - // we want to have reduce_only or be able to move an account out of bankruptcy. - let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?; - let pre_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?; - require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive); - msg!("pre_cpi_health {:?}", pre_cpi_health); - - // Prices for logging - let mut prices = vec![]; - for change in &changes { - let (_, oracle_price) = retriever.bank_and_oracle( - &account.fixed.group, - change.bank_index, - change.raw_token_index as TokenIndex, - )?; - - prices.push(oracle_price); - } - // Drop retriever as mut bank below uses health_ais - drop(retriever); - - // Apply the vault diffs to the bank positions - let mut deactivated_token_positions = vec![]; - let mut token_loan_details = Vec::with_capacity(changes.len()); - for (change, price) in changes.iter().zip(prices.iter()) { - let mut bank = health_ais[change.bank_index].load_mut::()?; - let position = account.token_get_mut_raw(change.raw_token_index); - let native = position.native(&bank); - let approved_amount = I80F48::from(bank.flash_loan_approved_amount); - - let loan = if native.is_positive() { - cm!(approved_amount - native).max(I80F48::ZERO) - } else { - approved_amount - }; - - let loan_origination_fee = cm!(loan * bank.loan_origination_fee_rate); - bank.collected_fees_native = cm!(bank.collected_fees_native + loan_origination_fee); - - let is_active = - bank.change_without_fee(position, cm!(change.amount - loan_origination_fee))?; - if !is_active { - deactivated_token_positions.push(change.raw_token_index); - } - - bank.flash_loan_approved_amount = 0; - bank.flash_loan_vault_initial = u64::MAX; - - token_loan_details.push(FlashLoanTokenDetail { - token_index: position.token_index, - change_amount: change.amount.to_bits(), - loan: loan.to_bits(), - loan_origination_fee: loan_origination_fee.to_bits(), - deposit_index: bank.deposit_index.to_bits(), - borrow_index: bank.borrow_index.to_bits(), - price: price.to_bits(), - }); - - emit!(TokenBalanceLog { - mango_account: ctx.accounts.account.key(), - token_index: bank.token_index as u16, - indexed_position: position.indexed_position.to_bits(), - deposit_index: bank.deposit_index.to_bits(), - borrow_index: bank.borrow_index.to_bits(), - price: price.to_bits(), - }); - } - - emit!(FlashLoanLog { - mango_account: ctx.accounts.account.key(), - token_loan_details - }); - - // Check post-cpi health - let post_cpi_health = - compute_health_from_fixed_accounts(&account.borrow(), HealthType::Init, health_ais)?; - require!(post_cpi_health >= 0, MangoError::HealthMustBePositive); - msg!("post_cpi_health {:?}", post_cpi_health); - - // Deactivate inactive token accounts after health check - for raw_token_index in deactivated_token_positions { - account.token_deactivate(raw_token_index); - } - - Ok(()) -} diff --git a/programs/mango-v4/src/instructions/flash_loan3.rs b/programs/mango-v4/src/instructions/flash_loan3.rs deleted file mode 100644 index caf47fa70..000000000 --- a/programs/mango-v4/src/instructions/flash_loan3.rs +++ /dev/null @@ -1,361 +0,0 @@ -use crate::accounts_zerocopy::*; -use crate::error::*; -use crate::group_seeds; -use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog}; -use crate::state::MangoAccount; -use crate::state::{ - compute_health, compute_health_from_fixed_accounts, new_fixed_order_account_retriever, - AccountLoaderDynamic, AccountRetriever, Bank, Group, HealthType, TokenIndex, -}; -use crate::util::checked_math as cm; -use anchor_lang::prelude::*; -use anchor_lang::solana_program::sysvar::instructions as tx_instructions; -use anchor_spl::token::{self, Token, TokenAccount}; -use fixed::types::I80F48; - -/// Sets up mango vaults for flash loan -/// -/// In addition to these accounts, there must be remaining_accounts: -/// 1. N banks (writable) -/// 2. N vaults (writable), matching the banks -/// 3. N token accounts (writable), in the same order as the vaults, -/// the loaned funds are transfered into these -#[derive(Accounts)] -pub struct FlashLoan3Begin<'info> { - pub group: AccountLoader<'info, Group>, - pub token_program: Program<'info, Token>, - - /// Instructions Sysvar for instruction introspection - #[account(address = tx_instructions::ID)] - pub instructions: UncheckedAccount<'info>, -} - -/// Finalizes a flash loan -/// -/// In addition to these accounts, there must be remaining_accounts: -/// 1. health accounts, and every bank that also appeared in FlashLoan3Begin must be writable -/// 2. N vaults (writable), matching what was in FlashLoan3Begin -/// 3. N token accounts (writable), matching what was in FlashLoan3Begin; -/// the `owner` must have authority to transfer tokens out of them -#[derive(Accounts)] -pub struct FlashLoan3End<'info> { - #[account(mut, has_one = owner)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, - pub owner: Signer<'info>, - - pub token_program: Program<'info, Token>, -} - -/// The `loan_amounts` argument lists the amount to be loaned from each bank/vault and -/// the order matches the order of bank accounts. -pub fn flash_loan3_begin<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan3Begin<'info>>, - loan_amounts: Vec, -) -> Result<()> { - let num_loans = loan_amounts.len(); - require_eq!(ctx.remaining_accounts.len(), 3 * num_loans); - let banks = &ctx.remaining_accounts[..num_loans]; - let vaults = &ctx.remaining_accounts[num_loans..2 * num_loans]; - let token_accounts = &ctx.remaining_accounts[2 * num_loans..]; - - let group = ctx.accounts.group.load()?; - let group_seeds = group_seeds!(group); - let seeds = [&group_seeds[..]]; - - // Check that the banks and vaults correspond - for (((bank_ai, vault_ai), token_account_ai), amount) in banks - .iter() - .zip(vaults.iter()) - .zip(token_accounts.iter()) - .zip(loan_amounts.iter()) - { - let mut bank = bank_ai.load_mut::()?; - require_keys_eq!(bank.group, ctx.accounts.group.key()); - require_keys_eq!(bank.vault, *vault_ai.key); - - let token_account = Account::::try_from(token_account_ai)?; - - bank.flash_loan_approved_amount = *amount; - bank.flash_loan_vault_initial = token_account.amount; - - // Transfer the loaned funds - if *amount > 0 { - // Provide a readable error message in case the vault doesn't have enough tokens - if token_account.amount < *amount { - return err!(MangoError::InsufficentBankVaultFunds).with_context(|| { - format!( - "bank vault {} does not have enough tokens, need {} but have {}", - vault_ai.key, amount, token_account.amount - ) - }); - } - - let transfer_ctx = CpiContext::new( - ctx.accounts.token_program.to_account_info(), - token::Transfer { - from: vault_ai.clone(), - to: token_account_ai.clone(), - authority: ctx.accounts.group.to_account_info(), - }, - ) - .with_signer(&seeds); - token::transfer(transfer_ctx, *amount)?; - } - } - - // Check if the other instructions in the transactions are compatible - { - let ixs = ctx.accounts.instructions.as_ref(); - let current_index = tx_instructions::load_current_index_checked(ixs)? as usize; - - // Forbid FlashLoan3Begin to be called from CPI (it does not have to be the first instruction) - let current_ix = tx_instructions::load_instruction_at_checked(current_index, ixs)?; - require_msg!( - current_ix.program_id == *ctx.program_id, - "FlashLoan3Begin must be a top-level instruction" - ); - - // The only other mango instruction that must appear before the end of the tx is - // the FlashLoan3End instruction. No other mango instructions are allowed. - let mut index = current_index + 1; - let mut found_end = false; - loop { - let ix = match tx_instructions::load_instruction_at_checked(index, ixs) { - Ok(ix) => ix, - Err(ProgramError::InvalidArgument) => break, // past the last instruction - Err(e) => return Err(e.into()), - }; - - // Check that the mango program key is not used - if ix.program_id == crate::id() { - // must be the last mango ix -- this could possibly be relaxed, but right now - // we need to guard against multiple FlashLoanEnds - require_msg!( - !found_end, - "the transaction must not contain a Mango instruction after FlashLoan3End" - ); - found_end = true; - - // must be the FlashLoan3End instruction - require!( - ix.data[0..8] == [163, 231, 155, 56, 201, 68, 84, 148], - MangoError::SomeError - ); - - // check that the same vaults are passed - let begin_accounts = &ctx.remaining_accounts[num_loans..]; - let end_accounts = &ix.accounts[ix.accounts.len() - 2 * num_loans..]; - for (begin_account, end_account) in begin_accounts.iter().zip(end_accounts.iter()) { - require_msg!(*begin_account.key == end_account.pubkey, "the trailing accounts passed to FlashLoan3Begin and End must match, found {} on begin and {} on end", begin_account.key, end_account.pubkey); - } - } else { - // ensure no one can cpi into mango either - for meta in ix.accounts.iter() { - require_msg!(meta.pubkey != crate::id(), "instructions between FlashLoan3Begin and End may not use the Mango program account"); - } - } - - index += 1; - } - require_msg!( - found_end, - "found no FlashLoan3End instruction in transaction" - ); - } - - Ok(()) -} - -struct TokenVaultChange { - token_index: TokenIndex, - bank_index: usize, - raw_token_index: usize, - amount: I80F48, -} - -pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan3End<'info>>, -) -> Result<()> { - let mut account = ctx.accounts.account.load_mut()?; - - require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt); - - // Find index at which vaults start - let vaults_index = ctx - .remaining_accounts - .iter() - .position(|ai| { - let maybe_token_account = Account::::try_from(ai); - if maybe_token_account.is_err() { - return false; - } - - maybe_token_account.unwrap().owner == account.fixed.group - }) - .ok_or_else(|| error_msg!("expected at least one vault token account to be passed"))?; - let vaults_len = (ctx.remaining_accounts.len() - vaults_index) / 2; - require_eq!(ctx.remaining_accounts.len(), vaults_index + 2 * vaults_len); - - // First initialize to the remaining delegated amount - let health_ais = &ctx.remaining_accounts[..vaults_index]; - let vaults = &ctx.remaining_accounts[vaults_index..vaults_index + vaults_len]; - let token_accounts = &ctx.remaining_accounts[vaults_index + vaults_len..]; - let mut vaults_with_banks = vec![false; vaults.len()]; - - // Loop over the banks, finding matching vaults - // TODO: must be moved into health.rs, because it assumes something about the health accounts structure - let mut changes = vec![]; - for (i, bank_ai) in health_ais.iter().enumerate() { - // iterate until the first non-bank - let bank = match bank_ai.load::() { - Ok(b) => b, - Err(_) => break, - }; - - // find a vault -- if there's none, skip - let (vault_index, vault_ai) = match vaults - .iter() - .enumerate() - .find(|(_, vault_ai)| vault_ai.key == &bank.vault) - { - Some(v) => v, - None => continue, - }; - - vaults_with_banks[vault_index] = true; - let token_account_ai = &token_accounts[vault_index]; - let token_account = Account::::try_from(&token_account_ai)?; - - // Ensure this bank/vault combination was mentioned in the Begin instruction: - // The Begin instruction only checks that End ends with the same vault accounts - - // but there could be an extra vault account in End, or a different bank could be - // used for the same vault. - require_neq!(bank.flash_loan_vault_initial, u64::MAX); - - // Create the token position now, so we can compute the pre-health with fixed order health accounts - let (_, raw_token_index, _) = account.token_get_mut_or_create(bank.token_index)?; - - // Transfer any excess over the inital balance of the token account back - // into the vault. Compute the total change in the vault balance. - let mut change = -I80F48::from(bank.flash_loan_approved_amount); - if token_account.amount > bank.flash_loan_vault_initial { - let transfer_ctx = CpiContext::new( - ctx.accounts.token_program.to_account_info(), - token::Transfer { - from: token_account_ai.clone(), - to: vault_ai.clone(), - authority: ctx.accounts.owner.to_account_info(), - }, - ); - let repay = token_account.amount - bank.flash_loan_vault_initial; - token::transfer(transfer_ctx, repay)?; - - let repay = I80F48::from(repay); - change = cm!(change + repay); - } - - changes.push(TokenVaultChange { - token_index: bank.token_index, - bank_index: i, - raw_token_index, - amount: change, - }); - } - - // all vaults must have had matching banks - for (i, has_bank) in vaults_with_banks.iter().enumerate() { - require_msg!( - has_bank, - "missing bank for vault index {}, address {}", - i, - vaults[i].key - ); - } - - // Check pre-cpi health - // NOTE: This health check isn't strictly necessary. It will be, later, when - // we want to have reduce_only or be able to move an account out of bankruptcy. - let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?; - let pre_cpi_health = compute_health(&account.borrow(), HealthType::Init, &retriever)?; - require!(pre_cpi_health >= 0, MangoError::HealthMustBePositive); - msg!("pre_cpi_health {:?}", pre_cpi_health); - - // Prices for logging - let mut prices = vec![]; - for change in &changes { - let (_, oracle_price) = retriever.bank_and_oracle( - &account.fixed.group, - change.bank_index, - change.token_index, - )?; - - prices.push(oracle_price); - } - // Drop retriever as mut bank below uses health_ais - drop(retriever); - - // Apply the vault diffs to the bank positions - let mut deactivated_token_positions = vec![]; - let mut token_loan_details = Vec::with_capacity(changes.len()); - for (change, price) in changes.iter().zip(prices.iter()) { - let mut bank = health_ais[change.bank_index].load_mut::()?; - let position = account.token_get_mut_raw(change.raw_token_index); - let native = position.native(&bank); - let approved_amount = I80F48::from(bank.flash_loan_approved_amount); - - let loan = if native.is_positive() { - cm!(approved_amount - native).max(I80F48::ZERO) - } else { - approved_amount - }; - - let loan_origination_fee = cm!(loan * bank.loan_origination_fee_rate); - bank.collected_fees_native = cm!(bank.collected_fees_native + loan_origination_fee); - - let is_active = - bank.change_without_fee(position, cm!(change.amount - loan_origination_fee))?; - if !is_active { - deactivated_token_positions.push(change.raw_token_index); - } - - bank.flash_loan_approved_amount = 0; - bank.flash_loan_vault_initial = u64::MAX; - - token_loan_details.push(FlashLoanTokenDetail { - token_index: position.token_index, - change_amount: change.amount.to_bits(), - loan: loan.to_bits(), - loan_origination_fee: loan_origination_fee.to_bits(), - deposit_index: bank.deposit_index.to_bits(), - borrow_index: bank.borrow_index.to_bits(), - price: price.to_bits(), - }); - - emit!(TokenBalanceLog { - mango_account: ctx.accounts.account.key(), - token_index: bank.token_index as u16, - indexed_position: position.indexed_position.to_bits(), - deposit_index: bank.deposit_index.to_bits(), - borrow_index: bank.borrow_index.to_bits(), - price: price.to_bits(), - }); - } - - emit!(FlashLoanLog { - mango_account: ctx.accounts.account.key(), - token_loan_details - }); - - // Check post-cpi health - let post_cpi_health = - compute_health_from_fixed_accounts(&account.borrow(), HealthType::Init, health_ais)?; - require!(post_cpi_health >= 0, MangoError::HealthMustBePositive); - msg!("post_cpi_health {:?}", post_cpi_health); - - // Deactivate inactive token accounts after health check - for raw_token_index in deactivated_token_positions { - account.token_deactivate(raw_token_index); - } - - Ok(()) -} diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index 9a8445afa..856c88dd3 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -5,8 +5,6 @@ pub use account_expand::*; pub use benchmark::*; pub use compute_account_data::*; pub use flash_loan::*; -pub use flash_loan2::*; -pub use flash_loan3::*; pub use group_close::*; pub use group_create::*; pub use group_edit::*; @@ -49,8 +47,6 @@ mod account_expand; mod benchmark; mod compute_account_data; mod flash_loan; -mod flash_loan2; -mod flash_loan3; mod group_close; mod group_create; mod group_edit; diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 5d13821b7..5c1549b87 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -193,39 +193,18 @@ pub mod mango_v4 { instructions::token_withdraw(ctx, amount, allow_borrow) } - pub fn flash_loan<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan<'info>>, - withdraws: Vec, - cpi_datas: Vec, - ) -> Result<()> { - instructions::flash_loan(ctx, withdraws, cpi_datas) - } - - pub fn flash_loan2_begin<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan2Begin<'info>>, + pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>( + ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanBegin<'info>>, loan_amounts: Vec, ) -> Result<()> { - instructions::flash_loan2_begin(ctx, loan_amounts) + instructions::flash_loan_begin(ctx, loan_amounts) } - pub fn flash_loan2_end<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan2End<'info>>, + // NOTE: keep disc synced in flash_loan.rs + pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( + ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanEnd<'info>>, ) -> Result<()> { - instructions::flash_loan2_end(ctx) - } - - pub fn flash_loan3_begin<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan3Begin<'info>>, - loan_amounts: Vec, - ) -> Result<()> { - instructions::flash_loan3_begin(ctx, loan_amounts) - } - - // NOTE: keep disc synced in flash_loan3.rs - pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan3End<'info>>, - ) -> Result<()> { - instructions::flash_loan3_end(ctx) + instructions::flash_loan_end(ctx) } /// diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index a1d4a9d47..95537b57a 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -6,8 +6,7 @@ use anchor_spl::token::{Token, TokenAccount}; use fixed::types::I80F48; use itertools::Itertools; use mango_v4::instructions::{ - CpiData, FlashLoanWithdraw, InterestRateParams, Serum3OrderType, Serum3SelfTradeBehavior, - Serum3Side, + InterestRateParams, Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side, }; use mango_v4::state::{MangoAccount, MangoAccountValue}; use solana_program::instruction::Instruction; @@ -344,203 +343,7 @@ pub async fn account_position_f64(solana: &SolanaCookie, account: Pubkey, bank: // ClientInstruction impl // -pub struct FlashLoanInstruction<'keypair> { - pub account: Pubkey, - pub owner: &'keypair Keypair, - pub mango_token_bank: Pubkey, - pub mango_token_vault: Pubkey, - pub withdraw_amount: u64, - pub margin_trade_program_id: Pubkey, - pub deposit_account: Pubkey, - pub deposit_account_owner: Pubkey, - pub margin_trade_program_ix_cpi_data: Vec, -} -#[async_trait::async_trait(?Send)] -impl<'keypair> ClientInstruction for FlashLoanInstruction<'keypair> { - type Accounts = mango_v4::accounts::FlashLoan; - type Instruction = mango_v4::instruction::FlashLoan; - async fn to_instruction( - &self, - account_loader: impl ClientAccountLoader + 'async_trait, - ) -> (Self::Accounts, instruction::Instruction) { - let program_id = mango_v4::id(); - - let account = account_loader - .load_mango_account(&self.account) - .await - .unwrap(); - - let accounts = Self::Accounts { - group: account.fixed.group, - account: self.account, - owner: self.owner.pubkey(), - token_program: Token::id(), - }; - - let health_check_metas = derive_health_check_remaining_account_metas( - &account_loader, - &account, - Some(self.mango_token_bank), - true, - None, - ) - .await; - - let instruction = Self::Instruction { - withdraws: vec![FlashLoanWithdraw { - index: 2, - amount: self.withdraw_amount, - }], - cpi_datas: vec![CpiData { - account_start: health_check_metas.len() as u8, - data: self.margin_trade_program_ix_cpi_data.clone(), - }], - }; - - let mut instruction = make_instruction(program_id, &accounts, instruction); - instruction.accounts.extend(health_check_metas.into_iter()); - instruction.accounts.push(AccountMeta { - pubkey: self.margin_trade_program_id, - is_writable: false, - is_signer: false, - }); - instruction.accounts.push(AccountMeta { - pubkey: self.mango_token_bank, - is_writable: false, - is_signer: false, - }); - instruction.accounts.push(AccountMeta { - pubkey: self.mango_token_vault, - is_writable: true, - is_signer: false, - }); - instruction.accounts.push(AccountMeta { - pubkey: self.deposit_account, - is_writable: true, - is_signer: false, - }); - instruction.accounts.push(AccountMeta { - pubkey: self.deposit_account_owner, - is_writable: false, - is_signer: false, - }); - instruction.accounts.push(AccountMeta { - pubkey: spl_token::ID, - is_writable: false, - is_signer: false, - }); - - (accounts, instruction) - } - - fn signers(&self) -> Vec<&Keypair> { - vec![self.owner] - } -} - -pub struct FlashLoan2BeginInstruction<'keypair> { - pub group: Pubkey, - pub mango_token_bank: Pubkey, - pub mango_token_vault: Pubkey, - pub withdraw_amount: u64, - pub temporary_vault_authority: &'keypair Keypair, -} -#[async_trait::async_trait(?Send)] -impl<'keypair> ClientInstruction for FlashLoan2BeginInstruction<'keypair> { - type Accounts = mango_v4::accounts::FlashLoan2Begin; - type Instruction = mango_v4::instruction::FlashLoan2Begin; - async fn to_instruction( - &self, - _account_loader: impl ClientAccountLoader + 'async_trait, - ) -> (Self::Accounts, instruction::Instruction) { - let program_id = mango_v4::id(); - - let accounts = Self::Accounts { - group: self.group, - temporary_vault_authority: self.temporary_vault_authority.pubkey(), - token_program: Token::id(), - instructions: solana_program::sysvar::instructions::id(), - }; - - let instruction = Self::Instruction { - loan_amounts: vec![self.withdraw_amount], - }; - - let mut instruction = make_instruction(program_id, &accounts, instruction); - instruction.accounts.push(AccountMeta { - pubkey: self.mango_token_bank, - is_writable: true, - is_signer: false, - }); - instruction.accounts.push(AccountMeta { - pubkey: self.mango_token_vault, - is_writable: true, - is_signer: false, - }); - - (accounts, instruction) - } - - fn signers(&self) -> Vec<&Keypair> { - vec![self.temporary_vault_authority] - } -} - -pub struct FlashLoan2EndInstruction<'keypair> { - pub account: Pubkey, - pub owner: &'keypair Keypair, - pub mango_token_bank: Pubkey, - pub mango_token_vault: Pubkey, -} -#[async_trait::async_trait(?Send)] -impl<'keypair> ClientInstruction for FlashLoan2EndInstruction<'keypair> { - type Accounts = mango_v4::accounts::FlashLoan2End; - type Instruction = mango_v4::instruction::FlashLoan2End; - async fn to_instruction( - &self, - account_loader: impl ClientAccountLoader + 'async_trait, - ) -> (Self::Accounts, instruction::Instruction) { - let program_id = mango_v4::id(); - let instruction = Self::Instruction {}; - - let account = account_loader - .load_mango_account(&self.account) - .await - .unwrap(); - - let health_check_metas = derive_health_check_remaining_account_metas( - &account_loader, - &account, - Some(self.mango_token_bank), - true, - None, - ) - .await; - - let accounts = Self::Accounts { - group: account.fixed.group, - account: self.account, - owner: self.owner.pubkey(), - token_program: Token::id(), - }; - - let mut instruction = make_instruction(program_id, &accounts, instruction); - instruction.accounts.extend(health_check_metas.into_iter()); - instruction.accounts.push(AccountMeta { - pubkey: self.mango_token_vault, - is_writable: true, - is_signer: false, - }); - - (accounts, instruction) - } - - fn signers(&self) -> Vec<&Keypair> { - vec![self.owner] - } -} - -pub struct FlashLoan3BeginInstruction { +pub struct FlashLoanBeginInstruction { pub group: Pubkey, pub mango_token_bank: Pubkey, pub mango_token_vault: Pubkey, @@ -548,9 +351,9 @@ pub struct FlashLoan3BeginInstruction { pub withdraw_amount: u64, } #[async_trait::async_trait(?Send)] -impl ClientInstruction for FlashLoan3BeginInstruction { - type Accounts = mango_v4::accounts::FlashLoan3Begin; - type Instruction = mango_v4::instruction::FlashLoan3Begin; +impl ClientInstruction for FlashLoanBeginInstruction { + type Accounts = mango_v4::accounts::FlashLoanBegin; + type Instruction = mango_v4::instruction::FlashLoanBegin; async fn to_instruction( &self, _account_loader: impl ClientAccountLoader + 'async_trait, @@ -592,7 +395,7 @@ impl ClientInstruction for FlashLoan3BeginInstruction { } } -pub struct FlashLoan3EndInstruction<'keypair> { +pub struct FlashLoanEndInstruction<'keypair> { pub account: Pubkey, pub owner: &'keypair Keypair, pub mango_token_bank: Pubkey, @@ -600,9 +403,9 @@ pub struct FlashLoan3EndInstruction<'keypair> { pub target_token_account: Pubkey, } #[async_trait::async_trait(?Send)] -impl<'keypair> ClientInstruction for FlashLoan3EndInstruction<'keypair> { - type Accounts = mango_v4::accounts::FlashLoan3End; - type Instruction = mango_v4::instruction::FlashLoan3End; +impl<'keypair> ClientInstruction for FlashLoanEndInstruction<'keypair> { + type Accounts = mango_v4::accounts::FlashLoanEnd; + type Instruction = mango_v4::instruction::FlashLoanEnd; async fn to_instruction( &self, account_loader: impl ClientAccountLoader + 'async_trait, diff --git a/programs/mango-v4/tests/test_margin_trade.rs b/programs/mango-v4/tests/test_margin_trade.rs index 02560e0b0..57254b720 100644 --- a/programs/mango-v4/tests/test_margin_trade.rs +++ b/programs/mango-v4/tests/test_margin_trade.rs @@ -1,6 +1,5 @@ #![cfg(feature = "test-bpf")] -use anchor_lang::InstructionData; use solana_program_test::*; use solana_sdk::signature::Keypair; use solana_sdk::signature::Signer; @@ -13,590 +12,7 @@ mod program_test; // This is an unspecific happy-case test that just runs a few instructions to check // that they work in principle. It should be split up / renamed. #[tokio::test] -async fn test_margin_trade1() -> Result<(), BanksClientError> { - let mut builder = TestContextBuilder::new(); - builder.test().set_compute_max_units(170000); - let margin_trade = builder.add_margin_trade_program(); - let context = builder.start_default().await; - let solana = &context.solana.clone(); - - let admin = &Keypair::new(); - let owner = &context.users[0].key; - let payer = &context.users[1].key; - let mints = &context.mints[0..2]; - let payer_mint0_account = context.users[1].token_accounts[0]; - let payer_mint1_account = context.users[1].token_accounts[1]; - let loan_origination_fee = 0.0005; - - // higher resolution that the loan_origination_fee for one token - let balance_f64eq = |a: f64, b: f64| (a - b).abs() < 0.0001; - - // - // SETUP: Create a group, account, register a token (mint0) - // - - let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig { - admin, - payer, - mints, - } - .create(solana) - .await; - let bank = tokens[0].bank; - let vault = tokens[0].vault; - - // - // provide some funds for tokens, so the test user can borrow - // - let provided_amount = 1000; - - let provider_account = send_tx( - solana, - AccountCreateInstruction { - account_num: 1, - account_size: AccountSize::Large, - group, - owner, - payer, - }, - ) - .await - .unwrap() - .account; - - send_tx( - solana, - TokenDepositInstruction { - amount: provided_amount, - account: provider_account, - token_account: payer_mint0_account, - token_authority: payer.clone(), - bank_index: 0, - }, - ) - .await - .unwrap(); - send_tx( - solana, - TokenDepositInstruction { - amount: provided_amount, - account: provider_account, - token_account: payer_mint1_account, - token_authority: payer.clone(), - bank_index: 0, - }, - ) - .await - .unwrap(); - - // - // create thes test user account - // - - let account = send_tx( - solana, - AccountCreateInstruction { - account_num: 0, - account_size: AccountSize::Large, - group, - owner, - payer, - }, - ) - .await - .unwrap() - .account; - - // - // TEST: Deposit funds - // - let deposit_amount_initial = 100; - { - let start_balance = solana.token_account_balance(payer_mint0_account).await; - - send_tx( - solana, - TokenDepositInstruction { - amount: deposit_amount_initial, - account, - token_account: payer_mint0_account, - token_authority: payer.clone(), - bank_index: 0, - }, - ) - .await - .unwrap(); - - assert_eq!( - solana.token_account_balance(vault).await, - provided_amount + deposit_amount_initial - ); - assert_eq!( - solana.token_account_balance(payer_mint0_account).await, - start_balance - deposit_amount_initial - ); - assert_eq!( - account_position(solana, account, bank).await, - deposit_amount_initial as i64, - ); - } - - // - // TEST: Margin trade - // - let withdraw_amount = 2; - let deposit_amount = 1; - { - send_tx( - solana, - FlashLoanInstruction { - account, - owner, - mango_token_bank: bank, - mango_token_vault: vault, - withdraw_amount, - margin_trade_program_id: margin_trade.program, - deposit_account: margin_trade.token_account.pubkey(), - deposit_account_owner: margin_trade.token_account_owner, - margin_trade_program_ix_cpi_data: { - let ix = margin_trade::instruction::MarginTrade { - amount_from: withdraw_amount, - amount_to: deposit_amount, - deposit_account_owner_bump_seeds: margin_trade.token_account_bump, - }; - ix.data() - }, - }, - ) - .await - .unwrap(); - } - assert_eq!( - solana.token_account_balance(vault).await, - provided_amount + deposit_amount_initial - withdraw_amount + deposit_amount - ); - assert_eq!( - solana - .token_account_balance(margin_trade.token_account.pubkey()) - .await, - withdraw_amount - deposit_amount - ); - // no fee because user had positive balance - assert!(balance_f64eq( - account_position_f64(solana, account, bank).await, - (deposit_amount_initial - withdraw_amount + deposit_amount) as f64 - )); - - // - // TEST: Bringing the balance to 0 deactivates the token - // - let deposit_amount_initial = account_position(solana, account, bank).await; - let margin_account_initial = solana - .token_account_balance(margin_trade.token_account.pubkey()) - .await; - let withdraw_amount = deposit_amount_initial as u64; - let deposit_amount = 0; - { - send_tx( - solana, - FlashLoanInstruction { - account, - owner, - mango_token_bank: bank, - mango_token_vault: vault, - withdraw_amount, - margin_trade_program_id: margin_trade.program, - deposit_account: margin_trade.token_account.pubkey(), - deposit_account_owner: margin_trade.token_account_owner, - margin_trade_program_ix_cpi_data: { - let ix = margin_trade::instruction::MarginTrade { - amount_from: withdraw_amount, - amount_to: deposit_amount, - deposit_account_owner_bump_seeds: margin_trade.token_account_bump, - }; - ix.data() - }, - }, - ) - .await - .unwrap(); - } - assert_eq!(solana.token_account_balance(vault).await, provided_amount); - assert_eq!( - solana - .token_account_balance(margin_trade.token_account.pubkey()) - .await, - margin_account_initial + withdraw_amount - ); - // Check that position is fully deactivated - let account_data = get_mango_account(solana, account).await; - assert_eq!(account_data.token_iter_active().count(), 0); - - // - // TEST: Activating a token via margin trade - // - let margin_account_initial = solana - .token_account_balance(margin_trade.token_account.pubkey()) - .await; - let withdraw_amount = 0; - let deposit_amount = margin_account_initial; - { - send_tx( - solana, - FlashLoanInstruction { - account, - owner, - mango_token_bank: bank, - mango_token_vault: vault, - withdraw_amount, - margin_trade_program_id: margin_trade.program, - deposit_account: margin_trade.token_account.pubkey(), - deposit_account_owner: margin_trade.token_account_owner, - margin_trade_program_ix_cpi_data: { - let ix = margin_trade::instruction::MarginTrade { - amount_from: withdraw_amount, - amount_to: deposit_amount, - deposit_account_owner_bump_seeds: margin_trade.token_account_bump, - }; - ix.data() - }, - }, - ) - .await - .unwrap(); - } - assert_eq!( - solana.token_account_balance(vault).await, - provided_amount + deposit_amount - ); - assert_eq!( - solana - .token_account_balance(margin_trade.token_account.pubkey()) - .await, - 0 - ); - assert!(balance_f64eq( - account_position_f64(solana, account, bank).await, - deposit_amount as f64 - )); - - // - // TEST: Try loan fees by withdrawing more than the user balance - // - let deposit_amount_initial = account_position(solana, account, bank).await as u64; - let withdraw_amount = 500; - let deposit_amount = 450; - { - send_tx( - solana, - FlashLoanInstruction { - account, - owner, - mango_token_bank: bank, - mango_token_vault: vault, - withdraw_amount, - margin_trade_program_id: margin_trade.program, - deposit_account: margin_trade.token_account.pubkey(), - deposit_account_owner: margin_trade.token_account_owner, - margin_trade_program_ix_cpi_data: { - let ix = margin_trade::instruction::MarginTrade { - amount_from: withdraw_amount, - amount_to: deposit_amount, - deposit_account_owner_bump_seeds: margin_trade.token_account_bump, - }; - ix.data() - }, - }, - ) - .await - .unwrap(); - } - assert_eq!( - solana.token_account_balance(vault).await, - provided_amount + deposit_amount_initial + deposit_amount - withdraw_amount - ); - assert_eq!( - solana - .token_account_balance(margin_trade.token_account.pubkey()) - .await, - withdraw_amount - deposit_amount - ); - assert!(balance_f64eq( - account_position_f64(solana, account, bank).await, - (deposit_amount_initial + deposit_amount - withdraw_amount) as f64 - - (withdraw_amount - deposit_amount_initial) as f64 * loan_origination_fee - )); - - Ok(()) -} - -// This is an unspecific happy-case test that just runs a few instructions to check -// that they work in principle. It should be split up / renamed. -#[tokio::test] -async fn test_margin_trade2() -> Result<(), BanksClientError> { - let builder = TestContextBuilder::new(); - let context = builder.start_default().await; - let solana = &context.solana.clone(); - - let admin = &Keypair::new(); - let owner = &context.users[0].key; - let payer = &context.users[1].key; - let mints = &context.mints[0..2]; - let payer_mint0_account = context.users[1].token_accounts[0]; - let payer_mint1_account = context.users[1].token_accounts[1]; - let loan_origination_fee = 0.0005; - - // higher resolution that the loan_origination_fee for one token - let balance_f64eq = |a: f64, b: f64| (a - b).abs() < 0.0001; - - // - // SETUP: Create a group, account, register a token (mint0) - // - - let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig { - admin, - payer, - mints, - } - .create(solana) - .await; - let bank = tokens[0].bank; - let vault = tokens[0].vault; - - // - // provide some funds for tokens, so the test user can borrow - // - let provided_amount = 1000; - - let provider_account = send_tx( - solana, - AccountCreateInstruction { - account_num: 1, - account_size: AccountSize::Large, - group, - owner, - payer, - }, - ) - .await - .unwrap() - .account; - - send_tx( - solana, - TokenDepositInstruction { - amount: provided_amount, - account: provider_account, - token_account: payer_mint0_account, - token_authority: payer.clone(), - bank_index: 0, - }, - ) - .await - .unwrap(); - send_tx( - solana, - TokenDepositInstruction { - amount: provided_amount, - account: provider_account, - token_account: payer_mint1_account, - token_authority: payer.clone(), - bank_index: 0, - }, - ) - .await - .unwrap(); - - // - // create thes test user account - // - - let account = send_tx( - solana, - AccountCreateInstruction { - account_num: 0, - account_size: AccountSize::Large, - group, - owner, - payer, - }, - ) - .await - .unwrap() - .account; - - // - // TEST: Deposit funds - // - let deposit_amount_initial = 100; - { - let start_balance = solana.token_account_balance(payer_mint0_account).await; - - send_tx( - solana, - TokenDepositInstruction { - amount: deposit_amount_initial, - account, - token_account: payer_mint0_account, - token_authority: payer.clone(), - bank_index: 0, - }, - ) - .await - .unwrap(); - - assert_eq!( - solana.token_account_balance(vault).await, - provided_amount + deposit_amount_initial - ); - assert_eq!( - solana.token_account_balance(payer_mint0_account).await, - start_balance - deposit_amount_initial - ); - assert_eq!( - account_position(solana, account, bank).await, - deposit_amount_initial as i64, - ); - } - - // - // TEST: Margin trade - // - let margin_account = payer_mint0_account; - let margin_account_initial = solana.token_account_balance(margin_account).await; - let withdraw_amount = 2; - let deposit_amount = 1; - let send_flash_loan_tx = |solana, withdraw_amount, deposit_amount| async move { - let temporary_vault_authority = &Keypair::new(); - - let mut tx = ClientTransaction::new(solana); - tx.add_instruction(FlashLoan2BeginInstruction { - group, - temporary_vault_authority, - mango_token_bank: bank, - mango_token_vault: vault, - withdraw_amount, - }) - .await; - if withdraw_amount > 0 { - tx.add_instruction_direct( - spl_token::instruction::transfer( - &spl_token::ID, - &vault, - &margin_account, - &temporary_vault_authority.pubkey(), - &[&temporary_vault_authority.pubkey()], - withdraw_amount, - ) - .unwrap(), - ); - } - if deposit_amount > 0 { - tx.add_instruction_direct( - spl_token::instruction::transfer( - &spl_token::ID, - &margin_account, - &vault, - &payer.pubkey(), - &[&payer.pubkey()], - deposit_amount, - ) - .unwrap(), - ); - tx.add_signer(&payer); - } - tx.add_instruction(FlashLoan2EndInstruction { - account, - owner, - mango_token_bank: bank, - mango_token_vault: vault, - }) - .await; - tx.send().await.unwrap(); - }; - send_flash_loan_tx(solana, withdraw_amount, deposit_amount).await; - - assert_eq!( - solana.token_account_balance(vault).await, - provided_amount + deposit_amount_initial - withdraw_amount + deposit_amount - ); - assert_eq!( - solana.token_account_balance(margin_account).await, - margin_account_initial + withdraw_amount - deposit_amount - ); - // no fee because user had positive balance - assert!(balance_f64eq( - account_position_f64(solana, account, bank).await, - (deposit_amount_initial - withdraw_amount + deposit_amount) as f64 - )); - - // - // TEST: Bringing the balance to 0 deactivates the token - // - let deposit_amount_initial = account_position(solana, account, bank).await; - let margin_account_initial = solana.token_account_balance(margin_account).await; - let withdraw_amount = deposit_amount_initial as u64; - let deposit_amount = 0; - send_flash_loan_tx(solana, withdraw_amount, deposit_amount).await; - assert_eq!(solana.token_account_balance(vault).await, provided_amount); - assert_eq!( - solana.token_account_balance(margin_account).await, - margin_account_initial + withdraw_amount - ); - // Check that position is fully deactivated - let account_data = get_mango_account(solana, account).await; - assert_eq!(account_data.token_iter_active().count(), 0); - - // - // TEST: Activating a token via margin trade - // - let margin_account_initial = solana.token_account_balance(margin_account).await; - let withdraw_amount = 0; - let deposit_amount = 100; - send_flash_loan_tx(solana, withdraw_amount, deposit_amount).await; - assert_eq!( - solana.token_account_balance(vault).await, - provided_amount + deposit_amount - ); - assert_eq!( - solana.token_account_balance(margin_account).await, - margin_account_initial - deposit_amount - ); - assert!(balance_f64eq( - account_position_f64(solana, account, bank).await, - deposit_amount as f64 - )); - - // - // TEST: Try loan fees by withdrawing more than the user balance - // - let margin_account_initial = solana.token_account_balance(margin_account).await; - let deposit_amount_initial = account_position(solana, account, bank).await as u64; - let withdraw_amount = 500; - let deposit_amount = 450; - println!("{}", deposit_amount_initial); - send_flash_loan_tx(solana, withdraw_amount, deposit_amount).await; - assert_eq!( - solana.token_account_balance(vault).await, - provided_amount + deposit_amount_initial + deposit_amount - withdraw_amount - ); - assert_eq!( - solana.token_account_balance(margin_account).await, - margin_account_initial + withdraw_amount - deposit_amount - ); - assert!(balance_f64eq( - account_position_f64(solana, account, bank).await, - (deposit_amount_initial + deposit_amount - withdraw_amount) as f64 - - (withdraw_amount - deposit_amount_initial) as f64 * loan_origination_fee - )); - - Ok(()) -} - -// This is an unspecific happy-case test that just runs a few instructions to check -// that they work in principle. It should be split up / renamed. -#[tokio::test] -async fn test_margin_trade3() -> Result<(), BanksClientError> { +async fn test_margin_trade() -> Result<(), BanksClientError> { let builder = TestContextBuilder::new(); let context = builder.start_default().await; let solana = &context.solana.clone(); @@ -732,7 +148,7 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> { let deposit_amount = 1; let send_flash_loan_tx = |solana, withdraw_amount, deposit_amount| async move { let mut tx = ClientTransaction::new(solana); - tx.add_instruction(FlashLoan3BeginInstruction { + tx.add_instruction(FlashLoanBeginInstruction { group, mango_token_bank: bank, mango_token_vault: vault, @@ -767,7 +183,7 @@ async fn test_margin_trade3() -> Result<(), BanksClientError> { ); tx.add_signer(&payer); } - tx.add_instruction(FlashLoan3EndInstruction { + tx.add_instruction(FlashLoanEndInstruction { account, owner, mango_token_bank: bank, diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index c21da3f64..e4b8ca58c 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -47,12 +47,10 @@ import { import { SERUM3_PROGRAM_ID } from './constants'; import { Id } from './ids'; import { IDL, MangoV4 } from './mango_v4'; -import { FlashLoanWithdraw } from './types'; import { getAssociatedTokenAddress, I64_MAX_BN, toNativeDecimals, - toU64, } from './utils'; import { simulate } from './utils/anchor'; @@ -1335,187 +1333,6 @@ export class MangoClient { if (!inputBank || !outputBank) throw new Error('Invalid token'); - const healthRemainingAccounts: PublicKey[] = - this.buildHealthRemainingAccounts(group, mangoAccount, [ - inputBank, - outputBank, - ]); - const parsedHealthAccounts = healthRemainingAccounts.map( - (pk) => - ({ - pubkey: pk, - isWritable: - pk.equals(inputBank.publicKey) || pk.equals(outputBank.publicKey) - ? true - : false, - isSigner: false, - } as AccountMeta), - ); - - /* - * Find or create associated token accounts - */ - let inputTokenAccountPk = await getAssociatedTokenAddress( - inputBank.mint, - mangoAccount.owner, - ); - const inputTokenAccExists = - await this.program.provider.connection.getAccountInfo( - inputTokenAccountPk, - ); - let preInstructions = []; - if (!inputTokenAccExists) { - preInstructions.push( - Token.createAssociatedTokenAccountInstruction( - mangoAccount.owner, - inputTokenAccountPk, - mangoAccount.owner, - inputBank.mint, - TOKEN_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID, - ), - ); - } - - let outputTokenAccountPk = await getAssociatedTokenAddress( - outputBank.mint, - mangoAccount.owner, - ); - const outputTokenAccExists = - await this.program.provider.connection.getAccountInfo( - outputTokenAccountPk, - ); - if (!outputTokenAccExists) { - preInstructions.push( - Token.createAssociatedTokenAccountInstruction( - mangoAccount.owner, - outputTokenAccountPk, - mangoAccount.owner, - outputBank.mint, - TOKEN_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID, - ), - ); - } - - /* - * Transfer input token to users wallet, then concat the passed in instructions - */ - const nativeInputAmount = toU64( - amountIn, - inputBank.mintDecimals, - ).toNumber(); - const instructions: TransactionInstruction[] = []; - - const transferIx = Token.createTransferInstruction( - TOKEN_PROGRAM_ID, - inputBank.vault, - inputTokenAccountPk, - inputBank.publicKey, - [], - nativeInputAmount, - ); - const inputBankKey = transferIx.keys[2]; - transferIx.keys[2] = { ...inputBankKey, isWritable: true, isSigner: false }; - instructions.push(transferIx); - - instructions.concat(userDefinedInstructions); - - const transferIx2 = Token.createTransferInstruction( - TOKEN_PROGRAM_ID, - outputTokenAccountPk, - outputBank.vault, - mangoAccount.owner, - [], - 0, // todo: use this for testing, this should be the amount to transfer back - ); - instructions.push(transferIx2); - - /* - * Create object of amounts that will be withdrawn from bank vaults - */ - const targetRemainingAccounts = instructions - .map((ix) => [ - { - pubkey: ix.programId, - isWritable: false, - isSigner: false, - } as AccountMeta, - ...ix.keys, - ]) - .flat(); - - const vaultIndex = targetRemainingAccounts - .map((x) => x.pubkey.toString()) - .lastIndexOf(inputBank.vault.toString()); - - const withdraws: FlashLoanWithdraw[] = [ - { - index: vaultIndex, - amount: toU64(amountIn, inputBank.mintDecimals), - }, - ]; - - /* - * Build cpi data objects for instructions - */ - let cpiDatas = []; - for (const [index, ix] of instructions.entries()) { - if (index === 0) { - cpiDatas.push({ - accountStart: new BN(parsedHealthAccounts.length), - data: ix.data, - }); - } else { - cpiDatas.push({ - accountStart: cpiDatas[index - 1].accountStart.add( - new BN(instructions[index - 1].keys.length + 1), - ), - data: ix.data, - }); - } - } - - if (preInstructions.length) { - const tx = new Transaction(); - for (const ix of preInstructions) { - tx.add(ix); - } - - await this.program.provider.sendAndConfirm(tx); - } - - return await this.program.methods - .flashLoan(withdraws, cpiDatas) - .accounts({ - group: group.publicKey, - account: mangoAccount.publicKey, - owner: (this.program.provider as AnchorProvider).wallet.publicKey, - }) - .remainingAccounts([...parsedHealthAccounts, ...targetRemainingAccounts]) - .rpc({ skipPreflight: true }); - } - - public async marginTrade3({ - group, - mangoAccount, - inputToken, - amountIn, - outputToken, - userDefinedInstructions, - }: { - group: Group; - mangoAccount: MangoAccount; - inputToken: string; - amountIn: number; - outputToken: string; - userDefinedInstructions: TransactionInstruction[]; - }): Promise { - const inputBank = group.banksMap.get(inputToken); - const outputBank = group.banksMap.get(outputToken); - - if (!inputBank || !outputBank) throw new Error('Invalid token'); - const healthRemainingAccounts: PublicKey[] = this.buildHealthRemainingAccounts(group, mangoAccount, [ inputBank, @@ -1621,7 +1438,7 @@ export class MangoClient { }; const flashLoanEndIx = await this.program.methods - .flashLoan3End() + .flashLoanEnd() .accounts({ account: mangoAccount.publicKey, owner: (this.program.provider as AnchorProvider).wallet.publicKey, @@ -1643,7 +1460,7 @@ export class MangoClient { // userDefinedInstructions.push(flashLoanEndIx); const flashLoanBeginIx = await this.program.methods - .flashLoan3Begin([ + .flashLoanBegin([ toNativeDecimals(amountIn, inputBank.mintDecimals), new BN( 0, diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 0f793982f..f19d227ba 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -1012,109 +1012,7 @@ export type MangoV4 = { ] }, { - "name": "flashLoan", - "accounts": [ - { - "name": "group", - "isMut": false, - "isSigner": false - }, - { - "name": "account", - "isMut": true, - "isSigner": false - }, - { - "name": "owner", - "isMut": false, - "isSigner": true - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "withdraws", - "type": { - "vec": { - "defined": "FlashLoanWithdraw" - } - } - }, - { - "name": "cpiDatas", - "type": { - "vec": { - "defined": "CpiData" - } - } - } - ] - }, - { - "name": "flashLoan2Begin", - "accounts": [ - { - "name": "group", - "isMut": false, - "isSigner": false - }, - { - "name": "temporaryVaultAuthority", - "isMut": false, - "isSigner": true - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "instructions", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "loanAmounts", - "type": { - "vec": "u64" - } - } - ] - }, - { - "name": "flashLoan2End", - "accounts": [ - { - "name": "group", - "isMut": false, - "isSigner": false - }, - { - "name": "account", - "isMut": true, - "isSigner": false - }, - { - "name": "owner", - "isMut": false, - "isSigner": true - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [] - }, - { - "name": "flashLoan3Begin", + "name": "flashLoanBegin", "accounts": [ { "name": "group", @@ -1145,7 +1043,7 @@ export type MangoV4 = { ] }, { - "name": "flashLoan3End", + "name": "flashLoanEnd", "accounts": [ { "name": "account", @@ -2808,7 +2706,7 @@ export type MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding", "type": { "array": [ "u8", @@ -2819,6 +2717,15 @@ export type MangoV4 = { { "name": "bankNum", "type": "u64" + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -2887,7 +2794,7 @@ export type MangoV4 = { "type": { "array": [ "u8", - 8 + 128 ] } } @@ -2943,7 +2850,7 @@ export type MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding", "type": { "array": [ "u8", @@ -2959,6 +2866,15 @@ export type MangoV4 = { "name": "netSettled", "type": "f32" }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 256 + ] + } + }, { "name": "padding1", "type": "u32" @@ -3024,7 +2940,7 @@ export type MangoV4 = { "type": "u16" }, { - "name": "padding", + "name": "padding1", "type": { "array": [ "u8", @@ -3071,13 +2987,22 @@ export type MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding2", "type": { "array": [ "u8", 6 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -3110,7 +3035,7 @@ export type MangoV4 = { "type": { "array": [ "u8", - 8 + 128 ] } } @@ -3172,6 +3097,15 @@ export type MangoV4 = { 1024 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -3194,7 +3128,7 @@ export type MangoV4 = { { "defined": "AnyEvent" }, - 512 + 488 ] } } @@ -3222,7 +3156,7 @@ export type MangoV4 = { "type": "u16" }, { - "name": "padding", + "name": "padding1", "type": { "array": [ "u8", @@ -3390,13 +3324,22 @@ export type MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding2", "type": { "array": [ "u8", 6 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -3419,7 +3362,7 @@ export type MangoV4 = { "type": "u16" }, { - "name": "padding", + "name": "padding1", "type": { "array": [ "u8", @@ -3453,13 +3396,22 @@ export type MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding2", "type": { "array": [ "u8", 5 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -3526,45 +3478,6 @@ export type MangoV4 = { ] } }, - { - "name": "FlashLoanWithdraw", - "type": { - "kind": "struct", - "fields": [ - { - "name": "index", - "docs": [ - "Account index of the vault to withdraw from in the target_accounts section.", - "Index is counted after health accounts." - ], - "type": "u8" - }, - { - "name": "amount", - "docs": [ - "Requested withdraw amount." - ], - "type": "u64" - } - ] - } - }, - { - "name": "CpiData", - "type": { - "kind": "struct", - "fields": [ - { - "name": "accountStart", - "type": "u8" - }, - { - "name": "data", - "type": "bytes" - } - ] - } - }, { "name": "InterestRateParams", "type": { @@ -3814,13 +3727,22 @@ export type MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding", "type": { "array": [ "u8", 5 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } } ] } @@ -3860,13 +3782,22 @@ export type MangoV4 = { "type": "u16" }, { - "name": "reserved", + "name": "padding", "type": { "array": [ "u8", 2 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } } ] } @@ -3881,7 +3812,7 @@ export type MangoV4 = { "type": "u16" }, { - "name": "reserved", + "name": "padding", "type": { "array": [ "u8", @@ -3946,6 +3877,15 @@ export type MangoV4 = { { "name": "takerQuoteLots", "type": "i64" + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } } ] } @@ -3962,7 +3902,7 @@ export type MangoV4 = { } }, { - "name": "reserved1", + "name": "padding1", "type": { "array": [ "u8", @@ -3975,7 +3915,7 @@ export type MangoV4 = { "type": "u16" }, { - "name": "reserved2", + "name": "padding2", "type": { "array": [ "u8", @@ -3990,6 +3930,15 @@ export type MangoV4 = { { "name": "orderId", "type": "i128" + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } } ] } @@ -4022,7 +3971,7 @@ export type MangoV4 = { "type": { "array": [ "u8", - 84 + 92 ] } } @@ -4063,7 +4012,7 @@ export type MangoV4 = { "type": { "array": [ "u8", - 199 + 207 ] } } @@ -5929,109 +5878,7 @@ export const IDL: MangoV4 = { ] }, { - "name": "flashLoan", - "accounts": [ - { - "name": "group", - "isMut": false, - "isSigner": false - }, - { - "name": "account", - "isMut": true, - "isSigner": false - }, - { - "name": "owner", - "isMut": false, - "isSigner": true - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "withdraws", - "type": { - "vec": { - "defined": "FlashLoanWithdraw" - } - } - }, - { - "name": "cpiDatas", - "type": { - "vec": { - "defined": "CpiData" - } - } - } - ] - }, - { - "name": "flashLoan2Begin", - "accounts": [ - { - "name": "group", - "isMut": false, - "isSigner": false - }, - { - "name": "temporaryVaultAuthority", - "isMut": false, - "isSigner": true - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "instructions", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "loanAmounts", - "type": { - "vec": "u64" - } - } - ] - }, - { - "name": "flashLoan2End", - "accounts": [ - { - "name": "group", - "isMut": false, - "isSigner": false - }, - { - "name": "account", - "isMut": true, - "isSigner": false - }, - { - "name": "owner", - "isMut": false, - "isSigner": true - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [] - }, - { - "name": "flashLoan3Begin", + "name": "flashLoanBegin", "accounts": [ { "name": "group", @@ -6062,7 +5909,7 @@ export const IDL: MangoV4 = { ] }, { - "name": "flashLoan3End", + "name": "flashLoanEnd", "accounts": [ { "name": "account", @@ -7725,7 +7572,7 @@ export const IDL: MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding", "type": { "array": [ "u8", @@ -7736,6 +7583,15 @@ export const IDL: MangoV4 = { { "name": "bankNum", "type": "u64" + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -7804,7 +7660,7 @@ export const IDL: MangoV4 = { "type": { "array": [ "u8", - 8 + 128 ] } } @@ -7860,7 +7716,7 @@ export const IDL: MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding", "type": { "array": [ "u8", @@ -7876,6 +7732,15 @@ export const IDL: MangoV4 = { "name": "netSettled", "type": "f32" }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 256 + ] + } + }, { "name": "padding1", "type": "u32" @@ -7941,7 +7806,7 @@ export const IDL: MangoV4 = { "type": "u16" }, { - "name": "padding", + "name": "padding1", "type": { "array": [ "u8", @@ -7988,13 +7853,22 @@ export const IDL: MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding2", "type": { "array": [ "u8", 6 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -8027,7 +7901,7 @@ export const IDL: MangoV4 = { "type": { "array": [ "u8", - 8 + 128 ] } } @@ -8089,6 +7963,15 @@ export const IDL: MangoV4 = { 1024 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -8111,7 +7994,7 @@ export const IDL: MangoV4 = { { "defined": "AnyEvent" }, - 512 + 488 ] } } @@ -8139,7 +8022,7 @@ export const IDL: MangoV4 = { "type": "u16" }, { - "name": "padding", + "name": "padding1", "type": { "array": [ "u8", @@ -8307,13 +8190,22 @@ export const IDL: MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding2", "type": { "array": [ "u8", 6 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -8336,7 +8228,7 @@ export const IDL: MangoV4 = { "type": "u16" }, { - "name": "padding", + "name": "padding1", "type": { "array": [ "u8", @@ -8370,13 +8262,22 @@ export const IDL: MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding2", "type": { "array": [ "u8", 5 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } } ] } @@ -8443,45 +8344,6 @@ export const IDL: MangoV4 = { ] } }, - { - "name": "FlashLoanWithdraw", - "type": { - "kind": "struct", - "fields": [ - { - "name": "index", - "docs": [ - "Account index of the vault to withdraw from in the target_accounts section.", - "Index is counted after health accounts." - ], - "type": "u8" - }, - { - "name": "amount", - "docs": [ - "Requested withdraw amount." - ], - "type": "u64" - } - ] - } - }, - { - "name": "CpiData", - "type": { - "kind": "struct", - "fields": [ - { - "name": "accountStart", - "type": "u8" - }, - { - "name": "data", - "type": "bytes" - } - ] - } - }, { "name": "InterestRateParams", "type": { @@ -8731,13 +8593,22 @@ export const IDL: MangoV4 = { "type": "u8" }, { - "name": "reserved", + "name": "padding", "type": { "array": [ "u8", 5 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } } ] } @@ -8777,13 +8648,22 @@ export const IDL: MangoV4 = { "type": "u16" }, { - "name": "reserved", + "name": "padding", "type": { "array": [ "u8", 2 ] } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } } ] } @@ -8798,7 +8678,7 @@ export const IDL: MangoV4 = { "type": "u16" }, { - "name": "reserved", + "name": "padding", "type": { "array": [ "u8", @@ -8863,6 +8743,15 @@ export const IDL: MangoV4 = { { "name": "takerQuoteLots", "type": "i64" + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } } ] } @@ -8879,7 +8768,7 @@ export const IDL: MangoV4 = { } }, { - "name": "reserved1", + "name": "padding1", "type": { "array": [ "u8", @@ -8892,7 +8781,7 @@ export const IDL: MangoV4 = { "type": "u16" }, { - "name": "reserved2", + "name": "padding2", "type": { "array": [ "u8", @@ -8907,6 +8796,15 @@ export const IDL: MangoV4 = { { "name": "orderId", "type": "i128" + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } } ] } @@ -8939,7 +8837,7 @@ export const IDL: MangoV4 = { "type": { "array": [ "u8", - 84 + 92 ] } } @@ -8980,7 +8878,7 @@ export const IDL: MangoV4 = { "type": { "array": [ "u8", - 199 + 207 ] } } diff --git a/ts/client/src/scripts/mb-flash-loan-3.ts b/ts/client/src/scripts/mb-flash-loan.ts similarity index 98% rename from ts/client/src/scripts/mb-flash-loan-3.ts rename to ts/client/src/scripts/mb-flash-loan.ts index 571a5b127..2a8d2f085 100644 --- a/ts/client/src/scripts/mb-flash-loan-3.ts +++ b/ts/client/src/scripts/mb-flash-loan.ts @@ -21,7 +21,7 @@ const MANGO_MAINNET_PAYER_KEYPAIR = '/Users/tylershipe/.config/solana/deploy.json'; // -// example script which shows usage of flash loan 3 ix using a jupiter swap +// example script which shows usage of flash loan ix using a jupiter swap // // NOTE: we assume that ATA for source and target already exist for wallet async function main() { @@ -142,7 +142,7 @@ async function main() { ); // 1. build flash loan end ix const flashLoadnEndIx = await client.program.methods - .flashLoan3End() + .flashLoanEnd() .accounts({ account: mangoAccount.publicKey, owner: (client.program.provider as AnchorProvider).wallet.publicKey, @@ -188,7 +188,7 @@ async function main() { // 2. build flash loan start ix, add end ix as a post ix try { res = await client.program.methods - .flashLoan3Begin([ + .flashLoanBegin([ new BN(sourceAmount), new BN( 0, @@ -198,7 +198,7 @@ async function main() { group: group.publicKey, // for observing ixs in the entire tx, // e.g. apart from flash loan start and end no other ix should target mango v4 program - // e.g. forbid FlashLoan3Begin been called from CPI + // e.g. forbid FlashLoanBegin been called from CPI instructions: SYSVAR_INSTRUCTIONS_PUBKEY, }) .remainingAccounts([