Skip to content

Commit

Permalink
Consolidate validation functions, remove appreciation rate
Browse files Browse the repository at this point in the history
  • Loading branch information
jgur-psyops committed Nov 7, 2024
1 parent f711b76 commit 259fd83
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 127 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::constants::{ASSET_TAG_STAKED, EMISSIONS_AUTH_SEED, EMISSIONS_TOKEN_ACCOUNT_SEED};
use crate::constants::{EMISSIONS_AUTH_SEED, EMISSIONS_TOKEN_ACCOUNT_SEED};
use crate::events::{GroupEventHeader, LendingPoolBankConfigureEvent};
use crate::prelude::MarginfiError;
use crate::{check, math_error, utils};
Expand All @@ -20,13 +20,8 @@ pub fn lending_pool_configure_bank(
bank.configure(&bank_config)?;

if bank_config.oracle.is_some() {
if bank.config.asset_tag == ASSET_TAG_STAKED {
bank.config
.validate_staked_oracle_setup(ctx.remaining_accounts)?;
} else {
bank.config
.validate_oracle_setup(ctx.remaining_accounts, None, None, None)?;
}
bank.config
.validate_oracle_setup(ctx.remaining_accounts, None, None, None)?;
}

emit!(LendingPoolBankConfigureEvent {
Expand Down Expand Up @@ -144,9 +139,11 @@ pub struct LendingPoolSetupEmissions<'info> {
)]
pub emissions_token_account: Box<InterfaceAccount<'info, TokenAccount>>,

/// NOTE: This is a TokenAccount, spl transfer will validate it.
///
/// CHECK: Account provided only for funding rewards
#[account(mut)]
pub emissions_funding_account: AccountInfo<'info>, // TODO why isn't this TokenAccount?
pub emissions_funding_account: AccountInfo<'info>,

pub token_program: Interface<'info, TokenInterface>,
pub system_program: Program<'info, System>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub fn propagate_staked_settings(ctx: Context<PropagateStakedSettings>) -> Resul
// Only validate the oracle if it has changed
if settings.oracle != bank.config.oracle_keys[0] {
bank.config
.validate_staked_oracle_setup(ctx.remaining_accounts)?;
.validate_oracle_setup(ctx.remaining_accounts, None, None, None)?;
}

bank.config.oracle_keys[0] = settings.oracle;
Expand Down
26 changes: 5 additions & 21 deletions programs/marginfi/src/state/marginfi_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,19 +490,10 @@ pub struct Bank {
pub emissions_remaining: WrappedI80F48,
pub emissions_mint: Pubkey,

/// For banks where `config.asset_tag == ASSET_TAG_STAKED`, this defines the last-cached
/// exchange rate of LST to SOL, i.e. the price appreciation of the LST. For example, if this is
/// 1, then the LST trades 1:1 for SOL. If this is 1.1, then 1 LST can be exchange for 1.1 SOL.
///
/// Currently, this cannot be less than 1 (but this may change if slashing is implemented)
///
/// For banks where `config.asset_tag != ASSET_TAG_STAKED` this field does nothing and may be 0,
/// 1, or any other value.
pub sol_appreciation_rate: WrappedI80F48,
/// Fees collected and pending withdraw for the `FeeState.global_fee_wallet`'s cannonical ATA for `mint`
pub collected_program_fees_outstanding: WrappedI80F48,

pub _padding_0: [[u64; 2]; 26],
pub _padding_0: [[u64; 2]; 27],
pub _padding_1: [[u64; 2]; 32], // 16 * 2 * 32 = 1024B
}

Expand Down Expand Up @@ -549,7 +540,7 @@ impl Bank {
emissions_rate: 0,
emissions_remaining: I80F48::ZERO.into(),
emissions_mint: Pubkey::default(),
sol_appreciation_rate: I80F48::ONE.into(),
collected_program_fees_outstanding: I80F48::ZERO.into(),
..Default::default()
}
}
Expand Down Expand Up @@ -1445,7 +1436,9 @@ impl BankConfig {
self.borrow_limit != u64::MAX
}

/// * lst_mint, stake_pool, sol_pool - required only if configuring `OracleSetup::StakedWithPythPush`
/// * lst_mint, stake_pool, sol_pool - required only if configuring
/// `OracleSetup::StakedWithPythPush` on initial setup. If configuring a staked bank after
/// initial setup, can be omitted
pub fn validate_oracle_setup(
&self,
ais: &[AccountInfo],
Expand All @@ -1457,15 +1450,6 @@ impl BankConfig {
Ok(())
}

/// Because the mint (and thus corresponding stake pool) of a staked collateral bank cannot
/// update after inception, this function validates just the oracle, ignoring the lst mint and
/// sol pool. This function works only for banks configured as StakedWithPythPush, and otherwise
/// errors
pub fn validate_staked_oracle_setup(&self, ais: &[AccountInfo]) -> MarginfiResult {
OraclePriceFeedAdapter::validate_staked_bank_config_light(self, ais)?;
Ok(())
}

pub fn usd_init_limit_active(&self) -> bool {
self.total_asset_value_init_limit != TOTAL_ASSET_VALUE_INIT_LIMIT_INACTIVE
}
Expand Down
158 changes: 75 additions & 83 deletions programs/marginfi/src/state/price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@ impl OraclePriceFeedAdapter {
}
}

/// * lst_mint, stake_pool, sol_pool - required only if configuring `OracleSetup::StakedWithPythPush`
/// * lst_mint, stake_pool, sol_pool - required only if configuring
/// `OracleSetup::StakedWithPythPush` initially. (subsequent validations of staked banks can
/// omit these)
pub fn validate_bank_config(
bank_config: &BankConfig,
oracle_ais: &[AccountInfo],
Expand Down Expand Up @@ -292,97 +294,87 @@ impl OraclePriceFeedAdapter {
Ok(())
}
OracleSetup::StakedWithPythPush => {
check!(oracle_ais.len() == 3, MarginfiError::InvalidOracleAccount);
if lst_mint.is_some() && stake_pool.is_some() && sol_pool.is_some() {
check!(oracle_ais.len() == 3, MarginfiError::InvalidOracleAccount);

// Note: mainnet/staging/devnet use "push" oracles, localnet uses legacy
if cfg!(any(
feature = "mainnet-beta",
feature = "staging",
feature = "devnet"
)) {
PythPushOraclePriceFeed::check_ai_and_feed_id(
&oracle_ais[0],
bank_config.get_pyth_push_oracle_feed_id().unwrap(),
)?;
} else {
// Localnet only
check!(
oracle_ais[0].key == &bank_config.oracle_keys[0],
MarginfiError::InvalidOracleAccount
);

PythLegacyPriceFeed::check_ais(&oracle_ais[0])?;
}

// Note: mainnet/staging/devnet use "push" oracles, localnet uses legacy
if cfg!(any(
feature = "mainnet-beta",
feature = "staging",
feature = "devnet"
)) {
PythPushOraclePriceFeed::check_ai_and_feed_id(
&oracle_ais[0],
bank_config.get_pyth_push_oracle_feed_id().unwrap(),
)?;
} else {
// Localnet only
let lst_mint = lst_mint.unwrap();
let stake_pool = stake_pool.unwrap();
let sol_pool = sol_pool.unwrap();

let program_id = &SPL_SINGLE_POOL_ID;
let stake_pool_bytes = &stake_pool.to_bytes();
// Validate the given stake_pool derives the same lst_mint, proving stake_pool is correct
let (exp_mint, _) =
Pubkey::find_program_address(&[b"mint", stake_pool_bytes], program_id);
check!(
oracle_ais[0].key == &bank_config.oracle_keys[0],
MarginfiError::InvalidOracleAccount
exp_mint == lst_mint,
MarginfiError::StakePoolValidationFailed
);
// Validate the now-proven stake_pool derives the given sol_pool
let (exp_pool, _) =
Pubkey::find_program_address(&[b"stake", stake_pool_bytes], program_id);
check!(
exp_pool == sol_pool.key(),
MarginfiError::StakePoolValidationFailed
);

PythLegacyPriceFeed::check_ais(&oracle_ais[0])?;
}

check!(
lst_mint.is_some() && stake_pool.is_some() && sol_pool.is_some(),
MarginfiError::StakePoolValidationFailed
);
let lst_mint = lst_mint.unwrap();
let stake_pool = stake_pool.unwrap();
let sol_pool = sol_pool.unwrap();

let program_id = &SPL_SINGLE_POOL_ID;
let stake_pool_bytes = &stake_pool.to_bytes();
// Validate the given stake_pool derives the same lst_mint, proving stake_pool is correct
let (exp_mint, _) =
Pubkey::find_program_address(&[b"mint", stake_pool_bytes], program_id);
check!(
exp_mint == lst_mint,
MarginfiError::StakePoolValidationFailed
);
// Validate the now-proven stake_pool derives the given sol_pool
let (exp_pool, _) =
Pubkey::find_program_address(&[b"stake", stake_pool_bytes], program_id);
check!(
exp_pool == sol_pool.key(),
MarginfiError::StakePoolValidationFailed
);
// Sanity check the mint. Note: spl-single-pool uses a classic Token, never Token22
check!(
oracle_ais[1].owner == &SPL_TOKEN_PROGRAM_ID
&& oracle_ais[1].key() == lst_mint,
MarginfiError::StakePoolValidationFailed
);
// Sanity check the pool is a native stake pool. Note: the native staking program is
// written in vanilla Solana and has no Anchor discriminator.
check!(
oracle_ais[2].owner == &NATIVE_STAKE_ID && oracle_ais[2].key() == sol_pool,
MarginfiError::StakePoolValidationFailed
);

// Sanity check the mint. Note: spl-single-pool uses a classic Token, never Token22
check!(
oracle_ais[1].owner == &SPL_TOKEN_PROGRAM_ID && oracle_ais[1].key() == lst_mint,
MarginfiError::StakePoolValidationFailed
);
// Sanity check the pool is a native stake pool. Note: the native staking program is
// written in vanilla Solana and has no Anchor discriminator.
check!(
oracle_ais[2].owner == &NATIVE_STAKE_ID && oracle_ais[2].key() == sol_pool,
MarginfiError::StakePoolValidationFailed
);
Ok(())
} else {
// light validation (after initial setup, only the Pyth oracle needs to be validated)
check!(oracle_ais.len() == 1, MarginfiError::InvalidOracleAccount);
// Note: mainnet/staging/devnet use push oracles, localnet uses legacy push
if cfg!(any(
feature = "mainnet-beta",
feature = "staging",
feature = "devnet"
)) {
PythPushOraclePriceFeed::check_ai_and_feed_id(
&oracle_ais[0],
bank_config.get_pyth_push_oracle_feed_id().unwrap(),
)?;
} else {
// Localnet only
PythLegacyPriceFeed::check_ais(&oracle_ais[0])?;
}

Ok(())
Ok(())
}
}
}
}

pub fn validate_staked_bank_config_light(
bank_config: &BankConfig,
oracle_ais: &[AccountInfo],
) -> MarginfiResult {
match bank_config.oracle_setup {
OracleSetup::StakedWithPythPush => Ok(()),
_ => err!(MarginfiError::StakePoolValidationFailed),
}?;

check!(oracle_ais.len() == 1, MarginfiError::InvalidOracleAccount);
// Note: mainnet/staging/devnet use push oracles, localnet uses legacy push
if cfg!(any(
feature = "mainnet-beta",
feature = "staging",
feature = "devnet"
)) {
PythPushOraclePriceFeed::check_ai_and_feed_id(
&oracle_ais[0],
bank_config.get_pyth_push_oracle_feed_id().unwrap(),
)?;
} else {
// Localnet only
PythLegacyPriceFeed::check_ais(&oracle_ais[0])?;
}

Ok(())
}
}

#[cfg_attr(feature = "client", derive(Clone, Debug))]
Expand Down
8 changes: 2 additions & 6 deletions programs/marginfi/tests/admin_actions/setup_bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ async fn add_bank_success() -> anyhow::Result<()> {
emissions_rate,
emissions_remaining,
emissions_mint,
sol_appreciation_rate,
collected_program_fees_outstanding,
_padding_0,
_padding_1,
Expand Down Expand Up @@ -116,10 +115,9 @@ async fn add_bank_success() -> anyhow::Result<()> {
assert_eq!(emissions_rate, 0);
assert_eq!(emissions_mint, Pubkey::new_from_array([0; 32]));
assert_eq!(emissions_remaining, I80F48!(0.0).into());
assert_eq!(sol_appreciation_rate, I80F48!(1.0).into());
assert_eq!(collected_program_fees_outstanding, I80F48!(0.0).into());

assert_eq!(_padding_0, <[[u64; 2]; 26] as Default>::default());
assert_eq!(_padding_0, <[[u64; 2]; 27] as Default>::default());
assert_eq!(_padding_1, <[[u64; 2]; 32] as Default>::default());

// this is the only loosely checked field
Expand Down Expand Up @@ -222,7 +220,6 @@ async fn add_bank_with_seed_success() -> anyhow::Result<()> {
emissions_rate,
emissions_remaining,
emissions_mint,
sol_appreciation_rate,
collected_program_fees_outstanding,
_padding_0,
_padding_1,
Expand Down Expand Up @@ -253,10 +250,9 @@ async fn add_bank_with_seed_success() -> anyhow::Result<()> {
assert_eq!(emissions_rate, 0);
assert_eq!(emissions_mint, Pubkey::new_from_array([0; 32]));
assert_eq!(emissions_remaining, I80F48!(0.0).into());
assert_eq!(sol_appreciation_rate, I80F48!(1.0).into());
assert_eq!(collected_program_fees_outstanding, I80F48!(0.0).into());

assert_eq!(_padding_0, <[[u64; 2]; 26] as Default>::default());
assert_eq!(_padding_0, <[[u64; 2]; 27] as Default>::default());
assert_eq!(_padding_1, <[[u64; 2]; 32] as Default>::default());

// this is the only loosely checked field
Expand Down
7 changes: 1 addition & 6 deletions programs/marginfi/tests/misc/regression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -663,18 +663,13 @@ async fn bank_field_values_reg() -> anyhow::Result<()> {
bank.emissions_mint,
pubkey!("2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo")
);
// legacy banks can have 0 for this field, it does nothing for banks not using ASSET_TAG_STAKED
assert_eq!(
I80F48::from(bank.sol_appreciation_rate),
I80F48::from_str("0").unwrap()
);
// Legacy banks have no program fees
assert_eq!(
I80F48::from(bank.collected_program_fees_outstanding),
I80F48::from_str("0").unwrap()
);

assert_eq!(bank._padding_0, [[0, 0]; 26]);
assert_eq!(bank._padding_0, [[0, 0]; 27]);
assert_eq!(bank._padding_1, [[0, 0]; 32]);

Ok(())
Expand Down
1 change: 0 additions & 1 deletion tests/s02_addBank.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,6 @@ describe("Init group and add banks with asset category flags", () => {
);
// Noteworthy fields
assert.equal(bank.config.assetTag, ASSET_TAG_STAKED);
assertI80F48Equal(bank.solAppreciationRate, I80F48_ONE);

// Standard fields
const config = bank.config;
Expand Down

0 comments on commit 259fd83

Please sign in to comment.