diff --git a/token-lending/cli/src/main.rs b/token-lending/cli/src/main.rs index 77c2282..0d4a454 100644 --- a/token-lending/cli/src/main.rs +++ b/token-lending/cli/src/main.rs @@ -140,6 +140,8 @@ fn main() { "borrow_fee_wad", "flash_loan_fee_wad", "host_fee_percentage", + "deposit_limit", + "borrow_limit", ] .into_iter() .map(build_u64_arg) @@ -514,6 +516,26 @@ fn main() { .default_value("20") .help("Amount of fee going to host account: [0, 100]"), ) + .arg( + Arg::with_name("deposit_limit") + .long("deposit-limit") + .validator(is_parsable::) + .value_name("AMOUNT") + .takes_value(true) + .required(true) + .default_value("18446744073709551615") + .help("Maximum deposit limit in terms of lamports"), + ) + .arg( + Arg::with_name("borrow_limit") + .long("borrow-limit") + .validator(is_parsable::) + .value_name("AMOUNT") + .takes_value(true) + .required(true) + .default_value("18446744073709551615") + .help("Maximum borrow limit in terms of lamports"), + ) ) .get_matches(); @@ -580,7 +602,10 @@ fn main() { let borrow_fee_wad = value_of(arg_matches, "borrow_fee_wad"); let flash_loan_fee_wad = value_of(arg_matches, "flash_loan_fee_wad"); let host_fee_percentage = value_of(arg_matches, "host_fee_percentage"); + let deposit_limit = value_of(arg_matches, "deposit_limit"); + let borrow_limit = value_of(arg_matches, "borrow_limit"); let deposit_staking_pool = pubkey_or_none_of(arg_matches, "deposit_staking_pool"); + let mut old_config = Reserve::unpack(&config.rpc_client.get_account(&reserve).unwrap().data) .unwrap() @@ -604,6 +629,8 @@ fn main() { host_fee_percentage.unwrap_or(old_config.fees.host_fee_percentage); old_config.fees.flash_loan_fee_wad = flash_loan_fee_wad.unwrap_or(old_config.fees.flash_loan_fee_wad); + old_config.deposit_limit = deposit_limit.unwrap_or(old_config.deposit_limit); + old_config.borrow_limit = borrow_limit.unwrap_or(old_config.borrow_limit); old_config.deposit_staking_pool = deposit_staking_pool.unwrap_or(old_config.deposit_staking_pool); command_update_reserve( @@ -681,6 +708,8 @@ fn main() { host_fee_percentage, }, deposit_staking_pool: COption::None, + deposit_limit: u64::MAX, + borrow_limit: u64::MAX, }, source_liquidity_pubkey, source_liquidity_owner_keypair, diff --git a/token-lending/program/src/instruction.rs b/token-lending/program/src/instruction.rs index f3a9f16..c3abafe 100644 --- a/token-lending/program/src/instruction.rs +++ b/token-lending/program/src/instruction.rs @@ -558,6 +558,8 @@ impl LendingInstruction { let (flash_loan_fee_wad, rest) = Self::unpack_u64(rest)?; let (host_fee_percentage, rest) = Self::unpack_u8(rest)?; let (deposit_staking_pool, rest) = Self::unpack_coption_key_compact(rest)?; + let (deposit_limit, rest) = Self::unpack_u64(rest)?; + let (borrow_limit, rest) = Self::unpack_u64(rest)?; Ok(( ReserveConfig { optimal_utilization_rate, @@ -573,6 +575,8 @@ impl LendingInstruction { host_fee_percentage, }, deposit_staking_pool, + deposit_limit, + borrow_limit, }, rest, )) @@ -699,6 +703,8 @@ impl LendingInstruction { host_fee_percentage, }, deposit_staking_pool, + deposit_limit, + borrow_limit, } = reserve_config; buf.extend_from_slice(&optimal_utilization_rate.to_le_bytes()); buf.extend_from_slice(&loan_to_value_ratio.to_le_bytes()); @@ -713,6 +719,8 @@ impl LendingInstruction { let mut coption_key_buf = [0u8; 33]; pack_coption_key_compact(&deposit_staking_pool, &mut coption_key_buf); buf.extend_from_slice(&coption_key_buf); + buf.extend_from_slice(&deposit_limit.to_le_bytes()); + buf.extend_from_slice(&borrow_limit.to_le_bytes()); } } diff --git a/token-lending/program/src/processor.rs b/token-lending/program/src/processor.rs index 9c272e4..61999b6 100644 --- a/token-lending/program/src/processor.rs +++ b/token-lending/program/src/processor.rs @@ -600,6 +600,15 @@ fn _deposit_reserve_liquidity<'a>( return Err(LendingError::InvalidMarketAuthority.into()); } + if Decimal::from(liquidity_amount) + .try_add(reserve.liquidity.total_supply()?)? + .try_floor_u64()? + > reserve.config.deposit_limit + { + msg!("Cannot deposit liquidity above the reserve deposit limit"); + return Err(LendingError::InvalidAmount.into()); + } + let collateral_amount = reserve.deposit_liquidity(liquidity_amount)?; reserve.last_update.mark_stale(); Reserve::pack(reserve, &mut reserve_info.data.borrow_mut())?; @@ -1496,6 +1505,15 @@ fn process_borrow_obligation_liquidity( msg!("Borrow reserve is stale and must be refreshed in the current slot"); return Err(LendingError::ReserveStale.into()); } + if liquidity_amount != u64::MAX + && Decimal::from(liquidity_amount) + .try_add(borrow_reserve.liquidity.borrowed_amount_wads)? + .try_floor_u64()? + > borrow_reserve.config.borrow_limit + { + msg!("Cannot borrow above the borrow limit"); + return Err(LendingError::InvalidAmount.into()); + } let mut obligation = Obligation::unpack(&obligation_info.data.borrow())?; if obligation_info.owner != program_id { diff --git a/token-lending/program/src/state/reserve.rs b/token-lending/program/src/state/reserve.rs index 44d36c5..91f2a11 100644 --- a/token-lending/program/src/state/reserve.rs +++ b/token-lending/program/src/state/reserve.rs @@ -620,6 +620,10 @@ pub struct ReserveConfig { pub fees: ReserveFees, /// corresponded staking pool pubkey of deposit pub deposit_staking_pool: COption, + /// Maximum deposit limit of liquidity in native units, u64::MAX for inf + pub deposit_limit: u64, + /// Maximum borrow limit of liquidity in native units, u64::MAX for inf + pub borrow_limit: u64, } /// Additional fee information on a reserve @@ -765,6 +769,8 @@ impl Pack for Reserve { config_fees_flash_loan_fee_wad, config_fees_host_fee_percentage, config_deposit_staking_pool, + config_deposit_limit, + config_borrow_limit, _padding, ) = mut_array_refs![ output, @@ -795,7 +801,9 @@ impl Pack for Reserve { 8, 1, 33, - 215 + 8, + 8, + 199 ]; // reserve @@ -841,6 +849,8 @@ impl Pack for Reserve { &self.config.deposit_staking_pool, config_deposit_staking_pool, ); + *config_deposit_limit = self.config.deposit_limit.to_le_bytes(); + *config_borrow_limit = self.config.borrow_limit.to_le_bytes(); } /// Unpacks a byte buffer into a [ReserveInfo](struct.ReserveInfo.html). @@ -875,6 +885,8 @@ impl Pack for Reserve { config_fees_flash_loan_fee_wad, config_fees_host_fee_percentage, config_deposit_staking_pool, + config_deposit_limit, + config_borrow_limit, _padding, ) = array_refs![ input, @@ -905,7 +917,9 @@ impl Pack for Reserve { 8, 1, 33, - 215 + 8, + 8, + 199 ]; let version = u8::from_le_bytes(*version); @@ -951,6 +965,8 @@ impl Pack for Reserve { host_fee_percentage: u8::from_le_bytes(*config_fees_host_fee_percentage), }, deposit_staking_pool: unpack_coption_key_compact(config_deposit_staking_pool)?, + deposit_limit: u64::from_le_bytes(*config_deposit_limit), + borrow_limit: u64::from_le_bytes(*config_borrow_limit), }, }) } diff --git a/token-lending/program/tests/helpers/mod.rs b/token-lending/program/tests/helpers/mod.rs index 2ba0a64..059634c 100644 --- a/token-lending/program/tests/helpers/mod.rs +++ b/token-lending/program/tests/helpers/mod.rs @@ -65,6 +65,8 @@ pub const TEST_RESERVE_CONFIG: ReserveConfig = ReserveConfig { host_fee_percentage: 20, }, deposit_staking_pool: COption::None, + deposit_limit: u64::MAX, + borrow_limit: u64::MAX, }; pub const SOL_PYTH_PRODUCT: &str = "3Mnn2fX6rQyUsyELYms1sBJyChWofzSNRoqYzvgMVz5E"; diff --git a/token-lending/program/tests/max_withdraw_bug_poc.rs b/token-lending/program/tests/max_withdraw_bug_poc.rs index 5996471..42f3616 100644 --- a/token-lending/program/tests/max_withdraw_bug_poc.rs +++ b/token-lending/program/tests/max_withdraw_bug_poc.rs @@ -70,6 +70,8 @@ async fn test_success() { host_fee_percentage: 20, }, deposit_staking_pool: COption::None, + deposit_limit: u64::MAX, + borrow_limit: u64::MAX, }; // oracle price doesn't matter so using usdc oracle for ease of computation @@ -106,6 +108,8 @@ async fn test_success() { host_fee_percentage: 20, }, deposit_staking_pool: COption::None, + deposit_limit: u64::MAX, + borrow_limit: u64::MAX, }; let usdc_mint = add_usdc_mint(&mut test); let usdc_oracle = add_usdc_pyth_oracle(&mut test); diff --git a/token-lending/program/tests/update_reserve.rs b/token-lending/program/tests/update_reserve.rs index c0455f7..32fd72b 100644 --- a/token-lending/program/tests/update_reserve.rs +++ b/token-lending/program/tests/update_reserve.rs @@ -61,6 +61,8 @@ async fn test_update_reserve() { host_fee_percentage: 15, }, deposit_staking_pool: COption::None, + deposit_limit: u64::MAX, + borrow_limit: u64::MAX, }; let before_test_reserve = usdc_test_reserve.get_state(&mut banks_client).await; assert_ne!(before_test_reserve.config, new_config);