diff --git a/programs/marginfi/src/instructions/marginfi_group/configure_bank.rs b/programs/marginfi/src/instructions/marginfi_group/configure_bank.rs index 9b26f2fb..1674c3c8 100644 --- a/programs/marginfi/src/instructions/marginfi_group/configure_bank.rs +++ b/programs/marginfi/src/instructions/marginfi_group/configure_bank.rs @@ -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}; @@ -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 { @@ -144,9 +139,11 @@ pub struct LendingPoolSetupEmissions<'info> { )] pub emissions_token_account: Box>, + /// 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>, diff --git a/programs/marginfi/src/instructions/marginfi_group/propagate_staked_settings.rs b/programs/marginfi/src/instructions/marginfi_group/propagate_staked_settings.rs index 94fc263b..d9515bef 100644 --- a/programs/marginfi/src/instructions/marginfi_group/propagate_staked_settings.rs +++ b/programs/marginfi/src/instructions/marginfi_group/propagate_staked_settings.rs @@ -12,7 +12,7 @@ pub fn propagate_staked_settings(ctx: Context) -> 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; diff --git a/programs/marginfi/src/state/marginfi_group.rs b/programs/marginfi/src/state/marginfi_group.rs index 7144bdbd..e919adff 100644 --- a/programs/marginfi/src/state/marginfi_group.rs +++ b/programs/marginfi/src/state/marginfi_group.rs @@ -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 } @@ -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() } } @@ -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], @@ -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 } diff --git a/programs/marginfi/src/state/price.rs b/programs/marginfi/src/state/price.rs index ebe97655..a841c799 100644 --- a/programs/marginfi/src/state/price.rs +++ b/programs/marginfi/src/state/price.rs @@ -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], @@ -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))] diff --git a/programs/marginfi/tests/admin_actions/setup_bank.rs b/programs/marginfi/tests/admin_actions/setup_bank.rs index 1fea7266..622cdbd8 100644 --- a/programs/marginfi/tests/admin_actions/setup_bank.rs +++ b/programs/marginfi/tests/admin_actions/setup_bank.rs @@ -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, @@ -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 @@ -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, @@ -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 diff --git a/programs/marginfi/tests/misc/regression.rs b/programs/marginfi/tests/misc/regression.rs index fbe0f4a4..5742cecc 100644 --- a/programs/marginfi/tests/misc/regression.rs +++ b/programs/marginfi/tests/misc/regression.rs @@ -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(()) diff --git a/tests/s02_addBank.spec.ts b/tests/s02_addBank.spec.ts index 2d0ab5d6..bf021446 100644 --- a/tests/s02_addBank.spec.ts +++ b/tests/s02_addBank.spec.ts @@ -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;