Skip to content

Commit

Permalink
Freeze settings v2 (#263)
Browse files Browse the repository at this point in the history
* To support arena, we added banks with frozen settings. After some consideration, we have determined that the pool administrator should be able to modify the deposit/borrow limits. Those settings are now configurable even on frozen pools.

* Adds a floor to oracle_max_age
  • Loading branch information
jgur-psyops authored Dec 19, 2024
1 parent 244a3b3 commit f5cfba8
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 46 deletions.
1 change: 1 addition & 0 deletions programs/marginfi/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ impl<'state> MarginfiFuzzContext<'state> {
} else {
marginfi::state::marginfi_group::RiskTier::Isolated
},
oracle_max_age: 100,
..Default::default()
},
)
Expand Down
3 changes: 3 additions & 0 deletions programs/marginfi/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub const INIT_BANK_ORIGINATION_FEE_DEFAULT: u32 = 10000;

pub const SECONDS_PER_YEAR: I80F48 = I80F48!(31_536_000);

/// Due to real-world constraints, oracles using an age less than this value are typically too
/// unreliable, and we want to restrict pools from picking an oracle that is effectively unusable
pub const ORACLE_MIN_AGE: u16 = 30;
pub const MAX_PYTH_ORACLE_AGE: u64 = 60;
pub const MAX_SWB_ORACLE_AGE: u64 = 3 * 60;

Expand Down
2 changes: 0 additions & 2 deletions programs/marginfi/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ pub enum MarginfiError {
T22MintRequired,
#[msg("Invalid ATA for global fee account")] // 6048
InvalidFeeAta,
#[msg("Bank settings are frozen and cannot be updated")] // 6049
BankSettingsFrozen,
}

impl From<MarginfiError> for ProgramError {
Expand Down
9 changes: 9 additions & 0 deletions programs/marginfi/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ pub struct LendingPoolBankConfigureEvent {
pub config: BankConfigOpt,
}

#[event]
pub struct LendingPoolBankConfigureFrozenEvent {
pub header: GroupEventHeader,
pub bank: Pubkey,
pub mint: Pubkey,
pub deposit_limit: u64,
pub borrow_limit: u64,
}

#[event]
pub struct LendingPoolBankAccrueInterestEvent {
pub header: GroupEventHeader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub fn lending_pool_add_bank(
let mut bank = bank_loader.load_init()?;

let liquidity_vault_bump = ctx.bumps.liquidity_vault;
let liquidity_vault_authority_bump = ctx.bumps.liquidity_vault_authority;
let liquidity_vault_authority_bump: u8 = ctx.bumps.liquidity_vault_authority;
let insurance_vault_bump = ctx.bumps.insurance_vault;
let insurance_vault_authority_bump = ctx.bumps.insurance_vault_authority;
let fee_vault_bump = ctx.bumps.fee_vault;
Expand Down
52 changes: 33 additions & 19 deletions programs/marginfi/src/instructions/marginfi_group/configure_bank.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::constants::{EMISSIONS_AUTH_SEED, EMISSIONS_TOKEN_ACCOUNT_SEED, FREEZE_SETTINGS};
use crate::events::{GroupEventHeader, LendingPoolBankConfigureEvent};
use crate::events::{
GroupEventHeader, LendingPoolBankConfigureEvent, LendingPoolBankConfigureFrozenEvent,
};
use crate::prelude::MarginfiError;
use crate::{check, math_error, utils};
use crate::{
Expand All @@ -17,27 +19,39 @@ pub fn lending_pool_configure_bank(
) -> MarginfiResult {
let mut bank = ctx.accounts.bank.load_mut()?;

check!(
!bank.get_flag(FREEZE_SETTINGS),
MarginfiError::BankSettingsFrozen
);

bank.configure(&bank_config)?;
// If settings are frozen, you can only update the deposit and borrow limits, everything else is ignored.
if bank.get_flag(FREEZE_SETTINGS) {
bank.configure_unfrozen_fields_only(&bank_config)?;

if bank_config.oracle.is_some() {
bank.config.validate_oracle_setup(ctx.remaining_accounts)?;
emit!(LendingPoolBankConfigureFrozenEvent {
header: GroupEventHeader {
marginfi_group: ctx.accounts.marginfi_group.key(),
signer: Some(*ctx.accounts.admin.key)
},
bank: ctx.accounts.bank.key(),
mint: bank.mint,
deposit_limit: bank.config.deposit_limit,
borrow_limit: bank.config.borrow_limit,
});
} else {
// Settings are not frozen, everything updates
bank.configure(&bank_config)?;

if bank_config.oracle.is_some() {
bank.config.validate_oracle_setup(ctx.remaining_accounts)?;
}

emit!(LendingPoolBankConfigureEvent {
header: GroupEventHeader {
marginfi_group: ctx.accounts.marginfi_group.key(),
signer: Some(*ctx.accounts.admin.key)
},
bank: ctx.accounts.bank.key(),
mint: bank.mint,
config: bank_config,
});
}

emit!(LendingPoolBankConfigureEvent {
header: GroupEventHeader {
marginfi_group: ctx.accounts.marginfi_group.key(),
signer: Some(*ctx.accounts.admin.key)
},
bank: ctx.accounts.bank.key(),
mint: bank.mint,
config: bank_config,
});

Ok(())
}

Expand Down
14 changes: 13 additions & 1 deletion programs/marginfi/src/state/marginfi_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
EMISSION_FLAGS, FEE_VAULT_AUTHORITY_SEED, FEE_VAULT_SEED, GROUP_FLAGS,
INSURANCE_VAULT_AUTHORITY_SEED, INSURANCE_VAULT_SEED, LIQUIDITY_VAULT_AUTHORITY_SEED,
LIQUIDITY_VAULT_SEED, MAX_ORACLE_KEYS, MAX_PYTH_ORACLE_AGE, MAX_SWB_ORACLE_AGE,
PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG, PYTH_ID, SECONDS_PER_YEAR,
ORACLE_MIN_AGE, PERMISSIONLESS_BAD_DEBT_SETTLEMENT_FLAG, PYTH_ID, SECONDS_PER_YEAR,
TOTAL_ASSET_VALUE_INIT_LIMIT_INACTIVE,
},
debug, math_error,
Expand Down Expand Up @@ -722,6 +722,14 @@ impl Bank {
Ok(())
}

/// Configures just the borrow and deposit limits, ignoring all other values
pub fn configure_unfrozen_fields_only(&mut self, config: &BankConfigOpt) -> MarginfiResult {
set_if_some!(self.config.deposit_limit, config.deposit_limit);
set_if_some!(self.config.borrow_limit, config.borrow_limit);
// weights didn't change so no validation is needed
Ok(())
}

/// Calculate the interest rate accrual state changes for a given time period
///
/// Collected protocol and insurance fees are stored in state.
Expand Down Expand Up @@ -1414,6 +1422,10 @@ impl BankConfig {
}

pub fn validate_oracle_setup(&self, ais: &[AccountInfo]) -> MarginfiResult {
check!(
self.oracle_max_age >= ORACLE_MIN_AGE,
MarginfiError::InvalidOracleSetup
);
OraclePriceFeedAdapter::validate_bank_config(self, ais)?;
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions test-utils/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ lazy_static! {
protocol_origination_fee: I80F48!(0).into(),
..Default::default()
},
oracle_max_age: 100,
..Default::default()
};
pub static ref DEFAULT_USDC_TEST_BANK_CONFIG: BankConfig = BankConfig {
Expand Down
54 changes: 31 additions & 23 deletions tests/04_configureBank.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BN, Program, workspace } from "@coral-xyz/anchor";
import { Transaction } from "@solana/web3.js";
import { PublicKey, Transaction } from "@solana/web3.js";
import { configureBank } from "./utils/instructions";
import { Marginfi } from "../target/types/marginfi";
import { bankKeypairUsdc, groupAdmin, marginfiGroup } from "./rootHooks";
Expand Down Expand Up @@ -117,28 +117,36 @@ describe("Lending pool configure bank", () => {
);
const bank = await program.account.bank.fetch(bankKeypairUsdc.publicKey);
assertBNEqual(bank.flags, FREEZE_SETTINGS);
});

it("(admin) Update settings after a freeze - only deposit/borrow caps update", async () => {
let configNew = defaultBankConfigOptRaw();
const newDepositLimit = new BN(2_000_000_000);
const newBorrowLimit = new BN(3_000_000_000);
configNew.depositLimit = newDepositLimit;
configNew.borrowLimit = newBorrowLimit;

// These will be ignored...
configNew.oracleMaxAge = 42;
configNew.freezeSettings = false;

await groupAdmin.mrgnProgram!.provider.sendAndConfirm!(
new Transaction().add(
await configureBank(program, {
marginfiGroup: marginfiGroup.publicKey,
admin: groupAdmin.wallet.publicKey,
bank: bankKeypairUsdc.publicKey,
bankConfigOpt: configNew,
})
)
);
const bank = await program.account.bank.fetch(bankKeypairUsdc.publicKey);
const config = bank.config;
assertBNEqual(config.depositLimit, newDepositLimit);
assertBNEqual(config.borrowLimit, newBorrowLimit);

// Attempting to config again should fail...
let failed = false;
try {
await groupAdmin.mrgnProgram!.provider.sendAndConfirm!(
new Transaction().add(
await configureBank(program, {
marginfiGroup: marginfiGroup.publicKey,
admin: groupAdmin.wallet.publicKey,
bank: bankKeypairUsdc.publicKey,
bankConfigOpt: defaultBankConfigOptRaw(),
})
)
);
} catch (err) {
assert.ok(
err.logs.some((log: string) =>
log.includes("Error Code: BankSettingsFrozen")
)
);
failed = true;
}
assert.ok(failed, "Transaction succeeded when it should have failed");
// Ignored fields didn't change..
assert.equal(config.oracleMaxAge, 100);
assertBNEqual(bank.flags, FREEZE_SETTINGS); // still frozen
});
});

0 comments on commit f5cfba8

Please sign in to comment.