reduce only order type and mode for tokens and perp markets (#353)
* reduce only order type and mode for tokens and perp markets Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fix from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fix from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * tests Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * remove token Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * fixes Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
b174ee13cc
commit
86c9331647
2
anchor
2
anchor
|
@ -1 +1 @@
|
|||
Subproject commit b3707b1faaf6816cb3dd600074c81a39d373e952
|
||||
Subproject commit 309c2c2f4cce7c0a13d307fab3c7e2985bff3fa5
|
|
@ -162,7 +162,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
let owner = client::keypair_from_cli(&cmd.owner);
|
||||
let mint = client::pubkey_from_cli(&cmd.mint);
|
||||
let client = MangoClient::new_for_existing_account(client, account, owner).await?;
|
||||
let txsig = client.token_deposit(mint, cmd.amount).await?;
|
||||
let txsig = client.token_deposit(mint, cmd.amount, false).await?;
|
||||
println!("{}", txsig);
|
||||
}
|
||||
Command::JupiterSwap(cmd) => {
|
||||
|
|
|
@ -302,7 +302,12 @@ impl MangoClient {
|
|||
)
|
||||
}
|
||||
|
||||
pub async fn token_deposit(&self, mint: Pubkey, amount: u64) -> anyhow::Result<Signature> {
|
||||
pub async fn token_deposit(
|
||||
&self,
|
||||
mint: Pubkey,
|
||||
amount: u64,
|
||||
reduce_only: bool,
|
||||
) -> anyhow::Result<Signature> {
|
||||
let token = self.context.token_by_mint(&mint)?;
|
||||
let token_index = token.token_index;
|
||||
let mint_info = token.mint_info;
|
||||
|
@ -333,6 +338,7 @@ impl MangoClient {
|
|||
},
|
||||
data: anchor_lang::InstructionData::data(&mango_v4::instruction::TokenDeposit {
|
||||
amount,
|
||||
reduce_only,
|
||||
}),
|
||||
};
|
||||
self.send_and_confirm_owner_tx(vec![ix]).await
|
||||
|
|
|
@ -117,7 +117,7 @@ async fn ensure_deposit(mango_client: &Arc<MangoClient>) -> Result<(), anyhow::E
|
|||
|
||||
log::info!("Depositing {} {}", deposit_native, bank.name());
|
||||
mango_client
|
||||
.token_deposit(bank.mint, desired_balance.to_num())
|
||||
.token_deposit(bank.mint, desired_balance.to_num(), false)
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,10 @@ pub enum MangoError {
|
|||
TokenPositionDoesNotExist,
|
||||
#[msg("token deposits into accounts that are being liquidated must bring their health above the init threshold")]
|
||||
DepositsIntoLiquidatingMustRecover,
|
||||
#[msg("token is in reduce only mode")]
|
||||
TokenInReduceOnlyMode,
|
||||
#[msg("market is in reduce only mode")]
|
||||
MarketInReduceOnlyMode,
|
||||
}
|
||||
|
||||
impl MangoError {
|
||||
|
|
|
@ -376,8 +376,10 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
let mut token_loan_details = Vec::with_capacity(changes.len());
|
||||
for (change, oracle_price) in changes.iter().zip(oracle_prices.iter()) {
|
||||
let mut bank = health_ais[change.bank_index].load_mut::<Bank>()?;
|
||||
|
||||
let position = account.token_position_mut_by_raw_index(change.raw_token_index);
|
||||
let native = position.native(&bank);
|
||||
|
||||
let approved_amount = I80F48::from(bank.flash_loan_approved_amount);
|
||||
|
||||
let loan = if native.is_positive() {
|
||||
|
@ -386,8 +388,21 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
approved_amount
|
||||
};
|
||||
|
||||
let loan_origination_fee = cm!(loan * bank.loan_origination_fee_rate);
|
||||
cm!(bank.collected_fees_native += loan_origination_fee);
|
||||
|
||||
let change_amount = cm!(change.amount - loan_origination_fee);
|
||||
let native_after_change = cm!(native + change_amount);
|
||||
if bank.is_reduce_only() {
|
||||
require!(
|
||||
(change_amount < 0 && native_after_change >= 0)
|
||||
|| (change_amount > 0 && native_after_change < 1),
|
||||
MangoError::TokenInReduceOnlyMode
|
||||
);
|
||||
}
|
||||
|
||||
// Enforce min vault to deposits ratio
|
||||
if loan > 0 {
|
||||
if native_after_change < 0 {
|
||||
let vault_ai = vaults
|
||||
.iter()
|
||||
.find(|vault_ai| vault_ai.key == &bank.vault)
|
||||
|
@ -395,9 +410,6 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
|
|||
bank.enforce_min_vault_to_deposits_ratio(vault_ai)?;
|
||||
}
|
||||
|
||||
let loan_origination_fee = cm!(loan * bank.loan_origination_fee_rate);
|
||||
cm!(bank.collected_fees_native += loan_origination_fee);
|
||||
|
||||
let is_active = bank.change_without_fee(
|
||||
position,
|
||||
cm!(change.amount - loan_origination_fee),
|
||||
|
|
|
@ -32,6 +32,7 @@ pub use serum3_cancel_order::*;
|
|||
pub use serum3_close_open_orders::*;
|
||||
pub use serum3_create_open_orders::*;
|
||||
pub use serum3_deregister_market::*;
|
||||
pub use serum3_edit_market::*;
|
||||
pub use serum3_liq_force_cancel_orders::*;
|
||||
pub use serum3_place_order::*;
|
||||
pub use serum3_register_market::*;
|
||||
|
@ -84,6 +85,7 @@ mod serum3_cancel_order;
|
|||
mod serum3_close_open_orders;
|
||||
mod serum3_create_open_orders;
|
||||
mod serum3_deregister_market;
|
||||
mod serum3_edit_market;
|
||||
mod serum3_liq_force_cancel_orders;
|
||||
mod serum3_place_order;
|
||||
mod serum3_register_market;
|
||||
|
|
|
@ -130,7 +130,8 @@ pub fn perp_create_market(
|
|||
settle_pnl_limit_factor,
|
||||
padding3: Default::default(),
|
||||
settle_pnl_limit_window_size_ts,
|
||||
reserved: [0; 1944],
|
||||
reduce_only: 0,
|
||||
reserved: [0; 1943],
|
||||
};
|
||||
|
||||
let oracle_price =
|
||||
|
|
|
@ -49,6 +49,7 @@ pub fn perp_edit_market(
|
|||
stable_price_growth_limit_opt: Option<f32>,
|
||||
settle_pnl_limit_factor_opt: Option<f32>,
|
||||
settle_pnl_limit_window_size_ts_opt: Option<u64>,
|
||||
reduce_only_opt: Option<bool>,
|
||||
) -> Result<()> {
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
|
||||
|
@ -171,6 +172,10 @@ pub fn perp_edit_market(
|
|||
perp_market.settle_pnl_limit_window_size_ts = settle_pnl_limit_window_size_ts;
|
||||
}
|
||||
|
||||
if let Some(reduce_only) = reduce_only_opt {
|
||||
perp_market.reduce_only = if reduce_only { 1 } else { 0 };
|
||||
};
|
||||
|
||||
emit!(PerpMarketMetaDataLog {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
perp_market: ctx.accounts.perp_market.key(),
|
||||
|
|
|
@ -3,6 +3,7 @@ use anchor_lang::prelude::*;
|
|||
use crate::accounts_zerocopy::*;
|
||||
use crate::error::*;
|
||||
use crate::health::{new_fixed_order_account_retriever, new_health_cache};
|
||||
use crate::state::Side;
|
||||
use crate::state::{
|
||||
BookSide, EventQueue, Group, MangoAccountFixed, MangoAccountLoader, Order, Orderbook,
|
||||
PerpMarket,
|
||||
|
@ -38,7 +39,7 @@ pub struct PerpPlaceOrder<'info> {
|
|||
|
||||
// TODO
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn perp_place_order(ctx: Context<PerpPlaceOrder>, order: Order, limit: u8) -> Result<()> {
|
||||
pub fn perp_place_order(ctx: Context<PerpPlaceOrder>, mut order: Order, limit: u8) -> Result<()> {
|
||||
require_gte!(order.max_base_lots, 0);
|
||||
require_gte!(order.max_quote_lots, 0);
|
||||
|
||||
|
@ -109,8 +110,35 @@ pub fn perp_place_order(ctx: Context<PerpPlaceOrder>, order: Order, limit: u8) -
|
|||
|
||||
let now_ts: u64 = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
|
||||
// TODO apply reduce_only flag to compute final base_lots, also process event queue
|
||||
require!(order.reduce_only == false, MangoError::SomeError);
|
||||
let pp = account.perp_position(perp_market_index)?;
|
||||
let effective_pos = pp.effective_base_position_lots();
|
||||
let max_base_lots = if order.reduce_only || perp_market.is_reduce_only() {
|
||||
if (order.side == Side::Bid && effective_pos >= 0)
|
||||
|| (order.side == Side::Ask && effective_pos <= 0)
|
||||
{
|
||||
0
|
||||
} else if order.side == Side::Bid {
|
||||
// ignores open asks
|
||||
(effective_pos + pp.bids_base_lots)
|
||||
.min(0)
|
||||
.abs()
|
||||
.min(order.max_base_lots)
|
||||
} else {
|
||||
// ignores open bids
|
||||
(effective_pos - pp.asks_base_lots)
|
||||
.max(0)
|
||||
.min(order.max_base_lots)
|
||||
}
|
||||
} else {
|
||||
order.max_base_lots
|
||||
};
|
||||
if perp_market.is_reduce_only() {
|
||||
require!(
|
||||
order.reduce_only || max_base_lots == order.max_base_lots,
|
||||
MangoError::MarketInReduceOnlyMode
|
||||
)
|
||||
};
|
||||
order.max_base_lots = max_base_lots;
|
||||
|
||||
book.new_order(
|
||||
order,
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::state::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(market_index: Serum3MarketIndex)]
|
||||
pub struct Serum3EditMarket<'info> {
|
||||
#[account(
|
||||
has_one = admin,
|
||||
constraint = group.load()?.serum3_supported()
|
||||
)]
|
||||
pub group: AccountLoader<'info, Group>,
|
||||
pub admin: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group
|
||||
)]
|
||||
pub market: AccountLoader<'info, Serum3Market>,
|
||||
}
|
||||
|
||||
pub fn serum3_edit_market(
|
||||
ctx: Context<Serum3EditMarket>,
|
||||
reduce_only_opt: Option<bool>,
|
||||
) -> Result<()> {
|
||||
if let Some(reduce_only) = reduce_only_opt {
|
||||
ctx.accounts.market.load_mut()?.reduce_only = u8::from(reduce_only);
|
||||
};
|
||||
Ok(())
|
||||
}
|
|
@ -212,6 +212,10 @@ pub fn serum3_place_order(
|
|||
limit: u16,
|
||||
) -> Result<()> {
|
||||
let serum_market = ctx.accounts.serum_market.load()?;
|
||||
require!(
|
||||
!serum_market.is_reduce_only(),
|
||||
MangoError::MarketInReduceOnlyMode
|
||||
);
|
||||
|
||||
//
|
||||
// Validation
|
||||
|
|
|
@ -78,15 +78,16 @@ pub fn serum3_register_market(
|
|||
|
||||
let mut serum_market = ctx.accounts.serum_market.load_init()?;
|
||||
*serum_market = Serum3Market {
|
||||
name: fill_from_str(&name)?,
|
||||
group: ctx.accounts.group.key(),
|
||||
base_token_index: base_bank.token_index,
|
||||
quote_token_index: quote_bank.token_index,
|
||||
reduce_only: 0,
|
||||
padding1: Default::default(),
|
||||
name: fill_from_str(&name)?,
|
||||
serum_program: ctx.accounts.serum_program.key(),
|
||||
serum_market_external: ctx.accounts.serum_market_external.key(),
|
||||
market_index,
|
||||
base_token_index: base_bank.token_index,
|
||||
quote_token_index: quote_bank.token_index,
|
||||
bump: *ctx.bumps.get("serum_market").ok_or(MangoError::SomeError)?,
|
||||
padding1: Default::default(),
|
||||
padding2: Default::default(),
|
||||
registration_time: Clock::get()?.unix_timestamp.try_into().unwrap(),
|
||||
reserved: [0; 128],
|
||||
|
|
|
@ -100,20 +100,44 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
&self,
|
||||
remaining_accounts: &[AccountInfo],
|
||||
amount: u64,
|
||||
reduce_only: bool,
|
||||
allow_token_account_closure: bool,
|
||||
) -> Result<()> {
|
||||
require_msg!(amount > 0, "deposit amount must be positive");
|
||||
|
||||
let token_index = self.bank.load()?.token_index;
|
||||
let mut bank = self.bank.load_mut()?;
|
||||
let token_index = bank.token_index;
|
||||
|
||||
let amount_i80f48 = {
|
||||
// Get the account's position for that token index
|
||||
let account = self.account.load_full()?;
|
||||
let position = account.token_position(token_index)?;
|
||||
|
||||
let amount_i80f48 = if reduce_only || bank.is_reduce_only() {
|
||||
position
|
||||
.native(&bank)
|
||||
.min(I80F48::ZERO)
|
||||
.abs()
|
||||
.ceil()
|
||||
.min(I80F48::from(amount))
|
||||
} else {
|
||||
I80F48::from(amount)
|
||||
};
|
||||
if bank.is_reduce_only() {
|
||||
require!(
|
||||
reduce_only || amount_i80f48 == I80F48::from(amount),
|
||||
MangoError::TokenInReduceOnlyMode
|
||||
);
|
||||
}
|
||||
amount_i80f48
|
||||
};
|
||||
|
||||
// Get the account's position for that token index
|
||||
let mut account = self.account.load_full_mut()?;
|
||||
|
||||
let (position, raw_token_index) = account.token_position_mut(token_index)?;
|
||||
|
||||
let amount_i80f48 = I80F48::from(amount);
|
||||
let position_is_active = {
|
||||
let mut bank = self.bank.load_mut()?;
|
||||
bank.deposit(
|
||||
position,
|
||||
amount_i80f48,
|
||||
|
@ -122,10 +146,9 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
};
|
||||
|
||||
// Transfer the actual tokens
|
||||
token::transfer(self.transfer_ctx(), amount)?;
|
||||
token::transfer(self.transfer_ctx(), amount_i80f48.to_num::<u64>())?;
|
||||
|
||||
let indexed_position = position.indexed_position;
|
||||
let bank = self.bank.load()?;
|
||||
let oracle_price = bank.oracle_price(
|
||||
&AccountInfoRef::borrow(self.oracle.as_ref())?,
|
||||
None, // staleness checked in health
|
||||
|
@ -143,6 +166,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
deposit_index: bank.deposit_index.to_bits(),
|
||||
borrow_index: bank.borrow_index.to_bits(),
|
||||
});
|
||||
drop(bank);
|
||||
|
||||
//
|
||||
// Health computation
|
||||
|
@ -187,7 +211,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
|
||||
pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64, reduce_only: bool) -> Result<()> {
|
||||
{
|
||||
let token_index = ctx.accounts.bank.load()?.token_index;
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
|
@ -204,12 +228,13 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
|
|||
token_authority: &ctx.accounts.token_authority,
|
||||
token_program: &ctx.accounts.token_program,
|
||||
}
|
||||
.deposit_into_existing(ctx.remaining_accounts, amount, true)
|
||||
.deposit_into_existing(ctx.remaining_accounts, amount, reduce_only, true)
|
||||
}
|
||||
|
||||
pub fn token_deposit_into_existing(
|
||||
ctx: Context<TokenDepositIntoExisting>,
|
||||
amount: u64,
|
||||
reduce_only: bool,
|
||||
) -> Result<()> {
|
||||
DepositCommon {
|
||||
group: &ctx.accounts.group,
|
||||
|
@ -221,5 +246,5 @@ pub fn token_deposit_into_existing(
|
|||
token_authority: &ctx.accounts.token_authority,
|
||||
token_program: &ctx.accounts.token_program,
|
||||
}
|
||||
.deposit_into_existing(ctx.remaining_accounts, amount, false)
|
||||
.deposit_into_existing(ctx.remaining_accounts, amount, reduce_only, false)
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ pub fn token_edit(
|
|||
deposit_weight_scale_start_quote_opt: Option<f64>,
|
||||
reset_stable_price: bool,
|
||||
reset_net_borrow_limit: bool,
|
||||
reduce_only_opt: Option<bool>,
|
||||
) -> Result<()> {
|
||||
let mut mint_info = ctx.accounts.mint_info.load_mut()?;
|
||||
mint_info.verify_banks_ais(ctx.remaining_accounts)?;
|
||||
|
@ -170,6 +171,10 @@ pub fn token_edit(
|
|||
bank.deposit_weight_scale_start_quote = deposit_weight_scale_start_quote;
|
||||
}
|
||||
|
||||
if let Some(reduce_only) = reduce_only_opt {
|
||||
bank.reduce_only = if reduce_only { 1 } else { 0 };
|
||||
};
|
||||
|
||||
// unchanged -
|
||||
// dust
|
||||
// flash_loan_token_account_initial
|
||||
|
|
|
@ -150,7 +150,8 @@ pub fn token_register(
|
|||
net_borrows_in_window: 0,
|
||||
borrow_weight_scale_start_quote: f64::MAX,
|
||||
deposit_weight_scale_start_quote: f64::MAX,
|
||||
reserved: [0; 2120],
|
||||
reduce_only: 0,
|
||||
reserved: [0; 2119],
|
||||
};
|
||||
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
||||
|
||||
|
|
|
@ -82,6 +82,12 @@ pub fn token_register_trustless(
|
|||
mint: ctx.accounts.mint.key(),
|
||||
vault: ctx.accounts.vault.key(),
|
||||
oracle: ctx.accounts.oracle.key(),
|
||||
oracle_config: OracleConfig {
|
||||
conf_filter: I80F48::from_num(0.10),
|
||||
max_staleness_slots: -1,
|
||||
reserved: [0; 72],
|
||||
},
|
||||
stable_price_model: StablePriceModel::default(),
|
||||
deposit_index: INDEX_START,
|
||||
borrow_index: INDEX_START,
|
||||
indexed_deposits: I80F48::ZERO,
|
||||
|
@ -111,12 +117,6 @@ pub fn token_register_trustless(
|
|||
bump: *ctx.bumps.get("bank").ok_or(MangoError::SomeError)?,
|
||||
mint_decimals: ctx.accounts.mint.decimals,
|
||||
bank_num: 0,
|
||||
oracle_config: OracleConfig {
|
||||
conf_filter: I80F48::from_num(0.10),
|
||||
max_staleness_slots: -1,
|
||||
reserved: [0; 72],
|
||||
},
|
||||
stable_price_model: StablePriceModel::default(),
|
||||
min_vault_to_deposits_ratio: 0.2,
|
||||
net_borrow_limit_window_size_ts,
|
||||
last_net_borrows_window_start_ts: now_ts / net_borrow_limit_window_size_ts
|
||||
|
@ -125,7 +125,8 @@ pub fn token_register_trustless(
|
|||
net_borrows_in_window: 0,
|
||||
borrow_weight_scale_start_quote: f64::MAX,
|
||||
deposit_weight_scale_start_quote: f64::MAX,
|
||||
reserved: [0; 2120],
|
||||
reduce_only: 0,
|
||||
reserved: [0; 2119],
|
||||
};
|
||||
require_gt!(bank.max_rate, MINIMUM_MAX_RATE);
|
||||
|
||||
|
|
|
@ -96,6 +96,9 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
|
|||
|
||||
let is_borrow = amount > native_position;
|
||||
require!(allow_borrow || !is_borrow, MangoError::SomeError);
|
||||
if bank.is_reduce_only() {
|
||||
require!(!is_borrow, MangoError::TokenInReduceOnlyMode);
|
||||
}
|
||||
|
||||
let amount_i80f48 = I80F48::from(amount);
|
||||
|
||||
|
|
|
@ -130,6 +130,7 @@ pub mod mango_v4 {
|
|||
deposit_weight_scale_start_quote_opt: Option<f64>,
|
||||
reset_stable_price: bool,
|
||||
reset_net_borrow_limit: bool,
|
||||
reduce_only_opt: Option<bool>,
|
||||
) -> Result<()> {
|
||||
instructions::token_edit(
|
||||
ctx,
|
||||
|
@ -154,6 +155,7 @@ pub mod mango_v4 {
|
|||
deposit_weight_scale_start_quote_opt,
|
||||
reset_stable_price,
|
||||
reset_net_borrow_limit,
|
||||
reduce_only_opt,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -235,15 +237,16 @@ pub mod mango_v4 {
|
|||
instructions::stub_oracle_set(ctx, price)
|
||||
}
|
||||
|
||||
pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
|
||||
instructions::token_deposit(ctx, amount)
|
||||
pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64, reduce_only: bool) -> Result<()> {
|
||||
instructions::token_deposit(ctx, amount, reduce_only)
|
||||
}
|
||||
|
||||
pub fn token_deposit_into_existing(
|
||||
ctx: Context<TokenDepositIntoExisting>,
|
||||
amount: u64,
|
||||
reduce_only: bool,
|
||||
) -> Result<()> {
|
||||
instructions::token_deposit_into_existing(ctx, amount)
|
||||
instructions::token_deposit_into_existing(ctx, amount, reduce_only)
|
||||
}
|
||||
|
||||
pub fn token_withdraw(
|
||||
|
@ -292,6 +295,13 @@ pub mod mango_v4 {
|
|||
instructions::serum3_register_market(ctx, market_index, name)
|
||||
}
|
||||
|
||||
pub fn serum3_edit_market(
|
||||
ctx: Context<Serum3EditMarket>,
|
||||
reduce_only_opt: Option<bool>,
|
||||
) -> Result<()> {
|
||||
instructions::serum3_edit_market(ctx, reduce_only_opt)
|
||||
}
|
||||
|
||||
// note:
|
||||
// pub fn serum3_edit_market - doesn't exist since a mango serum3 market only contains the properties
|
||||
// registered base and quote token pairs, and serum3 external market its pointing to, and none of them
|
||||
|
@ -491,6 +501,7 @@ pub mod mango_v4 {
|
|||
stable_price_growth_limit_opt: Option<f32>,
|
||||
settle_pnl_limit_factor_opt: Option<f32>,
|
||||
settle_pnl_limit_window_size_ts: Option<u64>,
|
||||
reduce_only_opt: Option<bool>,
|
||||
) -> Result<()> {
|
||||
instructions::perp_edit_market(
|
||||
ctx,
|
||||
|
@ -518,6 +529,7 @@ pub mod mango_v4 {
|
|||
stable_price_growth_limit_opt,
|
||||
settle_pnl_limit_factor_opt,
|
||||
settle_pnl_limit_window_size_ts,
|
||||
reduce_only_opt,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -127,8 +127,10 @@ pub struct Bank {
|
|||
/// See scaled_init_asset_weight().
|
||||
pub deposit_weight_scale_start_quote: f64,
|
||||
|
||||
pub reduce_only: u8,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub reserved: [u8; 2120],
|
||||
pub reserved: [u8; 2119],
|
||||
}
|
||||
const_assert_eq!(
|
||||
size_of::<Bank>(),
|
||||
|
@ -155,7 +157,8 @@ const_assert_eq!(
|
|||
+ 8 * 4
|
||||
+ 8
|
||||
+ 8
|
||||
+ 2120
|
||||
+ 1
|
||||
+ 2119
|
||||
);
|
||||
const_assert_eq!(size_of::<Bank>(), 3064);
|
||||
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
||||
|
@ -215,7 +218,8 @@ impl Bank {
|
|||
net_borrows_in_window: 0,
|
||||
borrow_weight_scale_start_quote: f64::MAX,
|
||||
deposit_weight_scale_start_quote: f64::MAX,
|
||||
reserved: [0; 2120],
|
||||
reduce_only: 0,
|
||||
reserved: [0; 2119],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,6 +229,10 @@ impl Bank {
|
|||
.trim_matches(char::from(0))
|
||||
}
|
||||
|
||||
pub fn is_reduce_only(&self) -> bool {
|
||||
self.reduce_only == 1
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn native_borrows(&self) -> I80F48 {
|
||||
cm!(self.borrow_index * self.indexed_borrows)
|
||||
|
|
|
@ -284,6 +284,11 @@ impl PerpPosition {
|
|||
self.base_position_lots
|
||||
}
|
||||
|
||||
// This takes into account base lots from unprocessed events, but not anything from open orders
|
||||
pub fn effective_base_position_lots(&self) -> i64 {
|
||||
self.base_position_lots + self.taker_base_lots
|
||||
}
|
||||
|
||||
pub fn quote_position_native(&self) -> I80F48 {
|
||||
self.quote_position_native
|
||||
}
|
||||
|
|
|
@ -105,7 +105,9 @@ pub struct PerpMarket {
|
|||
/// Window size in seconds for the perp settlement limit
|
||||
pub settle_pnl_limit_window_size_ts: u64,
|
||||
|
||||
pub reserved: [u8; 1944],
|
||||
pub reduce_only: u8,
|
||||
|
||||
pub reserved: [u8; 1943],
|
||||
}
|
||||
|
||||
const_assert_eq!(
|
||||
|
@ -138,7 +140,8 @@ const_assert_eq!(
|
|||
+ 4 * 3
|
||||
+ 8
|
||||
+ 8
|
||||
+ 1944
|
||||
+ 1
|
||||
+ 1943
|
||||
);
|
||||
const_assert_eq!(size_of::<PerpMarket>(), 2808);
|
||||
const_assert_eq!(size_of::<PerpMarket>() % 8, 0);
|
||||
|
@ -150,6 +153,10 @@ impl PerpMarket {
|
|||
.trim_matches(char::from(0))
|
||||
}
|
||||
|
||||
pub fn is_reduce_only(&self) -> bool {
|
||||
self.reduce_only == 1
|
||||
}
|
||||
|
||||
pub fn elligible_for_group_insurance_fund(&self) -> bool {
|
||||
self.group_insurance_fund == 1
|
||||
}
|
||||
|
@ -306,6 +313,8 @@ impl PerpMarket {
|
|||
perp_market_index: 0,
|
||||
trusted_market: 0,
|
||||
group_insurance_fund: 0,
|
||||
bump: 0,
|
||||
base_decimals: 0,
|
||||
name: Default::default(),
|
||||
bids: Pubkey::new_unique(),
|
||||
asks: Pubkey::new_unique(),
|
||||
|
@ -325,8 +334,6 @@ impl PerpMarket {
|
|||
init_liab_weight: I80F48::from(1),
|
||||
open_interest: 0,
|
||||
seq_num: 0,
|
||||
bump: 0,
|
||||
base_decimals: 0,
|
||||
registration_time: 0,
|
||||
min_funding: I80F48::ZERO,
|
||||
max_funding: I80F48::ZERO,
|
||||
|
@ -343,10 +350,11 @@ impl PerpMarket {
|
|||
settle_fee_flat: 0.0,
|
||||
settle_fee_amount_threshold: 0.0,
|
||||
settle_fee_fraction_low_health: 0.0,
|
||||
padding3: Default::default(),
|
||||
settle_pnl_limit_factor: 0.2,
|
||||
padding3: Default::default(),
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
reserved: [0; 1944],
|
||||
reduce_only: 0,
|
||||
reserved: [0; 1943],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@ pub struct Serum3Market {
|
|||
pub base_token_index: TokenIndex,
|
||||
// ABI: Clients rely on this being at offset 42
|
||||
pub quote_token_index: TokenIndex,
|
||||
pub padding1: [u8; 4],
|
||||
pub reduce_only: u8,
|
||||
pub padding1: [u8; 3],
|
||||
pub name: [u8; 16],
|
||||
pub serum_program: Pubkey,
|
||||
pub serum_market_external: Pubkey,
|
||||
|
@ -32,7 +33,7 @@ pub struct Serum3Market {
|
|||
}
|
||||
const_assert_eq!(
|
||||
size_of::<Serum3Market>(),
|
||||
32 + 2 + 2 + 4 + 16 + 2 * 32 + 2 + 1 + 5 + 8 + 128
|
||||
32 + 2 + 2 + 1 + 3 + 16 + 2 * 32 + 2 + 1 + 5 + 8 + 128
|
||||
);
|
||||
const_assert_eq!(size_of::<Serum3Market>(), 264);
|
||||
const_assert_eq!(size_of::<Serum3Market>() % 8, 0);
|
||||
|
@ -43,6 +44,10 @@ impl Serum3Market {
|
|||
.unwrap()
|
||||
.trim_matches(char::from(0))
|
||||
}
|
||||
|
||||
pub fn is_reduce_only(&self) -> bool {
|
||||
self.reduce_only == 1
|
||||
}
|
||||
}
|
||||
|
||||
#[account(zero_copy)]
|
||||
|
|
|
@ -617,7 +617,7 @@ impl ClientInstruction for TokenWithdrawInstruction {
|
|||
|
||||
pub struct TokenDepositInstruction {
|
||||
pub amount: u64,
|
||||
|
||||
pub reduce_only: bool,
|
||||
pub account: Pubkey,
|
||||
pub owner: TestKeypair,
|
||||
pub token_account: Pubkey,
|
||||
|
@ -635,6 +635,7 @@ impl ClientInstruction for TokenDepositInstruction {
|
|||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction {
|
||||
amount: self.amount,
|
||||
reduce_only: self.reduce_only,
|
||||
};
|
||||
|
||||
// load account so we know its mint
|
||||
|
@ -688,7 +689,7 @@ impl ClientInstruction for TokenDepositInstruction {
|
|||
|
||||
pub struct TokenDepositIntoExistingInstruction {
|
||||
pub amount: u64,
|
||||
|
||||
pub reduce_only: bool,
|
||||
pub account: Pubkey,
|
||||
pub token_account: Pubkey,
|
||||
pub token_authority: TestKeypair,
|
||||
|
@ -705,6 +706,7 @@ impl ClientInstruction for TokenDepositIntoExistingInstruction {
|
|||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction {
|
||||
amount: self.amount,
|
||||
reduce_only: self.reduce_only,
|
||||
};
|
||||
|
||||
// load account so we know its mint
|
||||
|
@ -1083,6 +1085,7 @@ impl ClientInstruction for TokenResetStablePriceModel {
|
|||
deposit_weight_scale_start_quote_opt: None,
|
||||
reset_stable_price: true,
|
||||
reset_net_borrow_limit: false,
|
||||
reduce_only_opt: None,
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
|
@ -1160,6 +1163,82 @@ impl ClientInstruction for TokenResetNetBorrows {
|
|||
deposit_weight_scale_start_quote_opt: None,
|
||||
reset_stable_price: false,
|
||||
reset_net_borrow_limit: true,
|
||||
reduce_only_opt: None,
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
admin: self.admin.pubkey(),
|
||||
mint_info: mint_info_key,
|
||||
oracle: mint_info.oracle,
|
||||
};
|
||||
|
||||
let mut instruction = make_instruction(program_id, &accounts, instruction);
|
||||
instruction
|
||||
.accounts
|
||||
.extend(mint_info.banks().iter().map(|&k| AccountMeta {
|
||||
pubkey: k,
|
||||
is_signer: false,
|
||||
is_writable: true,
|
||||
}));
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<TestKeypair> {
|
||||
vec![self.admin]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TokenMakeReduceOnly {
|
||||
pub group: Pubkey,
|
||||
pub admin: TestKeypair,
|
||||
pub mint: Pubkey,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for TokenMakeReduceOnly {
|
||||
type Accounts = mango_v4::accounts::TokenEdit;
|
||||
type Instruction = mango_v4::instruction::TokenEdit;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
|
||||
let mint_info_key = Pubkey::find_program_address(
|
||||
&[
|
||||
b"MintInfo".as_ref(),
|
||||
self.group.as_ref(),
|
||||
self.mint.as_ref(),
|
||||
],
|
||||
&program_id,
|
||||
)
|
||||
.0;
|
||||
let mint_info: MintInfo = account_loader.load(&mint_info_key).await.unwrap();
|
||||
|
||||
let instruction = Self::Instruction {
|
||||
oracle_opt: None,
|
||||
oracle_config_opt: None,
|
||||
group_insurance_fund_opt: None,
|
||||
interest_rate_params_opt: None,
|
||||
loan_fee_rate_opt: None,
|
||||
loan_origination_fee_rate_opt: None,
|
||||
maint_asset_weight_opt: None,
|
||||
init_asset_weight_opt: None,
|
||||
maint_liab_weight_opt: None,
|
||||
init_liab_weight_opt: None,
|
||||
liquidation_fee_opt: None,
|
||||
stable_price_delay_interval_seconds_opt: None,
|
||||
stable_price_delay_growth_limit_opt: None,
|
||||
stable_price_growth_limit_opt: None,
|
||||
min_vault_to_deposits_ratio_opt: None,
|
||||
net_borrow_limit_per_window_quote_opt: None,
|
||||
net_borrow_limit_window_size_ts_opt: None,
|
||||
borrow_weight_scale_start_quote_opt: None,
|
||||
deposit_weight_scale_start_quote_opt: None,
|
||||
reset_stable_price: false,
|
||||
reset_net_borrow_limit: false,
|
||||
reduce_only_opt: Some(true),
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
|
@ -2570,6 +2649,69 @@ impl ClientInstruction for PerpResetStablePriceModel {
|
|||
stable_price_growth_limit_opt: None,
|
||||
settle_pnl_limit_factor_opt: None,
|
||||
settle_pnl_limit_window_size_ts: None,
|
||||
reduce_only_opt: None,
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
group: self.group,
|
||||
admin: self.admin.pubkey(),
|
||||
perp_market: self.perp_market,
|
||||
oracle: perp_market.oracle,
|
||||
};
|
||||
|
||||
let instruction = make_instruction(program_id, &accounts, instruction);
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<TestKeypair> {
|
||||
vec![self.admin]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PerpMakeReduceOnly {
|
||||
pub group: Pubkey,
|
||||
pub admin: TestKeypair,
|
||||
pub perp_market: Pubkey,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for PerpMakeReduceOnly {
|
||||
type Accounts = mango_v4::accounts::PerpEditMarket;
|
||||
type Instruction = mango_v4::instruction::PerpEditMarket;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
|
||||
let perp_market: PerpMarket = account_loader.load(&self.perp_market).await.unwrap();
|
||||
|
||||
let instruction = Self::Instruction {
|
||||
oracle_opt: None,
|
||||
oracle_config_opt: None,
|
||||
base_decimals_opt: None,
|
||||
maint_asset_weight_opt: None,
|
||||
init_asset_weight_opt: None,
|
||||
maint_liab_weight_opt: None,
|
||||
init_liab_weight_opt: None,
|
||||
liquidation_fee_opt: None,
|
||||
maker_fee_opt: None,
|
||||
taker_fee_opt: None,
|
||||
min_funding_opt: None,
|
||||
max_funding_opt: None,
|
||||
impact_quantity_opt: None,
|
||||
group_insurance_fund_opt: None,
|
||||
trusted_market_opt: None,
|
||||
fee_penalty_opt: None,
|
||||
settle_fee_flat_opt: None,
|
||||
settle_fee_amount_threshold_opt: None,
|
||||
settle_fee_fraction_low_health_opt: None,
|
||||
stable_price_delay_interval_seconds_opt: None,
|
||||
stable_price_delay_growth_limit_opt: None,
|
||||
stable_price_growth_limit_opt: None,
|
||||
settle_pnl_limit_factor_opt: None,
|
||||
settle_pnl_limit_window_size_ts: None,
|
||||
reduce_only_opt: Some(true),
|
||||
};
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
|
@ -2666,6 +2808,7 @@ pub struct PerpPlaceOrderInstruction {
|
|||
pub price_lots: i64,
|
||||
pub max_base_lots: i64,
|
||||
pub max_quote_lots: i64,
|
||||
pub reduce_only: bool,
|
||||
pub client_order_id: u64,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
|
@ -2684,7 +2827,7 @@ impl ClientInstruction for PerpPlaceOrderInstruction {
|
|||
max_quote_lots: self.max_quote_lots,
|
||||
client_order_id: self.client_order_id,
|
||||
order_type: PlaceOrderType::Limit,
|
||||
reduce_only: false,
|
||||
reduce_only: self.reduce_only,
|
||||
expiry_timestamp: 0,
|
||||
limit: 10,
|
||||
};
|
||||
|
|
|
@ -179,6 +179,7 @@ pub async fn create_funded_account(
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: amounts,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer.token_accounts[mint.index],
|
||||
|
|
|
@ -61,6 +61,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 20,
|
||||
reduce_only: false,
|
||||
account: vault_account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
|
@ -97,6 +98,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit1_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[2],
|
||||
|
@ -110,6 +112,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit2_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[3],
|
||||
|
@ -350,6 +353,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: vault_amount,
|
||||
reduce_only: false,
|
||||
account: vault_account,
|
||||
owner,
|
||||
token_account,
|
||||
|
@ -367,6 +371,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 20,
|
||||
reduce_only: false,
|
||||
account: vault_account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
|
@ -403,6 +408,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit1_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[2],
|
||||
|
@ -416,6 +422,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit2_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[3],
|
||||
|
|
|
@ -105,6 +105,7 @@ async fn test_basic() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint0_account,
|
||||
|
|
|
@ -146,6 +146,7 @@ async fn test_health_compute_serum() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 10,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
|
@ -252,6 +253,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -262,6 +264,7 @@ async fn test_health_compute_perp() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 10,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
|
|
|
@ -64,6 +64,7 @@ async fn test_health_wrap() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 1,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
|
@ -91,6 +92,7 @@ async fn test_health_wrap() -> Result<(), TransportError> {
|
|||
.await;
|
||||
tx.add_instruction(TokenDepositInstruction {
|
||||
amount: repay_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
|
|
|
@ -95,6 +95,7 @@ async fn test_liq_perps_force_cancel() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 1,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
|
@ -119,6 +120,7 @@ async fn test_liq_perps_force_cancel() -> Result<(), TransportError> {
|
|||
// health was 1000 * 0.6 = 600; this order is -14*100*(1.4-1) = -560
|
||||
max_base_lots: 14,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -318,6 +320,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
price_lots,
|
||||
max_base_lots: 20,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -333,6 +336,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
price_lots,
|
||||
max_base_lots: 20,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -610,6 +614,7 @@ async fn test_liq_perps_base_position_and_bankruptcy() -> Result<(), TransportEr
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 1,
|
||||
reduce_only: false,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
|
|
|
@ -223,6 +223,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 100000,
|
||||
reduce_only: false,
|
||||
account: vault_account,
|
||||
owner,
|
||||
token_account,
|
||||
|
@ -260,6 +261,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit1_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[2],
|
||||
|
@ -273,6 +275,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit2_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[3],
|
||||
|
|
|
@ -88,6 +88,7 @@ async fn test_margin_trade() -> Result<(), BanksClientError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit_amount_initial,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint0_account,
|
||||
|
|
|
@ -109,6 +109,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -150,6 +151,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 1,
|
||||
},
|
||||
)
|
||||
|
@ -184,6 +186,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 2,
|
||||
},
|
||||
)
|
||||
|
@ -217,6 +220,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 4,
|
||||
},
|
||||
)
|
||||
|
@ -250,6 +254,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 5,
|
||||
},
|
||||
)
|
||||
|
@ -267,6 +272,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 6,
|
||||
},
|
||||
)
|
||||
|
@ -328,6 +334,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 7,
|
||||
},
|
||||
)
|
||||
|
@ -345,6 +352,7 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 8,
|
||||
},
|
||||
)
|
||||
|
@ -609,6 +617,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 6,
|
||||
},
|
||||
)
|
||||
|
@ -691,6 +700,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 60,
|
||||
},
|
||||
)
|
||||
|
@ -720,6 +730,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 2,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 61,
|
||||
},
|
||||
)
|
||||
|
@ -771,6 +782,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
|||
price_lots: price_lots + 2,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 62,
|
||||
},
|
||||
)
|
||||
|
@ -800,6 +812,7 @@ async fn test_perp_oracle_peg() -> Result<(), TransportError> {
|
|||
price_lots: price_lots + 3,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 63,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -139,6 +139,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -155,6 +156,7 @@ async fn test_perp_settle_pnl() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -613,6 +615,7 @@ async fn test_perp_settle_pnl_fees() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -629,6 +632,7 @@ async fn test_perp_settle_pnl_fees() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -889,6 +893,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -905,6 +910,7 @@ async fn test_perp_pnl_settle_limit() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -79,6 +79,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit_amount,
|
||||
reduce_only: false,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
|
@ -93,6 +94,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit_amount,
|
||||
reduce_only: false,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
|
@ -111,6 +113,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit_amount,
|
||||
reduce_only: false,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
|
@ -125,6 +128,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit_amount,
|
||||
reduce_only: false,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
|
@ -214,6 +218,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
@ -230,6 +235,7 @@ async fn test_perp_settle_fees() -> Result<(), TransportError> {
|
|||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
|
|
|
@ -78,6 +78,7 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
solana,
|
||||
TokenDepositIntoExistingInstruction {
|
||||
amount: deposit_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
|
@ -93,6 +94,7 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: deposit_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_token,
|
||||
|
@ -109,6 +111,7 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
solana,
|
||||
TokenDepositIntoExistingInstruction {
|
||||
amount: deposit_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
|
@ -160,6 +163,7 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: collateral_amount,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
|
@ -197,6 +201,7 @@ async fn test_position_lifetime() -> Result<()> {
|
|||
TokenDepositInstruction {
|
||||
// deposit withdraw amount + some more to cover loan origination fees
|
||||
amount: borrow_amount + 2,
|
||||
reduce_only: false,
|
||||
account,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[1],
|
||||
|
|
|
@ -0,0 +1,610 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
use fixed::types::I80F48;
|
||||
use mango_setup::*;
|
||||
use mango_v4::state::{Bank, MangoAccount, PerpMarket, Side};
|
||||
use program_test::*;
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::transport::TransportError;
|
||||
|
||||
mod program_test;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_reduce_only_token() -> 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 payer_mint_accounts = &context.users[1].token_accounts[0..=2];
|
||||
|
||||
let initial_token_deposit = 10_000;
|
||||
|
||||
//
|
||||
// SETUP: Create a group and an account
|
||||
//
|
||||
|
||||
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints: mints.to_vec(),
|
||||
..GroupWithTokensConfig::default()
|
||||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
|
||||
//
|
||||
// SETUP: Prepare accounts
|
||||
//
|
||||
let account_0 = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
0,
|
||||
&context.users[1],
|
||||
&mints[0..=2],
|
||||
initial_token_deposit,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
let account_1 = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
1,
|
||||
&context.users[1],
|
||||
&mints[0..=1],
|
||||
initial_token_deposit,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
// make token reduce only
|
||||
send_tx(
|
||||
solana,
|
||||
TokenMakeReduceOnly {
|
||||
admin,
|
||||
group,
|
||||
mint: mints[0].pubkey,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// Withdraw deposits
|
||||
//
|
||||
|
||||
// deposit without reduce_only should fail
|
||||
let res = send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 10,
|
||||
reduce_only: false,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
|
||||
// deposit with reduce_only should pass with no effect
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: 10,
|
||||
reduce_only: true,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
token_authority: payer,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
||||
let bank = solana.get_account::<Bank>(tokens[0].bank).await;
|
||||
let native = mango_account_0.tokens[0].native(&bank);
|
||||
assert_eq!(native.to_num::<u64>(), initial_token_deposit);
|
||||
|
||||
// withdraw all should pass
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: initial_token_deposit,
|
||||
allow_borrow: false,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// borrowing should fail
|
||||
let res = send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: 1,
|
||||
allow_borrow: true,
|
||||
account: account_0,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[0],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
|
||||
//
|
||||
// Repay borrows
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
TokenWithdrawInstruction {
|
||||
amount: initial_token_deposit / 2,
|
||||
allow_borrow: true,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[2],
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// make token reduce only
|
||||
send_tx(
|
||||
solana,
|
||||
TokenMakeReduceOnly {
|
||||
admin,
|
||||
group,
|
||||
mint: mints[2].pubkey,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
TokenDepositInstruction {
|
||||
amount: initial_token_deposit,
|
||||
reduce_only: true,
|
||||
account: account_1,
|
||||
owner,
|
||||
token_account: payer_mint_accounts[2],
|
||||
token_authority: payer,
|
||||
bank_index: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_perp_reduce_only() -> 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 initial_token_deposit = 1000_000;
|
||||
|
||||
//
|
||||
// SETUP: Create a group and an account
|
||||
//
|
||||
|
||||
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints: mints.to_vec(),
|
||||
..GroupWithTokensConfig::default()
|
||||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
|
||||
let account_0 = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
0,
|
||||
&context.users[1],
|
||||
&mints[0..1],
|
||||
initial_token_deposit,
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
let account_1 = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
owner,
|
||||
1,
|
||||
&context.users[1],
|
||||
&mints[0..1],
|
||||
initial_token_deposit * 100, // Fund 100x, so that this is not the bound for what account_0 can settle
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
|
||||
//
|
||||
// TEST: 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_asset_weight: 0.975,
|
||||
init_asset_weight: 0.95,
|
||||
maint_liab_weight: 1.025,
|
||||
init_liab_weight: 1.05,
|
||||
liquidation_fee: 0.012,
|
||||
maker_fee: 0.0002,
|
||||
taker_fee: 0.000,
|
||||
settle_pnl_limit_factor: -1.,
|
||||
settle_pnl_limit_window_size_ts: 24 * 60 * 60,
|
||||
..PerpCreateMarketInstruction::with_new_book_and_queue(&solana, &tokens[1]).await
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let price_lots = {
|
||||
let perp_market = solana.get_account::<PerpMarket>(perp_market).await;
|
||||
perp_market.native_price_to_lot(I80F48::from(1000))
|
||||
};
|
||||
|
||||
// Set the initial oracle price
|
||||
set_perp_stub_oracle_price(solana, group, perp_market, &tokens[1], admin, 1000.0).await;
|
||||
|
||||
//
|
||||
// Place orders and create a position
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_0,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
max_base_lots: 2,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_1,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
max_base_lots: 2,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
send_tx(
|
||||
solana,
|
||||
PerpConsumeEventsInstruction {
|
||||
perp_market,
|
||||
mango_accounts: vec![account_0, account_1],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// account_0 - place a new bid
|
||||
// when user has a long, and market is in reduce only,
|
||||
// to reduce incoming asks to reduce position, we ignore existing bids
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_0,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots: price_lots / 2,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
// account_1 - place a new ask
|
||||
// when user has a short, and market is in reduce only,
|
||||
// to reduce incoming bids to reduce position, we ignore existing asks
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_1,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
//
|
||||
// Make market reduce only
|
||||
//
|
||||
send_tx(
|
||||
solana,
|
||||
PerpMakeReduceOnly {
|
||||
group,
|
||||
admin,
|
||||
perp_market,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// account_0 - place a new bid with reduce only false should fail
|
||||
let res = send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_0,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
|
||||
// account_0 - place a new bid with reduce only true should do nothing
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_0,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: true,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
||||
assert_eq!(mango_account_0.perps[0].bids_base_lots, 1);
|
||||
|
||||
// account_0 - place a new ask should pass
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_0,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
||||
assert_eq!(mango_account_0.perps[0].asks_base_lots, 1);
|
||||
|
||||
// account_0 - place a new ask should pass
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_0,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: true,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
||||
assert_eq!(mango_account_0.perps[0].asks_base_lots, 2);
|
||||
|
||||
// account_0 - place a new ask should fail if not reduce_only
|
||||
let res = send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_0,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
|
||||
// account_0 - place a new ask should pass but have no effect
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_0,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: true,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mango_account_0 = solana.get_account::<MangoAccount>(account_0).await;
|
||||
assert_eq!(mango_account_0.perps[0].asks_base_lots, 2);
|
||||
|
||||
// account_1 - place a new ask with reduce only false should fail
|
||||
let res = send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_1,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
|
||||
// account_1 - place a new ask with reduce only true should pass
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_1,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Ask,
|
||||
price_lots,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: true,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(mango_account_1.perps[0].asks_base_lots, 1);
|
||||
|
||||
// account_1 - place a new bid should pass
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_1,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots: price_lots / 2,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(mango_account_1.perps[0].bids_base_lots, 1);
|
||||
|
||||
// account_1 - place a new bid should pass
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_1,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots: price_lots / 2,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(mango_account_1.perps[0].bids_base_lots, 2);
|
||||
|
||||
// account_1 - place a new bid should fail if reduce only is false
|
||||
let res = send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_1,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots: price_lots / 2,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: false,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
assert!(res.is_err());
|
||||
|
||||
// account_1 - place a new bid should pass but have no effect
|
||||
send_tx(
|
||||
solana,
|
||||
PerpPlaceOrderInstruction {
|
||||
account: account_1,
|
||||
perp_market,
|
||||
owner,
|
||||
side: Side::Bid,
|
||||
price_lots: price_lots / 2,
|
||||
max_base_lots: 1,
|
||||
max_quote_lots: i64::MAX,
|
||||
reduce_only: true,
|
||||
client_order_id: 0,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let mango_account_1 = solana.get_account::<MangoAccount>(account_1).await;
|
||||
assert_eq!(mango_account_1.perps[0].bids_base_lots, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -316,6 +316,7 @@ export class MangoClient {
|
|||
depositWeightScaleStartQuote: number | null,
|
||||
resetStablePrice: boolean | null,
|
||||
resetNetBorrowLimit: boolean | null,
|
||||
reduceOnly: boolean | null,
|
||||
): Promise<TransactionSignature> {
|
||||
const bank = group.getFirstBankByMint(mintPk);
|
||||
const mintInfo = group.mintInfosMapByTokenIndex.get(bank.tokenIndex)!;
|
||||
|
@ -347,6 +348,7 @@ export class MangoClient {
|
|||
depositWeightScaleStartQuote,
|
||||
resetStablePrice ?? false,
|
||||
resetNetBorrowLimit ?? false,
|
||||
reduceOnly,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
@ -802,6 +804,7 @@ export class MangoClient {
|
|||
mangoAccount: MangoAccount,
|
||||
mintPk: PublicKey,
|
||||
amount: number,
|
||||
reduceOnly = false,
|
||||
): Promise<TransactionSignature> {
|
||||
const decimals = group.getMintDecimals(mintPk);
|
||||
const nativeAmount = toNative(amount, decimals);
|
||||
|
@ -810,6 +813,7 @@ export class MangoClient {
|
|||
mangoAccount,
|
||||
mintPk,
|
||||
nativeAmount,
|
||||
reduceOnly,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -818,6 +822,7 @@ export class MangoClient {
|
|||
mangoAccount: MangoAccount,
|
||||
mintPk: PublicKey,
|
||||
nativeAmount: BN,
|
||||
reduceOnly = false,
|
||||
): Promise<TransactionSignature> {
|
||||
const bank = group.getFirstBankByMint(mintPk);
|
||||
|
||||
|
@ -868,7 +873,7 @@ export class MangoClient {
|
|||
);
|
||||
|
||||
const ix = await this.program.methods
|
||||
.tokenDeposit(new BN(nativeAmount))
|
||||
.tokenDeposit(new BN(nativeAmount), reduceOnly)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
account: mangoAccount.publicKey,
|
||||
|
@ -1571,6 +1576,7 @@ export class MangoClient {
|
|||
stablePriceGrowthLimit: number | null,
|
||||
settlePnlLimitFactor: number | null,
|
||||
settlePnlLimitWindowSize: number | null,
|
||||
reduceOnly: boolean | null,
|
||||
): Promise<TransactionSignature> {
|
||||
const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
|
||||
|
||||
|
@ -1602,6 +1608,7 @@ export class MangoClient {
|
|||
settlePnlLimitWindowSize !== null
|
||||
? new BN(settlePnlLimitWindowSize)
|
||||
: null,
|
||||
reduceOnly,
|
||||
)
|
||||
.accounts({
|
||||
group: group.publicKey,
|
||||
|
|
|
@ -644,6 +644,12 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "resetNetBorrowLimit",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnlyOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1200,6 +1206,10 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "amount",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnly",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1251,6 +1261,10 @@ export type MangoV4 = {
|
|||
{
|
||||
"name": "amount",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnly",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -1511,6 +1525,34 @@ export type MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3EditMarket",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "admin",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "market",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "reduceOnlyOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3DeregisterMarket",
|
||||
"accounts": [
|
||||
|
@ -2660,6 +2702,12 @@ export type MangoV4 = {
|
|||
"type": {
|
||||
"option": "u64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reduceOnlyOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -3736,12 +3784,16 @@ export type MangoV4 = {
|
|||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnly",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2120
|
||||
2119
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -4430,12 +4482,16 @@ export type MangoV4 = {
|
|||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnly",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
1944
|
||||
1943
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -4459,12 +4515,16 @@ export type MangoV4 = {
|
|||
"name": "quoteTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnly",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding1",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
3
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -7403,6 +7463,21 @@ export type MangoV4 = {
|
|||
"code": 6028,
|
||||
"name": "TokenPositionDoesNotExist",
|
||||
"msg": "token position does not exist"
|
||||
},
|
||||
{
|
||||
"code": 6029,
|
||||
"name": "DepositsIntoLiquidatingMustRecover",
|
||||
"msg": "token deposits into accounts that are being liquidated must bring their health above the init threshold"
|
||||
},
|
||||
{
|
||||
"code": 6030,
|
||||
"name": "TokenInReduceOnlyMode",
|
||||
"msg": "token is in reduce only mode"
|
||||
},
|
||||
{
|
||||
"code": 6031,
|
||||
"name": "MarketInReduceOnlyMode",
|
||||
"msg": "market is in reduce only mode"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -8053,6 +8128,12 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "resetNetBorrowLimit",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnlyOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -8609,6 +8690,10 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "amount",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnly",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -8660,6 +8745,10 @@ export const IDL: MangoV4 = {
|
|||
{
|
||||
"name": "amount",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnly",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -8920,6 +9009,34 @@ export const IDL: MangoV4 = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3EditMarket",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "admin",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "market",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "reduceOnlyOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3DeregisterMarket",
|
||||
"accounts": [
|
||||
|
@ -10069,6 +10186,12 @@ export const IDL: MangoV4 = {
|
|||
"type": {
|
||||
"option": "u64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "reduceOnlyOpt",
|
||||
"type": {
|
||||
"option": "bool"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -11145,12 +11268,16 @@ export const IDL: MangoV4 = {
|
|||
],
|
||||
"type": "f64"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnly",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
2120
|
||||
2119
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -11839,12 +11966,16 @@ export const IDL: MangoV4 = {
|
|||
],
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnly",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "reserved",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
1944
|
||||
1943
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -11868,12 +11999,16 @@ export const IDL: MangoV4 = {
|
|||
"name": "quoteTokenIndex",
|
||||
"type": "u16"
|
||||
},
|
||||
{
|
||||
"name": "reduceOnly",
|
||||
"type": "u8"
|
||||
},
|
||||
{
|
||||
"name": "padding1",
|
||||
"type": {
|
||||
"array": [
|
||||
"u8",
|
||||
4
|
||||
3
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -14812,6 +14947,21 @@ export const IDL: MangoV4 = {
|
|||
"code": 6028,
|
||||
"name": "TokenPositionDoesNotExist",
|
||||
"msg": "token position does not exist"
|
||||
},
|
||||
{
|
||||
"code": 6029,
|
||||
"name": "DepositsIntoLiquidatingMustRecover",
|
||||
"msg": "token deposits into accounts that are being liquidated must bring their health above the init threshold"
|
||||
},
|
||||
{
|
||||
"code": 6030,
|
||||
"name": "TokenInReduceOnlyMode",
|
||||
"msg": "token is in reduce only mode"
|
||||
},
|
||||
{
|
||||
"code": 6031,
|
||||
"name": "MarketInReduceOnlyMode",
|
||||
"msg": "market is in reduce only mode"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { AnchorProvider, Wallet } from '@project-serum/anchor';
|
||||
import {
|
||||
AddressLookupTableProgram,
|
||||
ComputeBudgetProgram,
|
||||
Connection,
|
||||
Keypair,
|
||||
PublicKey,
|
||||
SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
SystemProgram,
|
||||
} from '@solana/web3.js';
|
||||
import fs from 'fs';
|
||||
import { TokenIndex } from '../accounts/bank';
|
||||
|
@ -14,8 +18,13 @@ import {
|
|||
Serum3Side,
|
||||
} from '../accounts/serum3';
|
||||
import { MangoClient } from '../client';
|
||||
import { MANGO_V4_ID } from '../constants';
|
||||
import { MANGO_V4_ID, OPENBOOK_PROGRAM_ID } from '../constants';
|
||||
import { buildVersionedTx, toNative } from '../utils';
|
||||
import {
|
||||
TOKEN_PROGRAM_ID,
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
NATIVE_MINT,
|
||||
} from '@solana/spl-token';
|
||||
|
||||
const GROUP_NUM = Number(process.env.GROUP_NUM || 0);
|
||||
|
||||
|
@ -554,11 +563,25 @@ async function createAndPopulateAlt() {
|
|||
}
|
||||
|
||||
console.log(`ALT: extending using mango v4 relevant public keys`);
|
||||
|
||||
await extendTable(bankAddresses);
|
||||
await extendTable([OPENBOOK_PROGRAM_ID['mainnet-beta']]);
|
||||
await extendTable(serum3MarketAddresses);
|
||||
await extendTable(serum3ExternalMarketAddresses);
|
||||
|
||||
// TODO: dont extend for perps atm
|
||||
// await extendTable(perpMarketAddresses);
|
||||
|
||||
// Well known addressess
|
||||
await extendTable([
|
||||
SystemProgram.programId,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
TOKEN_PROGRAM_ID,
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
NATIVE_MINT,
|
||||
SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
ComputeBudgetProgram.programId,
|
||||
]);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
@ -595,7 +618,7 @@ async function main() {
|
|||
}
|
||||
|
||||
try {
|
||||
// createAndPopulateAlt();
|
||||
createAndPopulateAlt();
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@ async function editPerpMarket(perpMarketName: string) {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
console.log('Tx Successful:', signature);
|
||||
|
|
|
@ -123,6 +123,7 @@ async function main() {
|
|||
null,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
async function setPerpPrice(
|
||||
|
@ -158,6 +159,7 @@ async function main() {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -262,6 +264,7 @@ async function main() {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
try {
|
||||
// At a price of $1/ui-SOL we can buy 0.1 ui-SOL for the 100k native-USDC we have.
|
||||
|
@ -304,6 +307,7 @@ async function main() {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,7 @@ import { MANGO_V4_ID } from '../constants';
|
|||
//
|
||||
|
||||
const GROUP_NUM = Number(process.env.GROUP_NUM || 200);
|
||||
const CLUSTER_URL =
|
||||
process.env.CLUSTER_URL ||
|
||||
'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88';
|
||||
const CLUSTER_URL = process.env.CLUSTER_URL;
|
||||
const MANGO_MAINNET_PAYER_KEYPAIR =
|
||||
process.env.MANGO_MAINNET_PAYER_KEYPAIR || '';
|
||||
|
||||
|
|
Loading…
Reference in New Issue