diff --git a/programs/mango-v4/src/instructions/token_edit.rs b/programs/mango-v4/src/instructions/token_edit.rs index ee501abf8..db645f3ff 100644 --- a/programs/mango-v4/src/instructions/token_edit.rs +++ b/programs/mango-v4/src/instructions/token_edit.rs @@ -42,8 +42,6 @@ pub fn token_edit( token_conditional_swap_taker_fee_rate_opt: Option, token_conditional_swap_maker_fee_rate_opt: Option, flash_loan_swap_fee_rate_opt: Option, - interest_curve_scaling_opt: Option, - interest_target_utilization_opt: Option, ) -> Result<()> { let group = ctx.accounts.group.load()?; @@ -341,27 +339,6 @@ pub fn token_edit( bank.flash_loan_swap_fee_rate = fee_rate; require_group_admin = true; } - - if let Some(interest_curve_scaling) = interest_curve_scaling_opt { - msg!( - "Interest curve scaling old {:?}, new {:?}", - bank.interest_curve_scaling, - interest_curve_scaling - ); - require_gte!(interest_curve_scaling, 1.0); - bank.interest_curve_scaling = interest_curve_scaling.into(); - require_group_admin = true; - } - if let Some(interest_target_utilization) = interest_target_utilization_opt { - msg!( - "Interest target utilization old {:?}, new {:?}", - bank.interest_target_utilization, - interest_target_utilization - ); - require_gte!(interest_target_utilization, 0.0); - bank.interest_target_utilization = interest_target_utilization; - require_group_admin = true; - } } // account constraint #1 diff --git a/programs/mango-v4/src/instructions/token_register.rs b/programs/mango-v4/src/instructions/token_register.rs index ee9ed1226..88d1a68ff 100644 --- a/programs/mango-v4/src/instructions/token_register.rs +++ b/programs/mango-v4/src/instructions/token_register.rs @@ -38,8 +38,6 @@ pub fn token_register( token_conditional_swap_taker_fee_rate: f32, token_conditional_swap_maker_fee_rate: f32, flash_loan_swap_fee_rate: f32, - interest_curve_scaling: f32, - interest_target_utilization: f32, ) -> Result<()> { // Require token 0 to be in the insurance token if token_index == INSURANCE_TOKEN_INDEX { @@ -109,9 +107,9 @@ pub fn token_register( fees_withdrawn: 0, token_conditional_swap_taker_fee_rate, token_conditional_swap_maker_fee_rate, - flash_loan_swap_fee_rate: flash_loan_swap_fee_rate, - interest_target_utilization, - interest_curve_scaling: interest_curve_scaling.into(), + flash_loan_swap_fee_rate, + interest_target_utilization: 0.0, // unused in v0.20.0 + interest_curve_scaling: 0.0, // unused in v0.20.0 deposits_in_serum: 0, reserved: [0; 2072], }; diff --git a/programs/mango-v4/src/instructions/token_register_trustless.rs b/programs/mango-v4/src/instructions/token_register_trustless.rs index 75f7177d3..99ba833b9 100644 --- a/programs/mango-v4/src/instructions/token_register_trustless.rs +++ b/programs/mango-v4/src/instructions/token_register_trustless.rs @@ -94,8 +94,8 @@ pub fn token_register_trustless( token_conditional_swap_taker_fee_rate: 0.0005, token_conditional_swap_maker_fee_rate: 0.0005, flash_loan_swap_fee_rate: 0.0, - interest_target_utilization: 0.5, - interest_curve_scaling: 4.0, + interest_target_utilization: 0.0, // unused in v0.20.0 + interest_curve_scaling: 0.0, // unused in v0.20.0 deposits_in_serum: 0, reserved: [0; 2072], }; diff --git a/programs/mango-v4/src/instructions/token_update_index_and_rate.rs b/programs/mango-v4/src/instructions/token_update_index_and_rate.rs index 16ca665df..15cb1ec57 100644 --- a/programs/mango-v4/src/instructions/token_update_index_and_rate.rs +++ b/programs/mango-v4/src/instructions/token_update_index_and_rate.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use crate::accounts_ix::*; use crate::error::MangoError; -use crate::logs::{UpdateIndexLog, UpdateRateLogV2}; +use crate::logs::{UpdateIndexLog, UpdateRateLog}; use crate::state::HOUR; use crate::{ accounts_zerocopy::{AccountInfoRef, LoadMutZeroCopyRef, LoadZeroCopyRef}, @@ -140,54 +140,32 @@ pub fn token_update_index_and_rate(ctx: Context) -> Res // compute optimal rates, and max rate and set them on the bank { - let mut some_bank = ctx.remaining_accounts[0].load_mut::()?; + let some_bank = ctx.remaining_accounts[0].load::()?; let diff_ts = I80F48::from_num(now_ts - some_bank.bank_rate_last_updated); // update each hour if diff_ts > HOUR { - // First setup when new parameters are introduced - if some_bank.interest_curve_scaling == 0.0 { - let old_max_rate = 0.5; - some_bank.interest_curve_scaling = - some_bank.max_rate.to_num::() / old_max_rate; - some_bank.interest_target_utilization = some_bank.util0.to_num(); + let (rate0, rate1, max_rate) = some_bank.compute_rates(); - let descale_factor = I80F48::from_num(1.0 / some_bank.interest_curve_scaling); - some_bank.rate0 *= descale_factor; - some_bank.rate1 *= descale_factor; - some_bank.max_rate *= descale_factor; - } - - some_bank.update_interest_rate_scaling(); - - let rate0 = some_bank.rate0; - let rate1 = some_bank.rate1; - let max_rate = some_bank.max_rate; - let scaling = some_bank.interest_curve_scaling; - let target_util = some_bank.interest_target_utilization; - - emit!(UpdateRateLogV2 { + emit!(UpdateRateLog { mango_group: mint_info.group.key(), token_index: mint_info.token_index, rate0: rate0.to_bits(), - util0: some_bank.util0.to_bits(), rate1: rate1.to_bits(), - util1: some_bank.util1.to_bits(), max_rate: max_rate.to_bits(), - curve_scaling: some_bank.interest_curve_scaling, - target_utilization: some_bank.interest_target_utilization, }); drop(some_bank); - // Apply the new parameters to all banks + msg!("rate0 {}", rate0); + msg!("rate1 {}", rate1); + msg!("max_rate {}", max_rate); + for ai in ctx.remaining_accounts.iter() { let mut bank = ai.load_mut::()?; bank.bank_rate_last_updated = now_ts; - bank.interest_curve_scaling = scaling; - bank.interest_target_utilization = target_util; bank.rate0 = rate0; bank.rate1 = rate1; bank.max_rate = max_rate; diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 154f62508..df2716181 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -150,8 +150,6 @@ pub mod mango_v4 { token_conditional_swap_taker_fee_rate: f32, token_conditional_swap_maker_fee_rate: f32, flash_loan_swap_fee_rate: f32, - interest_curve_scaling: f32, - interest_target_utilization: f32, ) -> Result<()> { #[cfg(feature = "enable-gpl")] instructions::token_register( @@ -179,8 +177,6 @@ pub mod mango_v4 { token_conditional_swap_taker_fee_rate, token_conditional_swap_maker_fee_rate, flash_loan_swap_fee_rate, - interest_curve_scaling, - interest_target_utilization, )?; Ok(()) } @@ -225,8 +221,6 @@ pub mod mango_v4 { token_conditional_swap_taker_fee_rate_opt: Option, token_conditional_swap_maker_fee_rate_opt: Option, flash_loan_swap_fee_rate_opt: Option, - interest_curve_scaling_opt: Option, - interest_target_utilization_opt: Option, ) -> Result<()> { #[cfg(feature = "enable-gpl")] instructions::token_edit( @@ -258,8 +252,6 @@ pub mod mango_v4 { token_conditional_swap_taker_fee_rate_opt, token_conditional_swap_maker_fee_rate_opt, flash_loan_swap_fee_rate_opt, - interest_curve_scaling_opt, - interest_target_utilization_opt, )?; Ok(()) } diff --git a/programs/mango-v4/src/logs.rs b/programs/mango-v4/src/logs.rs index 67126daa2..51b897248 100644 --- a/programs/mango-v4/src/logs.rs +++ b/programs/mango-v4/src/logs.rs @@ -300,20 +300,6 @@ pub struct UpdateRateLog { pub max_rate: i128, // I80F48 } -#[event] -pub struct UpdateRateLogV2 { - pub mango_group: Pubkey, - pub token_index: u16, - // contrary to v1 these do not have curve_scaling factored in! - pub rate0: i128, // I80F48 - pub util0: i128, // I80F48 - pub rate1: i128, // I80F48 - pub util1: i128, // I80F48 - pub max_rate: i128, // I80F48 - pub curve_scaling: f64, - pub target_utilization: f32, -} - #[event] pub struct TokenLiqWithTokenLog { pub mango_group: Pubkey, diff --git a/programs/mango-v4/src/state/bank.rs b/programs/mango-v4/src/state/bank.rs index dcb176a71..b3dbc6c56 100644 --- a/programs/mango-v4/src/state/bank.rs +++ b/programs/mango-v4/src/state/bank.rs @@ -18,6 +18,7 @@ pub const DAY: i64 = 86400; pub const DAY_I80F48: I80F48 = I80F48::from_bits(86_400 * I80F48::ONE.to_bits()); pub const ONE_BPS: I80F48 = I80F48::from_bits(28147497671); pub const YEAR_I80F48: I80F48 = I80F48::from_bits(31_536_000 * I80F48::ONE.to_bits()); +pub const MINIMUM_MAX_RATE: I80F48 = I80F48::from_bits(I80F48::ONE.to_bits() / 2); #[derive(Derivative)] #[derivative(Debug)] @@ -150,12 +151,12 @@ pub struct Bank { /// Target utilization: If actual utilization is higher, scale up interest. /// If it's lower, scale down interest (if possible) - pub interest_target_utilization: f32, + pub interest_target_utilization: f32, // unused in v0.20.0 /// Current interest curve scaling, always >= 1.0 /// /// Except when first migrating to having this field, then 0.0 - pub interest_curve_scaling: f64, + pub interest_curve_scaling: f64, // unused in v0.20.0 // user deposits that were moved into serum open orders // can be negative due to multibank, then it'd need to be balanced in the keeper @@ -262,15 +263,15 @@ impl Bank { token_index: existing_bank.token_index, mint_decimals: existing_bank.mint_decimals, oracle_config: existing_bank.oracle_config, - stable_price_model: existing_bank.stable_price_model, + stable_price_model: StablePriceModel::default(), min_vault_to_deposits_ratio: existing_bank.min_vault_to_deposits_ratio, net_borrow_limit_per_window_quote: existing_bank.net_borrow_limit_per_window_quote, net_borrow_limit_window_size_ts: existing_bank.net_borrow_limit_window_size_ts, last_net_borrows_window_start_ts: existing_bank.last_net_borrows_window_start_ts, - borrow_weight_scale_start_quote: existing_bank.borrow_weight_scale_start_quote, - deposit_weight_scale_start_quote: existing_bank.deposit_weight_scale_start_quote, - reduce_only: existing_bank.reduce_only, - force_close: existing_bank.force_close, + borrow_weight_scale_start_quote: f64::MAX, + deposit_weight_scale_start_quote: f64::MAX, + reduce_only: 0, + force_close: 0, padding: [0; 6], token_conditional_swap_taker_fee_rate: existing_bank .token_conditional_swap_taker_fee_rate, @@ -289,7 +290,7 @@ impl Bank { require_gte!(self.rate0, I80F48::ZERO); require_gte!(self.util1, I80F48::ZERO); require_gte!(self.rate1, I80F48::ZERO); - require_gte!(self.max_rate, I80F48::ZERO); + require_gte!(self.max_rate, MINIMUM_MAX_RATE); require_gte!(self.loan_fee_rate, 0.0); require_gte!(self.loan_origination_fee_rate, 0.0); require_gte!(self.maint_asset_weight, 0.0); @@ -305,8 +306,6 @@ impl Bank { require_gte!(self.token_conditional_swap_taker_fee_rate, 0.0); require_gte!(self.token_conditional_swap_maker_fee_rate, 0.0); require_gte!(self.flash_loan_swap_fee_rate, 0.0); - require_gte!(self.interest_curve_scaling, 1.0); - require_gte!(self.interest_target_utilization, 0.0); Ok(()) } @@ -816,7 +815,6 @@ impl Bank { self.util1, self.rate1, self.max_rate, - self.interest_curve_scaling, ) } @@ -830,9 +828,8 @@ impl Bank { util1: I80F48, rate1: I80F48, max_rate: I80F48, - scaling: f64, ) -> I80F48 { - let v = if utilization <= util0 { + if utilization <= util0 { let slope = rate0 / util0; slope * utilization } else if utilization <= util1 { @@ -843,13 +840,6 @@ impl Bank { let extra_util = utilization - util1; let slope = (max_rate - rate1) / (I80F48::ONE - util1); rate1 + slope * extra_util - }; - - // scaling will be 0 when it's introduced - if scaling == 0.0 { - v - } else { - v * I80F48::from_num(scaling) } } @@ -885,23 +875,34 @@ impl Bank { } // computes new optimal rates and max rate - pub fn update_interest_rate_scaling(&mut self) { - // Interest increases above target_util, decreases below - let target_util = self.interest_target_utilization as f64; - + pub fn compute_rates(&self) -> (I80F48, I80F48, I80F48) { + // interest rate legs 2 and 3 are seen as punitive legs, encouraging utilization to move towards optimal utilization + // lets choose util0 as optimal utilization and 0 to utli0 as the leg where we want the utlization to preferably be + let optimal_util = self.util0; // use avg_utilization and not instantaneous_utilization so that rates cannot be manipulated easily - let avg_util = self.avg_utilization.to_num::(); - + let avg_util = self.avg_utilization; // move rates up when utilization is above optimal utilization, and vice versa // util factor is between -1 (avg util = 0) and +1 (avg util = 100%) - let util_factor = if avg_util > target_util { - (avg_util - target_util) / (1.0 - target_util) + let util_factor = if avg_util > optimal_util { + (avg_util - optimal_util) / (I80F48::ONE - optimal_util) } else { - (avg_util - target_util) / target_util + (avg_util - optimal_util) / optimal_util }; - let adjustment = 1.0 + self.adjustment_factor.to_num::() * util_factor; + let adjustment = I80F48::ONE + self.adjustment_factor * util_factor; - self.interest_curve_scaling = (self.interest_curve_scaling * adjustment).max(1.0) + // 1. irrespective of which leg current utilization is in, update all rates + // 2. only update rates as long as new adjusted rates are above MINIMUM_MAX_RATE, + // since we don't want to fall to such low rates that it would take a long time to + // recover to high rates if utilization suddently increases to a high value + if (self.max_rate * adjustment) > MINIMUM_MAX_RATE { + ( + (self.rate0 * adjustment), + (self.rate1 * adjustment), + (self.max_rate * adjustment), + ) + } else { + (self.rate0, self.rate1, self.max_rate) + } } pub fn oracle_price( diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 848e9990e..a24e16356 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -997,8 +997,6 @@ impl ClientInstruction for TokenRegisterInstruction { token_conditional_swap_taker_fee_rate: 0.0, token_conditional_swap_maker_fee_rate: 0.0, flash_loan_swap_fee_rate: 0.0, - interest_curve_scaling: 1.0, - interest_target_utilization: 0.5, }; let bank = Pubkey::find_program_address( @@ -1243,8 +1241,6 @@ pub fn token_edit_instruction_default() -> mango_v4::instruction::TokenEdit { token_conditional_swap_taker_fee_rate_opt: None, token_conditional_swap_maker_fee_rate_opt: None, flash_loan_swap_fee_rate_opt: None, - interest_curve_scaling_opt: None, - interest_target_utilization_opt: None, } } diff --git a/ts/client/src/clientIxParamBuilder.ts b/ts/client/src/clientIxParamBuilder.ts index d34245327..d49c3c76d 100644 --- a/ts/client/src/clientIxParamBuilder.ts +++ b/ts/client/src/clientIxParamBuilder.ts @@ -25,8 +25,6 @@ export interface TokenRegisterParams { tokenConditionalSwapTakerFeeRate: number; tokenConditionalSwapMakerFeeRate: number; flashLoanSwapFeeRate: number; - interestCurveScaling: number; - interestTargetUtilization: number; } export const DefaultTokenRegisterParams: TokenRegisterParams = { @@ -37,10 +35,10 @@ export const DefaultTokenRegisterParams: TokenRegisterParams = { groupInsuranceFund: false, interestRateParams: { util0: 0.5, - rate0: 0.018, + rate0: 0.072, util1: 0.8, - rate1: 0.05, - maxRate: 0.5, + rate1: 0.2, + maxRate: 2, adjustmentFactor: 0.004, }, loanFeeRate: 0.0005, @@ -62,8 +60,6 @@ export const DefaultTokenRegisterParams: TokenRegisterParams = { tokenConditionalSwapTakerFeeRate: 0.0005, tokenConditionalSwapMakerFeeRate: 0.0005, flashLoanSwapFeeRate: 0.0005, - interestCurveScaling: 4.0, - interestTargetUtilization: 0.5, }; export interface TokenEditParams { @@ -94,8 +90,6 @@ export interface TokenEditParams { tokenConditionalSwapTakerFeeRate: number | null; tokenConditionalSwapMakerFeeRate: number | null; flashLoanSwapFeeRate: number | null; - interestCurveScaling: number | null; - interestTargetUtilization: number | null; } export const NullTokenEditParams: TokenEditParams = { @@ -126,8 +120,6 @@ export const NullTokenEditParams: TokenEditParams = { tokenConditionalSwapTakerFeeRate: null, tokenConditionalSwapMakerFeeRate: null, flashLoanSwapFeeRate: null, - interestCurveScaling: null, - interestTargetUtilization: null, }; export interface PerpEditParams {