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:
parent
f132f30874
commit
ef7d2862da
|
@ -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(())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
|
@ -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<()> {
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(())
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue