wip: dynamic rates (#98)

* dynamic rates

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>

* fmt

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2022-07-11 17:08:32 +02:00 committed by GitHub
parent f132f30874
commit ef7d2862da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 486 additions and 165 deletions

View File

@ -2,7 +2,7 @@ use std::{sync::Arc, time::Duration};
use crate::MangoClient;
use anchor_lang::__private::bytemuck::cast_ref;
use anchor_lang::{__private::bytemuck::cast_ref, solana_program};
use futures::Future;
use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, PerpMarket, TokenIndex};
use solana_sdk::{
@ -19,7 +19,7 @@ pub async fn runner(
.banks_cache
.values()
.map(|banks_for_a_token| {
loop_update_index(
loop_update_index_and_rate(
mango_client.clone(),
banks_for_a_token.get(0).unwrap().1.token_index,
)
@ -48,7 +48,7 @@ pub async fn runner(
Ok(())
}
pub async fn loop_update_index(mango_client: Arc<MangoClient>, token_index: TokenIndex) {
pub async fn loop_update_index_and_rate(mango_client: Arc<MangoClient>, token_index: TokenIndex) {
let mut interval = time::interval(Duration::from_secs(5));
loop {
interval.tick().await;
@ -74,11 +74,15 @@ pub async fn loop_update_index(mango_client: Arc<MangoClient>, token_index: Toke
let mut ix = Instruction {
program_id: mango_v4::id(),
accounts: anchor_lang::ToAccountMetas::to_account_metas(
&mango_v4::accounts::TokenUpdateIndex { mint_info, oracle },
&mango_v4::accounts::TokenUpdateIndexAndRate {
mint_info,
oracle,
instructions: solana_program::sysvar::instructions::id(),
},
None,
),
data: anchor_lang::InstructionData::data(
&mango_v4::instruction::TokenUpdateIndex {},
&mango_v4::instruction::TokenUpdateIndexAndRate {},
),
};
let mut banks = bank_pubkeys_for_a_token
@ -97,7 +101,11 @@ pub async fn loop_update_index(mango_client: Arc<MangoClient>, token_index: Toke
if let Err(e) = sig_result {
log::error!("{:?}", e)
} else {
log::info!("update_index {} {:?}", token_name, sig_result.unwrap())
log::info!(
"update_index_and_rate {} {:?}",
token_name,
sig_result.unwrap()
)
}
Ok(())

View File

@ -115,7 +115,7 @@ pub fn flash_loan3_begin<'key, 'accounts, 'remaining, 'info>(
let ix = match tx_instructions::load_instruction_at_checked(index, ixs) {
Ok(ix) => ix,
Err(ProgramError::InvalidArgument) => break, // past the last instruction
Err(e) => Err(e)?,
Err(e) => return Err(e.into()),
};
// Check that the mango program key is not used
@ -129,9 +129,9 @@ pub fn flash_loan3_begin<'key, 'accounts, 'remaining, 'info>(
found_end = true;
// must be the FlashLoan3End instruction
require_msg!(
&ix.data[0..8] == &[163, 231, 155, 56, 201, 68, 84, 148],
"the next Mango instruction after FlashLoan3Begin must be FlashLoan3End"
require!(
ix.data[0..8] == [163, 231, 155, 56, 201, 68, 84, 148],
MangoError::SomeError
);
// check that the same vaults are passed

View File

@ -37,7 +37,7 @@ pub use token_deposit::*;
pub use token_deregister::*;
pub use token_edit::*;
pub use token_register::*;
pub use token_update_index::*;
pub use token_update_index_and_rate::*;
pub use token_withdraw::*;
mod account_close;
@ -79,5 +79,5 @@ mod token_deposit;
mod token_deregister;
mod token_edit;
mod token_register;
mod token_update_index;
mod token_update_index_and_rate;
mod token_withdraw;

View File

@ -73,6 +73,7 @@ pub fn token_edit(
if let Some(ref interest_rate_params) = interest_rate_params_opt {
// TODO: add a require! verifying relation between the parameters
bank.adjustment_factor = I80F48::from_num(interest_rate_params.adjustment_factor);
bank.util0 = I80F48::from_num(interest_rate_params.util0);
bank.rate0 = I80F48::from_num(interest_rate_params.rate0);
bank.util1 = I80F48::from_num(interest_rate_params.util1);

View File

@ -82,6 +82,7 @@ pub struct InterestRateParams {
pub util1: f32,
pub rate1: f32,
pub max_rate: f32,
pub adjustment_factor: f32,
}
// TODO: should this be "configure_mint", we pass an explicit index, and allow
@ -128,8 +129,11 @@ pub fn token_register(
cached_indexed_total_borrows: I80F48::ZERO,
indexed_deposits: I80F48::ZERO,
indexed_borrows: I80F48::ZERO,
last_updated: Clock::get()?.unix_timestamp,
index_last_updated: Clock::get()?.unix_timestamp,
bank_rate_last_updated: Clock::get()?.unix_timestamp,
// TODO: add a require! verifying relation between the parameters
avg_utilization: I80F48::ZERO,
adjustment_factor: I80F48::from_num(interest_rate_params.adjustment_factor),
util0: I80F48::from_num(interest_rate_params.util0),
rate0: I80F48::from_num(interest_rate_params.rate0),
util1: I80F48::from_num(interest_rate_params.util1),

View File

@ -1,86 +0,0 @@
use anchor_lang::prelude::*;
use crate::logs::UpdateIndexLog;
use crate::{
accounts_zerocopy::{AccountInfoRef, LoadMutZeroCopyRef, LoadZeroCopyRef},
state::{oracle_price, Bank, MintInfo},
};
use checked_math as cm;
use fixed::types::I80F48;
#[derive(Accounts)]
pub struct TokenUpdateIndex<'info> {
pub mint_info: AccountLoader<'info, MintInfo>,
pub oracle: UncheckedAccount<'info>,
}
pub fn token_update_index(ctx: Context<TokenUpdateIndex>) -> Result<()> {
let mint_info = ctx.accounts.mint_info.load()?;
require_keys_eq!(mint_info.oracle.key(), ctx.accounts.oracle.key());
ctx.accounts
.mint_info
.load()?
.verify_banks_ais(ctx.remaining_accounts)?;
let mut indexed_total_deposits = I80F48::ZERO;
let mut indexed_total_borrows = I80F48::ZERO;
for ai in ctx.remaining_accounts.iter() {
let bank = ai.load::<Bank>()?;
indexed_total_deposits = cm!(indexed_total_deposits + bank.indexed_deposits);
indexed_total_borrows = cm!(indexed_total_borrows + bank.indexed_borrows);
}
let now_ts = Clock::get()?.unix_timestamp;
let (diff_ts, deposit_index, borrow_index, oracle_conf_filter, base_token_decimals) = {
let mut some_bank = ctx.remaining_accounts[0].load_mut::<Bank>()?;
// TODO: should we enforce a minimum window between 2 update_index ix calls?
let diff_ts = I80F48::from_num(now_ts - some_bank.last_updated);
let (deposit_index, borrow_index) =
some_bank.compute_index(indexed_total_deposits, indexed_total_borrows, diff_ts)?;
(
diff_ts,
deposit_index,
borrow_index,
some_bank.oracle_config.conf_filter,
some_bank.mint_decimals,
)
};
msg!("indexed_total_deposits {}", indexed_total_deposits);
msg!("indexed_total_borrows {}", indexed_total_borrows);
msg!("diff_ts {}", diff_ts);
msg!("deposit_index {}", deposit_index);
msg!("borrow_index {}", borrow_index);
for ai in ctx.remaining_accounts.iter() {
let mut bank = ai.load_mut::<Bank>()?;
bank.cached_indexed_total_deposits = indexed_total_deposits;
bank.cached_indexed_total_borrows = indexed_total_borrows;
bank.last_updated = now_ts;
bank.charge_loan_fee(diff_ts);
bank.deposit_index = deposit_index;
bank.borrow_index = borrow_index;
}
let price = oracle_price(
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
oracle_conf_filter,
base_token_decimals,
)?;
emit!(UpdateIndexLog {
mango_group: mint_info.group.key(),
token_index: mint_info.token_index,
deposit_index: deposit_index.to_bits(),
borrow_index: borrow_index.to_bits(),
price: price.to_bits(),
});
Ok(())
}

View File

@ -0,0 +1,163 @@
use anchor_lang::prelude::*;
use crate::error::MangoError;
use crate::logs::{UpdateIndexLog, UpdateRateLog};
use crate::state::HOUR;
use crate::{
accounts_zerocopy::{AccountInfoRef, LoadMutZeroCopyRef, LoadZeroCopyRef},
state::{oracle_price, Bank, MintInfo},
};
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
use checked_math as cm;
use fixed::types::I80F48;
#[derive(Accounts)]
pub struct TokenUpdateIndexAndRate<'info> {
#[account(
has_one = oracle
)]
pub mint_info: AccountLoader<'info, MintInfo>,
pub oracle: UncheckedAccount<'info>,
#[account(address = tx_instructions::ID)]
pub instructions: UncheckedAccount<'info>,
}
pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Result<()> {
{
let ixs = ctx.accounts.instructions.as_ref();
let mut index = 0;
loop {
let ix = match tx_instructions::load_instruction_at_checked(index, ixs) {
Ok(ix) => ix,
Err(ProgramError::InvalidArgument) => break,
Err(e) => return Err(e.into()),
};
// 1. we want to forbid token deposit and token withdraw and similar
// (serum3 place order could be used as a withdraw and a serum3 cancel order as a deposit)
// to be called in same tx as this ix to prevent index or rate manipulation,
// for now we just whitelist to other token_update_index_and_rate ix
// 2. we want to forbid cpi, since ix we would like to blacklist could just be called from cpi
require!(
ix.program_id == crate::id()
&& ix.data[0..8] == [131, 136, 194, 39, 11, 50, 10, 198], // token_update_index_and_rate
MangoError::SomeError
);
index += 1;
}
}
let mint_info = ctx.accounts.mint_info.load()?;
ctx.accounts
.mint_info
.load()?
.verify_banks_ais(ctx.remaining_accounts)?;
let now_ts = Clock::get()?.unix_timestamp;
// compute indexed_total
let mut indexed_total_deposits = I80F48::ZERO;
let mut indexed_total_borrows = I80F48::ZERO;
for ai in ctx.remaining_accounts.iter() {
let bank = ai.load::<Bank>()?;
indexed_total_deposits = cm!(indexed_total_deposits + bank.indexed_deposits);
indexed_total_borrows = cm!(indexed_total_borrows + bank.indexed_borrows);
}
// compute and set latest index and average utilization on each bank
{
let some_bank = ctx.remaining_accounts[0].load::<Bank>()?;
let now_ts_i80f48 = I80F48::from_num(now_ts);
let diff_ts = I80F48::from_num(now_ts - some_bank.index_last_updated);
let (deposit_index, borrow_index) =
some_bank.compute_index(indexed_total_deposits, indexed_total_borrows, diff_ts)?;
let new_avg_utilization = some_bank.compute_new_avg_utilization(
indexed_total_deposits,
indexed_total_borrows,
now_ts_i80f48,
);
let price = oracle_price(
&AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?,
some_bank.oracle_config.conf_filter,
some_bank.mint_decimals,
)?;
emit!(UpdateIndexLog {
mango_group: mint_info.group.key(),
token_index: mint_info.token_index,
deposit_index: deposit_index.to_bits(),
borrow_index: borrow_index.to_bits(),
avg_utilization: new_avg_utilization.to_bits(),
price: price.to_bits()
});
drop(some_bank);
msg!("indexed_total_deposits {}", indexed_total_deposits);
msg!("indexed_total_borrows {}", indexed_total_borrows);
msg!("diff_ts {}", diff_ts);
msg!("deposit_index {}", deposit_index);
msg!("borrow_index {}", borrow_index);
msg!("avg_utilization {}", new_avg_utilization);
for ai in ctx.remaining_accounts.iter() {
let mut bank = ai.load_mut::<Bank>()?;
bank.cached_indexed_total_deposits = indexed_total_deposits;
bank.cached_indexed_total_borrows = indexed_total_borrows;
bank.index_last_updated = now_ts;
bank.charge_loan_fee(diff_ts);
bank.deposit_index = deposit_index;
bank.borrow_index = borrow_index;
bank.avg_utilization = new_avg_utilization;
}
}
// compute optimal rates, and max rate and set them on the bank
{
let some_bank = ctx.remaining_accounts[0].load::<Bank>()?;
let diff_ts = I80F48::from_num(now_ts - some_bank.bank_rate_last_updated);
// update each hour
if diff_ts > HOUR {
let (rate0, rate1, max_rate) = some_bank.compute_rates();
emit!(UpdateRateLog {
mango_group: mint_info.group.key(),
token_index: mint_info.token_index,
rate0: rate0.to_bits(),
rate1: rate1.to_bits(),
max_rate: max_rate.to_bits(),
});
drop(some_bank);
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.bank_rate_last_updated = now_ts;
bank.rate0 = rate0;
bank.rate1 = rate1;
bank.max_rate = max_rate;
}
}
}
Ok(())
}

View File

@ -118,8 +118,8 @@ pub mod mango_v4 {
instructions::token_deregister(ctx, token_index)
}
pub fn token_update_index(ctx: Context<TokenUpdateIndex>) -> Result<()> {
instructions::token_update_index(ctx)
pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Result<()> {
instructions::token_update_index_and_rate(ctx)
}
pub fn account_create(
@ -159,10 +159,12 @@ pub mod mango_v4 {
instructions::stub_oracle_set(ctx, price)
}
// NOTE: keep disc synced in token_update_index_and_rate ix
pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
instructions::token_deposit(ctx, amount)
}
// NOTE: keep disc synced in token_update_index_and_rate ix
pub fn token_withdraw(
ctx: Context<TokenWithdraw>,
amount: u64,
@ -199,6 +201,7 @@ pub mod mango_v4 {
instructions::flash_loan3_begin(ctx, loan_amounts)
}
// NOTE: keep disc synced in flash_loan3.rs
pub fn flash_loan3_end<'key, 'accounts, 'remaining, 'info>(
ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoan3End<'info>>,
) -> Result<()> {

View File

@ -130,9 +130,19 @@ pub struct UpdateFundingLog {
pub struct UpdateIndexLog {
pub mango_group: Pubkey,
pub token_index: u16,
pub deposit_index: i128, // I80F48
pub borrow_index: i128, // I80F48
pub price: i128, // I80F48
pub deposit_index: i128, // I80F48
pub borrow_index: i128, // I80F48
pub avg_utilization: i128, // I80F48
pub price: i128, // I80F48
}
#[event]
pub struct UpdateRateLog {
pub mango_group: Pubkey,
pub token_index: u16,
pub rate0: i128, // I80F48
pub rate1: i128, // I80F48
pub max_rate: i128, // I80F48
}
#[event]

View File

@ -7,8 +7,10 @@ use static_assertions::const_assert_eq;
use std::mem::size_of;
pub const DAY: I80F48 = I80F48!(86400);
pub const YEAR: I80F48 = I80F48!(31536000);
pub const HOUR: i64 = 3600;
pub const DAY: i64 = 86400;
pub const DAY_I80F48: I80F48 = I80F48!(86400);
pub const YEAR_I80F48: I80F48 = I80F48!(31536000);
#[account(zero_copy)]
pub struct Bank {
@ -28,8 +30,8 @@ pub struct Bank {
pub deposit_index: I80F48,
pub borrow_index: I80F48,
/// total deposits/borrows, only updated during UpdateIndex
/// TODO: These values could be dropped from the bank, they're written in UpdateIndex
/// total deposits/borrows, only updated during UpdateIndexAndRate
/// TODO: These values could be dropped from the bank, they're written in UpdateIndexAndRate
/// and never read.
pub cached_indexed_total_deposits: I80F48,
pub cached_indexed_total_borrows: I80F48,
@ -42,11 +44,16 @@ pub struct Bank {
///
/// The vault amount is not deducable from these values.
///
/// These become meaningful when summed over all banks (like in update_index).
/// These become meaningful when summed over all banks (like in update_index_and_rate).
pub indexed_deposits: I80F48,
pub indexed_borrows: I80F48,
pub last_updated: i64,
pub index_last_updated: i64,
pub bank_rate_last_updated: i64,
pub avg_utilization: I80F48,
pub adjustment_factor: I80F48,
pub util0: I80F48,
pub rate0: I80F48,
pub util1: I80F48,
@ -92,7 +99,7 @@ pub struct Bank {
}
const_assert_eq!(
size_of::<Bank>(),
16 + 32 * 4 + 8 + 16 * 21 + 2 * 8 + 2 + 1 + 1 + 4 + 8
16 + 32 * 4 + 8 * 2 + 16 * 23 + 2 * 8 + 2 + 1 + 1 + 4 + 8
);
const_assert_eq!(size_of::<Bank>() % 8, 0);
@ -117,7 +124,9 @@ impl std::fmt::Debug for Bank {
)
.field("indexed_deposits", &self.indexed_deposits)
.field("indexed_borrows", &self.indexed_borrows)
.field("last_updated", &self.last_updated)
.field("index_last_updated", &self.index_last_updated)
.field("bank_rate_last_updated", &self.bank_rate_last_updated)
.field("avg_utilization", &self.avg_utilization)
.field("util0", &self.util0)
.field("rate0", &self.rate0)
.field("util1", &self.util1)
@ -158,7 +167,10 @@ impl Bank {
cached_indexed_total_borrows: existing_bank.cached_indexed_total_borrows,
indexed_deposits: I80F48::ZERO,
indexed_borrows: I80F48::ZERO,
last_updated: existing_bank.last_updated,
index_last_updated: existing_bank.index_last_updated,
bank_rate_last_updated: existing_bank.bank_rate_last_updated,
avg_utilization: existing_bank.avg_utilization,
adjustment_factor: existing_bank.adjustment_factor,
util0: existing_bank.util0,
rate0: existing_bank.rate0,
util1: existing_bank.util1,
@ -375,31 +387,32 @@ impl Bank {
pub fn charge_loan_fee(&mut self, diff_ts: I80F48) {
let native_borrows_old = self.native_borrows();
self.indexed_borrows =
cm!((self.indexed_borrows * (I80F48::ONE + self.loan_fee_rate * (diff_ts / YEAR))));
cm!((self.indexed_borrows
* (I80F48::ONE + self.loan_fee_rate * (diff_ts / YEAR_I80F48))));
self.collected_fees_native =
cm!(self.collected_fees_native + self.native_borrows() - native_borrows_old);
}
pub fn compute_index(
&mut self,
&self,
indexed_total_deposits: I80F48,
indexed_total_borrows: I80F48,
diff_ts: I80F48,
) -> Result<(I80F48, I80F48)> {
// compute index based on utilization
let native_total_deposits = self.deposit_index * indexed_total_deposits;
let native_total_borrows = self.borrow_index * indexed_total_borrows;
let native_total_deposits = cm!(self.deposit_index * indexed_total_deposits);
let native_total_borrows = cm!(self.borrow_index * indexed_total_borrows);
let utilization = if native_total_deposits == I80F48::ZERO {
let instantaneous_utilization = if native_total_deposits == I80F48::ZERO {
I80F48::ZERO
} else {
cm!(native_total_borrows / native_total_deposits)
};
let interest_rate = self.compute_interest_rate(utilization);
let borrow_interest_rate = self.compute_interest_rate(instantaneous_utilization);
let borrow_interest: I80F48 = cm!(interest_rate * diff_ts);
let deposit_interest = cm!(borrow_interest * utilization);
let borrow_interest: I80F48 = cm!(borrow_interest_rate * diff_ts);
let deposit_interest = cm!(borrow_interest * instantaneous_utilization);
// msg!("utilization {}", utilization);
// msg!("interest_rate {}", interest_rate);
@ -410,9 +423,10 @@ impl Bank {
return Ok((self.deposit_index, self.borrow_index));
}
let borrow_index = cm!((self.borrow_index * borrow_interest) / YEAR + self.borrow_index);
let borrow_index =
cm!((self.borrow_index * borrow_interest) / YEAR_I80F48 + self.borrow_index);
let deposit_index =
cm!((self.deposit_index * deposit_interest) / YEAR + self.deposit_index);
cm!((self.deposit_index * deposit_interest) / YEAR_I80F48 + self.deposit_index);
Ok((deposit_index, borrow_index))
}
@ -441,7 +455,6 @@ impl Bank {
rate1: I80F48,
max_rate: I80F48,
) -> I80F48 {
// TODO: daffy: use optimal interest from oracle
if utilization <= util0 {
let slope = cm!(rate0 / util0);
cm!(slope * utilization)
@ -455,6 +468,50 @@ impl Bank {
cm!(rate1 + slope * extra_util)
}
}
// compute new avg utilization
pub fn compute_new_avg_utilization(
&self,
indexed_total_deposits: I80F48,
indexed_total_borrows: I80F48,
now_ts: I80F48,
) -> I80F48 {
if now_ts == I80F48::ZERO {
return I80F48::ZERO;
}
let native_total_deposits = self.deposit_index * indexed_total_deposits;
let native_total_borrows = self.borrow_index * indexed_total_borrows;
let instantaneous_utilization = if native_total_deposits == I80F48::ZERO {
I80F48::ZERO
} else {
cm!(native_total_borrows / native_total_deposits)
};
// combine old and new with relevant factors to form new avg_utilization
// scaling factor for previous avg_utilization is old_ts/new_ts
// scaling factor for instantaneous utilization is (new_ts - old_ts) / new_ts
let bank_rate_last_updated_i80f48 = I80F48::from_num(self.bank_rate_last_updated);
(self.avg_utilization * bank_rate_last_updated_i80f48
+ instantaneous_utilization * (now_ts - bank_rate_last_updated_i80f48))
/ now_ts
}
// computes new optimal rates and max rate
pub fn compute_rates(&self) -> (I80F48, I80F48, I80F48) {
// since we have 3 interest rate legs, consider the middle point of the middle leg as the optimal util
let optimal_util = (self.util0 + self.util1) / 2;
// use avg_utilization and not instantaneous_utilization so that rates cannot be manupulated easily
let util_diff = self.avg_utilization - optimal_util;
// move rates up when utilization is above optimal utilization, and vice versa
let adjustment = I80F48::ONE + self.adjustment_factor * util_diff;
// irrespective of which leg current utilization is in, update all rates
(
cm!(self.rate0 * adjustment),
cm!(self.rate1 * adjustment),
cm!(self.max_rate * adjustment),
)
}
}
#[macro_export]
@ -583,4 +640,34 @@ mod tests {
}
Ok(())
}
#[test]
fn test_compute_new_avg_utilization() {
let mut bank = Bank::zeroed();
bank.deposit_index = I80F48::from_num(1.0);
bank.borrow_index = I80F48::from_num(1.0);
bank.bank_rate_last_updated = 0;
let compute_new_avg_utilization_runner =
|bank: &mut Bank, utilization: I80F48, now_ts: i64| {
bank.avg_utilization = bank.compute_new_avg_utilization(
I80F48::ONE,
utilization,
I80F48::from_num(now_ts),
);
bank.bank_rate_last_updated = now_ts;
};
compute_new_avg_utilization_runner(&mut bank, I80F48::ZERO, 0);
assert_eq!(bank.avg_utilization, I80F48::ZERO);
compute_new_avg_utilization_runner(&mut bank, I80F48::from_num(0.5), 10);
assert!((bank.avg_utilization - I80F48::from_num(0.5)).abs() < 0.0001);
compute_new_avg_utilization_runner(&mut bank, I80F48::from_num(0.8), 15);
assert!((bank.avg_utilization - I80F48::from_num(0.6)).abs() < 0.0001);
compute_new_avg_utilization_runner(&mut bank, I80F48::ONE, 20);
assert!((bank.avg_utilization - I80F48::from_num(0.7)).abs() < 0.0001);
}
}

View File

@ -6,10 +6,10 @@ use fixed::types::I80F48;
use static_assertions::const_assert_eq;
use crate::state::orderbook::order_type::Side;
use crate::state::{TokenIndex, DAY};
use crate::state::TokenIndex;
use crate::util::checked_math as cm;
use super::{Book, OracleConfig};
use super::{Book, OracleConfig, DAY_I80F48};
pub type PerpMarketIndex = u16;
@ -134,7 +134,7 @@ impl PerpMarket {
};
let diff_ts = I80F48::from_num(now_ts - self.funding_last_updated as u64);
let time_factor = cm!(diff_ts / DAY);
let time_factor = cm!(diff_ts / DAY_I80F48);
let base_lot_size = I80F48::from_num(self.base_lot_size);
let funding_delta = cm!(index_price * diff_price * base_lot_size * time_factor);

View File

@ -769,6 +769,7 @@ impl ClientInstruction for TokenDepositInstruction {
pub struct TokenRegisterInstruction<'keypair> {
pub token_index: TokenIndex,
pub decimals: u8,
pub adjustment_factor: f32,
pub util0: f32,
pub rate0: f32,
pub util1: f32,
@ -805,6 +806,7 @@ impl<'keypair> ClientInstruction for TokenRegisterInstruction<'keypair> {
conf_filter: I80F48::from_num::<f32>(0.10),
},
interest_rate_params: InterestRateParams {
adjustment_factor: self.adjustment_factor,
util0: self.util0,
rate0: self.rate0,
util1: self.util1,
@ -2545,13 +2547,13 @@ impl ClientInstruction for BenchmarkInstruction {
vec![]
}
}
pub struct TokenUpdateIndexInstruction {
pub struct TokenUpdateIndexAndRateInstruction {
pub mint_info: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for TokenUpdateIndexInstruction {
type Accounts = mango_v4::accounts::TokenUpdateIndex;
type Instruction = mango_v4::instruction::TokenUpdateIndex;
impl ClientInstruction for TokenUpdateIndexAndRateInstruction {
type Accounts = mango_v4::accounts::TokenUpdateIndexAndRate;
type Instruction = mango_v4::instruction::TokenUpdateIndexAndRate;
async fn to_instruction(
&self,
loader: impl ClientAccountLoader + 'async_trait,
@ -2564,6 +2566,7 @@ impl ClientInstruction for TokenUpdateIndexInstruction {
let accounts = Self::Accounts {
mint_info: self.mint_info,
oracle: mint_info.oracle,
instructions: solana_program::sysvar::instructions::id(),
};
let mut instruction = make_instruction(program_id, &accounts, instruction);

View File

@ -83,6 +83,7 @@ impl<'a> GroupWithTokensConfig<'a> {
TokenRegisterInstruction {
token_index,
decimals: mint.decimals,
adjustment_factor: 0.01,
util0: 0.40,
rate0: 0.07,
util1: 0.80,

View File

@ -155,7 +155,7 @@ async fn test_basic() -> Result<(), TransportError> {
// withdraw whatever is remaining, can't close bank vault without this
send_tx(
solana,
TokenUpdateIndexInstruction {
TokenUpdateIndexAndRateInstruction {
mint_info: tokens[0].mint_info,
},
)
@ -179,7 +179,7 @@ async fn test_basic() -> Result<(), TransportError> {
// close account
send_tx(
solana,
AccountCloseInstruction {
CloseAccountInstruction {
group,
account,
owner,
@ -217,7 +217,7 @@ async fn test_basic() -> Result<(), TransportError> {
// close stub oracle
send_tx(
solana,
StubOracleCloseInstruction {
CloseStubOracleInstruction {
group,
mint: bank_data.mint,
admin,
@ -230,7 +230,7 @@ async fn test_basic() -> Result<(), TransportError> {
// close group
send_tx(
solana,
GroupCloseInstruction {
CloseGroupInstruction {
group,
admin,
sol_destination: payer.pubkey(),

View File

@ -9,7 +9,7 @@ use program_test::*;
mod program_test;
#[tokio::test]
async fn test_update_index() -> Result<(), TransportError> {
async fn test_token_update_index_and_rate() -> Result<(), TransportError> {
let context = TestContext::new().await;
let solana = &context.solana.clone();
@ -99,24 +99,30 @@ async fn test_update_index() -> Result<(), TransportError> {
.await
.unwrap();
let bank_before_update_index = solana.get_account::<Bank>(tokens[0].bank).await;
let bank_before_update_index_and_rate = solana.get_account::<Bank>(tokens[0].bank).await;
solana.advance_clock().await;
send_tx(
solana,
TokenUpdateIndexInstruction {
TokenUpdateIndexAndRateInstruction {
mint_info: tokens[0].mint_info,
},
)
.await
.unwrap();
let bank_after_update_index = solana.get_account::<Bank>(tokens[0].bank).await;
dbg!(bank_after_update_index);
dbg!(bank_after_update_index);
assert!(bank_before_update_index.deposit_index < bank_after_update_index.deposit_index);
assert!(bank_before_update_index.borrow_index < bank_after_update_index.borrow_index);
let bank_after_update_index_and_rate = solana.get_account::<Bank>(tokens[0].bank).await;
dbg!(bank_after_update_index_and_rate);
dbg!(bank_after_update_index_and_rate);
assert!(
bank_before_update_index_and_rate.deposit_index
< bank_after_update_index_and_rate.deposit_index
);
assert!(
bank_before_update_index_and_rate.borrow_index
< bank_after_update_index_and_rate.borrow_index
);
Ok(())
}

View File

@ -140,6 +140,7 @@ export class MangoClient {
oracleConfFilter: number,
tokenIndex: number,
name: string,
adjustmentFactor: number,
util0: number,
rate0: number,
util1: number,
@ -163,7 +164,7 @@ export class MangoClient {
val: I80F48.fromNumber(oracleConfFilter).getData(),
},
} as any, // future: nested custom types dont typecheck, fix if possible?
{ util0, rate0, util1, rate1, maxRate },
{ adjustmentFactor, util0, rate0, util1, rate1, maxRate },
loanFeeRate,
loanOriginationFeeRate,
maintAssetWeight,
@ -188,6 +189,7 @@ export class MangoClient {
tokenName: string,
oracle: PublicKey,
oracleConfFilter: number,
adjustmentFactor: number,
util0: number,
rate0: number,
util1: number,
@ -213,7 +215,7 @@ export class MangoClient {
val: I80F48.fromNumber(oracleConfFilter).getData(),
},
} as any, // future: nested custom types dont typecheck, fix if possible?
{ util0, rate0, util1, rate1, maxRate },
{ adjustmentFactor, util0, rate0, util1, rate1, maxRate },
loanFeeRate,
loanOriginationFeeRate,
maintAssetWeight,

View File

@ -3,7 +3,7 @@ export type MangoV4 = {
"name": "mango_v4",
"instructions": [
{
"name": "groupCreate",
"name": "createGroup",
"accounts": [
{
"name": "group",
@ -91,7 +91,7 @@ export type MangoV4 = {
]
},
{
"name": "groupClose",
"name": "closeGroup",
"accounts": [
{
"name": "group",
@ -568,7 +568,7 @@ export type MangoV4 = {
]
},
{
"name": "tokenUpdateIndex",
"name": "updateIndexAndRate",
"accounts": [
{
"name": "mintInfo",
@ -584,7 +584,7 @@ export type MangoV4 = {
"args": []
},
{
"name": "accountCreate",
"name": "createAccount",
"accounts": [
{
"name": "group",
@ -648,7 +648,7 @@ export type MangoV4 = {
]
},
{
"name": "accountEdit",
"name": "editAccount",
"accounts": [
{
"name": "group",
@ -682,7 +682,7 @@ export type MangoV4 = {
]
},
{
"name": "accountClose",
"name": "closeAccount",
"accounts": [
{
"name": "group",
@ -713,7 +713,7 @@ export type MangoV4 = {
"args": []
},
{
"name": "stubOracleCreate",
"name": "createStubOracle",
"accounts": [
{
"name": "group",
@ -776,7 +776,7 @@ export type MangoV4 = {
]
},
{
"name": "stubOracleClose",
"name": "closeStubOracle",
"accounts": [
{
"name": "group",
@ -807,7 +807,7 @@ export type MangoV4 = {
"args": []
},
{
"name": "stubOracleSet",
"name": "setStubOracle",
"accounts": [
{
"name": "group",
@ -2560,9 +2560,25 @@ export type MangoV4 = {
}
},
{
"name": "lastUpdated",
"name": "indexLastUpdated",
"type": "i64"
},
{
"name": "bankRateLastUpdated",
"type": "i64"
},
{
"name": "avgUtilization",
"type": {
"defined": "I80F48"
}
},
{
"name": "adjustmentFactor",
"type": {
"defined": "I80F48"
}
},
{
"name": "util0",
"type": {
@ -3362,6 +3378,10 @@ export type MangoV4 = {
{
"name": "maxRate",
"type": "f32"
},
{
"name": "adjustmentFactor",
"type": "f32"
}
]
}
@ -4399,6 +4419,41 @@ export type MangoV4 = {
}
]
},
{
"name": "UpdateRateLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "avgUtilization",
"type": "i128",
"index": false
},
{
"name": "rate0",
"type": "i128",
"index": false
},
{
"name": "rate1",
"type": "i128",
"index": false
},
{
"name": "maxRate",
"type": "i128",
"index": false
}
]
},
{
"name": "LiquidateTokenAndTokenLog",
"fields": [
@ -4539,7 +4594,7 @@ export const IDL: MangoV4 = {
"name": "mango_v4",
"instructions": [
{
"name": "groupCreate",
"name": "createGroup",
"accounts": [
{
"name": "group",
@ -4627,7 +4682,7 @@ export const IDL: MangoV4 = {
]
},
{
"name": "groupClose",
"name": "closeGroup",
"accounts": [
{
"name": "group",
@ -5104,7 +5159,7 @@ export const IDL: MangoV4 = {
]
},
{
"name": "tokenUpdateIndex",
"name": "updateIndexAndRate",
"accounts": [
{
"name": "mintInfo",
@ -5120,7 +5175,7 @@ export const IDL: MangoV4 = {
"args": []
},
{
"name": "accountCreate",
"name": "createAccount",
"accounts": [
{
"name": "group",
@ -5184,7 +5239,7 @@ export const IDL: MangoV4 = {
]
},
{
"name": "accountEdit",
"name": "editAccount",
"accounts": [
{
"name": "group",
@ -5218,7 +5273,7 @@ export const IDL: MangoV4 = {
]
},
{
"name": "accountClose",
"name": "closeAccount",
"accounts": [
{
"name": "group",
@ -5249,7 +5304,7 @@ export const IDL: MangoV4 = {
"args": []
},
{
"name": "stubOracleCreate",
"name": "createStubOracle",
"accounts": [
{
"name": "group",
@ -5312,7 +5367,7 @@ export const IDL: MangoV4 = {
]
},
{
"name": "stubOracleClose",
"name": "closeStubOracle",
"accounts": [
{
"name": "group",
@ -5343,7 +5398,7 @@ export const IDL: MangoV4 = {
"args": []
},
{
"name": "stubOracleSet",
"name": "setStubOracle",
"accounts": [
{
"name": "group",
@ -7096,9 +7151,25 @@ export const IDL: MangoV4 = {
}
},
{
"name": "lastUpdated",
"name": "indexLastUpdated",
"type": "i64"
},
{
"name": "bankRateLastUpdated",
"type": "i64"
},
{
"name": "avgUtilization",
"type": {
"defined": "I80F48"
}
},
{
"name": "adjustmentFactor",
"type": {
"defined": "I80F48"
}
},
{
"name": "util0",
"type": {
@ -7898,6 +7969,10 @@ export const IDL: MangoV4 = {
{
"name": "maxRate",
"type": "f32"
},
{
"name": "adjustmentFactor",
"type": "f32"
}
]
}
@ -8935,6 +9010,41 @@ export const IDL: MangoV4 = {
}
]
},
{
"name": "UpdateRateLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "avgUtilization",
"type": "i128",
"index": false
},
{
"name": "rate0",
"type": "i128",
"index": false
},
{
"name": "rate1",
"type": "i128",
"index": false
},
{
"name": "maxRate",
"type": "i128",
"index": false
}
]
},
{
"name": "LiquidateTokenAndTokenLog",
"fields": [

View File

@ -74,6 +74,7 @@ async function main() {
0.1,
1, // tokenIndex
'BTC',
0.01,
0.4,
0.07,
0.8,
@ -112,6 +113,7 @@ async function main() {
0.1,
0, // tokenIndex
'USDC',
0.01,
0.4,
0.07,
0.8,
@ -140,6 +142,7 @@ async function main() {
0.1,
2, // tokenIndex
'SOL',
0.01,
0.4,
0.07,
0.8,
@ -170,6 +173,7 @@ async function main() {
0.1,
3, // tokenIndex
'ORCA',
0.01,
0.4,
0.07,
0.8,
@ -266,6 +270,7 @@ async function main() {
'USDC',
btcDevnetOracle,
0.1,
0.01,
0.3,
0.08,
0.81,
@ -292,6 +297,7 @@ async function main() {
'USDC',
usdcDevnetOracle.publicKey,
0.1,
0.01,
0.4,
0.07,
0.8,

View File

@ -60,6 +60,7 @@ async function main() {
0.1,
0,
'BTC',
0.01,
0.4,
0.07,
0.8,
@ -100,6 +101,7 @@ async function main() {
0.1,
1,
'USDC',
0.01,
0.4,
0.07,
0.8,
@ -130,6 +132,7 @@ async function main() {
0.1,
2, // tokenIndex
'SOL',
0.01,
0.4,
0.07,
0.8,