Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flashloans #121

Merged
merged 48 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
fc4c388
feat: flashloans
jkbpvsc Jul 15, 2023
7075539
Merge remote-tracking branch 'origin/main' into j/16-flashloans-2
jkbpvsc Jul 15, 2023
046d5b6
fix: RiskEngine constructor
jkbpvsc Jul 15, 2023
0bfbcc2
fix: clippy fix
jkbpvsc Jul 15, 2023
ac67d65
fix: test setup, set account flags ixs
Aug 10, 2023
5f9a852
fix: flashloans test
Aug 11, 2023
8ac9d21
fix: more tests
jkbpvsc Aug 11, 2023
584a201
fix: even more tests
Aug 11, 2023
c4096bb
Merge remote-tracking branch 'origin/main' into j/16-flashloans-2
Aug 14, 2023
7e84335
fix: fmt + anchor lint
Aug 14, 2023
b19aa30
fix: fmt
Aug 14, 2023
c2fd7e9
fix: cli imports
jkbpvsc Sep 6, 2023
4e9a153
Merge remote-tracking branch 'origin/main' into j/16-flashloans-2
Nov 8, 2023
707ff43
Merge remote-tracking branch 'origin/main' into j/16-flashloans-2
jkbpvsc Nov 17, 2023
2a94625
feat: reworked risk engine value calculations
Dec 4, 2023
8de92b1
fix: update imports, correct log name
Dec 5, 2023
e2c5520
fix: fix indexer
Dec 5, 2023
e7ceb85
fix: use correct oracle type for price bands
Dec 12, 2023
f4a7c14
fix: format
Dec 15, 2023
89383ad
fix: again with cyrills unreasonable demands
Dec 15, 2023
9553a5a
fix: check start and end flashloan ixs are not in CPI
Dec 15, 2023
c1747a2
fix: add tests
Dec 15, 2023
10753c0
fix: fmt
Dec 15, 2023
8a4149e
Merge branch 'main' into j/16-flashloans-2
jkbpvsc Dec 15, 2023
5e2bb01
Merge branch 'main' into 142-ignore-stale-banks-for-isolated-deposits
jkbpvsc Dec 15, 2023
880dbf7
fix: update indexer, and cli
Dec 15, 2023
023c375
Merge remote-tracking branch 'origin/142-ignore-stale-banks-for-isola…
Dec 15, 2023
e5ec574
fix: fuzz tests
Dec 15, 2023
0c8db68
fix: fuzzer generation
Dec 15, 2023
a5e0f5f
Merge branch 'main' into j/fuzz-update
jkbpvsc Dec 15, 2023
90446f0
Merge branch 'main' into j/fuzz-update
jkbpvsc Dec 15, 2023
994fc78
fix: fuzz github action
Dec 15, 2023
db94a86
Merge remote-tracking branch 'origin/j/fuzz-update' into j/fuzz-update
Dec 15, 2023
ac664d8
fix: action
Dec 15, 2023
859389d
fix: actions
Dec 15, 2023
7eab57b
fix: temp disabled manual actions
Dec 15, 2023
3b95304
fix: install cargo fuzz
Dec 15, 2023
ead9b1d
fix: gha
Dec 15, 2023
0e8f218
Merge branch 'main' into j/16-flashloans-2
jkbpvsc Dec 15, 2023
d634cb8
Merge remote-tracking branch 'origin/j/16-flashloans-2' into j/16-fla…
Dec 15, 2023
5736cbe
Merge branch 'j/fuzz-update' into j/16-flashloans-2
Dec 15, 2023
fe1716a
Merge remote-tracking branch 'origin/j/16-flashloans-2' into j/16-fla…
Dec 15, 2023
300bfd5
Merge branch 'main' into j/16-flashloans-2
jkbpvsc Dec 15, 2023
a9ace7c
fix: assert start and end fl ix are not in CPI
jkbpvsc Dec 17, 2023
e50f14b
Merge branch 'main' into j/16-flashloans-2
jkbpvsc Dec 17, 2023
faa4428
fix: nit
Dec 19, 2023
42ead6b
Merge remote-tracking branch 'origin/j/16-flashloans-2' into j/16-fla…
Dec 19, 2023
3d1afab
Merge branch 'main' into j/16-flashloans-2
jkbpvsc Jan 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions programs/marginfi/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,15 @@ pub enum MarginfiError {
EmissionsUpdateError,
#[msg("Account disabled")] // 6037
AccountDisabled,
#[msg("Account can't temporarily open new balances, please close a balance first")]
#[msg("Account can't temporarily open 3 balances, please close a balance first")] // 6038
AccountTempActiveBalanceLimitExceeded,
#[msg("Illegal balance state")] // 6038
#[msg("Illegal action during flashloan")] // 6039
AccountInFlashloan,
#[msg("Illegal flashloan")] // 6040
IllegalFlashloan,
#[msg("Illegal flag")] // 6041
IllegalFlag,
#[msg("Illegal balance state")] // 6043
IllegalBalanceState,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ use crate::{
events::{AccountEventHeader, LendingAccountBorrowEvent},
prelude::{MarginfiError, MarginfiGroup, MarginfiResult},
state::{
marginfi_account::{
BankAccountWrapper, MarginfiAccount, RiskEngine, RiskRequirementType, DISABLED_FLAG,
},
marginfi_account::{BankAccountWrapper, MarginfiAccount, RiskEngine, DISABLED_FLAG},
marginfi_group::{Bank, BankVaultType},
},
};
Expand Down Expand Up @@ -87,8 +85,7 @@ pub fn lending_account_borrow(ctx: Context<LendingAccountBorrow>, amount: u64) -

// Check account health, if below threshold fail transaction
// Assuming `ctx.remaining_accounts` holds only oracle accounts
RiskEngine::new(&marginfi_account, ctx.remaining_accounts)?
.check_account_health(RiskRequirementType::Initial)?;
RiskEngine::check_account_init_health(&marginfi_account, ctx.remaining_accounts)?;

Ok(())
}
Expand Down
147 changes: 147 additions & 0 deletions programs/marginfi/src/instructions/marginfi_account/flashloan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use anchor_lang::{prelude::*, Discriminator};
use solana_program::{
instruction::{get_stack_height, TRANSACTION_LEVEL_STACK_HEIGHT},
sysvar::{self, instructions},
};

use crate::{
check,
prelude::*,
state::marginfi_account::{
MarginfiAccount, RiskEngine, DISABLED_FLAG, FLASHLOAN_ENABLED_FLAG, IN_FLASHLOAN_FLAG,
},
};

pub fn lending_account_start_flashloan(
ctx: Context<LendingAccountStartFlashloan>,
end_index: u64,
) -> MarginfiResult<()> {
check_flashloan_can_start(
&ctx.accounts.marginfi_account,
&ctx.accounts.ixs_sysvar,
end_index as usize,
)?;

let mut marginfi_account = ctx.accounts.marginfi_account.load_mut()?;
marginfi_account.set_flag(IN_FLASHLOAN_FLAG);

Ok(())
}

#[derive(Accounts)]
pub struct LendingAccountStartFlashloan<'info> {
#[account(mut)]
pub marginfi_account: AccountLoader<'info, MarginfiAccount>,
#[account(address = marginfi_account.load()?.authority)]
pub signer: Signer<'info>,
/// CHECK: Instructions sysvar
#[account(address = sysvar::instructions::ID)]
pub ixs_sysvar: AccountInfo<'info>,
}

const END_FL_IX_MARGINFI_ACCOUNT_AI_IDX: usize = 0;

/// Checklist
/// 1. `end_flashloan` ix index is after `start_flashloan` ix index
/// 2. Ixs has an `end_flashloan` ix present
/// 3. `end_flashloan` ix is for the marginfi program
/// 3. `end_flashloan` ix is for the same marginfi account
/// 4. Account is not disabled
/// 5. Account is not already in a flashloan
/// 6. Start flashloan ix is not in CPI
/// 7. End flashloan ix is not in CPI
pub fn check_flashloan_can_start(
marginfi_account: &AccountLoader<MarginfiAccount>,
sysvar_ixs: &AccountInfo,
end_fl_idx: usize,
) -> MarginfiResult<()> {
check!(
marginfi_account.load()?.get_flag(FLASHLOAN_ENABLED_FLAG),
MarginfiError::IllegalFlashloan
);

let current_ix_idx: usize = instructions::load_current_index_checked(sysvar_ixs)?.into();

check!(current_ix_idx < end_fl_idx, MarginfiError::IllegalFlashloan);

// Check current ix is not a CPI
let current_ix = instructions::load_instruction_at_checked(current_ix_idx, sysvar_ixs)?;

check!(
get_stack_height() == TRANSACTION_LEVEL_STACK_HEIGHT,
MarginfiError::IllegalFlashloan,
"Start flashloan ix should not be in CPI"
);

check!(
current_ix.program_id.eq(&crate::id()),
MarginfiError::IllegalFlashloan,
"Start flashloan ix should not be in CPI"
);

// Will error if ix doesn't exist
let unchecked_end_fl_ix = instructions::load_instruction_at_checked(end_fl_idx, sysvar_ixs)?;

check!(
unchecked_end_fl_ix.data[..8]
.eq(&crate::instruction::LendingAccountEndFlashloan::DISCRIMINATOR),
MarginfiError::IllegalFlashloan
);

check!(
unchecked_end_fl_ix.program_id.eq(&crate::id()),
MarginfiError::IllegalFlashloan
);

let end_fl_ix = unchecked_end_fl_ix;

let end_fl_marginfi_account = end_fl_ix
.accounts
.get(END_FL_IX_MARGINFI_ACCOUNT_AI_IDX)
.ok_or(MarginfiError::IllegalFlashloan)?;

check!(
end_fl_marginfi_account.pubkey.eq(&marginfi_account.key()),
MarginfiError::IllegalFlashloan
);

let marginf_account = marginfi_account.load()?;

check!(
!marginf_account.get_flag(DISABLED_FLAG),
MarginfiError::AccountDisabled
);

check!(
!marginf_account.get_flag(IN_FLASHLOAN_FLAG),
MarginfiError::IllegalFlashloan
);

Ok(())
}

pub fn lending_account_end_flashloan(
ctx: Context<LendingAccountEndFlashloan>,
) -> MarginfiResult<()> {
check!(
get_stack_height() == TRANSACTION_LEVEL_STACK_HEIGHT,
MarginfiError::IllegalFlashloan,
"End flashloan ix should not be in CPI"
);

let mut marginfi_account = ctx.accounts.marginfi_account.load_mut()?;

marginfi_account.unset_flag(IN_FLASHLOAN_FLAG);

RiskEngine::check_account_init_health(&marginfi_account, ctx.remaining_accounts)?;

Ok(())
}

#[derive(Accounts)]
pub struct LendingAccountEndFlashloan<'info> {
#[account(mut)]
pub marginfi_account: AccountLoader<'info, MarginfiAccount>,
#[account(address = marginfi_account.load()?.authority)]
pub signer: Signer<'info>,
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::constants::{
INSURANCE_VAULT_SEED, LIQUIDATION_INSURANCE_FEE, LIQUIDATION_LIQUIDATOR_FEE, MAX_PRICE_AGE_SEC,
};
use crate::events::{AccountEventHeader, LendingAccountLiquidateEvent, LiquidationBalances};
use crate::state::marginfi_account::{calc_amount, calc_value, RiskEngine, RiskRequirementType};
use crate::state::marginfi_account::{calc_amount, calc_value, RiskEngine};
use crate::state::marginfi_group::{Bank, BankVaultType};
use crate::state::price::{OraclePriceFeedAdapter, OraclePriceType, PriceAdapter, PriceBias};
use crate::{
Expand Down Expand Up @@ -343,8 +343,10 @@ pub fn lending_account_liquidate(
)?;

// Verify liquidator account health
RiskEngine::new(&liquidator_marginfi_account, liquidator_remaining_accounts)?
.check_account_health(RiskRequirementType::Initial)?;
RiskEngine::check_account_init_health(
&liquidator_marginfi_account,
liquidator_remaining_accounts,
)?;

emit!(LendingAccountLiquidateEvent {
header: AccountEventHeader {
Expand Down
2 changes: 2 additions & 0 deletions programs/marginfi/src/instructions/marginfi_account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod borrow;
mod close_balance;
mod deposit;
mod emissions;
mod flashloan;
mod initialize;
mod liquidate;
mod repay;
Expand All @@ -11,6 +12,7 @@ pub use borrow::*;
pub use close_balance::*;
pub use deposit::*;
pub use emissions::*;
pub use flashloan::*;
pub use initialize::*;
pub use liquidate::*;
pub use repay::*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ use crate::{
events::{AccountEventHeader, LendingAccountWithdrawEvent},
prelude::*,
state::{
marginfi_account::{
BankAccountWrapper, MarginfiAccount, RiskEngine, RiskRequirementType, DISABLED_FLAG,
},
marginfi_account::{BankAccountWrapper, MarginfiAccount, RiskEngine, DISABLED_FLAG},
marginfi_group::{Bank, BankVaultType},
},
};
Expand Down Expand Up @@ -100,8 +98,7 @@ pub fn lending_account_withdraw(

// Check account health, if below threshold fail transaction
// Assuming `ctx.remaining_accounts` holds only oracle accounts
RiskEngine::new(&marginfi_account, ctx.remaining_accounts)?
.check_account_health(RiskRequirementType::Initial)?;
RiskEngine::check_account_init_health(&marginfi_account, ctx.remaining_accounts)?;

Ok(())
}
Expand Down
91 changes: 91 additions & 0 deletions programs/marginfi/src/instructions/marginfi_group/configure.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::check;
use crate::events::{GroupEventHeader, MarginfiGroupConfigureEvent};
use crate::prelude::MarginfiError;
use crate::state::marginfi_account::{MarginfiAccount, FLASHLOAN_ENABLED_FLAG};
use crate::{
state::marginfi_group::{GroupConfig, MarginfiGroup},
MarginfiResult,
Expand Down Expand Up @@ -34,3 +37,91 @@ pub struct MarginfiGroupConfigure<'info> {
)]
pub admin: Signer<'info>,
}

/// Only these flags can be configured
///
/// Example:
/// CONFIGURABLE_FLAGS = 0b0110
///
/// 0b0010 is a valid flag
/// 0b0110 is a valid flag
/// 0b1000 is not a valid flag
/// 0b0101 is not a valid flag
const CONFIGURABLE_FLAGS: u64 = FLASHLOAN_ENABLED_FLAG;

fn flag_can_be_set(flag: u64) -> bool {
// If bitwise AND operation between flag and its bitwise NOT of CONFIGURABLE_FLAGS is 0,
// it means no bit in flag is set outside the configurable bits.
(flag & !CONFIGURABLE_FLAGS) == 0
}

pub fn set_account_flag(ctx: Context<SetAccountFlag>, flag: u64) -> MarginfiResult {
check!(flag_can_be_set(flag), MarginfiError::IllegalFlag);

let mut marginfi_account = ctx.accounts.marginfi_account.load_mut()?;

marginfi_account.set_flag(flag);

Ok(())
}

#[derive(Accounts)]
pub struct SetAccountFlag<'info> {
#[account(address = marginfi_account.load()?.group)]
pub marginfi_group: AccountLoader<'info, MarginfiGroup>,

#[account(mut)]
pub marginfi_account: AccountLoader<'info, MarginfiAccount>,

/// Admin only
#[account(address = marginfi_group.load()?.admin)]
pub admin: Signer<'info>,
}

pub fn unset_account_flag(ctx: Context<UnsetAccountFlag>, flag: u64) -> MarginfiResult {
check!(flag_can_be_set(flag), MarginfiError::IllegalFlag);

let mut marginfi_account = ctx.accounts.marginfi_account.load_mut()?;

marginfi_account.unset_flag(flag);

Ok(())
}

#[derive(Accounts)]
pub struct UnsetAccountFlag<'info> {
#[account(address = marginfi_account.load()?.group)]
pub marginfi_group: AccountLoader<'info, MarginfiGroup>,

#[account(mut)]
pub marginfi_account: AccountLoader<'info, MarginfiAccount>,

/// Admin only
#[account(address = marginfi_group.load()?.admin)]
pub admin: Signer<'info>,
}

#[cfg(test)]
mod tests {
use crate::state::marginfi_account::{
DISABLED_FLAG, FLASHLOAN_ENABLED_FLAG, IN_FLASHLOAN_FLAG,
};

#[test]
///
/// 0b0001 is a valid flag
/// 0b0011 is a invalid flag
/// 0b0101 is a invalid flag
/// 0b1000 is a invalid flag
fn test_check_flag() {
let flag1 = FLASHLOAN_ENABLED_FLAG;
let flag2 = FLASHLOAN_ENABLED_FLAG + IN_FLASHLOAN_FLAG;
let flag3 = IN_FLASHLOAN_FLAG + DISABLED_FLAG + FLASHLOAN_ENABLED_FLAG;
let flag4 = DISABLED_FLAG + IN_FLASHLOAN_FLAG;

assert!(super::flag_can_be_set(flag1));
assert!(!super::flag_can_be_set(flag2));
assert!(!super::flag_can_be_set(flag3));
assert!(!super::flag_can_be_set(flag4));
}
}
21 changes: 21 additions & 0 deletions programs/marginfi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,19 @@ pub mod marginfi {
marginfi_account::lending_account_liquidate(ctx, asset_amount)
}

pub fn lending_account_start_flashloan(
ctx: Context<LendingAccountStartFlashloan>,
end_index: u64,
) -> MarginfiResult {
marginfi_account::lending_account_start_flashloan(ctx, end_index)
}

pub fn lending_account_end_flashloan(
ctx: Context<LendingAccountEndFlashloan>,
) -> MarginfiResult {
marginfi_account::lending_account_end_flashloan(ctx)
}

// Operational instructions
pub fn lending_pool_accrue_bank_interest(
ctx: Context<LendingPoolAccrueBankInterest>,
Expand All @@ -156,4 +169,12 @@ pub mod marginfi {
) -> MarginfiResult {
marginfi_group::lending_pool_collect_bank_fees(ctx)
}

pub fn set_account_flag(ctx: Context<SetAccountFlag>, flag: u64) -> MarginfiResult {
marginfi_group::set_account_flag(ctx, flag)
}

pub fn unset_account_flag(ctx: Context<UnsetAccountFlag>, flag: u64) -> MarginfiResult {
marginfi_group::unset_account_flag(ctx, flag)
}
}
Loading
Loading