diff --git a/token-lending/program/tests/refresh_reserve.rs b/token-lending/program/tests/refresh_reserve.rs index 300323008bb..407768d0012 100644 --- a/token-lending/program/tests/refresh_reserve.rs +++ b/token-lending/program/tests/refresh_reserve.rs @@ -358,25 +358,26 @@ async fn test_success_only_switchboard_reserve() { #[tokio::test] async fn test_use_price_weight() { - let (mut test, lending_market, reserves, _obligations, _users, _) = custom_scenario( - &[ReserveArgs { - mint: wsol_mint::id(), - config: ReserveConfig { - added_price_weight_bps: 5_000, - ..test_reserve_config() - }, - liquidity_amount: 100_000 * FRACTIONAL_TO_USDC, - price: PriceArgs { - price: 10, - conf: 0, - expo: 0, - ema_price: 10, - ema_conf: 0, - }, - }], - &[], - ) - .await; + let (mut test, lending_market, reserves, _obligations, _users, lending_market_owner) = + custom_scenario( + &[ReserveArgs { + mint: wsol_mint::id(), + config: ReserveConfig { + added_price_weight_bps: 2_000, + ..test_reserve_config() + }, + liquidity_amount: 100_000 * FRACTIONAL_TO_USDC, + price: PriceArgs { + price: 10, + conf: 0, + expo: 0, + ema_price: 10, + ema_conf: 0, + }, + }], + &[], + ) + .await; test.set_price( &wsol_mint::id(), @@ -384,7 +385,7 @@ async fn test_use_price_weight() { price: 10, conf: 0, expo: 0, - ema_price: 12, + ema_price: 20, ema_conf: 0, }, ) @@ -400,10 +401,44 @@ async fn test_use_price_weight() { let wsol_reserve = test.load_account::(reserves[0].pubkey).await; assert_eq!( wsol_reserve.account.liquidity.market_price, - Decimal::from(15u64) + Decimal::from(12u64) + ); + assert_eq!( + wsol_reserve.account.liquidity.smoothed_market_price, + Decimal::from(24u64) + ); + + test.advance_clock_by_slots(1).await; + + lending_market + .update_reserve_config( + &mut test, + &lending_market_owner, + &wsol_reserve, + ReserveConfig { + added_price_weight_bps: -2_000, + ..wsol_reserve.account.config + }, + wsol_reserve.account.rate_limiter.config, + None, + ) + .await + .unwrap(); + + test.advance_clock_by_slots(1).await; + + lending_market + .refresh_reserve(&mut test, &reserves[0]) + .await + .unwrap(); + + let wsol_reserve = test.load_account::(reserves[0].pubkey).await; + assert_eq!( + wsol_reserve.account.liquidity.market_price, + Decimal::from(8u64) ); assert_eq!( wsol_reserve.account.liquidity.smoothed_market_price, - Decimal::from(18u64) + Decimal::from(16u64) ); } diff --git a/token-lending/sdk/src/instruction.rs b/token-lending/sdk/src/instruction.rs index 0dfb078feab..20d46323ff1 100644 --- a/token-lending/sdk/src/instruction.rs +++ b/token-lending/sdk/src/instruction.rs @@ -562,7 +562,7 @@ impl LendingInstruction { let (asset_type, rest) = Self::unpack_u8(rest)?; let (max_liquidation_bonus, rest) = Self::unpack_u8(rest)?; let (max_liquidation_threshold, rest) = Self::unpack_u8(rest)?; - let (added_price_weight_bps, _rest) = Self::unpack_u64(rest)?; + let (added_price_weight_bps, _rest) = Self::unpack_i64(rest)?; Self::InitReserve { liquidity_amount, config: ReserveConfig { @@ -658,7 +658,7 @@ impl LendingInstruction { let (asset_type, rest) = Self::unpack_u8(rest)?; let (max_liquidation_bonus, rest) = Self::unpack_u8(rest)?; let (max_liquidation_threshold, rest) = Self::unpack_u8(rest)?; - let (added_price_weight_bps, rest) = Self::unpack_u64(rest)?; + let (added_price_weight_bps, rest) = Self::unpack_i64(rest)?; let (window_duration, rest) = Self::unpack_u64(rest)?; let (max_outflow, _rest) = Self::unpack_u64(rest)?; @@ -738,6 +738,20 @@ impl LendingInstruction { Ok((value, rest)) } + fn unpack_i64(input: &[u8]) -> Result<(i64, &[u8]), ProgramError> { + if input.len() < 8 { + msg!("i64 cannot be unpacked"); + return Err(LendingError::InstructionUnpackError.into()); + } + let (bytes, rest) = input.split_at(8); + let value = bytes + .get(..8) + .and_then(|slice| slice.try_into().ok()) + .map(i64::from_le_bytes) + .ok_or(LendingError::InstructionUnpackError)?; + Ok((value, rest)) + } + fn unpack_u8(input: &[u8]) -> Result<(u8, &[u8]), ProgramError> { if input.is_empty() { msg!("u8 cannot be unpacked"); @@ -1764,7 +1778,7 @@ mod test { protocol_take_rate: rng.gen::(), added_borrow_weight_bps: rng.gen::(), reserve_type: ReserveType::from_u8(rng.gen::() % 2).unwrap(), - added_price_weight_bps: rng.gen::(), + added_price_weight_bps: rng.gen(), }, }; @@ -1925,7 +1939,7 @@ mod test { protocol_take_rate: rng.gen::(), added_borrow_weight_bps: rng.gen::(), reserve_type: ReserveType::from_u8(rng.gen::() % 2).unwrap(), - added_price_weight_bps: rng.gen::(), + added_price_weight_bps: rng.gen(), }, rate_limiter_config: RateLimiterConfig { window_duration: rng.gen::(), diff --git a/token-lending/sdk/src/state/reserve.rs b/token-lending/sdk/src/state/reserve.rs index ece303c3795..1b2d12b1085 100644 --- a/token-lending/sdk/src/state/reserve.rs +++ b/token-lending/sdk/src/state/reserve.rs @@ -35,8 +35,11 @@ pub const MAX_BONUS_PCT: u8 = 25; /// Maximum protocol liquidation fee in deca bps (1 deca bp = 10 bps) pub const MAX_PROTOCOL_LIQUIDATION_FEE_DECA_BPS: u8 = 50; -/// Upper bound on price weight -pub const MAX_PRICE_WEIGHT_BPS: u64 = 10000; +/// Upper bound on added price weight +pub const MAX_ADDED_PRICE_WEIGHT_BPS: i64 = 2000; + +/// Lower bound on added price weight +pub const MIN_ADDED_PRICE_WEIGHT_BPS: i64 = -2000; /// Lending market reserve state #[derive(Clone, Debug, Default, PartialEq)] @@ -85,9 +88,16 @@ impl Reserve { /// get price weight. Guaranteed to be greater than 1 pub fn price_weight(&self) -> Decimal { - Decimal::one() - .try_add(Decimal::from_bps(self.config.added_price_weight_bps)) - .unwrap() + let added_price_weight_bps = min( + MAX_ADDED_PRICE_WEIGHT_BPS, + max( + MIN_ADDED_PRICE_WEIGHT_BPS, + self.config.added_price_weight_bps, + ), + ); + + let price_weight_bps = 10_000 + added_price_weight_bps; + Decimal::from_bps(price_weight_bps as u64) } /// get loan to value ratio as a Rate @@ -912,7 +922,7 @@ pub struct ReserveConfig { pub reserve_type: ReserveType, /// Added price weight in basis points. Exclusively used to calculate a more reliable asset price for /// staked assets (mSOL, stETH). - pub added_price_weight_bps: u64, + pub added_price_weight_bps: i64, } /// validates reserve configs @@ -1001,10 +1011,13 @@ pub fn validate_reserve_config(config: ReserveConfig) -> ProgramResult { return Err(LendingError::InvalidConfig.into()); } - if config.added_price_weight_bps > MAX_PRICE_WEIGHT_BPS { + if config.added_price_weight_bps < MIN_ADDED_PRICE_WEIGHT_BPS + || config.added_price_weight_bps > MAX_ADDED_PRICE_WEIGHT_BPS + { msg!( - "Added price weight must be in range [0, {}]", - MAX_PRICE_WEIGHT_BPS + "Added price weight must be in range [{}, {}]", + MIN_ADDED_PRICE_WEIGHT_BPS, + MAX_ADDED_PRICE_WEIGHT_BPS ); return Err(LendingError::InvalidConfig.into()); } @@ -1489,7 +1502,7 @@ impl Pack for Reserve { protocol_take_rate: u8::from_le_bytes(*config_protocol_take_rate), added_borrow_weight_bps: u64::from_le_bytes(*config_added_borrow_weight_bps), reserve_type: ReserveType::from_u8(config_asset_type[0]).unwrap(), - added_price_weight_bps: u64::from_le_bytes(*config_added_price_weight_bps), + added_price_weight_bps: i64::from_le_bytes(*config_added_price_weight_bps), }, rate_limiter: RateLimiter::unpack_from_slice(rate_limiter)?, }) @@ -2125,14 +2138,28 @@ mod test { }), Just(ReserveConfigTestCase { config: ReserveConfig { - added_price_weight_bps: 10001, + added_price_weight_bps: 2001, + ..ReserveConfig::default() + }, + result: Err(LendingError::InvalidConfig.into()), + }), + Just(ReserveConfigTestCase { + config: ReserveConfig { + added_price_weight_bps: 1999, + ..ReserveConfig::default() + }, + result: Ok(()) + }), + Just(ReserveConfigTestCase { + config: ReserveConfig { + added_price_weight_bps: -2001, ..ReserveConfig::default() }, result: Err(LendingError::InvalidConfig.into()), }), Just(ReserveConfigTestCase { config: ReserveConfig { - added_price_weight_bps: 9999, + added_price_weight_bps: -1999, ..ReserveConfig::default() }, result: Ok(())