Add collateral fees (#868)
- New permissionless instruction to regularly charge collateral fees - Bank and group configuration to set rate and interval - Keeper addition to call the instruction
This commit is contained in:
parent
ae833621ad
commit
e57dcdc2a9
|
@ -1,12 +1,27 @@
|
|||
use std::{collections::HashSet, sync::Arc, time::Duration, time::Instant};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use crate::MangoClient;
|
||||
use anyhow::Context;
|
||||
use itertools::Itertools;
|
||||
|
||||
use anchor_lang::{__private::bytemuck::cast_ref, solana_program};
|
||||
use anchor_lang::{__private::bytemuck::cast_ref, solana_program, Discriminator};
|
||||
use futures::Future;
|
||||
use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, TokenIndex};
|
||||
use mango_v4_client::PerpMarketContext;
|
||||
use mango_v4::{
|
||||
accounts_zerocopy::AccountReader,
|
||||
state::{
|
||||
EventQueue, EventType, FillEvent, Group, MangoAccount, MangoAccountValue, OutEvent,
|
||||
TokenIndex,
|
||||
},
|
||||
};
|
||||
use mango_v4_client::{
|
||||
account_fetcher_fetch_anchor_account, AccountFetcher, PerpMarketContext, PreparedInstructions,
|
||||
RpcAccountFetcher, TransactionBuilder,
|
||||
};
|
||||
use prometheus::{register_histogram, Encoder, Histogram, IntCounter, Registry};
|
||||
use solana_sdk::{
|
||||
instruction::{AccountMeta, Instruction},
|
||||
|
@ -81,6 +96,7 @@ pub async fn runner(
|
|||
interval_consume_events: u64,
|
||||
interval_update_funding: u64,
|
||||
interval_check_for_changes_and_abort: u64,
|
||||
interval_charge_collateral_fees: u64,
|
||||
extra_jobs: Vec<JoinHandle<()>>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let handles1 = mango_client
|
||||
|
@ -140,6 +156,7 @@ pub async fn runner(
|
|||
futures::future::join_all(handles1),
|
||||
futures::future::join_all(handles2),
|
||||
futures::future::join_all(handles3),
|
||||
loop_charge_collateral_fees(mango_client.clone(), interval_charge_collateral_fees),
|
||||
MangoClient::loop_check_for_context_changes_and_abort(
|
||||
mango_client.clone(),
|
||||
Duration::from_secs(interval_check_for_changes_and_abort),
|
||||
|
@ -412,3 +429,122 @@ pub async fn loop_update_funding(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn loop_charge_collateral_fees(mango_client: Arc<MangoClient>, interval: u64) {
|
||||
if interval == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a new one separate from the mango_client.account_fetcher,
|
||||
// because we don't want cached responses
|
||||
let fetcher = RpcAccountFetcher {
|
||||
rpc: mango_client.client.new_rpc_async(),
|
||||
};
|
||||
|
||||
let group: Group = account_fetcher_fetch_anchor_account(&fetcher, &mango_client.context.group)
|
||||
.await
|
||||
.unwrap();
|
||||
let collateral_fee_interval = group.collateral_fee_interval;
|
||||
|
||||
let mut interval = mango_v4_client::delay_interval(Duration::from_secs(interval));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
match charge_collateral_fees_inner(&mango_client, &fetcher, collateral_fee_interval).await {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
error!("charge_collateral_fees error: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn charge_collateral_fees_inner(
|
||||
client: &MangoClient,
|
||||
fetcher: &RpcAccountFetcher,
|
||||
collateral_fee_interval: u64,
|
||||
) -> anyhow::Result<()> {
|
||||
let mango_accounts = fetcher
|
||||
.fetch_program_accounts(&mango_v4::id(), MangoAccount::DISCRIMINATOR)
|
||||
.await
|
||||
.context("fetching mango accounts")?
|
||||
.into_iter()
|
||||
.filter_map(
|
||||
|(pk, data)| match MangoAccountValue::from_bytes(&data.data()[8..]) {
|
||||
Ok(acc) => Some((pk, acc)),
|
||||
Err(err) => {
|
||||
error!(pk=%pk, "charge_collateral_fees could not parse account: {err:?}");
|
||||
None
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let mut ix_to_send = Vec::new();
|
||||
let now_ts = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as u64;
|
||||
for (pk, account) in mango_accounts {
|
||||
let should_reset =
|
||||
collateral_fee_interval == 0 && account.fixed.last_collateral_fee_charge > 0;
|
||||
let should_charge = collateral_fee_interval > 0
|
||||
&& now_ts > account.fixed.last_collateral_fee_charge + collateral_fee_interval;
|
||||
if !(should_reset || should_charge) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ixs = match client
|
||||
.token_charge_collateral_fees_instruction((&pk, &account))
|
||||
.await
|
||||
{
|
||||
Ok(ixs) => ixs,
|
||||
Err(err) => {
|
||||
error!(pk=%pk, "charge_collateral_fees could not build instruction: {err:?}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
ix_to_send.push(ixs);
|
||||
}
|
||||
|
||||
send_batched_log_errors_no_confirm(
|
||||
client.transaction_builder().await?,
|
||||
&client.client,
|
||||
&ix_to_send,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Try to batch the instructions into transactions and send them
|
||||
async fn send_batched_log_errors_no_confirm(
|
||||
mut tx_builder: TransactionBuilder,
|
||||
client: &mango_v4_client::Client,
|
||||
ixs_list: &[PreparedInstructions],
|
||||
) {
|
||||
let mut current_batch = PreparedInstructions::new();
|
||||
for ixs in ixs_list {
|
||||
let previous_batch = current_batch.clone();
|
||||
current_batch.append(ixs.clone());
|
||||
|
||||
tx_builder.instructions = current_batch.clone().to_instructions();
|
||||
if !tx_builder.transaction_size().is_ok() {
|
||||
tx_builder.instructions = previous_batch.to_instructions();
|
||||
match tx_builder.send(client).await {
|
||||
Err(err) => error!("could not send transaction: {err:?}"),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
current_batch = ixs.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if !current_batch.is_empty() {
|
||||
tx_builder.instructions = current_batch.to_instructions();
|
||||
match tx_builder.send(client).await {
|
||||
Err(err) => error!("could not send transaction: {err:?}"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,9 @@ struct Cli {
|
|||
#[clap(long, env, default_value_t = 120)]
|
||||
interval_check_new_listings_and_abort: u64,
|
||||
|
||||
#[clap(long, env, default_value_t = 300)]
|
||||
interval_charge_collateral_fees: u64,
|
||||
|
||||
#[clap(long, env, default_value_t = 10)]
|
||||
timeout: u64,
|
||||
|
||||
|
@ -153,6 +156,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
cli.interval_consume_events,
|
||||
cli.interval_update_funding,
|
||||
cli.interval_check_new_listings_and_abort,
|
||||
cli.interval_charge_collateral_fees,
|
||||
prio_jobs,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -1487,6 +1487,43 @@ impl MangoClient {
|
|||
))
|
||||
}
|
||||
|
||||
pub async fn token_charge_collateral_fees_instruction(
|
||||
&self,
|
||||
account: (&Pubkey, &MangoAccountValue),
|
||||
) -> anyhow::Result<PreparedInstructions> {
|
||||
let (mut health_remaining_ams, health_cu) = self
|
||||
.derive_health_check_remaining_account_metas(account.1, vec![], vec![], vec![])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// The instruction requires mutable banks
|
||||
for am in &mut health_remaining_ams[0..account.1.active_token_positions().count()] {
|
||||
am.is_writable = true;
|
||||
}
|
||||
|
||||
let ix = Instruction {
|
||||
program_id: mango_v4::id(),
|
||||
accounts: {
|
||||
let mut ams = anchor_lang::ToAccountMetas::to_account_metas(
|
||||
&mango_v4::accounts::TokenChargeCollateralFees {
|
||||
group: self.group(),
|
||||
account: *account.0,
|
||||
},
|
||||
None,
|
||||
);
|
||||
ams.extend(health_remaining_ams);
|
||||
ams
|
||||
},
|
||||
data: anchor_lang::InstructionData::data(
|
||||
&mango_v4::instruction::TokenChargeCollateralFees {},
|
||||
),
|
||||
};
|
||||
Ok(PreparedInstructions::from_single(
|
||||
ix,
|
||||
self.instruction_cu(health_cu),
|
||||
))
|
||||
}
|
||||
|
||||
//
|
||||
// Liquidation
|
||||
//
|
||||
|
|
126
mango_v4.json
126
mango_v4.json
|
@ -277,6 +277,12 @@
|
|||
"type": {
|
||||
"option": "u16"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeeIntervalOpt",
|
||||
"type": {
|
||||
"option": "u64"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -635,6 +641,10 @@
|
|||
{
|
||||
"name": "disableAssetLiquidation",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "collateralFeePerDay",
|
||||
"type": "f32"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1051,6 +1061,12 @@
|
|||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeePerDayOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -5963,6 +5979,25 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tokenChargeCollateralFees",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group"
|
||||
]
|
||||
}
|
||||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "altSet",
|
||||
"accounts": [
|
||||
|
@ -7531,12 +7566,30 @@
|
|||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collectedCollateralFees",
|
||||
"docs": [
|
||||
"Collateral fees that have been collected (in native tokens)",
|
||||
"",
|
||||
"See also collected_fees_native and fees_withdrawn."
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeePerDay",
|
||||
"docs": [
|
||||
"The daily collateral fees rate for fully utilized collateral."
|
||||
],
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
1920
|
||||
1900
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -7664,12 +7717,28 @@
|
|||
],
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "padding2",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeeInterval",
|
||||
"docs": [
|
||||
"Intervals in which collateral fee is applied"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
1812
|
||||
1800
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -7791,12 +7860,27 @@
|
|||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "temporaryDelegate",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "temporaryDelegateExpiry",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lastCollateralFeeCharge",
|
||||
"docs": [
|
||||
"Time at which the last collateral fee was charged"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
200
|
||||
152
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -9566,12 +9650,16 @@
|
|||
"name": "temporaryDelegateExpiry",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lastCollateralFeeCharge",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
160
|
||||
152
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -13699,6 +13787,36 @@
|
|||
"index": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "TokenCollateralFeeLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "tokenIndex",
|
||||
"type": "u16",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "assetUsageFraction",
|
||||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"type": "i128",
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
|
|
|
@ -59,6 +59,7 @@ pub use stub_oracle_close::*;
|
|||
pub use stub_oracle_create::*;
|
||||
pub use stub_oracle_set::*;
|
||||
pub use token_add_bank::*;
|
||||
pub use token_charge_collateral_fees::*;
|
||||
pub use token_conditional_swap_cancel::*;
|
||||
pub use token_conditional_swap_create::*;
|
||||
pub use token_conditional_swap_start::*;
|
||||
|
@ -135,6 +136,7 @@ mod stub_oracle_close;
|
|||
mod stub_oracle_create;
|
||||
mod stub_oracle_set;
|
||||
mod token_add_bank;
|
||||
mod token_charge_collateral_fees;
|
||||
mod token_conditional_swap_cancel;
|
||||
mod token_conditional_swap_create;
|
||||
mod token_conditional_swap_start;
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
use crate::error::MangoError;
|
||||
use crate::state::*;
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
/// Charges collateral fees on an account
|
||||
#[derive(Accounts)]
|
||||
pub struct TokenChargeCollateralFees<'info> {
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen
|
||||
)]
|
||||
pub account: AccountLoader<'info, MangoAccountFixed>,
|
||||
}
|
|
@ -19,6 +19,7 @@ pub fn group_edit(
|
|||
mngo_token_index_opt: Option<TokenIndex>,
|
||||
buyback_fees_expiry_interval_opt: Option<u64>,
|
||||
allowed_fast_listings_per_interval_opt: Option<u16>,
|
||||
collateral_fee_interval_opt: Option<u64>,
|
||||
) -> Result<()> {
|
||||
let mut group = ctx.accounts.group.load_mut()?;
|
||||
|
||||
|
@ -116,5 +117,14 @@ pub fn group_edit(
|
|||
group.allowed_fast_listings_per_interval = allowed_fast_listings_per_interval;
|
||||
}
|
||||
|
||||
if let Some(collateral_fee_interval) = collateral_fee_interval_opt {
|
||||
msg!(
|
||||
"Collateral fee interval old {:?}, new {:?}",
|
||||
group.collateral_fee_interval,
|
||||
collateral_fee_interval
|
||||
);
|
||||
group.collateral_fee_interval = collateral_fee_interval;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ pub use stub_oracle_close::*;
|
|||
pub use stub_oracle_create::*;
|
||||
pub use stub_oracle_set::*;
|
||||
pub use token_add_bank::*;
|
||||
pub use token_charge_collateral_fees::*;
|
||||
pub use token_conditional_swap_cancel::*;
|
||||
pub use token_conditional_swap_create::*;
|
||||
pub use token_conditional_swap_start::*;
|
||||
|
@ -117,6 +118,7 @@ mod stub_oracle_close;
|
|||
mod stub_oracle_create;
|
||||
mod stub_oracle_set;
|
||||
mod token_add_bank;
|
||||
mod token_charge_collateral_fees;
|
||||
mod token_conditional_swap_cancel;
|
||||
mod token_conditional_swap_create;
|
||||
mod token_conditional_swap_start;
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
use crate::accounts_zerocopy::*;
|
||||
use crate::health::*;
|
||||
use crate::state::*;
|
||||
use anchor_lang::prelude::*;
|
||||
use fixed::types::I80F48;
|
||||
|
||||
use crate::accounts_ix::*;
|
||||
use crate::logs::{emit_stack, TokenCollateralFeeLog};
|
||||
|
||||
pub fn token_charge_collateral_fees(ctx: Context<TokenChargeCollateralFees>) -> Result<()> {
|
||||
let group = ctx.accounts.group.load()?;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
|
||||
if group.collateral_fee_interval == 0 {
|
||||
// By resetting, a new enabling of collateral fees will not immediately create a charge
|
||||
account.fixed.last_collateral_fee_charge = 0;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// When collateral fees are enabled the first time, don't immediately charge
|
||||
if account.fixed.last_collateral_fee_charge == 0 {
|
||||
account.fixed.last_collateral_fee_charge = now_ts;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Is the next fee-charging due?
|
||||
let last_charge_ts = account.fixed.last_collateral_fee_charge;
|
||||
if now_ts < last_charge_ts + group.collateral_fee_interval {
|
||||
return Ok(());
|
||||
}
|
||||
account.fixed.last_collateral_fee_charge = now_ts;
|
||||
|
||||
// Charge the user at most for 2x the interval. So if no one calls this for a long time
|
||||
// there won't be a huge charge based only on the end state.
|
||||
let charge_seconds = (now_ts - last_charge_ts).min(2 * group.collateral_fee_interval);
|
||||
|
||||
// The fees are configured in "interest per day" so we need to get the fraction of days
|
||||
// that has passed since the last update for scaling
|
||||
let inv_seconds_per_day = I80F48::from_num(1.157407407407e-5); // 1 / (24 * 60 * 60)
|
||||
let time_scaling = I80F48::from(charge_seconds) * inv_seconds_per_day;
|
||||
|
||||
let health_cache = {
|
||||
let retriever =
|
||||
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
|
||||
new_health_cache(&account.borrow(), &retriever, now_ts)?
|
||||
};
|
||||
|
||||
// We want to find the total asset health and total liab health, but don't want
|
||||
// to treat borrows that moved into open orders accounts as realized. Hence we
|
||||
// pretend all spot orders are closed and settled and add their funds back to
|
||||
// the token positions.
|
||||
let mut token_balances = health_cache.effective_token_balances(HealthType::Maint);
|
||||
for s3info in health_cache.serum3_infos.iter() {
|
||||
token_balances[s3info.base_info_index].spot_and_perp += s3info.reserved_base;
|
||||
token_balances[s3info.quote_info_index].spot_and_perp += s3info.reserved_quote;
|
||||
}
|
||||
|
||||
let mut total_liab_health = I80F48::ZERO;
|
||||
let mut total_asset_health = I80F48::ZERO;
|
||||
for (info, balance) in health_cache.token_infos.iter().zip(token_balances.iter()) {
|
||||
let health = info.health_contribution(HealthType::Maint, balance.spot_and_perp);
|
||||
if health.is_positive() {
|
||||
total_asset_health += health;
|
||||
} else {
|
||||
total_liab_health -= health;
|
||||
}
|
||||
}
|
||||
|
||||
// Users only pay for assets that are actively used to cover their liabilities.
|
||||
let asset_usage_scaling = (total_liab_health / total_asset_health)
|
||||
.max(I80F48::ZERO)
|
||||
.min(I80F48::ONE);
|
||||
|
||||
let scaling = asset_usage_scaling * time_scaling;
|
||||
|
||||
let token_position_count = account.active_token_positions().count();
|
||||
for bank_ai in &ctx.remaining_accounts[0..token_position_count] {
|
||||
let mut bank = bank_ai.load_mut::<Bank>()?;
|
||||
if bank.collateral_fee_per_day <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (token_position, raw_token_index) = account.token_position_mut(bank.token_index)?;
|
||||
let token_balance = token_position.native(&bank);
|
||||
if token_balance <= 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fee = token_balance * scaling * I80F48::from_num(bank.collateral_fee_per_day);
|
||||
assert!(fee <= token_balance);
|
||||
|
||||
let is_active = bank.withdraw_without_fee(token_position, fee, now_ts)?;
|
||||
if !is_active {
|
||||
account.deactivate_token_position_and_log(raw_token_index, ctx.accounts.account.key());
|
||||
}
|
||||
|
||||
bank.collected_fees_native += fee;
|
||||
bank.collected_collateral_fees += fee;
|
||||
|
||||
emit_stack(TokenCollateralFeeLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.account.key(),
|
||||
token_index: bank.token_index,
|
||||
fee: fee.to_bits(),
|
||||
asset_usage_fraction: asset_usage_scaling.to_bits(),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -54,6 +54,7 @@ pub fn token_edit(
|
|||
zero_util_rate: Option<f32>,
|
||||
platform_liquidation_fee: Option<f32>,
|
||||
disable_asset_liquidation_opt: Option<bool>,
|
||||
collateral_fee_per_day: Option<f32>,
|
||||
) -> Result<()> {
|
||||
let group = ctx.accounts.group.load()?;
|
||||
|
||||
|
@ -483,7 +484,21 @@ pub fn token_edit(
|
|||
platform_liquidation_fee
|
||||
);
|
||||
bank.platform_liquidation_fee = I80F48::from_num(platform_liquidation_fee);
|
||||
require_group_admin = true;
|
||||
if platform_liquidation_fee != 0.0 {
|
||||
require_group_admin = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(collateral_fee_per_day) = collateral_fee_per_day {
|
||||
msg!(
|
||||
"Collateral fee per day old {:?}, new {:?}",
|
||||
bank.collateral_fee_per_day,
|
||||
collateral_fee_per_day
|
||||
);
|
||||
bank.collateral_fee_per_day = collateral_fee_per_day;
|
||||
if collateral_fee_per_day != 0.0 {
|
||||
require_group_admin = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(disable_asset_liquidation) = disable_asset_liquidation_opt {
|
||||
|
|
|
@ -45,6 +45,7 @@ pub fn token_register(
|
|||
zero_util_rate: f32,
|
||||
platform_liquidation_fee: f32,
|
||||
disable_asset_liquidation: bool,
|
||||
collateral_fee_per_day: f32,
|
||||
) -> Result<()> {
|
||||
// Require token 0 to be in the insurance token
|
||||
if token_index == INSURANCE_TOKEN_INDEX {
|
||||
|
@ -129,7 +130,9 @@ pub fn token_register(
|
|||
zero_util_rate: I80F48::from_num(zero_util_rate),
|
||||
platform_liquidation_fee: I80F48::from_num(platform_liquidation_fee),
|
||||
collected_liquidation_fees: I80F48::ZERO,
|
||||
reserved: [0; 1920],
|
||||
collected_collateral_fees: I80F48::ZERO,
|
||||
collateral_fee_per_day,
|
||||
reserved: [0; 1900],
|
||||
};
|
||||
|
||||
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
|
||||
|
|
|
@ -108,7 +108,9 @@ pub fn token_register_trustless(
|
|||
deposit_limit: 0,
|
||||
zero_util_rate: I80F48::ZERO,
|
||||
collected_liquidation_fees: I80F48::ZERO,
|
||||
reserved: [0; 1920],
|
||||
collected_collateral_fees: I80F48::ZERO,
|
||||
collateral_fee_per_day: 0.0, // TODO
|
||||
reserved: [0; 1900],
|
||||
};
|
||||
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
|
||||
if let Ok(oracle_price) = bank.oracle_price(&OracleAccountInfos::from_reader(oracle_ref), None)
|
||||
|
|
|
@ -84,6 +84,7 @@ pub mod mango_v4 {
|
|||
mngo_token_index_opt: Option<TokenIndex>,
|
||||
buyback_fees_expiry_interval_opt: Option<u64>,
|
||||
allowed_fast_listings_per_interval_opt: Option<u16>,
|
||||
collateral_fee_interval_opt: Option<u64>,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::group_edit(
|
||||
|
@ -100,6 +101,7 @@ pub mod mango_v4 {
|
|||
mngo_token_index_opt,
|
||||
buyback_fees_expiry_interval_opt,
|
||||
allowed_fast_listings_per_interval_opt,
|
||||
collateral_fee_interval_opt,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -158,6 +160,7 @@ pub mod mango_v4 {
|
|||
zero_util_rate: f32,
|
||||
platform_liquidation_fee: f32,
|
||||
disable_asset_liquidation: bool,
|
||||
collateral_fee_per_day: f32,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::token_register(
|
||||
|
@ -192,6 +195,7 @@ pub mod mango_v4 {
|
|||
zero_util_rate,
|
||||
platform_liquidation_fee,
|
||||
disable_asset_liquidation,
|
||||
collateral_fee_per_day,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -248,6 +252,7 @@ pub mod mango_v4 {
|
|||
zero_util_rate_opt: Option<f32>,
|
||||
platform_liquidation_fee_opt: Option<f32>,
|
||||
disable_asset_liquidation_opt: Option<bool>,
|
||||
collateral_fee_per_day_opt: Option<f32>,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::token_edit(
|
||||
|
@ -291,6 +296,7 @@ pub mod mango_v4 {
|
|||
zero_util_rate_opt,
|
||||
platform_liquidation_fee_opt,
|
||||
disable_asset_liquidation_opt,
|
||||
collateral_fee_per_day_opt,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1609,6 +1615,12 @@ pub mod mango_v4 {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn token_charge_collateral_fees(ctx: Context<TokenChargeCollateralFees>) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::token_charge_collateral_fees(ctx)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn alt_set(ctx: Context<AltSet>, index: u8) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::alt_set(ctx, index)?;
|
||||
|
|
|
@ -779,3 +779,12 @@ pub struct TokenConditionalSwapStartLog {
|
|||
pub incentive_token_index: u16,
|
||||
pub incentive_amount: u64,
|
||||
}
|
||||
|
||||
#[event]
|
||||
pub struct TokenCollateralFeeLog {
|
||||
pub mango_group: Pubkey,
|
||||
pub mango_account: Pubkey,
|
||||
pub token_index: u16,
|
||||
pub asset_usage_fraction: i128,
|
||||
pub fee: i128,
|
||||
}
|
||||
|
|
|
@ -221,8 +221,16 @@ pub struct Bank {
|
|||
/// See also collected_fees_native and fees_withdrawn.
|
||||
pub collected_liquidation_fees: I80F48,
|
||||
|
||||
/// Collateral fees that have been collected (in native tokens)
|
||||
///
|
||||
/// See also collected_fees_native and fees_withdrawn.
|
||||
pub collected_collateral_fees: I80F48,
|
||||
|
||||
/// The daily collateral fees rate for fully utilized collateral.
|
||||
pub collateral_fee_per_day: f32,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 1920],
|
||||
pub reserved: [u8; 1900],
|
||||
}
|
||||
const_assert_eq!(
|
||||
size_of::<Bank>(),
|
||||
|
@ -259,8 +267,9 @@ const_assert_eq!(
|
|||
+ 16 * 3
|
||||
+ 32
|
||||
+ 8
|
||||
+ 16 * 3
|
||||
+ 1920
|
||||
+ 16 * 4
|
||||
+ 4
|
||||
+ 1900
|
||||
);
|
||||
const_assert_eq!(size_of::<Bank>(), 3064);
|
||||
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
||||
|
@ -304,6 +313,7 @@ impl Bank {
|
|||
indexed_borrows: I80F48::ZERO,
|
||||
collected_fees_native: I80F48::ZERO,
|
||||
collected_liquidation_fees: I80F48::ZERO,
|
||||
collected_collateral_fees: I80F48::ZERO,
|
||||
fees_withdrawn: 0,
|
||||
dust: I80F48::ZERO,
|
||||
flash_loan_approved_amount: 0,
|
||||
|
@ -368,7 +378,8 @@ impl Bank {
|
|||
deposit_limit: existing_bank.deposit_limit,
|
||||
zero_util_rate: existing_bank.zero_util_rate,
|
||||
platform_liquidation_fee: existing_bank.platform_liquidation_fee,
|
||||
reserved: [0; 1920],
|
||||
collateral_fee_per_day: existing_bank.collateral_fee_per_day,
|
||||
reserved: [0; 1900],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -405,6 +416,7 @@ impl Bank {
|
|||
require!(self.are_borrows_reduce_only(), MangoError::SomeError);
|
||||
require_eq!(self.maint_asset_weight, I80F48::ZERO);
|
||||
}
|
||||
require_gte!(self.collateral_fee_per_day, 0.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -98,11 +98,32 @@ pub struct Group {
|
|||
/// Number of fast listings that are allowed per interval
|
||||
pub allowed_fast_listings_per_interval: u16,
|
||||
|
||||
pub reserved: [u8; 1812],
|
||||
pub padding2: [u8; 4],
|
||||
|
||||
/// Intervals in which collateral fee is applied
|
||||
pub collateral_fee_interval: u64,
|
||||
|
||||
pub reserved: [u8; 1800],
|
||||
}
|
||||
const_assert_eq!(
|
||||
size_of::<Group>(),
|
||||
32 + 4 + 32 * 2 + 4 + 32 * 2 + 4 + 4 + 20 * 32 + 32 + 8 + 16 + 32 + 8 + 8 + 2 * 2 + 1812
|
||||
32 + 4
|
||||
+ 32 * 2
|
||||
+ 4
|
||||
+ 32 * 2
|
||||
+ 4
|
||||
+ 4
|
||||
+ 20 * 32
|
||||
+ 32
|
||||
+ 8
|
||||
+ 16
|
||||
+ 32
|
||||
+ 8
|
||||
+ 8
|
||||
+ 2 * 2
|
||||
+ 4
|
||||
+ 8
|
||||
+ 1800
|
||||
);
|
||||
const_assert_eq!(size_of::<Group>(), 2736);
|
||||
const_assert_eq!(size_of::<Group>() % 8, 0);
|
||||
|
|
|
@ -151,8 +151,14 @@ pub struct MangoAccount {
|
|||
/// Next id to use when adding a token condition swap
|
||||
pub next_token_conditional_swap_id: u64,
|
||||
|
||||
pub temporary_delegate: Pubkey,
|
||||
pub temporary_delegate_expiry: u64,
|
||||
|
||||
/// Time at which the last collateral fee was charged
|
||||
pub last_collateral_fee_charge: u64,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 200],
|
||||
pub reserved: [u8; 152],
|
||||
|
||||
// dynamic
|
||||
pub header_version: u8,
|
||||
|
@ -203,7 +209,10 @@ impl MangoAccount {
|
|||
buyback_fees_accrued_previous: 0,
|
||||
buyback_fees_expiry_timestamp: 0,
|
||||
next_token_conditional_swap_id: 0,
|
||||
reserved: [0; 200],
|
||||
temporary_delegate: Pubkey::default(),
|
||||
temporary_delegate_expiry: 0,
|
||||
last_collateral_fee_charge: 0,
|
||||
reserved: [0; 152],
|
||||
header_version: DEFAULT_MANGO_ACCOUNT_VERSION,
|
||||
padding3: Default::default(),
|
||||
padding4: Default::default(),
|
||||
|
@ -327,11 +336,12 @@ pub struct MangoAccountFixed {
|
|||
pub next_token_conditional_swap_id: u64,
|
||||
pub temporary_delegate: Pubkey,
|
||||
pub temporary_delegate_expiry: u64,
|
||||
pub reserved: [u8; 160],
|
||||
pub last_collateral_fee_charge: u64,
|
||||
pub reserved: [u8; 152],
|
||||
}
|
||||
const_assert_eq!(
|
||||
size_of::<MangoAccountFixed>(),
|
||||
32 * 4 + 8 + 8 * 8 + 32 + 8 + 160
|
||||
32 * 4 + 8 + 8 * 8 + 32 + 8 + 8 + 152
|
||||
);
|
||||
const_assert_eq!(size_of::<MangoAccountFixed>(), 400);
|
||||
const_assert_eq!(size_of::<MangoAccountFixed>() % 8, 0);
|
||||
|
|
|
@ -17,6 +17,7 @@ mod test_bankrupt_tokens;
|
|||
mod test_basic;
|
||||
mod test_benchmark;
|
||||
mod test_borrow_limits;
|
||||
mod test_collateral_fees;
|
||||
mod test_delegate;
|
||||
mod test_fees_buyback_with_mngo;
|
||||
mod test_force_close;
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_collateral_fees() -> Result<(), TransportError> {
|
||||
let context = TestContext::new().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
let admin = TestKeypair::new();
|
||||
let owner = context.users[0].key;
|
||||
let payer = context.users[1].key;
|
||||
let mints = &context.mints[0..2];
|
||||
|
||||
let mango_setup::GroupWithTokens { group, tokens, .. } = mango_setup::GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints: mints.to_vec(),
|
||||
..mango_setup::GroupWithTokensConfig::default()
|
||||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
|
||||
// fund the vaults to allow borrowing
|
||||
create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
0,
|
||||
&context.users[1],
|
||||
mints,
|
||||
1_000_000,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
let account = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
1,
|
||||
&context.users[1],
|
||||
&mints[0..1],
|
||||
1_500, // maint: 0.8 * 1500 = 1200
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
let hour = 60 * 60;
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
GroupEdit {
|
||||
group,
|
||||
admin,
|
||||
options: mango_v4::instruction::GroupEdit {
|
||||
collateral_fee_interval_opt: Some(6 * hour),
|
||||
..group_edit_instruction_default()
|
||||
},
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
TokenEdit {
|
||||
group,
|
||||
admin,
|
||||
mint: mints[0].pubkey,
|
||||
fallback_oracle: Pubkey::default(),
|
||||
options: mango_v4::instruction::TokenEdit {
|
||||
collateral_fee_per_day_opt: Some(0.1),
|
||||
..token_edit_instruction_default()
|
||||
},
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
TokenEdit {
|
||||
group,
|
||||
admin,
|
||||
mint: mints[1].pubkey,
|
||||
fallback_oracle: Pubkey::default(),
|
||||
options: mango_v4::instruction::TokenEdit {
|
||||
loan_origination_fee_rate_opt: Some(0.0),
|
||||
..token_edit_instruction_default()
|
||||
},
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// TEST: Without borrows, charging collateral fees has no effect
|
||||
//
|
||||
|
||||
send_tx(solana, TokenChargeCollateralFeesInstruction { account })
|
||||
.await
|
||||
.unwrap();
|
||||
let mut last_time = solana.clock_timestamp().await;
|
||||
// no effect
|
||||
assert_eq!(
|
||||
account_position(solana, account, tokens[0].bank).await,
|
||||
1_500
|
||||
);
|
||||
|
||||
//
|
||||
// TEST: With borrows, there's an effect depending on the time that has passed
|
||||
//
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 500, // maint: -1.2 * 500 = -600 (half of 1200)
|
||||
allow_borrow: true,
|
||||
account,
|
||||
owner,
|
||||
token_account: context.users[1].token_accounts[1],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
solana.set_clock_timestamp(last_time + 9 * hour).await;
|
||||
|
||||
send_tx(solana, TokenChargeCollateralFeesInstruction { account })
|
||||
.await
|
||||
.unwrap();
|
||||
last_time = solana.clock_timestamp().await;
|
||||
assert!(assert_equal_f64_f64(
|
||||
account_position_f64(solana, account, tokens[0].bank).await,
|
||||
1500.0 * (1.0 - 0.1 * (9.0 / 24.0) * (600.0 / 1200.0)),
|
||||
0.01
|
||||
));
|
||||
let last_balance = account_position_f64(solana, account, tokens[0].bank).await;
|
||||
|
||||
//
|
||||
// TEST: More borrows
|
||||
//
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 100, // maint: -1.2 * 600 = -720
|
||||
allow_borrow: true,
|
||||
account,
|
||||
owner,
|
||||
token_account: context.users[1].token_accounts[1],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
solana.set_clock_timestamp(last_time + 7 * hour).await;
|
||||
|
||||
send_tx(solana, TokenChargeCollateralFeesInstruction { account })
|
||||
.await
|
||||
.unwrap();
|
||||
//last_time = solana.clock_timestamp().await;
|
||||
assert!(assert_equal_f64_f64(
|
||||
account_position_f64(solana, account, tokens[0].bank).await,
|
||||
last_balance * (1.0 - 0.1 * (7.0 / 24.0) * (720.0 / (last_balance * 0.8))),
|
||||
0.01
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1078,6 +1078,7 @@ impl ClientInstruction for TokenRegisterInstruction {
|
|||
zero_util_rate: 0.0,
|
||||
platform_liquidation_fee: self.platform_liquidation_fee,
|
||||
disable_asset_liquidation: false,
|
||||
collateral_fee_per_day: 0.0,
|
||||
};
|
||||
|
||||
let bank = Pubkey::find_program_address(
|
||||
|
@ -1326,6 +1327,7 @@ pub fn token_edit_instruction_default() -> mango_v4::instruction::TokenEdit {
|
|||
zero_util_rate_opt: None,
|
||||
platform_liquidation_fee_opt: None,
|
||||
disable_asset_liquidation_opt: None,
|
||||
collateral_fee_per_day_opt: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1844,6 +1846,7 @@ pub fn group_edit_instruction_default() -> mango_v4::instruction::GroupEdit {
|
|||
mngo_token_index_opt: None,
|
||||
buyback_fees_expiry_interval_opt: None,
|
||||
allowed_fast_listings_per_interval_opt: None,
|
||||
collateral_fee_interval_opt: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5038,3 +5041,48 @@ impl ClientInstruction for TokenConditionalSwapStartInstruction {
|
|||
vec![self.liqor_owner]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TokenChargeCollateralFeesInstruction {
|
||||
pub account: Pubkey,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for TokenChargeCollateralFeesInstruction {
|
||||
type Accounts = mango_v4::accounts::TokenChargeCollateralFees;
|
||||
type Instruction = mango_v4::instruction::TokenChargeCollateralFees;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
|
||||
let account = account_loader
|
||||
.load_mango_account(&self.account)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let instruction = Self::Instruction {};
|
||||
|
||||
let health_check_metas = derive_health_check_remaining_account_metas(
|
||||
&account_loader,
|
||||
&account,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: account.fixed.group,
|
||||
account: self.account,
|
||||
};
|
||||
|
||||
let mut instruction = make_instruction(program_id, &accounts, &instruction);
|
||||
instruction.accounts.extend(health_check_metas.into_iter());
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<TestKeypair> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ export class Bank implements BankForHealth {
|
|||
public zeroUtilRate: I80F48;
|
||||
public platformLiquidationFee: I80F48;
|
||||
public collectedLiquidationFees: I80F48;
|
||||
public collectedCollateralFees: I80F48;
|
||||
|
||||
static from(
|
||||
publicKey: PublicKey,
|
||||
|
@ -148,6 +149,8 @@ export class Bank implements BankForHealth {
|
|||
zeroUtilRate: I80F48Dto;
|
||||
platformLiquidationFee: I80F48Dto;
|
||||
collectedLiquidationFees: I80F48Dto;
|
||||
collectedCollateralFees: I80F48Dto;
|
||||
collateralFeePerDay: number;
|
||||
},
|
||||
): Bank {
|
||||
return new Bank(
|
||||
|
@ -213,6 +216,8 @@ export class Bank implements BankForHealth {
|
|||
obj.platformLiquidationFee,
|
||||
obj.collectedLiquidationFees,
|
||||
obj.disableAssetLiquidation == 0,
|
||||
obj.collectedCollateralFees,
|
||||
obj.collateralFeePerDay,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -279,6 +284,8 @@ export class Bank implements BankForHealth {
|
|||
platformLiquidationFee: I80F48Dto,
|
||||
collectedLiquidationFees: I80F48Dto,
|
||||
public allowAssetLiquidation: boolean,
|
||||
collectedCollateralFees: I80F48Dto,
|
||||
public collateralFeePerDay: number,
|
||||
) {
|
||||
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
||||
this.oracleConfig = {
|
||||
|
@ -311,6 +318,7 @@ export class Bank implements BankForHealth {
|
|||
this.zeroUtilRate = I80F48.from(zeroUtilRate);
|
||||
this.platformLiquidationFee = I80F48.from(platformLiquidationFee);
|
||||
this.collectedLiquidationFees = I80F48.from(collectedLiquidationFees);
|
||||
this.collectedCollateralFees = I80F48.from(collectedCollateralFees);
|
||||
this._price = undefined;
|
||||
this._uiPrice = undefined;
|
||||
this._oracleLastUpdatedSlot = undefined;
|
||||
|
|
|
@ -50,6 +50,7 @@ export class Group {
|
|||
fastListingIntervalStart: BN;
|
||||
fastListingsInInterval: number;
|
||||
allowedFastListingsPerInterval: number;
|
||||
collateralFeeInterval: BN;
|
||||
},
|
||||
): Group {
|
||||
return new Group(
|
||||
|
@ -74,6 +75,7 @@ export class Group {
|
|||
obj.fastListingIntervalStart,
|
||||
obj.fastListingsInInterval,
|
||||
obj.allowedFastListingsPerInterval,
|
||||
obj.collateralFeeInterval,
|
||||
[], // addressLookupTablesList
|
||||
new Map(), // banksMapByName
|
||||
new Map(), // banksMapByMint
|
||||
|
@ -113,6 +115,7 @@ export class Group {
|
|||
public fastListingIntervalStart: BN,
|
||||
public fastListingsInInterval: number,
|
||||
public allowedFastListingsPerInterval: number,
|
||||
public collateralFeeInterval: BN,
|
||||
public addressLookupTablesList: AddressLookupTableAccount[],
|
||||
public banksMapByName: Map<string, Bank[]>,
|
||||
public banksMapByMint: Map<string, Bank[]>,
|
||||
|
|
|
@ -304,6 +304,7 @@ export class MangoClient {
|
|||
feesMngoTokenIndex?: TokenIndex,
|
||||
feesExpiryInterval?: BN,
|
||||
allowedFastListingsPerInterval?: number,
|
||||
collateralFeeInterval?: BN,
|
||||
): Promise<MangoSignatureStatus> {
|
||||
const ix = await this.program.methods
|
||||
.groupEdit(
|
||||
|
@ -319,6 +320,7 @@ export class MangoClient {
|
|||
feesMngoTokenIndex ?? null,
|
||||
feesExpiryInterval ?? null,
|
||||
allowedFastListingsPerInterval ?? null,
|
||||
collateralFeeInterval ?? null,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -462,6 +464,7 @@ export class MangoClient {
|
|||
params.zeroUtilRate,
|
||||
params.platformLiquidationFee,
|
||||
params.disableAssetLiquidation,
|
||||
params.collateralFeePerDay,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -550,6 +553,7 @@ export class MangoClient {
|
|||
params.zeroUtilRate,
|
||||
params.platformLiquidationFee,
|
||||
params.disableAssetLiquidation,
|
||||
params.collateralFeePerDay,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
|
|
@ -31,6 +31,7 @@ export interface TokenRegisterParams {
|
|||
zeroUtilRate: number;
|
||||
platformLiquidationFee: number;
|
||||
disableAssetLiquidation: boolean;
|
||||
collateralFeePerDay: number;
|
||||
}
|
||||
|
||||
export const DefaultTokenRegisterParams: TokenRegisterParams = {
|
||||
|
@ -72,6 +73,7 @@ export const DefaultTokenRegisterParams: TokenRegisterParams = {
|
|||
zeroUtilRate: 0.0,
|
||||
platformLiquidationFee: 0.0,
|
||||
disableAssetLiquidation: false,
|
||||
collateralFeePerDay: 0.0,
|
||||
};
|
||||
|
||||
export interface TokenEditParams {
|
||||
|
@ -114,6 +116,7 @@ export interface TokenEditParams {
|
|||
zeroUtilRate: number | null;
|
||||
platformLiquidationFee: number | null;
|
||||
disableAssetLiquidation: boolean | null;
|
||||
collateralFeePerDay: number | null;
|
||||
}
|
||||
|
||||
export const NullTokenEditParams: TokenEditParams = {
|
||||
|
@ -156,6 +159,7 @@ export const NullTokenEditParams: TokenEditParams = {
|
|||
zeroUtilRate: null,
|
||||
platformLiquidationFee: null,
|
||||
disableAssetLiquidation: null,
|
||||
collateralFeePerDay: null,
|
||||
};
|
||||
|
||||
export interface PerpEditParams {
|
||||
|
|
|
@ -277,6 +277,12 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"option": "u16"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeeIntervalOpt",
|
||||
"type": {
|
||||
"option": "u64"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -635,6 +641,10 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "disableAssetLiquidation",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "collateralFeePerDay",
|
||||
"type": "f32"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1051,6 +1061,12 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeePerDayOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -5963,6 +5979,25 @@ export type MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tokenChargeCollateralFees",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group"
|
||||
]
|
||||
}
|
||||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "altSet",
|
||||
"accounts": [
|
||||
|
@ -7531,12 +7566,30 @@ export type MangoV4 = {
|
|||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collectedCollateralFees",
|
||||
"docs": [
|
||||
"Collateral fees that have been collected (in native tokens)",
|
||||
"",
|
||||
"See also collected_fees_native and fees_withdrawn."
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeePerDay",
|
||||
"docs": [
|
||||
"The daily collateral fees rate for fully utilized collateral."
|
||||
],
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
1920
|
||||
1900
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -7664,12 +7717,28 @@ export type MangoV4 = {
|
|||
],
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "padding2",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeeInterval",
|
||||
"docs": [
|
||||
"Intervals in which collateral fee is applied"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
1812
|
||||
1800
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -7791,12 +7860,27 @@ export type MangoV4 = {
|
|||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "temporaryDelegate",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "temporaryDelegateExpiry",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lastCollateralFeeCharge",
|
||||
"docs": [
|
||||
"Time at which the last collateral fee was charged"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
200
|
||||
152
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -9566,12 +9650,16 @@ export type MangoV4 = {
|
|||
"name": "temporaryDelegateExpiry",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lastCollateralFeeCharge",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
160
|
||||
152
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -13699,6 +13787,36 @@ export type MangoV4 = {
|
|||
"index": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "TokenCollateralFeeLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "tokenIndex",
|
||||
"type": "u16",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "assetUsageFraction",
|
||||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"type": "i128",
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
|
@ -14334,6 +14452,12 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"option": "u16"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeeIntervalOpt",
|
||||
"type": {
|
||||
"option": "u64"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -14692,6 +14816,10 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "disableAssetLiquidation",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "collateralFeePerDay",
|
||||
"type": "f32"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -15108,6 +15236,12 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeePerDayOpt",
|
||||
"type": {
|
||||
"option": "f32"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -20020,6 +20154,25 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tokenChargeCollateralFees",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false,
|
||||
"relations": [
|
||||
"group"
|
||||
]
|
||||
}
|
||||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "altSet",
|
||||
"accounts": [
|
||||
|
@ -21588,12 +21741,30 @@ export const IDL: MangoV4 = {
|
|||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collectedCollateralFees",
|
||||
"docs": [
|
||||
"Collateral fees that have been collected (in native tokens)",
|
||||
"",
|
||||
"See also collected_fees_native and fees_withdrawn."
|
||||
],
|
||||
"type": {
|
||||
"defined": "I80F48"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeePerDay",
|
||||
"docs": [
|
||||
"The daily collateral fees rate for fully utilized collateral."
|
||||
],
|
||||
"type": "f32"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
1920
|
||||
1900
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -21721,12 +21892,28 @@ export const IDL: MangoV4 = {
|
|||
],
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "padding2",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "collateralFeeInterval",
|
||||
"docs": [
|
||||
"Intervals in which collateral fee is applied"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
1812
|
||||
1800
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -21848,12 +22035,27 @@ export const IDL: MangoV4 = {
|
|||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "temporaryDelegate",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "temporaryDelegateExpiry",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lastCollateralFeeCharge",
|
||||
"docs": [
|
||||
"Time at which the last collateral fee was charged"
|
||||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
200
|
||||
152
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -23623,12 +23825,16 @@ export const IDL: MangoV4 = {
|
|||
"name": "temporaryDelegateExpiry",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "lastCollateralFeeCharge",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
160
|
||||
152
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -27756,6 +27962,36 @@ export const IDL: MangoV4 = {
|
|||
"index": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "TokenCollateralFeeLog",
|
||||
"fields": [
|
||||
{
|
||||
"name": "mangoGroup",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "mangoAccount",
|
||||
"type": "publicKey",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "tokenIndex",
|
||||
"type": "u16",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "assetUsageFraction",
|
||||
"type": "i128",
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "fee",
|
||||
"type": "i128",
|
||||
"index": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"errors": [
|
||||
|
|
Loading…
Reference in New Issue