2023-02-14 23:42:07 -08:00
use crate ::accounts_ix ::* ;
2022-06-08 04:43:12 -07:00
use crate ::accounts_zerocopy ::* ;
2022-08-01 07:55:17 -07:00
use crate ::error ::* ;
use crate ::group_seeds ;
2022-12-08 04:12:43 -08:00
use crate ::health ::{ new_fixed_order_account_retriever , new_health_cache , AccountRetriever } ;
2023-11-22 07:00:47 -08:00
use crate ::logs ::{ emit_stack , FlashLoanLogV3 , FlashLoanTokenDetailV3 , TokenBalanceLog } ;
2022-12-29 02:48:46 -08:00
use crate ::state ::* ;
2023-02-24 02:56:33 -08:00
2022-03-02 21:15:28 -08:00
use anchor_lang ::prelude ::* ;
2022-08-01 07:55:17 -07:00
use anchor_lang ::solana_program ::sysvar ::instructions as tx_instructions ;
2022-08-05 11:32:57 -07:00
use anchor_lang ::Discriminator ;
2022-11-09 00:35:13 -08:00
use anchor_spl ::associated_token ::AssociatedToken ;
2023-02-14 23:42:07 -08:00
use anchor_spl ::token ::{ self , TokenAccount } ;
2022-03-26 11:34:44 -07:00
use fixed ::types ::I80F48 ;
2022-05-19 04:45:46 -07:00
2022-08-01 07:55:17 -07:00
/// 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 > (
2023-10-06 07:18:59 -07:00
program_id : & Pubkey ,
account_ai : & AccountLoader < ' info , MangoAccountFixed > ,
owner_pk : & Pubkey ,
instructions_ai : & AccountInfo < ' info > ,
token_program_ai : & AccountInfo < ' info > ,
remaining_accounts : & [ AccountInfo < ' info > ] ,
2022-08-01 07:55:17 -07:00
loan_amounts : Vec < u64 > ,
2022-03-04 05:30:53 -08:00
) -> Result < ( ) > {
2023-04-14 06:18:02 -07:00
let num_loans = loan_amounts . len ( ) ;
require_gt! ( num_loans , 0 ) ;
// Loans of 0 are acceptable and common: Users often want to loan some of token A,
// nothing of token B, swap A to B and then deposit the gains.
2023-10-06 07:18:59 -07:00
let account = account_ai . load_full_mut ( ) ? ;
2022-11-09 00:35:13 -08:00
// account constraint #1
require! (
2023-10-06 07:18:59 -07:00
account . fixed . is_owner_or_delegate ( * owner_pk ) ,
2022-11-09 00:35:13 -08:00
MangoError ::SomeError
) ;
2023-10-06 07:18:59 -07:00
require_eq! ( remaining_accounts . len ( ) , 3 * num_loans + 1 ) ;
let banks = & remaining_accounts [ .. num_loans ] ;
let vaults = & remaining_accounts [ num_loans .. 2 * num_loans ] ;
let token_accounts = & remaining_accounts [ 2 * num_loans .. 3 * num_loans ] ;
let group_ai = & remaining_accounts [ 3 * num_loans ] ;
2022-06-18 07:43:45 -07:00
2022-08-30 04:46:39 -07:00
let group_al = AccountLoader ::< Group > ::try_from ( group_ai ) ? ;
2022-08-16 01:19:15 -07:00
let group = group_al . load ( ) ? ;
2023-01-26 11:27:39 -08:00
require! (
group . is_ix_enabled ( IxGate ::FlashLoan ) ,
MangoError ::IxIsDisabled
) ;
2022-08-01 07:55:17 -07:00
let group_seeds = group_seeds! ( group ) ;
let seeds = [ & group_seeds [ .. ] ] ;
2022-03-04 05:30:53 -08:00
2022-08-16 01:19:15 -07:00
// This instruction does not currently deal with:
// - borrowing twice from the same bank
// - borrowing from two different banks for the same token
// Hence we collect all token_indexes and ensure each appears only once.
let mut seen_token_indexes = Vec ::with_capacity ( num_loans ) ;
2022-08-01 07:55:17 -07:00
// 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 ::< Bank > ( ) ? ;
2022-08-16 01:19:15 -07:00
require_keys_eq! ( bank . group , group_ai . key ( ) ) ;
2022-08-01 07:55:17 -07:00
require_keys_eq! ( bank . vault , * vault_ai . key ) ;
2022-08-16 01:19:15 -07:00
require_msg! (
! seen_token_indexes . contains ( & bank . token_index ) ,
" each loan must be for a unique token_index "
) ;
seen_token_indexes . push ( bank . token_index ) ;
2022-08-06 03:59:13 -07:00
let vault = Account ::< TokenAccount > ::try_from ( vault_ai ) ? ;
2022-08-01 07:55:17 -07:00
let token_account = Account ::< TokenAccount > ::try_from ( token_account_ai ) ? ;
2023-04-14 06:18:02 -07:00
require_keys_eq! ( token_account . mint , bank . mint ) ;
// This check is likely unnecessary
2022-08-16 01:19:15 -07:00
require_keys_neq! ( token_account . owner , group_ai . key ( ) ) ;
2023-04-14 06:18:02 -07:00
require_eq! ( bank . flash_loan_approved_amount , 0 ) ;
require_eq! ( bank . flash_loan_token_account_initial , u64 ::MAX ) ;
2022-08-01 07:55:17 -07:00
bank . flash_loan_approved_amount = * amount ;
2022-08-06 03:59:13 -07:00
bank . flash_loan_token_account_initial = token_account . amount ;
2022-08-01 07:55:17 -07:00
// Transfer the loaned funds
if * amount > 0 {
// Provide a readable error message in case the vault doesn't have enough tokens
2022-08-06 03:59:13 -07:00
if vault . amount < * amount {
2022-08-01 07:55:17 -07:00
return err! ( MangoError ::InsufficentBankVaultFunds ) . with_context ( | | {
format! (
" bank vault {} does not have enough tokens, need {} but have {} " ,
2022-08-06 03:59:13 -07:00
vault_ai . key , amount , vault . amount
2022-08-01 07:55:17 -07:00
)
} ) ;
2022-05-19 04:45:46 -07:00
}
2022-08-01 07:55:17 -07:00
let transfer_ctx = CpiContext ::new (
2023-10-06 07:18:59 -07:00
token_program_ai . clone ( ) ,
2022-08-01 07:55:17 -07:00
token ::Transfer {
from : vault_ai . clone ( ) ,
to : token_account_ai . clone ( ) ,
2022-08-16 01:19:15 -07:00
authority : group_ai . clone ( ) ,
2022-08-01 07:55:17 -07:00
} ,
)
. with_signer ( & seeds ) ;
token ::transfer ( transfer_ctx , * amount ) ? ;
}
2022-05-19 04:45:46 -07:00
}
2022-03-04 05:30:53 -08:00
2022-08-01 07:55:17 -07:00
// Check if the other instructions in the transactions are compatible
2022-06-30 05:35:05 -07:00
{
2023-10-06 07:18:59 -07:00
let ixs = instructions_ai ;
2022-08-01 07:55:17 -07:00
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! (
2023-10-06 07:18:59 -07:00
& current_ix . program_id = = program_id ,
2022-08-01 07:55:17 -07:00
" 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 ( ) ) ,
} ;
2023-10-06 07:18:59 -07:00
if account . fixed . is_delegate ( * owner_pk ) {
2022-11-09 00:35:13 -08:00
require_msg! (
ix . program_id = = AssociatedToken ::id ( )
| | ix . program_id = = jupiter_mainnet_3 ::ID
2023-04-19 07:17:26 -07:00
| | ix . program_id = = jupiter_mainnet_4 ::ID
2023-10-02 10:40:43 -07:00
| | ix . program_id = = jupiter_mainnet_6 ::ID
2023-05-04 05:46:09 -07:00
| | ix . program_id = = compute_budget ::ID
2023-04-19 07:17:26 -07:00
| | ix . program_id = = crate ::id ( ) ,
2023-10-02 10:40:43 -07:00
" delegate is only allowed to pass in ixs to ATA or Jupiter v3/v4/v6 programs, passed ({}) " , ix . program_id
2022-11-09 00:35:13 -08:00
) ;
}
2022-08-01 07:55:17 -07:00
// Check that the mango program key is not used
if ix . program_id = = crate ::id ( ) {
// must be the FlashLoanEnd instruction
require! (
2023-04-14 06:18:02 -07:00
ix . data [ 0 .. 8 ] = = crate ::instruction ::FlashLoanEndV2 ::discriminator ( ) ,
2022-08-01 07:55:17 -07:00
MangoError ::SomeError
) ;
2023-04-14 06:18:02 -07:00
// the correct number of loans is passed to the End instruction
require_eq! ( ix . data [ 8 ] as usize , num_loans ) ;
2022-08-01 07:55:17 -07:00
2022-11-09 00:35:13 -08:00
require_msg! (
2023-10-06 07:18:59 -07:00
account_ai . key ( ) = = ix . accounts [ 0 ] . pubkey ,
2022-11-09 00:35:13 -08:00
" the mango account passed to FlashLoanBegin and End must match "
) ;
2022-08-06 04:36:21 -07:00
// check that the same vaults and token accounts are passed
2023-10-06 07:18:59 -07:00
let begin_accounts = & remaining_accounts [ num_loans .. ] ;
2022-08-16 01:19:15 -07:00
let end_accounts = & ix . accounts [ ix . accounts . len ( ) - begin_accounts . len ( ) .. ] ;
2022-08-01 07:55:17 -07:00
for ( begin_account , end_account ) in begin_accounts . iter ( ) . zip ( end_accounts . iter ( ) ) {
2022-08-16 01:19:15 -07:00
require_msg! ( * begin_account . key = = end_account . pubkey , " the trailing vault, token and group accounts passed to FlashLoanBegin and End must match, found {} on begin and {} on end " , begin_account . key , end_account . pubkey ) ;
2022-08-01 07:55:17 -07:00
}
2023-08-18 06:37:15 -07:00
// No need to check any instructions after the end instruction.
// "Duplicate FlashLoanEnd" is guarded against the same way as "End without Begin":
// The End instruction requires at least one bank-vault pair and that bank
// must have flash_loan_token_account_initial set - which only happens in Begin.
found_end = true ;
break ;
2022-08-01 07:55:17 -07:00
} 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 " ) ;
}
2022-05-20 01:55:48 -07:00
}
2022-05-26 12:12:02 -07:00
2022-08-01 07:55:17 -07:00
index + = 1 ;
}
require_msg! (
found_end ,
" found no FlashLoanEnd instruction in transaction "
) ;
}
Ok ( ( ) )
}
2023-10-06 07:18:59 -07:00
pub fn flash_loan_swap_begin < ' key , ' accounts , ' remaining , ' info > (
ctx : Context < ' key , ' accounts , ' remaining , ' info , FlashLoanSwapBegin < ' info > > ,
loan_amount : u64 ,
) -> Result < ( ) > {
// Create missing token accounts if needed. We do this here because
// it uses up fewer tx bytes than emitting the two create-idempotent instructions
// separately. Primarily because top-level ix program addresses can't be in
// an address lookup table.
// Remaining accounts are banks, vaults, token accounts, group
let rlen = ctx . remaining_accounts . len ( ) ;
require_eq! ( rlen , 2 + 2 + 2 + 1 ) ;
{
let input_account = & ctx . remaining_accounts [ rlen - 3 ] ;
let ctx = CpiContext ::new (
ctx . accounts . associated_token_program . to_account_info ( ) ,
anchor_spl ::associated_token ::Create {
payer : ctx . accounts . owner . to_account_info ( ) ,
associated_token : input_account . clone ( ) ,
authority : ctx . accounts . owner . to_account_info ( ) ,
mint : ctx . accounts . input_mint . to_account_info ( ) ,
system_program : ctx . accounts . system_program . to_account_info ( ) ,
token_program : ctx . accounts . token_program . to_account_info ( ) ,
} ,
) ;
anchor_spl ::associated_token ::create_idempotent ( ctx ) ? ;
}
{
let output_account = & ctx . remaining_accounts [ rlen - 2 ] ;
let ctx = CpiContext ::new (
ctx . accounts . associated_token_program . to_account_info ( ) ,
anchor_spl ::associated_token ::Create {
payer : ctx . accounts . owner . to_account_info ( ) ,
associated_token : output_account . clone ( ) ,
authority : ctx . accounts . owner . to_account_info ( ) ,
mint : ctx . accounts . output_mint . to_account_info ( ) ,
system_program : ctx . accounts . system_program . to_account_info ( ) ,
token_program : ctx . accounts . token_program . to_account_info ( ) ,
} ,
) ;
anchor_spl ::associated_token ::create_idempotent ( ctx ) ? ;
}
flash_loan_begin (
ctx . program_id ,
& ctx . accounts . account ,
ctx . accounts . owner . key ,
& ctx . accounts . instructions ,
& ctx . accounts . token_program ,
ctx . remaining_accounts ,
vec! [ loan_amount , 0 ] ,
) ? ;
Ok ( ( ) )
}
2022-08-01 07:55:17 -07:00
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 > > ,
2023-04-14 06:18:02 -07:00
num_loans : u8 ,
2022-08-17 03:36:55 -07:00
flash_loan_type : FlashLoanType ,
2022-08-01 07:55:17 -07:00
) -> Result < ( ) > {
2023-04-14 06:18:02 -07:00
require_gt! ( num_loans , 0 ) ;
// FlashLoanEnd can only be called in the same tx as a FlashLoanBegin because:
// - FlashLoanBegin checks for a matching FlashLoanEnd in the same tx
// - FlashLoanBegin sets flash_loan_token_account_initial on a bank, which is
// validated below. (and there must be at least one bank-vault-token account triple)
2022-12-29 02:48:46 -08:00
let mut account = ctx . accounts . account . load_full_mut ( ) ? ;
2022-11-09 00:35:13 -08:00
// account constraint #1
require! (
account . fixed . is_owner_or_delegate ( ctx . accounts . owner . key ( ) ) ,
MangoError ::SomeError
) ;
2022-08-05 10:11:44 -07:00
let group = account . fixed . group ;
2022-08-01 07:55:17 -07:00
2022-08-16 01:19:15 -07:00
let remaining_len = ctx . remaining_accounts . len ( ) ;
let group_ai = & ctx . remaining_accounts [ remaining_len - 1 ] ;
require_keys_eq! ( group , group_ai . key ( ) ) ;
2022-08-01 07:55:17 -07:00
// Find index at which vaults start
2023-04-14 06:18:02 -07:00
let vaults_len : usize = num_loans . into ( ) ;
2022-08-16 01:19:15 -07:00
let vaults_index = remaining_len - 2 * vaults_len - 1 ;
2022-08-01 07:55:17 -07:00
let health_ais = & ctx . remaining_accounts [ .. vaults_index ] ;
let vaults = & ctx . remaining_accounts [ vaults_index .. vaults_index + vaults_len ] ;
2022-08-16 01:19:15 -07:00
let token_accounts =
& ctx . remaining_accounts [ vaults_index + vaults_len .. vaults_index + 2 * vaults_len ] ;
2022-08-17 07:21:56 -07:00
// Verify that each mentioned vault has a bank in the health accounts
2022-08-01 07:55:17 -07:00
let mut vaults_with_banks = vec! [ false ; vaults . len ( ) ] ;
2023-10-13 00:02:23 -07:00
// Biggest flash_loan_swap_fee_rate over all involved banks
let mut max_swap_fee_rate = 0.0 f32 ;
2023-08-09 04:27:25 -07:00
2022-08-01 07:55:17 -07:00
// 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 ::< Bank > ( ) {
Ok ( b ) = > b ,
Err ( _ ) = > break ,
} ;
2022-08-16 01:19:15 -07:00
require_keys_eq! ( bank . group , group ) ;
2022-05-20 01:55:48 -07:00
2022-08-01 07:55:17 -07:00
// 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 ,
} ;
2022-05-20 01:55:48 -07:00
2022-08-01 07:55:17 -07:00
vaults_with_banks [ vault_index ] = true ;
let token_account_ai = & token_accounts [ vault_index ] ;
2022-08-02 00:04:02 -07:00
let token_account = Account ::< TokenAccount > ::try_from ( token_account_ai ) ? ;
2022-05-20 01:55:48 -07:00
2023-04-14 06:18:02 -07:00
// The token account could have been re-initialized for a different mint
require_keys_eq! ( token_account . mint , bank . mint ) ;
2022-08-01 07:55:17 -07:00
// 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.
2023-04-14 06:18:02 -07:00
// This check guarantees that FlashLoanBegin was called on this bank.
2022-08-06 03:59:13 -07:00
require_neq! ( bank . flash_loan_token_account_initial , u64 ::MAX ) ;
2022-06-30 05:35:05 -07:00
2022-08-01 07:55:17 -07:00
// Create the token position now, so we can compute the pre-health with fixed order health accounts
2022-08-18 04:45:31 -07:00
let ( _ , raw_token_index , _ ) = account . ensure_token_position ( bank . token_index ) ? ;
2022-03-02 21:15:28 -08:00
2024-01-29 04:39:33 -08:00
// Transfer any excess over the initial balance of the token account back
2022-08-01 07:55:17 -07:00
// into the vault. Compute the total change in the vault balance.
let mut change = - I80F48 ::from ( bank . flash_loan_approved_amount ) ;
2022-08-06 03:59:13 -07:00
if token_account . amount > bank . flash_loan_token_account_initial {
2022-08-01 07:55:17 -07:00
let transfer_ctx = CpiContext ::new (
2022-05-20 01:55:48 -07:00
ctx . accounts . token_program . to_account_info ( ) ,
2022-08-01 07:55:17 -07:00
token ::Transfer {
from : token_account_ai . clone ( ) ,
to : vault_ai . clone ( ) ,
authority : ctx . accounts . owner . to_account_info ( ) ,
2022-05-20 01:55:48 -07:00
} ,
2022-08-01 07:55:17 -07:00
) ;
2022-08-06 03:59:13 -07:00
let repay = token_account . amount - bank . flash_loan_token_account_initial ;
2022-08-01 07:55:17 -07:00
token ::transfer ( transfer_ctx , repay ) ? ;
let repay = I80F48 ::from ( repay ) ;
2023-02-24 02:56:33 -08:00
change + = repay ;
2022-05-20 01:55:48 -07:00
}
2022-08-01 07:55:17 -07:00
2023-10-13 00:02:23 -07:00
max_swap_fee_rate = max_swap_fee_rate . max ( bank . flash_loan_swap_fee_rate ) ;
2023-08-09 04:27:25 -07:00
2022-08-01 07:55:17 -07:00
changes . push ( TokenVaultChange {
token_index : bank . token_index ,
bank_index : i ,
raw_token_index ,
amount : change ,
} ) ;
2022-05-20 01:55:48 -07:00
}
2022-03-02 21:15:28 -08:00
2022-08-01 07:55:17 -07:00
// 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
) ;
}
2022-06-23 07:41:24 -07:00
2023-07-10 07:20:43 -07:00
match flash_loan_type {
FlashLoanType ::Unknown = > { }
2024-02-19 00:00:30 -08:00
FlashLoanType ::Swap | FlashLoanType ::SwapWithoutFee = > {
2023-07-10 07:20:43 -07:00
require_msg! (
changes . len ( ) = = 2 ,
2024-02-19 00:00:30 -08:00
" when flash_loan_type is Swap or SwapWithoutFee there must be exactly 2 token vault changes "
2023-07-10 07:20:43 -07:00
)
}
}
2022-08-17 07:21:56 -07:00
// Check health before balance adjustments
2022-08-01 07:55:17 -07:00
let retriever = new_fixed_order_account_retriever ( health_ais , & account . borrow ( ) ) ? ;
2023-11-14 05:52:04 -08:00
let now_ts : u64 = Clock ::get ( ) ? . unix_timestamp . try_into ( ) . unwrap ( ) ;
let health_cache = new_health_cache ( & account . borrow ( ) , & retriever , now_ts ) ? ;
2023-02-10 00:00:36 -08:00
let pre_init_health = account . check_health_pre ( & health_cache ) ? ;
2022-08-01 07:55:17 -07:00
2022-12-02 03:24:11 -08:00
// Prices for logging and net borrow checks
let mut oracle_prices = vec! [ ] ;
2022-08-01 07:55:17 -07:00
for change in & changes {
let ( _ , oracle_price ) = retriever . bank_and_oracle (
& account . fixed . group ,
change . bank_index ,
change . token_index ,
) ? ;
2022-05-19 04:45:46 -07:00
2022-12-02 03:24:11 -08:00
oracle_prices . push ( oracle_price ) ;
2022-08-01 07:55:17 -07:00
}
// 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 ( ) ) ;
2022-12-02 03:24:11 -08:00
for ( change , oracle_price ) in changes . iter ( ) . zip ( oracle_prices . iter ( ) ) {
2022-08-01 07:55:17 -07:00
let mut bank = health_ais [ change . bank_index ] . load_mut ::< Bank > ( ) ? ;
2023-01-04 00:24:40 -08:00
2022-08-18 04:45:31 -07:00
let position = account . token_position_mut_by_raw_index ( change . raw_token_index ) ;
2022-08-01 07:55:17 -07:00
let native = position . native ( & bank ) ;
2023-01-04 00:24:40 -08:00
2023-08-30 01:57:41 -07:00
let approved_amount_u64 = bank . flash_loan_approved_amount ;
let approved_amount = I80F48 ::from ( approved_amount_u64 ) ;
2022-08-01 07:55:17 -07:00
let loan = if native . is_positive ( ) {
2023-02-24 02:56:33 -08:00
( approved_amount - native ) . max ( I80F48 ::ZERO )
2022-06-18 07:43:45 -07:00
} else {
2022-08-01 07:55:17 -07:00
approved_amount
2022-06-18 07:43:45 -07:00
} ;
2023-02-24 02:56:33 -08:00
let loan_origination_fee = loan * bank . loan_origination_fee_rate ;
bank . collected_fees_native + = loan_origination_fee ;
2023-01-04 00:24:40 -08:00
2023-10-13 00:02:23 -07:00
let swap_fee = if change . amount < 0 & & flash_loan_type = = FlashLoanType ::Swap {
- change . amount * I80F48 ::from_num ( max_swap_fee_rate )
2023-08-09 04:27:25 -07:00
} else {
I80F48 ::ZERO
} ;
2023-10-13 00:02:23 -07:00
bank . collected_fees_native + = swap_fee ;
2023-08-09 04:27:25 -07:00
2023-10-13 00:02:23 -07:00
let change_amount = change . amount - loan_origination_fee - swap_fee ;
2023-02-24 02:56:33 -08:00
let native_after_change = native + change_amount ;
2023-04-13 03:44:12 -07:00
if bank . are_deposits_reduce_only ( ) {
2023-01-04 00:24:40 -08:00
require! (
2023-04-13 03:44:12 -07:00
native_after_change < 1 | | native_after_change < = native ,
MangoError ::TokenInReduceOnlyMode
) ;
}
if bank . are_borrows_reduce_only ( ) {
require! (
native_after_change > = native | | native_after_change > = 0 ,
2023-01-04 00:24:40 -08:00
MangoError ::TokenInReduceOnlyMode
) ;
}
2022-11-25 04:45:17 -08:00
let is_active = bank . change_without_fee (
position ,
2023-04-12 23:56:33 -07:00
change_amount ,
2022-11-25 04:45:17 -08:00
Clock ::get ( ) ? . unix_timestamp . try_into ( ) . unwrap ( ) ,
) ? ;
2022-08-01 07:55:17 -07:00
if ! is_active {
deactivated_token_positions . push ( change . raw_token_index ) ;
2022-05-20 01:55:48 -07:00
}
2022-05-24 04:00:32 -07:00
2023-04-12 23:56:33 -07:00
if change_amount < 0 & & native_after_change < 0 {
2023-12-30 08:45:35 -08:00
bank . enforce_max_utilization_on_borrow ( ) ? ;
2023-04-12 23:56:33 -07:00
bank . check_net_borrows ( * oracle_price ) ? ;
2023-12-30 08:45:35 -08:00
} else {
bank . enforce_borrows_lte_deposits ( ) ? ;
2023-04-12 23:56:33 -07:00
}
2023-12-05 06:43:38 -08:00
if change_amount > 0 & & native_after_change > 0 {
bank . check_deposit_and_oo_limit ( ) ? ;
}
2022-08-01 07:55:17 -07:00
bank . flash_loan_approved_amount = 0 ;
2022-08-06 03:59:13 -07:00
bank . flash_loan_token_account_initial = u64 ::MAX ;
2022-05-24 04:00:32 -07:00
2023-10-13 00:02:23 -07:00
token_loan_details . push ( FlashLoanTokenDetailV3 {
2022-08-01 07:55:17 -07:00
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 ( ) ,
2022-12-02 03:24:11 -08:00
price : oracle_price . to_bits ( ) ,
2023-10-13 00:02:23 -07:00
swap_fee : swap_fee . to_bits ( ) ,
2023-08-30 01:57:41 -07:00
approved_amount : approved_amount_u64 ,
2022-08-01 07:55:17 -07:00
} ) ;
2022-06-30 05:35:05 -07:00
2023-11-22 07:00:47 -08:00
emit_stack ( TokenBalanceLog {
2022-08-05 10:11:44 -07:00
mango_group : group . key ( ) ,
2022-06-30 05:35:05 -07:00
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 ( ) ,
} ) ;
}
2023-11-22 07:00:47 -08:00
emit_stack ( FlashLoanLogV3 {
2022-08-05 10:11:44 -07:00
mango_group : group . key ( ) ,
2022-06-30 05:35:05 -07:00
mango_account : ctx . accounts . account . key ( ) ,
2022-08-17 03:36:55 -07:00
flash_loan_type ,
2023-11-22 07:00:47 -08:00
token_loan_details ,
2022-06-30 05:35:05 -07:00
} ) ;
2022-08-17 07:21:56 -07:00
// Check health after account position changes
2022-08-24 07:39:36 -07:00
let retriever = new_fixed_order_account_retriever ( health_ais , & account . borrow ( ) ) ? ;
2023-11-14 05:52:04 -08:00
let health_cache = new_health_cache ( & account . borrow ( ) , & retriever , now_ts ) ? ;
2023-02-10 00:00:36 -08:00
account . check_health_post ( & health_cache , pre_init_health ) ? ;
2022-08-01 07:55:17 -07:00
// Deactivate inactive token accounts after health check
for raw_token_index in deactivated_token_positions {
2022-09-28 23:04:33 -07:00
account . deactivate_token_position_and_log ( raw_token_index , ctx . accounts . account . key ( ) ) ;
2022-05-24 04:00:32 -07:00
}
2022-05-19 04:45:46 -07:00
Ok ( ( ) )
2022-03-04 05:30:53 -08:00
}