buyback perp fees with mngo at a discount (#464)

buyback perp fees with mngo

Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
microwavedcola1 2023-02-25 20:34:16 +01:00 committed by GitHub
parent 5c7a2e3e10
commit d88d44b34a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 862 additions and 50 deletions

View File

@ -0,0 +1,52 @@
use crate::error::*;
use crate::state::*;
use anchor_lang::prelude::*;
#[derive(Accounts)]
pub struct AccountBuybackFeesWithMngo<'info> {
#[account(
constraint = group.load()?.is_ix_enabled(IxGate::AccountBuybackFeesWithMngo) @ MangoError::IxIsDisabled,
constraint = group.load()?.buyback_fees() @ MangoError::SomeError
)]
pub group: AccountLoader<'info, Group>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen
// owner is checked at #1
)]
pub account: AccountLoader<'info, MangoAccountFixed>,
pub owner: Signer<'info>,
#[account(
mut,
has_one = group,
constraint = account.load()?.is_operational() @ MangoError::AccountIsFrozen,
address = group.load()?.buyback_fees_swap_mango_account
)]
pub dao_account: AccountLoader<'info, MangoAccountFixed>,
#[account(
mut,
has_one = group,
constraint = mngo_bank.load()?.token_index == group.load()?.mngo_token_index,
constraint = mngo_bank.load()?.token_index != 0, // should not be unset
)]
pub mngo_bank: AccountLoader<'info, Bank>,
/// CHECK: Oracle can have different account types
#[account(address = mngo_bank.load()?.oracle)]
pub mngo_oracle: UncheckedAccount<'info>,
#[account(
mut,
has_one = group,
constraint = fees_bank.load()?.token_index == QUOTE_TOKEN_INDEX
)]
pub fees_bank: AccountLoader<'info, Bank>,
/// CHECK: Oracle can have different account types
#[account(address = fees_bank.load()?.oracle)]
pub fees_oracle: UncheckedAccount<'info>,
}

View File

@ -1,3 +1,4 @@
pub use account_buyback_fees_with_mngo::*;
pub use account_close::*;
pub use account_create::*;
pub use account_edit::*;
@ -53,6 +54,7 @@ pub use token_register_trustless::*;
pub use token_update_index_and_rate::*;
pub use token_withdraw::*;
mod account_buyback_fees_with_mngo;
mod account_close;
mod account_create;
mod account_edit;

View File

@ -0,0 +1,154 @@
use anchor_lang::prelude::*;
use fixed::types::I80F48;
use crate::accounts_zerocopy::*;
use crate::error::MangoError;
use crate::state::*;
use crate::accounts_ix::*;
pub fn account_buyback_fees_with_mngo(
ctx: Context<AccountBuybackFeesWithMngo>,
max_buyback: u64,
) -> Result<()> {
// Cannot buyback from yourself
require_keys_neq!(
ctx.accounts.account.key(),
ctx.accounts.dao_account.key(),
MangoError::SomeError
);
let mut account = ctx.accounts.account.load_full_mut()?;
// account constraint #1
require!(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
let mut dao_account = ctx.accounts.dao_account.load_full_mut()?;
let group = ctx.accounts.group.load()?;
let mut mngo_bank = ctx.accounts.mngo_bank.load_mut()?;
let mut fees_bank = ctx.accounts.fees_bank.load_mut()?;
let bonus_factor = I80F48::from_num(group.buyback_fees_mngo_bonus_factor);
let now_ts = Clock::get()?.unix_timestamp.try_into().unwrap();
// quick return if nothing to buyback
let mut max_buyback = {
let dao_fees_token_position = dao_account.ensure_token_position(fees_bank.token_index)?.0;
let dao_fees_native = dao_fees_token_position.native(&fees_bank);
I80F48::from_num::<u64>(max_buyback.min(account.fixed.buyback_fees_accrued))
.min(dao_fees_native)
};
if max_buyback <= I80F48::ZERO {
msg!(
"nothing to buyback, (buyback_fees_accrued {})",
account.fixed.buyback_fees_accrued
);
return Ok(());
}
// if mngo token position has borrows, skip buyback
let account_mngo_native = account
.token_position(mngo_bank.token_index)
.map(|tp| tp.native(&mngo_bank))
.unwrap_or(I80F48::ZERO);
if account_mngo_native <= I80F48::ZERO {
msg!(
"account mngo token position ({} native mngo) is <= 0, nothing will be bought back",
account_mngo_native
);
return Ok(());
}
let (account_mngo_token_position, account_mngo_raw_token_index, _) =
account.ensure_token_position(mngo_bank.token_index)?;
// compute max mngo to swap for fees
let mngo_oracle_price = mngo_bank.oracle_price(
&AccountInfoRef::borrow(&ctx.accounts.mngo_oracle.as_ref())?,
Some(Clock::get()?.slot),
)?;
let mngo_buyback_price = mngo_oracle_price * bonus_factor;
// mngo is exchanged at a discount
let mut max_buyback_mngo = max_buyback / mngo_buyback_price;
// buyback is restricted to account's token position
max_buyback_mngo = max_buyback_mngo.min(account_mngo_native);
max_buyback = max_buyback_mngo * mngo_buyback_price;
// move mngo from user to dao
let (dao_mngo_token_position, dao_mngo_raw_token_index, _) =
dao_account.ensure_token_position(mngo_bank.token_index)?;
require!(
dao_mngo_token_position.indexed_position >= I80F48::ZERO,
MangoError::SomeError
);
let in_use = mngo_bank.withdraw_without_fee(
account_mngo_token_position,
max_buyback_mngo,
now_ts,
mngo_oracle_price,
)?;
if !in_use {
account.deactivate_token_position_and_log(
account_mngo_raw_token_index,
ctx.accounts.account.key(),
);
}
mngo_bank.deposit(dao_mngo_token_position, max_buyback_mngo, now_ts)?;
// move fees from dao to user
let (account_fees_token_position, account_fees_raw_token_index, _) =
account.ensure_token_position(fees_bank.token_index)?;
let (dao_fees_token_position, dao_fees_raw_token_index, _) =
dao_account.ensure_token_position(fees_bank.token_index)?;
let dao_fees_native = dao_fees_token_position.native(&fees_bank);
assert!(dao_fees_native >= max_buyback);
let in_use = fees_bank.withdraw_without_fee(
dao_fees_token_position,
max_buyback,
now_ts,
mngo_oracle_price,
)?;
if !in_use {
dao_account.deactivate_token_position_and_log(
dao_fees_raw_token_index,
ctx.accounts.dao_account.key(),
);
}
let in_use = fees_bank.deposit(account_fees_token_position, max_buyback, now_ts)?;
if !in_use {
account.deactivate_token_position_and_log(
account_fees_raw_token_index,
ctx.accounts.account.key(),
);
}
account.fixed.buyback_fees_accrued = account
.fixed
.buyback_fees_accrued
.saturating_sub(max_buyback.ceil().to_num::<u64>());
msg!(
"bought back {} native fees with {} native mngo",
max_buyback,
max_buyback_mngo
);
// ensure dao mango account has no liabilities after we do the token swap
for ele in dao_account.all_token_positions() {
require!(!ele.indexed_position.is_negative(), MangoError::SomeError);
}
require_eq!(
dao_account.active_perp_positions().count(),
0,
MangoError::SomeError
);
require_eq!(
dao_account.active_serum3_orders().count(),
0,
MangoError::SomeError
);
Ok(())
}

View File

@ -1,5 +1,3 @@
use std::str::FromStr;
use anchor_lang::prelude::*;
use fixed::types::{I80F48, U80F48};
use solana_program::{log::sol_log_compute_units, program_memory::sol_memcmp};

View File

@ -1,9 +1,10 @@
use anchor_lang::prelude::*;
use crate::accounts_ix::*;
use crate::{accounts_ix::*, state::TokenIndex};
// use case - transfer group ownership to governance, where
// admin and fast_listing_admin are PDAs
#[allow(clippy::too_many_arguments)]
pub fn group_edit(
ctx: Context<GroupEdit>,
admin_opt: Option<Pubkey>,
@ -12,6 +13,10 @@ pub fn group_edit(
testing_opt: Option<u8>,
version_opt: Option<u8>,
deposit_limit_quote_opt: Option<u64>,
buyback_fees_opt: Option<bool>,
buyback_fees_bonus_factor_opt: Option<f32>,
buyback_fees_swap_mango_account_opt: Option<Pubkey>,
mngo_token_index_opt: Option<TokenIndex>,
) -> Result<()> {
let mut group = ctx.accounts.group.load_mut()?;
@ -58,5 +63,38 @@ pub fn group_edit(
group.deposit_limit_quote = deposit_limit_quote;
}
if let Some(buyback_fees) = buyback_fees_opt {
msg!(
"Buyback fees old {:?}, new {:?}",
group.buyback_fees,
buyback_fees
);
group.buyback_fees = u8::from(buyback_fees);
}
if let Some(buyback_fees_mngo_bonus_factor) = buyback_fees_bonus_factor_opt {
msg!(
"Buyback fees mngo bonus factor old {:?}, new {:?}",
group.buyback_fees_mngo_bonus_factor,
buyback_fees_mngo_bonus_factor
);
group.buyback_fees_mngo_bonus_factor = buyback_fees_mngo_bonus_factor;
}
if let Some(buyback_fees_swap_mango_account) = buyback_fees_swap_mango_account_opt {
msg!(
"Buyback fees swap mango account old {:?}, new {:?}",
group.buyback_fees_swap_mango_account,
buyback_fees_swap_mango_account
);
group.buyback_fees_swap_mango_account = buyback_fees_swap_mango_account;
}
if let Some(mngo_token_index) = mngo_token_index_opt {
msg!(
"Mngo token index old {:?}, new {:?}",
group.mngo_token_index,
mngo_token_index
);
group.mngo_token_index = mngo_token_index;
}
Ok(())
}

View File

@ -1,3 +1,4 @@
pub use account_buyback_fees_with_mngo::*;
pub use account_close::*;
pub use account_create::*;
pub use account_edit::*;
@ -53,6 +54,7 @@ pub use token_register_trustless::*;
pub use token_update_index_and_rate::*;
pub use token_withdraw::*;
mod account_buyback_fees_with_mngo;
mod account_close;
mod account_create;
mod account_edit;

View File

@ -51,6 +51,7 @@ pub mod mango_v4 {
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn group_edit(
ctx: Context<GroupEdit>,
admin_opt: Option<Pubkey>,
@ -59,6 +60,10 @@ pub mod mango_v4 {
testing_opt: Option<u8>,
version_opt: Option<u8>,
deposit_limit_quote_opt: Option<u64>,
buyback_fees_opt: Option<bool>,
buyback_fees_bonus_factor_opt: Option<f32>,
buyback_fees_swap_mango_account_opt: Option<Pubkey>,
mngo_token_index_opt: Option<TokenIndex>,
) -> Result<()> {
#[cfg(feature = "enable-gpl")]
instructions::group_edit(
@ -69,6 +74,10 @@ pub mod mango_v4 {
testing_opt,
version_opt,
deposit_limit_quote_opt,
buyback_fees_opt,
buyback_fees_bonus_factor_opt,
buyback_fees_swap_mango_account_opt,
mngo_token_index_opt,
)?;
Ok(())
}
@ -270,6 +279,15 @@ pub mod mango_v4 {
Ok(())
}
pub fn account_buyback_fees_with_mngo(
ctx: Context<AccountBuybackFeesWithMngo>,
max_buyback: u64,
) -> Result<()> {
#[cfg(feature = "enable-gpl")]
instructions::account_buyback_fees_with_mngo(ctx, max_buyback)?;
Ok(())
}
// todo:
// ckamm: generally, using an I80F48 arg will make it harder to call
// because generic anchor clients won't know how to deal with it

View File

@ -20,7 +20,9 @@ pub struct Group {
// TODO: unused, use case - listing shit tokens with conservative parameters (mostly defaults)
pub fast_listing_admin: Pubkey,
pub padding: [u8; 4],
// This is the token index of the mngo token listed on the group
pub mngo_token_index: TokenIndex,
pub padding: [u8; 2],
pub insurance_vault: Pubkey,
pub insurance_mint: Pubkey,
@ -31,7 +33,11 @@ pub struct Group {
pub version: u8,
pub padding2: [u8; 5],
// Buyback fees with Mngo: allow exchanging fees with mngo at a bonus
pub buyback_fees: u8,
// Buyback fees with Mngo: how much should the bonus be,
// e.g. a bonus factor of 1.2 means 120$ worth fees could be swapped for mngo worth 100$ at current market price
pub buyback_fees_mngo_bonus_factor: f32,
pub address_lookup_tables: [Pubkey; 20],
@ -45,16 +51,29 @@ pub struct Group {
// 0 is chosen as enabled, becase we want to start out with all ixs enabled, 1 is disabled
pub ix_gate: u128,
pub reserved: [u8; 1864],
// Buyback fees with Mngo:
// A mango account which would be counter party for settling fees with mngo
// This ensures that the system doesn't have a net deficit of tokens
// The workflow should be something like this
// - the dao deposits quote tokens in its respective mango account
// - the user deposits some mngo tokens in his mango account
// - the user then claims quote for mngo at a bonus rate
pub buyback_fees_swap_mango_account: Pubkey,
pub reserved: [u8; 1832],
}
const_assert_eq!(
size_of::<Group>(),
32 + 4 + 32 * 2 + 4 + 32 * 2 + 3 + 5 + 20 * 32 + 32 + 8 + 16 + 1864
32 + 4 + 32 * 2 + 4 + 32 * 2 + 4 + 4 + 20 * 32 + 32 + 8 + 16 + 32 + 1832
);
const_assert_eq!(size_of::<Group>(), 2736);
const_assert_eq!(size_of::<Group>() % 8, 0);
impl Group {
pub fn buyback_fees(&self) -> bool {
self.buyback_fees == 1
}
pub fn is_testing(&self) -> bool {
self.testing == 1
}
@ -139,6 +158,7 @@ pub enum IxGate {
TokenRegisterTrustless = 45,
TokenUpdateIndexAndRate = 46,
TokenWithdraw = 47,
AccountBuybackFeesWithMngo = 48,
}
// note: using creator instead of admin, since admin can be changed

View File

@ -87,7 +87,9 @@ pub struct MangoAccount {
pub frozen_until: u64,
pub reserved: [u8; 232],
pub buyback_fees_accrued: u64,
pub reserved: [u8; 224],
// dynamic
pub header_version: u8,
@ -122,7 +124,8 @@ impl MangoAccount {
net_deposits: 0,
health_region_begin_init_health: 0,
frozen_until: 0,
reserved: [0; 232],
buyback_fees_accrued: 0,
reserved: [0; 224],
header_version: DEFAULT_MANGO_ACCOUNT_VERSION,
padding3: Default::default(),
padding4: Default::default(),
@ -204,9 +207,13 @@ pub struct MangoAccountFixed {
pub perp_spot_transfers: i64,
pub health_region_begin_init_health: i64,
pub frozen_until: u64,
pub reserved: [u8; 232],
pub buyback_fees_accrued: u64,
pub reserved: [u8; 224],
}
const_assert_eq!(size_of::<MangoAccountFixed>(), 32 * 4 + 8 + 3 * 8 + 8 + 232);
const_assert_eq!(
size_of::<MangoAccountFixed>(),
32 * 4 + 8 + 3 * 8 + 8 + 8 + 224
);
const_assert_eq!(size_of::<MangoAccountFixed>(), 400);
const_assert_eq!(size_of::<MangoAccountFixed>() % 8, 0);
@ -891,13 +898,15 @@ impl<
perp_market: &mut PerpMarket,
fill: &FillEvent,
) -> Result<()> {
let pa = self.perp_position_mut(perp_market_index)?;
pa.settle_funding(perp_market);
let side = fill.taker_side().invert_side();
let (base_change, quote_change) = fill.base_quote_change(side);
let quote = I80F48::from(perp_market.quote_lot_size) * I80F48::from(quote_change);
let fees = quote.abs() * I80F48::from_num(fill.maker_fee);
if fees.is_positive() {
self.fixed_mut().buyback_fees_accrued += fees.floor().to_num::<u64>();
}
let pa = self.perp_position_mut(perp_market_index)?;
pa.settle_funding(perp_market);
pa.record_trading_fee(fees);
pa.record_trade(perp_market, base_change, quote);

View File

@ -1,4 +1,4 @@
use crate::state::{MangoAccountRefMut, PerpPosition};
use crate::state::MangoAccountRefMut;
use crate::{
error::*,
state::{orderbook::bookside::*, EventQueue, PerpMarket},
@ -58,16 +58,17 @@ impl<'a> Orderbook<'a> {
let post_only = order.is_post_only();
let mut post_target = order.post_target();
let (price_lots, price_data) = order.price(now_ts, oracle_price_lots, self)?;
let perp_position = mango_account.perp_position_mut(market.perp_market_index)?;
// generate new order id
let order_id = market.gen_order_id(side, price_data);
// IOC orders have a fee penalty applied regardless of match
if order.needs_penalty_fee() {
apply_penalty(market, perp_position)?;
apply_penalty(market, mango_account)?;
}
let perp_position = mango_account.perp_position_mut(market.perp_market_index)?;
// Iterate through book and match against this new order.
//
// Any changes to matching orders on the other side of the book are collected in
@ -164,7 +165,7 @@ impl<'a> Orderbook<'a> {
// realized when the fill event gets executed
if total_quote_lots_taken > 0 || total_base_lots_taken > 0 {
perp_position.add_taker_trade(side, total_base_lots_taken, total_quote_lots_taken);
apply_fees(market, perp_position, total_quote_lots_taken)?;
apply_fees(market, mango_account, total_quote_lots_taken)?;
}
// Apply changes to matched asks (handles invalidate on delete!)
@ -350,7 +351,7 @@ impl<'a> Orderbook<'a> {
/// both the maker and taker fees.
fn apply_fees(
market: &mut PerpMarket,
perp_position: &mut PerpPosition,
account: &mut MangoAccountRefMut,
quote_lots: i64,
) -> Result<()> {
let quote_native = I80F48::from_num(market.quote_lot_size * quote_lots);
@ -362,6 +363,8 @@ fn apply_fees(
require_gte!(taker_fees, 0);
// The maker fees apply to the maker's account only when the fill event is consumed.
account.fixed.buyback_fees_accrued += taker_fees.floor().to_num::<u64>();
let perp_position = account.perp_position_mut(market.perp_market_index)?;
perp_position.record_trading_fee(taker_fees);
perp_position.taker_volume += taker_fees.to_num::<u64>();
@ -374,8 +377,11 @@ fn apply_fees(
}
/// Applies a fixed penalty fee to the account, and update the market's fees_accrued
fn apply_penalty(market: &mut PerpMarket, perp_position: &mut PerpPosition) -> Result<()> {
fn apply_penalty(market: &mut PerpMarket, account: &mut MangoAccountRefMut) -> Result<()> {
let fee_penalty = I80F48::from_num(market.fee_penalty);
account.fixed.buyback_fees_accrued += fee_penalty.floor().to_num::<u64>();
let perp_position = account.perp_position_mut(market.perp_market_index)?;
perp_position.record_trading_fee(fee_penalty);
market.fees_accrued += fee_penalty;
Ok(())

View File

@ -17,6 +17,7 @@ mod test_basic;
mod test_benchmark;
mod test_borrow_limits;
mod test_delegate;
mod test_fees_buyback_with_mngo;
mod test_health_compute;
mod test_health_region;
mod test_ix_gate_set;

View File

@ -0,0 +1,196 @@
use super::*;
#[tokio::test]
async fn test_fees_buyback_with_mngo() -> 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];
//
// SETUP: Create a group and an account
//
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
admin,
payer,
mints: mints.to_vec(),
..GroupWithTokensConfig::default()
}
.create(solana)
.await;
let deposit_amount = 100_000_000;
let account_0 = create_funded_account(
solana,
group,
owner,
0,
&context.users[1],
mints,
deposit_amount,
0,
)
.await;
let account_1 = create_funded_account(
solana,
group,
owner,
1,
&context.users[1],
mints,
deposit_amount,
0,
)
.await;
let account_2 = create_funded_account(
solana,
group,
owner,
2,
&context.users[1],
mints,
deposit_amount,
0,
)
.await;
//
// Create a perp market
//
let mango_v4::accounts::PerpCreateMarket { perp_market, .. } = send_tx(
solana,
PerpCreateMarketInstruction {
group,
admin,
payer,
perp_market_index: 0,
quote_lot_size: 10,
base_lot_size: 100,
maint_base_asset_weight: 0.975,
init_base_asset_weight: 0.95,
maint_base_liab_weight: 1.025,
init_base_liab_weight: 1.05,
base_liquidation_fee: 0.012,
maker_fee: -0.01,
taker_fee: 0.02,
settle_pnl_limit_factor: -1.0,
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
..PerpCreateMarketInstruction::with_new_book_and_queue(solana, &tokens[0]).await
},
)
.await
.unwrap();
let price_lots = {
let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
perp_market.native_price_to_lot(I80F48::from(1))
};
//
// Place a bid, corresponding ask, and consume event
//
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_0,
perp_market,
owner,
side: Side::Bid,
price_lots,
max_base_lots: 10,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 5,
},
)
.await
.unwrap();
send_tx(
solana,
PerpPlaceOrderInstruction {
account: account_1,
perp_market,
owner,
side: Side::Ask,
price_lots,
max_base_lots: 10,
max_quote_lots: i64::MAX,
reduce_only: false,
client_order_id: 6,
},
)
.await
.unwrap();
send_tx(
solana,
PerpConsumeEventsInstruction {
perp_market,
mango_accounts: vec![account_0, account_1],
},
)
.await
.unwrap();
//
// Test: Account buyback fees accrued with mngo
//
send_tx(
solana,
GroupEditFeeParameters {
group,
admin,
fees_mngo_token_index: 1 as TokenIndex,
fees_swap_mango_account: account_2,
fees_mngo_bonus_factor: 1.2,
},
)
.await
.unwrap();
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
let before_fees_accrued = mango_account_1.buyback_fees_accrued;
let fees_token_position_before =
mango_account_1.tokens[0].native(&solana.get_account::<Bank>(tokens[0].bank).await);
let mngo_token_position_before =
mango_account_1.tokens[1].native(&solana.get_account::<Bank>(tokens[1].bank).await);
send_tx(
solana,
AccountBuybackFeesWithMngo {
owner,
account: account_1,
mngo_bank: tokens[1].bank,
fees_bank: tokens[0].bank,
},
)
.await
.unwrap();
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
let after_fees_accrued = solana
.get_account::<MangoAccount>(account_1)
.await
.buyback_fees_accrued;
let fees_token_position_after =
mango_account_1.tokens[0].native(&solana.get_account::<Bank>(tokens[0].bank).await);
let mngo_token_position_after =
mango_account_1.tokens[1].native(&solana.get_account::<Bank>(tokens[1].bank).await);
assert_eq!(before_fees_accrued - after_fees_accrued, 19);
// token[1] swapped at discount for token[0]
assert!(
(fees_token_position_after - fees_token_position_before) - I80F48::from_num(20)
< I80F48::from_num(0.000001)
);
assert!(
(mngo_token_position_before - mngo_token_position_after) - I80F48::from_num(16.666666)
< I80F48::from_num(0.000001)
);
Ok(())
}

View File

@ -1503,6 +1503,59 @@ impl ClientInstruction for GroupCreateInstruction {
}
}
fn group_edit_instruction_default() -> mango_v4::instruction::GroupEdit {
mango_v4::instruction::GroupEdit {
admin_opt: None,
fast_listing_admin_opt: None,
security_admin_opt: None,
testing_opt: None,
version_opt: None,
deposit_limit_quote_opt: None,
buyback_fees_opt: None,
buyback_fees_bonus_factor_opt: None,
buyback_fees_swap_mango_account_opt: None,
mngo_token_index_opt: None,
}
}
pub struct GroupEditFeeParameters {
pub group: Pubkey,
pub admin: TestKeypair,
pub fees_mngo_bonus_factor: f32,
pub fees_mngo_token_index: TokenIndex,
pub fees_swap_mango_account: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for GroupEditFeeParameters {
type Accounts = mango_v4::accounts::GroupEdit;
type Instruction = mango_v4::instruction::GroupEdit;
async fn to_instruction(
&self,
_account_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
buyback_fees_opt: Some(true),
buyback_fees_bonus_factor_opt: Some(self.fees_mngo_bonus_factor),
buyback_fees_swap_mango_account_opt: Some(self.fees_swap_mango_account),
mngo_token_index_opt: Some(self.fees_mngo_token_index),
..group_edit_instruction_default()
};
let accounts = Self::Accounts {
group: self.group,
admin: self.admin.pubkey(),
};
let instruction = make_instruction(program_id, &accounts, instruction);
(accounts, instruction)
}
fn signers(&self) -> Vec<TestKeypair> {
vec![self.admin]
}
}
pub struct IxGateSetInstruction {
pub group: Pubkey,
pub admin: TestKeypair,
@ -1765,6 +1818,55 @@ impl ClientInstruction for AccountCloseInstruction {
}
}
pub struct AccountBuybackFeesWithMngo {
pub owner: TestKeypair,
pub account: Pubkey,
pub mngo_bank: Pubkey,
pub fees_bank: Pubkey,
}
#[async_trait::async_trait(?Send)]
impl ClientInstruction for AccountBuybackFeesWithMngo {
type Accounts = mango_v4::accounts::AccountBuybackFeesWithMngo;
type Instruction = mango_v4::instruction::AccountBuybackFeesWithMngo;
async fn to_instruction(
&self,
account_loader: impl ClientAccountLoader + 'async_trait,
) -> (Self::Accounts, instruction::Instruction) {
let program_id = mango_v4::id();
let instruction = Self::Instruction {
max_buyback: u64::MAX,
};
let account = account_loader
.load_mango_account(&self.account)
.await
.unwrap();
let group = account_loader
.load::<Group>(&account.fixed.group)
.await
.unwrap();
let mngo_bank: Bank = account_loader.load(&self.mngo_bank).await.unwrap();
let fees_bank: Bank = account_loader.load(&self.fees_bank).await.unwrap();
let accounts = Self::Accounts {
group: account.fixed.group,
owner: self.owner.pubkey(),
account: self.account,
dao_account: group.buyback_fees_swap_mango_account,
mngo_bank: self.mngo_bank,
mngo_oracle: mngo_bank.oracle,
fees_bank: self.fees_bank,
fees_oracle: fees_bank.oracle,
};
let instruction = make_instruction(program_id, &accounts, instruction);
(accounts, instruction)
}
fn signers(&self) -> Vec<TestKeypair> {
vec![self.owner]
}
}
pub struct Serum3RegisterMarketInstruction {
pub group: Pubkey,
pub admin: TestKeypair,

View File

@ -475,7 +475,7 @@ export class MintInfo {
obj.vaults,
obj.oracle,
obj.registrationTime,
obj.groupInsuranceFund,
obj.groupInsuranceFund == 1,
);
}
@ -488,7 +488,7 @@ export class MintInfo {
public vaults: PublicKey[],
public oracle: PublicKey,
public registrationTime: BN,
public groupInsuranceFund: number,
public groupInsuranceFund: boolean,
) {}
public firstBank(): PublicKey {

View File

@ -32,13 +32,18 @@ export class Group {
groupNum: number;
admin: PublicKey;
fastListingAdmin: PublicKey;
securityAdmin: PublicKey;
feesMngoTokenIndex: number;
insuranceMint: PublicKey;
insuranceVault: PublicKey;
testing: number;
version: number;
ixGate: BN;
feesPayWithMngo: number;
feesMngoBonusFactor: number;
addressLookupTables: PublicKey[];
securityAdmin: PublicKey;
depositLimitQuote: BN;
ixGate: BN;
feesSwapMangoAccount: PublicKey;
},
): Group {
return new Group(
@ -47,13 +52,18 @@ export class Group {
obj.groupNum,
obj.admin,
obj.fastListingAdmin,
obj.securityAdmin,
obj.feesMngoTokenIndex as TokenIndex,
obj.insuranceMint,
obj.insuranceVault,
obj.testing,
obj.version,
obj.ixGate,
obj.feesPayWithMngo == 1,
obj.feesMngoBonusFactor,
obj.addressLookupTables,
obj.securityAdmin,
obj.depositLimitQuote,
obj.ixGate,
obj.feesSwapMangoAccount,
[], // addressLookupTablesList
new Map(), // banksMapByName
new Map(), // banksMapByMint
@ -76,13 +86,18 @@ export class Group {
public groupNum: number,
public admin: PublicKey,
public fastListingAdmin: PublicKey,
public securityAdmin: PublicKey,
public feesMngoTokenIndex: TokenIndex,
public insuranceMint: PublicKey,
public insuranceVault: PublicKey,
public testing: number,
public version: number,
public ixGate: BN,
public feesPayWithMngo: boolean,
public feesMngoBonusFactor: number,
public addressLookupTables: PublicKey[],
public securityAdmin: PublicKey,
public depositLimitQuote,
public ixGate: BN,
public feesSwapMangoAccount: PublicKey,
public addressLookupTablesList: AddressLookupTableAccount[],
public banksMapByName: Map<string, Bank[]>,
public banksMapByMint: Map<string, Bank[]>,

View File

@ -32,6 +32,7 @@ export class MangoAccount {
perpSpotTransfers: BN;
healthRegionBeginInitHealth: BN;
frozenUntil: BN;
buybackFeesAccrued: BN;
headerVersion: number;
tokens: unknown;
serum3: unknown;
@ -52,6 +53,7 @@ export class MangoAccount {
obj.perpSpotTransfers,
obj.healthRegionBeginInitHealth,
obj.frozenUntil,
obj.buybackFeesAccrued,
obj.headerVersion,
obj.tokens as TokenPositionDto[],
obj.serum3 as Serum3PositionDto[],
@ -74,6 +76,7 @@ export class MangoAccount {
public perpSpotTransfers: BN,
public healthRegionBeginInitHealth: BN,
public frozenUntil: BN,
public buybackFeesAccrued: BN,
public headerVersion: number,
tokens: TokenPositionDto[],
serum3: Serum3PositionDto[],

View File

@ -158,6 +158,10 @@ export class MangoClient {
testing?: number,
version?: number,
depositLimitQuote?: BN,
feesPayWithMngo?: boolean,
feesMngoBonusRate?: number,
feesSwapMangoAccount?: PublicKey,
feesMngoTokenIndex?: TokenIndex,
): Promise<TransactionSignature> {
const ix = await this.program.methods
.groupEdit(
@ -167,6 +171,10 @@ export class MangoClient {
testing ?? null,
version ?? null,
depositLimitQuote !== undefined ? depositLimitQuote : null,
feesPayWithMngo ?? null,
feesMngoBonusRate ?? null,
feesSwapMangoAccount ?? null,
feesMngoTokenIndex ?? null,
)
.accounts({
group: group.publicKey,

View File

@ -287,6 +287,8 @@ export function buildIxGate(p: IxGateParams): BN {
toggleIx(ixGate, p, 'TokenRegisterTrustless', 45);
toggleIx(ixGate, p, 'TokenUpdateIndexAndRate', 46);
toggleIx(ixGate, p, 'TokenWithdraw', 47);
toggleIx(ixGate, p, 'AccountSettleFeesWithMngo', 48);
return ixGate;
}

View File

@ -144,6 +144,30 @@ export type MangoV4 = {
"type": {
"option": "u64"
}
},
{
"name": "feesPayWithMngoOpt",
"type": {
"option": "bool"
}
},
{
"name": "feesMngoBonusFactorOpt",
"type": {
"option": "f32"
}
},
{
"name": "feesSwapMangoAccountOpt",
"type": {
"option": "publicKey"
}
},
{
"name": "feesMngoTokenIndexOpt",
"type": {
"option": "u16"
}
}
]
},
@ -1094,6 +1118,57 @@ export type MangoV4 = {
}
]
},
{
"name": "accountBuybackFeesWithMngo",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "daoAccount",
"isMut": true,
"isSigner": false
},
{
"name": "mngoBank",
"isMut": true,
"isSigner": false
},
{
"name": "mngoOracle",
"isMut": false,
"isSigner": false
},
{
"name": "feesBank",
"isMut": true,
"isSigner": false
},
{
"name": "feesOracle",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "maxBuyback",
"type": "u64"
}
]
},
{
"name": "stubOracleCreate",
"accounts": [
@ -3947,12 +4022,16 @@ export type MangoV4 = {
"name": "fastListingAdmin",
"type": "publicKey"
},
{
"name": "feesMngoTokenIndex",
"type": "u16"
},
{
"name": "padding",
"type": {
"array": [
"u8",
4
2
]
}
},
@ -3977,13 +4056,12 @@ export type MangoV4 = {
"type": "u8"
},
{
"name": "padding2",
"type": {
"array": [
"u8",
5
]
}
"name": "feesPayWithMngo",
"type": "u8"
},
{
"name": "feesMngoBonusFactor",
"type": "f32"
},
{
"name": "addressLookupTables",
@ -4006,12 +4084,16 @@ export type MangoV4 = {
"name": "ixGate",
"type": "u128"
},
{
"name": "feesSwapMangoAccount",
"type": "publicKey"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
1864
1832
]
}
}
@ -4104,12 +4186,16 @@ export type MangoV4 = {
"name": "frozenUntil",
"type": "u64"
},
{
"name": "discountBuybackFeesAccrued",
"type": "u64"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
232
224
]
}
},
@ -5679,12 +5765,16 @@ export type MangoV4 = {
"name": "frozenUntil",
"type": "u64"
},
{
"name": "discountBuybackFeesAccrued",
"type": "u64"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
232
224
]
}
}
@ -6682,6 +6772,9 @@ export type MangoV4 = {
},
{
"name": "TokenWithdraw"
},
{
"name": "AccountBuybackFeesWithMngo"
}
]
}
@ -8490,6 +8583,30 @@ export const IDL: MangoV4 = {
"type": {
"option": "u64"
}
},
{
"name": "feesPayWithMngoOpt",
"type": {
"option": "bool"
}
},
{
"name": "feesMngoBonusFactorOpt",
"type": {
"option": "f32"
}
},
{
"name": "feesSwapMangoAccountOpt",
"type": {
"option": "publicKey"
}
},
{
"name": "feesMngoTokenIndexOpt",
"type": {
"option": "u16"
}
}
]
},
@ -9440,6 +9557,57 @@ export const IDL: MangoV4 = {
}
]
},
{
"name": "accountBuybackFeesWithMngo",
"accounts": [
{
"name": "group",
"isMut": false,
"isSigner": false
},
{
"name": "account",
"isMut": true,
"isSigner": false
},
{
"name": "owner",
"isMut": false,
"isSigner": true
},
{
"name": "daoAccount",
"isMut": true,
"isSigner": false
},
{
"name": "mngoBank",
"isMut": true,
"isSigner": false
},
{
"name": "mngoOracle",
"isMut": false,
"isSigner": false
},
{
"name": "feesBank",
"isMut": true,
"isSigner": false
},
{
"name": "feesOracle",
"isMut": false,
"isSigner": false
}
],
"args": [
{
"name": "maxBuyback",
"type": "u64"
}
]
},
{
"name": "stubOracleCreate",
"accounts": [
@ -12293,12 +12461,16 @@ export const IDL: MangoV4 = {
"name": "fastListingAdmin",
"type": "publicKey"
},
{
"name": "feesMngoTokenIndex",
"type": "u16"
},
{
"name": "padding",
"type": {
"array": [
"u8",
4
2
]
}
},
@ -12323,13 +12495,12 @@ export const IDL: MangoV4 = {
"type": "u8"
},
{
"name": "padding2",
"type": {
"array": [
"u8",
5
]
}
"name": "feesPayWithMngo",
"type": "u8"
},
{
"name": "feesMngoBonusFactor",
"type": "f32"
},
{
"name": "addressLookupTables",
@ -12352,12 +12523,16 @@ export const IDL: MangoV4 = {
"name": "ixGate",
"type": "u128"
},
{
"name": "feesSwapMangoAccount",
"type": "publicKey"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
1864
1832
]
}
}
@ -12450,12 +12625,16 @@ export const IDL: MangoV4 = {
"name": "frozenUntil",
"type": "u64"
},
{
"name": "discountBuybackFeesAccrued",
"type": "u64"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
232
224
]
}
},
@ -14025,12 +14204,16 @@ export const IDL: MangoV4 = {
"name": "frozenUntil",
"type": "u64"
},
{
"name": "discountBuybackFeesAccrued",
"type": "u64"
},
{
"name": "reserved",
"type": {
"array": [
"u8",
232
224
]
}
}
@ -15028,6 +15211,9 @@ export const IDL: MangoV4 = {
},
{
"name": "TokenWithdraw"
},
{
"name": "AccountBuybackFeesWithMngo"
}
]
}