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 crate::MangoClient;
|
||||||
|
use anyhow::Context;
|
||||||
use itertools::Itertools;
|
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 futures::Future;
|
||||||
use mango_v4::state::{EventQueue, EventType, FillEvent, OutEvent, TokenIndex};
|
use mango_v4::{
|
||||||
use mango_v4_client::PerpMarketContext;
|
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 prometheus::{register_histogram, Encoder, Histogram, IntCounter, Registry};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
instruction::{AccountMeta, Instruction},
|
instruction::{AccountMeta, Instruction},
|
||||||
|
@ -81,6 +96,7 @@ pub async fn runner(
|
||||||
interval_consume_events: u64,
|
interval_consume_events: u64,
|
||||||
interval_update_funding: u64,
|
interval_update_funding: u64,
|
||||||
interval_check_for_changes_and_abort: u64,
|
interval_check_for_changes_and_abort: u64,
|
||||||
|
interval_charge_collateral_fees: u64,
|
||||||
extra_jobs: Vec<JoinHandle<()>>,
|
extra_jobs: Vec<JoinHandle<()>>,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
let handles1 = mango_client
|
let handles1 = mango_client
|
||||||
|
@ -140,6 +156,7 @@ pub async fn runner(
|
||||||
futures::future::join_all(handles1),
|
futures::future::join_all(handles1),
|
||||||
futures::future::join_all(handles2),
|
futures::future::join_all(handles2),
|
||||||
futures::future::join_all(handles3),
|
futures::future::join_all(handles3),
|
||||||
|
loop_charge_collateral_fees(mango_client.clone(), interval_charge_collateral_fees),
|
||||||
MangoClient::loop_check_for_context_changes_and_abort(
|
MangoClient::loop_check_for_context_changes_and_abort(
|
||||||
mango_client.clone(),
|
mango_client.clone(),
|
||||||
Duration::from_secs(interval_check_for_changes_and_abort),
|
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)]
|
#[clap(long, env, default_value_t = 120)]
|
||||||
interval_check_new_listings_and_abort: u64,
|
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)]
|
#[clap(long, env, default_value_t = 10)]
|
||||||
timeout: u64,
|
timeout: u64,
|
||||||
|
|
||||||
|
@ -153,6 +156,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
cli.interval_consume_events,
|
cli.interval_consume_events,
|
||||||
cli.interval_update_funding,
|
cli.interval_update_funding,
|
||||||
cli.interval_check_new_listings_and_abort,
|
cli.interval_check_new_listings_and_abort,
|
||||||
|
cli.interval_charge_collateral_fees,
|
||||||
prio_jobs,
|
prio_jobs,
|
||||||
)
|
)
|
||||||
.await
|
.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
|
// Liquidation
|
||||||
//
|
//
|
||||||
|
|
126
mango_v4.json
126
mango_v4.json
|
@ -277,6 +277,12 @@
|
||||||
"type": {
|
"type": {
|
||||||
"option": "u16"
|
"option": "u16"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collateralFeeIntervalOpt",
|
||||||
|
"type": {
|
||||||
|
"option": "u64"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -635,6 +641,10 @@
|
||||||
{
|
{
|
||||||
"name": "disableAssetLiquidation",
|
"name": "disableAssetLiquidation",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collateralFeePerDay",
|
||||||
|
"type": "f32"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1051,6 +1061,12 @@
|
||||||
"type": {
|
"type": {
|
||||||
"option": "bool"
|
"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",
|
"name": "altSet",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
|
@ -7531,12 +7566,30 @@
|
||||||
"defined": "I80F48"
|
"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",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
1920
|
1900
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7664,12 +7717,28 @@
|
||||||
],
|
],
|
||||||
"type": "u16"
|
"type": "u16"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "padding2",
|
||||||
|
"type": {
|
||||||
|
"array": [
|
||||||
|
"u8",
|
||||||
|
4
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collateralFeeInterval",
|
||||||
|
"docs": [
|
||||||
|
"Intervals in which collateral fee is applied"
|
||||||
|
],
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reserved",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
1812
|
1800
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7791,12 +7860,27 @@
|
||||||
],
|
],
|
||||||
"type": "u64"
|
"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",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
200
|
152
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -9566,12 +9650,16 @@
|
||||||
"name": "temporaryDelegateExpiry",
|
"name": "temporaryDelegateExpiry",
|
||||||
"type": "u64"
|
"type": "u64"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "lastCollateralFeeCharge",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reserved",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
160
|
152
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13699,6 +13787,36 @@
|
||||||
"index": false
|
"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": [
|
"errors": [
|
||||||
|
|
|
@ -59,6 +59,7 @@ pub use stub_oracle_close::*;
|
||||||
pub use stub_oracle_create::*;
|
pub use stub_oracle_create::*;
|
||||||
pub use stub_oracle_set::*;
|
pub use stub_oracle_set::*;
|
||||||
pub use token_add_bank::*;
|
pub use token_add_bank::*;
|
||||||
|
pub use token_charge_collateral_fees::*;
|
||||||
pub use token_conditional_swap_cancel::*;
|
pub use token_conditional_swap_cancel::*;
|
||||||
pub use token_conditional_swap_create::*;
|
pub use token_conditional_swap_create::*;
|
||||||
pub use token_conditional_swap_start::*;
|
pub use token_conditional_swap_start::*;
|
||||||
|
@ -135,6 +136,7 @@ mod stub_oracle_close;
|
||||||
mod stub_oracle_create;
|
mod stub_oracle_create;
|
||||||
mod stub_oracle_set;
|
mod stub_oracle_set;
|
||||||
mod token_add_bank;
|
mod token_add_bank;
|
||||||
|
mod token_charge_collateral_fees;
|
||||||
mod token_conditional_swap_cancel;
|
mod token_conditional_swap_cancel;
|
||||||
mod token_conditional_swap_create;
|
mod token_conditional_swap_create;
|
||||||
mod token_conditional_swap_start;
|
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>,
|
mngo_token_index_opt: Option<TokenIndex>,
|
||||||
buyback_fees_expiry_interval_opt: Option<u64>,
|
buyback_fees_expiry_interval_opt: Option<u64>,
|
||||||
allowed_fast_listings_per_interval_opt: Option<u16>,
|
allowed_fast_listings_per_interval_opt: Option<u16>,
|
||||||
|
collateral_fee_interval_opt: Option<u64>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut group = ctx.accounts.group.load_mut()?;
|
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;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ pub use stub_oracle_close::*;
|
||||||
pub use stub_oracle_create::*;
|
pub use stub_oracle_create::*;
|
||||||
pub use stub_oracle_set::*;
|
pub use stub_oracle_set::*;
|
||||||
pub use token_add_bank::*;
|
pub use token_add_bank::*;
|
||||||
|
pub use token_charge_collateral_fees::*;
|
||||||
pub use token_conditional_swap_cancel::*;
|
pub use token_conditional_swap_cancel::*;
|
||||||
pub use token_conditional_swap_create::*;
|
pub use token_conditional_swap_create::*;
|
||||||
pub use token_conditional_swap_start::*;
|
pub use token_conditional_swap_start::*;
|
||||||
|
@ -117,6 +118,7 @@ mod stub_oracle_close;
|
||||||
mod stub_oracle_create;
|
mod stub_oracle_create;
|
||||||
mod stub_oracle_set;
|
mod stub_oracle_set;
|
||||||
mod token_add_bank;
|
mod token_add_bank;
|
||||||
|
mod token_charge_collateral_fees;
|
||||||
mod token_conditional_swap_cancel;
|
mod token_conditional_swap_cancel;
|
||||||
mod token_conditional_swap_create;
|
mod token_conditional_swap_create;
|
||||||
mod token_conditional_swap_start;
|
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>,
|
zero_util_rate: Option<f32>,
|
||||||
platform_liquidation_fee: Option<f32>,
|
platform_liquidation_fee: Option<f32>,
|
||||||
disable_asset_liquidation_opt: Option<bool>,
|
disable_asset_liquidation_opt: Option<bool>,
|
||||||
|
collateral_fee_per_day: Option<f32>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let group = ctx.accounts.group.load()?;
|
let group = ctx.accounts.group.load()?;
|
||||||
|
|
||||||
|
@ -483,7 +484,21 @@ pub fn token_edit(
|
||||||
platform_liquidation_fee
|
platform_liquidation_fee
|
||||||
);
|
);
|
||||||
bank.platform_liquidation_fee = I80F48::from_num(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 {
|
if let Some(disable_asset_liquidation) = disable_asset_liquidation_opt {
|
||||||
|
|
|
@ -45,6 +45,7 @@ pub fn token_register(
|
||||||
zero_util_rate: f32,
|
zero_util_rate: f32,
|
||||||
platform_liquidation_fee: f32,
|
platform_liquidation_fee: f32,
|
||||||
disable_asset_liquidation: bool,
|
disable_asset_liquidation: bool,
|
||||||
|
collateral_fee_per_day: f32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Require token 0 to be in the insurance token
|
// Require token 0 to be in the insurance token
|
||||||
if token_index == INSURANCE_TOKEN_INDEX {
|
if token_index == INSURANCE_TOKEN_INDEX {
|
||||||
|
@ -129,7 +130,9 @@ pub fn token_register(
|
||||||
zero_util_rate: I80F48::from_num(zero_util_rate),
|
zero_util_rate: I80F48::from_num(zero_util_rate),
|
||||||
platform_liquidation_fee: I80F48::from_num(platform_liquidation_fee),
|
platform_liquidation_fee: I80F48::from_num(platform_liquidation_fee),
|
||||||
collected_liquidation_fees: I80F48::ZERO,
|
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())?;
|
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
|
||||||
|
|
|
@ -108,7 +108,9 @@ pub fn token_register_trustless(
|
||||||
deposit_limit: 0,
|
deposit_limit: 0,
|
||||||
zero_util_rate: I80F48::ZERO,
|
zero_util_rate: I80F48::ZERO,
|
||||||
collected_liquidation_fees: 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())?;
|
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
|
||||||
if let Ok(oracle_price) = bank.oracle_price(&OracleAccountInfos::from_reader(oracle_ref), None)
|
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>,
|
mngo_token_index_opt: Option<TokenIndex>,
|
||||||
buyback_fees_expiry_interval_opt: Option<u64>,
|
buyback_fees_expiry_interval_opt: Option<u64>,
|
||||||
allowed_fast_listings_per_interval_opt: Option<u16>,
|
allowed_fast_listings_per_interval_opt: Option<u16>,
|
||||||
|
collateral_fee_interval_opt: Option<u64>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
#[cfg(feature = "enable-gpl")]
|
#[cfg(feature = "enable-gpl")]
|
||||||
instructions::group_edit(
|
instructions::group_edit(
|
||||||
|
@ -100,6 +101,7 @@ pub mod mango_v4 {
|
||||||
mngo_token_index_opt,
|
mngo_token_index_opt,
|
||||||
buyback_fees_expiry_interval_opt,
|
buyback_fees_expiry_interval_opt,
|
||||||
allowed_fast_listings_per_interval_opt,
|
allowed_fast_listings_per_interval_opt,
|
||||||
|
collateral_fee_interval_opt,
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -158,6 +160,7 @@ pub mod mango_v4 {
|
||||||
zero_util_rate: f32,
|
zero_util_rate: f32,
|
||||||
platform_liquidation_fee: f32,
|
platform_liquidation_fee: f32,
|
||||||
disable_asset_liquidation: bool,
|
disable_asset_liquidation: bool,
|
||||||
|
collateral_fee_per_day: f32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
#[cfg(feature = "enable-gpl")]
|
#[cfg(feature = "enable-gpl")]
|
||||||
instructions::token_register(
|
instructions::token_register(
|
||||||
|
@ -192,6 +195,7 @@ pub mod mango_v4 {
|
||||||
zero_util_rate,
|
zero_util_rate,
|
||||||
platform_liquidation_fee,
|
platform_liquidation_fee,
|
||||||
disable_asset_liquidation,
|
disable_asset_liquidation,
|
||||||
|
collateral_fee_per_day,
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -248,6 +252,7 @@ pub mod mango_v4 {
|
||||||
zero_util_rate_opt: Option<f32>,
|
zero_util_rate_opt: Option<f32>,
|
||||||
platform_liquidation_fee_opt: Option<f32>,
|
platform_liquidation_fee_opt: Option<f32>,
|
||||||
disable_asset_liquidation_opt: Option<bool>,
|
disable_asset_liquidation_opt: Option<bool>,
|
||||||
|
collateral_fee_per_day_opt: Option<f32>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
#[cfg(feature = "enable-gpl")]
|
#[cfg(feature = "enable-gpl")]
|
||||||
instructions::token_edit(
|
instructions::token_edit(
|
||||||
|
@ -291,6 +296,7 @@ pub mod mango_v4 {
|
||||||
zero_util_rate_opt,
|
zero_util_rate_opt,
|
||||||
platform_liquidation_fee_opt,
|
platform_liquidation_fee_opt,
|
||||||
disable_asset_liquidation_opt,
|
disable_asset_liquidation_opt,
|
||||||
|
collateral_fee_per_day_opt,
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1609,6 +1615,12 @@ pub mod mango_v4 {
|
||||||
Ok(())
|
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<()> {
|
pub fn alt_set(ctx: Context<AltSet>, index: u8) -> Result<()> {
|
||||||
#[cfg(feature = "enable-gpl")]
|
#[cfg(feature = "enable-gpl")]
|
||||||
instructions::alt_set(ctx, index)?;
|
instructions::alt_set(ctx, index)?;
|
||||||
|
|
|
@ -779,3 +779,12 @@ pub struct TokenConditionalSwapStartLog {
|
||||||
pub incentive_token_index: u16,
|
pub incentive_token_index: u16,
|
||||||
pub incentive_amount: u64,
|
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.
|
/// See also collected_fees_native and fees_withdrawn.
|
||||||
pub collected_liquidation_fees: I80F48,
|
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")]
|
#[derivative(Debug = "ignore")]
|
||||||
pub reserved: [u8; 1920],
|
pub reserved: [u8; 1900],
|
||||||
}
|
}
|
||||||
const_assert_eq!(
|
const_assert_eq!(
|
||||||
size_of::<Bank>(),
|
size_of::<Bank>(),
|
||||||
|
@ -259,8 +267,9 @@ const_assert_eq!(
|
||||||
+ 16 * 3
|
+ 16 * 3
|
||||||
+ 32
|
+ 32
|
||||||
+ 8
|
+ 8
|
||||||
+ 16 * 3
|
+ 16 * 4
|
||||||
+ 1920
|
+ 4
|
||||||
|
+ 1900
|
||||||
);
|
);
|
||||||
const_assert_eq!(size_of::<Bank>(), 3064);
|
const_assert_eq!(size_of::<Bank>(), 3064);
|
||||||
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
||||||
|
@ -304,6 +313,7 @@ impl Bank {
|
||||||
indexed_borrows: I80F48::ZERO,
|
indexed_borrows: I80F48::ZERO,
|
||||||
collected_fees_native: I80F48::ZERO,
|
collected_fees_native: I80F48::ZERO,
|
||||||
collected_liquidation_fees: I80F48::ZERO,
|
collected_liquidation_fees: I80F48::ZERO,
|
||||||
|
collected_collateral_fees: I80F48::ZERO,
|
||||||
fees_withdrawn: 0,
|
fees_withdrawn: 0,
|
||||||
dust: I80F48::ZERO,
|
dust: I80F48::ZERO,
|
||||||
flash_loan_approved_amount: 0,
|
flash_loan_approved_amount: 0,
|
||||||
|
@ -368,7 +378,8 @@ impl Bank {
|
||||||
deposit_limit: existing_bank.deposit_limit,
|
deposit_limit: existing_bank.deposit_limit,
|
||||||
zero_util_rate: existing_bank.zero_util_rate,
|
zero_util_rate: existing_bank.zero_util_rate,
|
||||||
platform_liquidation_fee: existing_bank.platform_liquidation_fee,
|
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!(self.are_borrows_reduce_only(), MangoError::SomeError);
|
||||||
require_eq!(self.maint_asset_weight, I80F48::ZERO);
|
require_eq!(self.maint_asset_weight, I80F48::ZERO);
|
||||||
}
|
}
|
||||||
|
require_gte!(self.collateral_fee_per_day, 0.0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,11 +98,32 @@ pub struct Group {
|
||||||
/// Number of fast listings that are allowed per interval
|
/// Number of fast listings that are allowed per interval
|
||||||
pub allowed_fast_listings_per_interval: u16,
|
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!(
|
const_assert_eq!(
|
||||||
size_of::<Group>(),
|
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>(), 2736);
|
||||||
const_assert_eq!(size_of::<Group>() % 8, 0);
|
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
|
/// Next id to use when adding a token condition swap
|
||||||
pub next_token_conditional_swap_id: u64,
|
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")]
|
#[derivative(Debug = "ignore")]
|
||||||
pub reserved: [u8; 200],
|
pub reserved: [u8; 152],
|
||||||
|
|
||||||
// dynamic
|
// dynamic
|
||||||
pub header_version: u8,
|
pub header_version: u8,
|
||||||
|
@ -203,7 +209,10 @@ impl MangoAccount {
|
||||||
buyback_fees_accrued_previous: 0,
|
buyback_fees_accrued_previous: 0,
|
||||||
buyback_fees_expiry_timestamp: 0,
|
buyback_fees_expiry_timestamp: 0,
|
||||||
next_token_conditional_swap_id: 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,
|
header_version: DEFAULT_MANGO_ACCOUNT_VERSION,
|
||||||
padding3: Default::default(),
|
padding3: Default::default(),
|
||||||
padding4: Default::default(),
|
padding4: Default::default(),
|
||||||
|
@ -327,11 +336,12 @@ pub struct MangoAccountFixed {
|
||||||
pub next_token_conditional_swap_id: u64,
|
pub next_token_conditional_swap_id: u64,
|
||||||
pub temporary_delegate: Pubkey,
|
pub temporary_delegate: Pubkey,
|
||||||
pub temporary_delegate_expiry: u64,
|
pub temporary_delegate_expiry: u64,
|
||||||
pub reserved: [u8; 160],
|
pub last_collateral_fee_charge: u64,
|
||||||
|
pub reserved: [u8; 152],
|
||||||
}
|
}
|
||||||
const_assert_eq!(
|
const_assert_eq!(
|
||||||
size_of::<MangoAccountFixed>(),
|
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>(), 400);
|
||||||
const_assert_eq!(size_of::<MangoAccountFixed>() % 8, 0);
|
const_assert_eq!(size_of::<MangoAccountFixed>() % 8, 0);
|
||||||
|
|
|
@ -17,6 +17,7 @@ mod test_bankrupt_tokens;
|
||||||
mod test_basic;
|
mod test_basic;
|
||||||
mod test_benchmark;
|
mod test_benchmark;
|
||||||
mod test_borrow_limits;
|
mod test_borrow_limits;
|
||||||
|
mod test_collateral_fees;
|
||||||
mod test_delegate;
|
mod test_delegate;
|
||||||
mod test_fees_buyback_with_mngo;
|
mod test_fees_buyback_with_mngo;
|
||||||
mod test_force_close;
|
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,
|
zero_util_rate: 0.0,
|
||||||
platform_liquidation_fee: self.platform_liquidation_fee,
|
platform_liquidation_fee: self.platform_liquidation_fee,
|
||||||
disable_asset_liquidation: false,
|
disable_asset_liquidation: false,
|
||||||
|
collateral_fee_per_day: 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let bank = Pubkey::find_program_address(
|
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,
|
zero_util_rate_opt: None,
|
||||||
platform_liquidation_fee_opt: None,
|
platform_liquidation_fee_opt: None,
|
||||||
disable_asset_liquidation_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,
|
mngo_token_index_opt: None,
|
||||||
buyback_fees_expiry_interval_opt: None,
|
buyback_fees_expiry_interval_opt: None,
|
||||||
allowed_fast_listings_per_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]
|
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 zeroUtilRate: I80F48;
|
||||||
public platformLiquidationFee: I80F48;
|
public platformLiquidationFee: I80F48;
|
||||||
public collectedLiquidationFees: I80F48;
|
public collectedLiquidationFees: I80F48;
|
||||||
|
public collectedCollateralFees: I80F48;
|
||||||
|
|
||||||
static from(
|
static from(
|
||||||
publicKey: PublicKey,
|
publicKey: PublicKey,
|
||||||
|
@ -148,6 +149,8 @@ export class Bank implements BankForHealth {
|
||||||
zeroUtilRate: I80F48Dto;
|
zeroUtilRate: I80F48Dto;
|
||||||
platformLiquidationFee: I80F48Dto;
|
platformLiquidationFee: I80F48Dto;
|
||||||
collectedLiquidationFees: I80F48Dto;
|
collectedLiquidationFees: I80F48Dto;
|
||||||
|
collectedCollateralFees: I80F48Dto;
|
||||||
|
collateralFeePerDay: number;
|
||||||
},
|
},
|
||||||
): Bank {
|
): Bank {
|
||||||
return new Bank(
|
return new Bank(
|
||||||
|
@ -213,6 +216,8 @@ export class Bank implements BankForHealth {
|
||||||
obj.platformLiquidationFee,
|
obj.platformLiquidationFee,
|
||||||
obj.collectedLiquidationFees,
|
obj.collectedLiquidationFees,
|
||||||
obj.disableAssetLiquidation == 0,
|
obj.disableAssetLiquidation == 0,
|
||||||
|
obj.collectedCollateralFees,
|
||||||
|
obj.collateralFeePerDay,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,6 +284,8 @@ export class Bank implements BankForHealth {
|
||||||
platformLiquidationFee: I80F48Dto,
|
platformLiquidationFee: I80F48Dto,
|
||||||
collectedLiquidationFees: I80F48Dto,
|
collectedLiquidationFees: I80F48Dto,
|
||||||
public allowAssetLiquidation: boolean,
|
public allowAssetLiquidation: boolean,
|
||||||
|
collectedCollateralFees: I80F48Dto,
|
||||||
|
public collateralFeePerDay: number,
|
||||||
) {
|
) {
|
||||||
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
||||||
this.oracleConfig = {
|
this.oracleConfig = {
|
||||||
|
@ -311,6 +318,7 @@ export class Bank implements BankForHealth {
|
||||||
this.zeroUtilRate = I80F48.from(zeroUtilRate);
|
this.zeroUtilRate = I80F48.from(zeroUtilRate);
|
||||||
this.platformLiquidationFee = I80F48.from(platformLiquidationFee);
|
this.platformLiquidationFee = I80F48.from(platformLiquidationFee);
|
||||||
this.collectedLiquidationFees = I80F48.from(collectedLiquidationFees);
|
this.collectedLiquidationFees = I80F48.from(collectedLiquidationFees);
|
||||||
|
this.collectedCollateralFees = I80F48.from(collectedCollateralFees);
|
||||||
this._price = undefined;
|
this._price = undefined;
|
||||||
this._uiPrice = undefined;
|
this._uiPrice = undefined;
|
||||||
this._oracleLastUpdatedSlot = undefined;
|
this._oracleLastUpdatedSlot = undefined;
|
||||||
|
|
|
@ -50,6 +50,7 @@ export class Group {
|
||||||
fastListingIntervalStart: BN;
|
fastListingIntervalStart: BN;
|
||||||
fastListingsInInterval: number;
|
fastListingsInInterval: number;
|
||||||
allowedFastListingsPerInterval: number;
|
allowedFastListingsPerInterval: number;
|
||||||
|
collateralFeeInterval: BN;
|
||||||
},
|
},
|
||||||
): Group {
|
): Group {
|
||||||
return new Group(
|
return new Group(
|
||||||
|
@ -74,6 +75,7 @@ export class Group {
|
||||||
obj.fastListingIntervalStart,
|
obj.fastListingIntervalStart,
|
||||||
obj.fastListingsInInterval,
|
obj.fastListingsInInterval,
|
||||||
obj.allowedFastListingsPerInterval,
|
obj.allowedFastListingsPerInterval,
|
||||||
|
obj.collateralFeeInterval,
|
||||||
[], // addressLookupTablesList
|
[], // addressLookupTablesList
|
||||||
new Map(), // banksMapByName
|
new Map(), // banksMapByName
|
||||||
new Map(), // banksMapByMint
|
new Map(), // banksMapByMint
|
||||||
|
@ -113,6 +115,7 @@ export class Group {
|
||||||
public fastListingIntervalStart: BN,
|
public fastListingIntervalStart: BN,
|
||||||
public fastListingsInInterval: number,
|
public fastListingsInInterval: number,
|
||||||
public allowedFastListingsPerInterval: number,
|
public allowedFastListingsPerInterval: number,
|
||||||
|
public collateralFeeInterval: BN,
|
||||||
public addressLookupTablesList: AddressLookupTableAccount[],
|
public addressLookupTablesList: AddressLookupTableAccount[],
|
||||||
public banksMapByName: Map<string, Bank[]>,
|
public banksMapByName: Map<string, Bank[]>,
|
||||||
public banksMapByMint: Map<string, Bank[]>,
|
public banksMapByMint: Map<string, Bank[]>,
|
||||||
|
|
|
@ -304,6 +304,7 @@ export class MangoClient {
|
||||||
feesMngoTokenIndex?: TokenIndex,
|
feesMngoTokenIndex?: TokenIndex,
|
||||||
feesExpiryInterval?: BN,
|
feesExpiryInterval?: BN,
|
||||||
allowedFastListingsPerInterval?: number,
|
allowedFastListingsPerInterval?: number,
|
||||||
|
collateralFeeInterval?: BN,
|
||||||
): Promise<MangoSignatureStatus> {
|
): Promise<MangoSignatureStatus> {
|
||||||
const ix = await this.program.methods
|
const ix = await this.program.methods
|
||||||
.groupEdit(
|
.groupEdit(
|
||||||
|
@ -319,6 +320,7 @@ export class MangoClient {
|
||||||
feesMngoTokenIndex ?? null,
|
feesMngoTokenIndex ?? null,
|
||||||
feesExpiryInterval ?? null,
|
feesExpiryInterval ?? null,
|
||||||
allowedFastListingsPerInterval ?? null,
|
allowedFastListingsPerInterval ?? null,
|
||||||
|
collateralFeeInterval ?? null,
|
||||||
)
|
)
|
||||||
.accounts({
|
.accounts({
|
||||||
group: group.publicKey,
|
group: group.publicKey,
|
||||||
|
@ -462,6 +464,7 @@ export class MangoClient {
|
||||||
params.zeroUtilRate,
|
params.zeroUtilRate,
|
||||||
params.platformLiquidationFee,
|
params.platformLiquidationFee,
|
||||||
params.disableAssetLiquidation,
|
params.disableAssetLiquidation,
|
||||||
|
params.collateralFeePerDay,
|
||||||
)
|
)
|
||||||
.accounts({
|
.accounts({
|
||||||
group: group.publicKey,
|
group: group.publicKey,
|
||||||
|
@ -550,6 +553,7 @@ export class MangoClient {
|
||||||
params.zeroUtilRate,
|
params.zeroUtilRate,
|
||||||
params.platformLiquidationFee,
|
params.platformLiquidationFee,
|
||||||
params.disableAssetLiquidation,
|
params.disableAssetLiquidation,
|
||||||
|
params.collateralFeePerDay,
|
||||||
)
|
)
|
||||||
.accounts({
|
.accounts({
|
||||||
group: group.publicKey,
|
group: group.publicKey,
|
||||||
|
|
|
@ -31,6 +31,7 @@ export interface TokenRegisterParams {
|
||||||
zeroUtilRate: number;
|
zeroUtilRate: number;
|
||||||
platformLiquidationFee: number;
|
platformLiquidationFee: number;
|
||||||
disableAssetLiquidation: boolean;
|
disableAssetLiquidation: boolean;
|
||||||
|
collateralFeePerDay: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DefaultTokenRegisterParams: TokenRegisterParams = {
|
export const DefaultTokenRegisterParams: TokenRegisterParams = {
|
||||||
|
@ -72,6 +73,7 @@ export const DefaultTokenRegisterParams: TokenRegisterParams = {
|
||||||
zeroUtilRate: 0.0,
|
zeroUtilRate: 0.0,
|
||||||
platformLiquidationFee: 0.0,
|
platformLiquidationFee: 0.0,
|
||||||
disableAssetLiquidation: false,
|
disableAssetLiquidation: false,
|
||||||
|
collateralFeePerDay: 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface TokenEditParams {
|
export interface TokenEditParams {
|
||||||
|
@ -114,6 +116,7 @@ export interface TokenEditParams {
|
||||||
zeroUtilRate: number | null;
|
zeroUtilRate: number | null;
|
||||||
platformLiquidationFee: number | null;
|
platformLiquidationFee: number | null;
|
||||||
disableAssetLiquidation: boolean | null;
|
disableAssetLiquidation: boolean | null;
|
||||||
|
collateralFeePerDay: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NullTokenEditParams: TokenEditParams = {
|
export const NullTokenEditParams: TokenEditParams = {
|
||||||
|
@ -156,6 +159,7 @@ export const NullTokenEditParams: TokenEditParams = {
|
||||||
zeroUtilRate: null,
|
zeroUtilRate: null,
|
||||||
platformLiquidationFee: null,
|
platformLiquidationFee: null,
|
||||||
disableAssetLiquidation: null,
|
disableAssetLiquidation: null,
|
||||||
|
collateralFeePerDay: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface PerpEditParams {
|
export interface PerpEditParams {
|
||||||
|
|
|
@ -277,6 +277,12 @@ export type MangoV4 = {
|
||||||
"type": {
|
"type": {
|
||||||
"option": "u16"
|
"option": "u16"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collateralFeeIntervalOpt",
|
||||||
|
"type": {
|
||||||
|
"option": "u64"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -635,6 +641,10 @@ export type MangoV4 = {
|
||||||
{
|
{
|
||||||
"name": "disableAssetLiquidation",
|
"name": "disableAssetLiquidation",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collateralFeePerDay",
|
||||||
|
"type": "f32"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1051,6 +1061,12 @@ export type MangoV4 = {
|
||||||
"type": {
|
"type": {
|
||||||
"option": "bool"
|
"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",
|
"name": "altSet",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
|
@ -7531,12 +7566,30 @@ export type MangoV4 = {
|
||||||
"defined": "I80F48"
|
"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",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
1920
|
1900
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7664,12 +7717,28 @@ export type MangoV4 = {
|
||||||
],
|
],
|
||||||
"type": "u16"
|
"type": "u16"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "padding2",
|
||||||
|
"type": {
|
||||||
|
"array": [
|
||||||
|
"u8",
|
||||||
|
4
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collateralFeeInterval",
|
||||||
|
"docs": [
|
||||||
|
"Intervals in which collateral fee is applied"
|
||||||
|
],
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reserved",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
1812
|
1800
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7791,12 +7860,27 @@ export type MangoV4 = {
|
||||||
],
|
],
|
||||||
"type": "u64"
|
"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",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
200
|
152
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -9566,12 +9650,16 @@ export type MangoV4 = {
|
||||||
"name": "temporaryDelegateExpiry",
|
"name": "temporaryDelegateExpiry",
|
||||||
"type": "u64"
|
"type": "u64"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "lastCollateralFeeCharge",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reserved",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
160
|
152
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13699,6 +13787,36 @@ export type MangoV4 = {
|
||||||
"index": false
|
"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": [
|
"errors": [
|
||||||
|
@ -14334,6 +14452,12 @@ export const IDL: MangoV4 = {
|
||||||
"type": {
|
"type": {
|
||||||
"option": "u16"
|
"option": "u16"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collateralFeeIntervalOpt",
|
||||||
|
"type": {
|
||||||
|
"option": "u64"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -14692,6 +14816,10 @@ export const IDL: MangoV4 = {
|
||||||
{
|
{
|
||||||
"name": "disableAssetLiquidation",
|
"name": "disableAssetLiquidation",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collateralFeePerDay",
|
||||||
|
"type": "f32"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -15108,6 +15236,12 @@ export const IDL: MangoV4 = {
|
||||||
"type": {
|
"type": {
|
||||||
"option": "bool"
|
"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",
|
"name": "altSet",
|
||||||
"accounts": [
|
"accounts": [
|
||||||
|
@ -21588,12 +21741,30 @@ export const IDL: MangoV4 = {
|
||||||
"defined": "I80F48"
|
"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",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
1920
|
1900
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21721,12 +21892,28 @@ export const IDL: MangoV4 = {
|
||||||
],
|
],
|
||||||
"type": "u16"
|
"type": "u16"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "padding2",
|
||||||
|
"type": {
|
||||||
|
"array": [
|
||||||
|
"u8",
|
||||||
|
4
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collateralFeeInterval",
|
||||||
|
"docs": [
|
||||||
|
"Intervals in which collateral fee is applied"
|
||||||
|
],
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reserved",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
1812
|
1800
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21848,12 +22035,27 @@ export const IDL: MangoV4 = {
|
||||||
],
|
],
|
||||||
"type": "u64"
|
"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",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
200
|
152
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -23623,12 +23825,16 @@ export const IDL: MangoV4 = {
|
||||||
"name": "temporaryDelegateExpiry",
|
"name": "temporaryDelegateExpiry",
|
||||||
"type": "u64"
|
"type": "u64"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "lastCollateralFeeCharge",
|
||||||
|
"type": "u64"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reserved",
|
"name": "reserved",
|
||||||
"type": {
|
"type": {
|
||||||
"array": [
|
"array": [
|
||||||
"u8",
|
"u8",
|
||||||
160
|
152
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27756,6 +27962,36 @@ export const IDL: MangoV4 = {
|
||||||
"index": false
|
"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": [
|
"errors": [
|
||||||
|
|
Loading…
Reference in New Issue