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

Freeze settings v2 #263

Merged
merged 2 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
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
});
});
Loading