From cf3ebb5d966d807450a722a6d34edcbff8d2b20a Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Thu, 29 Dec 2022 14:13:57 +0100 Subject: [PATCH 1/9] Minor README updates --- README.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 80da290c6..7cd3349a6 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,23 @@ -### Development +## Development -- rust version 1.59.0 (9d1b2106e 2022-02-23) -- solana-cli 1.9.13 -- anchor-cli 0.24.2 +### Dependencies + +- rust version 1.65.0 +- solana-cli 1.14.9 - npm 8.1.2 - node v16.13.1 -Devnet deployment - 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg +### Submodules + +After cloning this repo you'll need to init and update its git submodules. +Consider setting the git option `submodule.recurse=true`. + +### Deployments + +Devnet - 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg +Mainnet-beta - 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg + +### Notes For testing latest program changes while developing, just run below scripts in given order form any branch, From 9ada3f0574d81e0dfd2f8835589dfee91093d4bd Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Thu, 29 Dec 2022 14:14:47 +0100 Subject: [PATCH 2/9] README formatting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7cd3349a6..606d5302c 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ Consider setting the git option `submodule.recurse=true`. ### Deployments -Devnet - 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg -Mainnet-beta - 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg +- devnet: 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg +- mainnet-beta: 4MangoMjqJ2firMokCjjGgoK8d4MXcrgL7XJaL3w6fVg ### Notes From 31bd72e84a46a5e65697cd2bd9c0079a72f6f658 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Thu, 29 Dec 2022 11:48:46 +0100 Subject: [PATCH 3/9] Drop AccountLoaderDynamic This gives us better compatibility with released anchor versions. Instead of using AccountLoaderDynamic, we now use a standard AccountLoader. This will generally work (except for load_init(), which is dangerous). A new trait, MangoAccountLoader, provides load_full(), load_full_mut() etc on the AccountLoader to create accessor structs that can read and write to the dynamic part of the mango account data. --- anchor | 2 +- liquidator/src/util.rs | 6 +- .../src/instructions/account_close.rs | 4 +- .../src/instructions/account_create.rs | 4 +- .../mango-v4/src/instructions/account_edit.rs | 4 +- .../src/instructions/account_expand.rs | 4 +- .../src/instructions/compute_account_data.rs | 4 +- .../mango-v4/src/instructions/flash_loan.rs | 11 +- .../src/instructions/health_region.rs | 8 +- .../instructions/perp_cancel_all_orders.rs | 6 +- .../perp_cancel_all_orders_by_side.rs | 10 +- .../src/instructions/perp_cancel_order.rs | 6 +- .../perp_cancel_order_by_client_order_id.rs | 6 +- .../src/instructions/perp_consume_events.rs | 25 +- .../instructions/perp_deactivate_position.rs | 4 +- .../src/instructions/perp_liq_bankruptcy.rs | 8 +- .../instructions/perp_liq_base_position.rs | 8 +- .../perp_liq_force_cancel_orders.rs | 4 +- .../src/instructions/perp_place_order.rs | 8 +- .../src/instructions/perp_settle_fees.rs | 7 +- .../src/instructions/perp_settle_pnl.rs | 15 +- .../instructions/serum3_cancel_all_orders.rs | 4 +- .../src/instructions/serum3_cancel_order.rs | 4 +- .../instructions/serum3_close_open_orders.rs | 4 +- .../instructions/serum3_create_open_orders.rs | 4 +- .../serum3_liq_force_cancel_orders.rs | 10 +- .../src/instructions/serum3_place_order.rs | 6 +- .../src/instructions/serum3_settle_funds.rs | 8 +- .../src/instructions/token_deposit.rs | 10 +- .../src/instructions/token_liq_bankruptcy.rs | 8 +- .../src/instructions/token_liq_with_token.rs | 8 +- .../src/instructions/token_withdraw.rs | 4 +- .../mango-v4/src/state/dynamic_account.rs | 231 +----------------- programs/mango-v4/src/state/mango_account.rs | 126 ++++++++-- 34 files changed, 221 insertions(+), 360 deletions(-) diff --git a/anchor b/anchor index 309c2c2f4..b3707b1fa 160000 --- a/anchor +++ b/anchor @@ -1 +1 @@ -Subproject commit 309c2c2f4cce7c0a13d307fab3c7e2985bff3fa5 +Subproject commit b3707b1faaf6816cb3dd600074c81a39d373e952 diff --git a/liquidator/src/util.rs b/liquidator/src/util.rs index 880562ca2..59b7d3414 100644 --- a/liquidator/src/util.rs +++ b/liquidator/src/util.rs @@ -1,7 +1,7 @@ use anchor_lang::Discriminator; use arrayref::array_ref; -use mango_v4::state::{Bank, MangoAccount, MangoAccountRefWithHeader, MintInfo, PerpMarket}; +use mango_v4::state::{Bank, MangoAccount, MangoAccountLoadedRef, MintInfo, PerpMarket}; use solana_sdk::account::{AccountSharedData, ReadableAccount}; use solana_sdk::pubkey::Pubkey; @@ -10,7 +10,7 @@ pub fn is_mango_account<'a>( account: &'a AccountSharedData, program_id: &Pubkey, group_id: &Pubkey, -) -> Option> { +) -> Option> { let data = account.data(); if account.owner() != program_id || data.len() < 8 { return None; @@ -21,7 +21,7 @@ pub fn is_mango_account<'a>( return None; } - if let Ok(mango_account) = MangoAccountRefWithHeader::from_bytes(&data[8..]) { + if let Ok(mango_account) = MangoAccountLoadedRef::from_bytes(&data[8..]) { if mango_account.fixed.group != *group_id { return None; } diff --git a/programs/mango-v4/src/instructions/account_close.rs b/programs/mango-v4/src/instructions/account_close.rs index ee7e34c23..017969a76 100644 --- a/programs/mango-v4/src/instructions/account_close.rs +++ b/programs/mango-v4/src/instructions/account_close.rs @@ -14,7 +14,7 @@ pub struct AccountClose<'info> { has_one = owner, close = sol_destination )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account(mut)] @@ -25,7 +25,7 @@ pub struct AccountClose<'info> { } pub fn account_close(ctx: Context, force_close: bool) -> Result<()> { - let account = ctx.accounts.account.load_mut()?; + let account = ctx.accounts.account.load_full_mut()?; if !ctx.accounts.group.load()?.is_testing() { require!(!force_close, MangoError::SomeError); diff --git a/programs/mango-v4/src/instructions/account_create.rs b/programs/mango-v4/src/instructions/account_create.rs index 3da39f06f..d52e0cf27 100644 --- a/programs/mango-v4/src/instructions/account_create.rs +++ b/programs/mango-v4/src/instructions/account_create.rs @@ -16,7 +16,7 @@ pub struct AccountCreate<'info> { payer = payer, space = MangoAccount::space(token_count, serum3_count, perp_count, perp_oo_count)?, )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account(mut)] @@ -34,7 +34,7 @@ pub fn account_create( perp_oo_count: u8, name: String, ) -> Result<()> { - let mut account = ctx.accounts.account.load_init()?; + let mut account = ctx.accounts.account.load_full_init()?; msg!( "Initialized account with header version {}", diff --git a/programs/mango-v4/src/instructions/account_edit.rs b/programs/mango-v4/src/instructions/account_edit.rs index 52596f7d6..70621c7dc 100644 --- a/programs/mango-v4/src/instructions/account_edit.rs +++ b/programs/mango-v4/src/instructions/account_edit.rs @@ -13,7 +13,7 @@ pub struct AccountEdit<'info> { has_one = group, has_one = owner )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, } @@ -28,7 +28,7 @@ pub fn account_edit( MangoError::SomeError ); - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; // note: unchanged fields are inline, and match exact definition in create_account // please maintain, and don't remove, makes it easy to reason about which support modification by owner diff --git a/programs/mango-v4/src/instructions/account_expand.rs b/programs/mango-v4/src/instructions/account_expand.rs index 8f0397d12..fe655228e 100644 --- a/programs/mango-v4/src/instructions/account_expand.rs +++ b/programs/mango-v4/src/instructions/account_expand.rs @@ -12,7 +12,7 @@ pub struct AccountExpand<'info> { has_one = group, has_one = owner )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account(mut)] @@ -53,7 +53,7 @@ pub fn account_expand( realloc_account.realloc(new_space, false)?; // expand dynamic content, e.g. to grow token positions, we need to slide serum3orders further later, and so on.... - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; account.expand_dynamic_content(token_count, serum3_count, perp_count, perp_oo_count)?; Ok(()) diff --git a/programs/mango-v4/src/instructions/compute_account_data.rs b/programs/mango-v4/src/instructions/compute_account_data.rs index 4acdd586b..6a7732991 100644 --- a/programs/mango-v4/src/instructions/compute_account_data.rs +++ b/programs/mango-v4/src/instructions/compute_account_data.rs @@ -6,13 +6,13 @@ pub struct ComputeAccountData<'info> { pub group: AccountLoader<'info, Group>, #[account(has_one = group)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, } pub fn compute_account_data(ctx: Context) -> Result<()> { let group_pk = ctx.accounts.group.key(); - let account = ctx.accounts.account.load()?; + let account = ctx.accounts.account.load_full()?; let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, &group_pk)?; diff --git a/programs/mango-v4/src/instructions/flash_loan.rs b/programs/mango-v4/src/instructions/flash_loan.rs index 14355a0dd..5f8d1a56f 100644 --- a/programs/mango-v4/src/instructions/flash_loan.rs +++ b/programs/mango-v4/src/instructions/flash_loan.rs @@ -3,8 +3,7 @@ use crate::error::*; use crate::group_seeds; use crate::health::{new_fixed_order_account_retriever, new_health_cache, AccountRetriever}; use crate::logs::{FlashLoanLog, FlashLoanTokenDetail, TokenBalanceLog}; -use crate::state::MangoAccount; -use crate::state::{AccountLoaderDynamic, Bank, Group, TokenIndex}; +use crate::state::*; use crate::util::checked_math as cm; use anchor_lang::prelude::*; use anchor_lang::solana_program::sysvar::instructions as tx_instructions; @@ -32,7 +31,7 @@ pub mod jupiter_mainnet_3 { /// 4. the mango group #[derive(Accounts)] pub struct FlashLoanBegin<'info> { - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, // owner is checked at #1 pub owner: Signer<'info>, @@ -55,7 +54,7 @@ pub struct FlashLoanBegin<'info> { #[derive(Accounts)] pub struct FlashLoanEnd<'info> { #[account(mut)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, // owner is checked at #1 pub owner: Signer<'info>, @@ -75,7 +74,7 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>( ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanBegin<'info>>, loan_amounts: Vec, ) -> Result<()> { - let account = ctx.accounts.account.load_mut()?; + let account = ctx.accounts.account.load_full_mut()?; // account constraint #1 require!( @@ -239,7 +238,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanEnd<'info>>, flash_loan_type: FlashLoanType, ) -> Result<()> { - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; // account constraint #1 require!( diff --git a/programs/mango-v4/src/instructions/health_region.rs b/programs/mango-v4/src/instructions/health_region.rs index 285380c4b..312b75d15 100644 --- a/programs/mango-v4/src/instructions/health_region.rs +++ b/programs/mango-v4/src/instructions/health_region.rs @@ -19,7 +19,7 @@ pub struct HealthRegionBegin<'info> { pub instructions: UncheckedAccount<'info>, #[account(mut)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, } /// Ends a health region. @@ -28,7 +28,7 @@ pub struct HealthRegionBegin<'info> { #[derive(Accounts)] pub struct HealthRegionEnd<'info> { #[account(mut)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, } pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>( @@ -69,7 +69,7 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>( ); } - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; require_msg!( !account.fixed.is_in_health_region(), "account must not already be health wrapped" @@ -91,7 +91,7 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>( pub fn health_region_end<'key, 'accounts, 'remaining, 'info>( ctx: Context<'key, 'accounts, 'remaining, 'info, HealthRegionEnd<'info>>, ) -> Result<()> { - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; require_msg!( account.fixed.is_in_health_region(), "account must be health wrapped" diff --git a/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs b/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs index ae442f57b..3145c79a3 100644 --- a/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs +++ b/programs/mango-v4/src/instructions/perp_cancel_all_orders.rs @@ -1,14 +1,14 @@ use anchor_lang::prelude::*; use crate::error::MangoError; -use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket}; +use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket}; #[derive(Accounts)] pub struct PerpCancelAllOrders<'info> { pub group: AccountLoader<'info, Group>, #[account(mut, has_one = group)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account( @@ -25,7 +25,7 @@ pub struct PerpCancelAllOrders<'info> { } pub fn perp_cancel_all_orders(ctx: Context, limit: u8) -> Result<()> { - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), MangoError::SomeError diff --git a/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs b/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs index f983d0f6b..75d1befb7 100644 --- a/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs +++ b/programs/mango-v4/src/instructions/perp_cancel_all_orders_by_side.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use crate::error::MangoError; use crate::state::{ - AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket, Side, + BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket, Side, }; #[derive(Accounts)] @@ -10,7 +10,7 @@ pub struct PerpCancelAllOrdersBySide<'info> { pub group: AccountLoader<'info, Group>, #[account(mut, has_one = group)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account( @@ -31,7 +31,7 @@ pub fn perp_cancel_all_orders_by_side( side_option: Option, limit: u8, ) -> Result<()> { - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; require_keys_eq!(account.fixed.group, ctx.accounts.group.key()); require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), @@ -40,8 +40,8 @@ pub fn perp_cancel_all_orders_by_side( let mut perp_market = ctx.accounts.perp_market.load_mut()?; let mut book = Orderbook { - bids: ctx.accounts.bids.load_init()?, - asks: ctx.accounts.asks.load_init()?, + bids: ctx.accounts.bids.load_mut()?, + asks: ctx.accounts.asks.load_mut()?, }; book.cancel_all_orders( diff --git a/programs/mango-v4/src/instructions/perp_cancel_order.rs b/programs/mango-v4/src/instructions/perp_cancel_order.rs index c60666eb9..e688c7ec2 100644 --- a/programs/mango-v4/src/instructions/perp_cancel_order.rs +++ b/programs/mango-v4/src/instructions/perp_cancel_order.rs @@ -1,14 +1,14 @@ use anchor_lang::prelude::*; use crate::error::*; -use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket}; +use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket}; #[derive(Accounts)] pub struct PerpCancelOrder<'info> { pub group: AccountLoader<'info, Group>, #[account(mut, has_one = group)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account( @@ -25,7 +25,7 @@ pub struct PerpCancelOrder<'info> { } pub fn perp_cancel_order(ctx: Context, order_id: u128) -> Result<()> { - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), MangoError::SomeError diff --git a/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs b/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs index 0009b607a..f9baaae0e 100644 --- a/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs +++ b/programs/mango-v4/src/instructions/perp_cancel_order_by_client_order_id.rs @@ -1,14 +1,14 @@ use anchor_lang::prelude::*; use crate::error::*; -use crate::state::{AccountLoaderDynamic, BookSide, Group, MangoAccount, Orderbook, PerpMarket}; +use crate::state::{BookSide, Group, MangoAccountFixed, MangoAccountLoader, Orderbook, PerpMarket}; #[derive(Accounts)] pub struct PerpCancelOrderByClientOrderId<'info> { pub group: AccountLoader<'info, Group>, #[account(mut, has_one = group)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account( @@ -28,7 +28,7 @@ pub fn perp_cancel_order_by_client_order_id( ctx: Context, client_order_id: u64, ) -> Result<()> { - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), MangoError::SomeError diff --git a/programs/mango-v4/src/instructions/perp_consume_events.rs b/programs/mango-v4/src/instructions/perp_consume_events.rs index 68fcbc9d8..411085f78 100644 --- a/programs/mango-v4/src/instructions/perp_consume_events.rs +++ b/programs/mango-v4/src/instructions/perp_consume_events.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use bytemuck::cast_ref; use crate::error::MangoError; -use crate::state::{AccountLoaderDynamic, EventQueue, MangoAccount}; +use crate::state::{EventQueue, MangoAccountFixed, MangoAccountLoader}; use crate::state::{EventType, FillEvent, Group, OutEvent, PerpMarket}; use crate::logs::{emit_perp_balances, FillLog}; @@ -55,9 +55,9 @@ pub fn perp_consume_events(ctx: Context, limit: usize) -> Res continue; } - let mal: AccountLoaderDynamic = - AccountLoaderDynamic::try_from(ai)?; - let mut ma = mal.load_mut()?; + let mal: AccountLoader = + AccountLoader::try_from(ai)?; + let mut ma = mal.load_full_mut()?; ma.execute_perp_maker( perp_market.perp_market_index, &mut perp_market, @@ -90,9 +90,9 @@ pub fn perp_consume_events(ctx: Context, limit: usize) -> Res continue; } - let mal: AccountLoaderDynamic = - AccountLoaderDynamic::try_from(ai)?; - let mut maker = mal.load_mut()?; + let mal: AccountLoader = + AccountLoader::try_from(ai)?; + let mut maker = mal.load_full_mut()?; match mango_account_ais.iter().find(|ai| ai.key == &fill.taker) { None => { @@ -106,9 +106,9 @@ pub fn perp_consume_events(ctx: Context, limit: usize) -> Res continue; } - let mal: AccountLoaderDynamic = - AccountLoaderDynamic::try_from(ai)?; - let mut taker = mal.load_mut()?; + let mal: AccountLoader = + AccountLoader::try_from(ai)?; + let mut taker = mal.load_full_mut()?; maker.execute_perp_maker( perp_market.perp_market_index, @@ -174,9 +174,8 @@ pub fn perp_consume_events(ctx: Context, limit: usize) -> Res continue; } - let mal: AccountLoaderDynamic = - AccountLoaderDynamic::try_from(ai)?; - let mut ma = mal.load_mut()?; + let mal: AccountLoader = AccountLoader::try_from(ai)?; + let mut ma = mal.load_full_mut()?; ma.remove_perp_order(out.owner_slot as usize, out.quantity)?; } diff --git a/programs/mango-v4/src/instructions/perp_deactivate_position.rs b/programs/mango-v4/src/instructions/perp_deactivate_position.rs index fc866bcbf..6c6668c20 100644 --- a/programs/mango-v4/src/instructions/perp_deactivate_position.rs +++ b/programs/mango-v4/src/instructions/perp_deactivate_position.rs @@ -12,7 +12,7 @@ pub struct PerpDeactivatePosition<'info> { has_one = group // owner is checked at #1 )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account(has_one = group)] @@ -20,7 +20,7 @@ pub struct PerpDeactivatePosition<'info> { } pub fn perp_deactivate_position(ctx: Context) -> Result<()> { - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; // account constraint #1 require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), diff --git a/programs/mango-v4/src/instructions/perp_liq_bankruptcy.rs b/programs/mango-v4/src/instructions/perp_liq_bankruptcy.rs index 33b104ff3..007355976 100644 --- a/programs/mango-v4/src/instructions/perp_liq_bankruptcy.rs +++ b/programs/mango-v4/src/instructions/perp_liq_bankruptcy.rs @@ -28,14 +28,14 @@ pub struct PerpLiqBankruptcy<'info> { has_one = group // liqor_owner is checked at #1 )] - pub liqor: AccountLoaderDynamic<'info, MangoAccount>, + pub liqor: AccountLoader<'info, MangoAccountFixed>, pub liqor_owner: Signer<'info>, #[account( mut, has_one = group )] - pub liqee: AccountLoaderDynamic<'info, MangoAccount>, + pub liqee: AccountLoader<'info, MangoAccountFixed>, #[account( mut, @@ -78,7 +78,7 @@ pub fn perp_liq_bankruptcy(ctx: Context, max_liab_transfer: u let group = ctx.accounts.group.load()?; let group_pk = &ctx.accounts.group.key(); - let mut liqor = ctx.accounts.liqor.load_mut()?; + let mut liqor = ctx.accounts.liqor.load_full_mut()?; // account constraint #1 require!( liqor @@ -88,7 +88,7 @@ pub fn perp_liq_bankruptcy(ctx: Context, max_liab_transfer: u ); require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated); - let mut liqee = ctx.accounts.liqee.load_mut()?; + let mut liqee = ctx.accounts.liqee.load_full_mut()?; let mut liqee_health_cache = { let account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk)?; new_health_cache(&liqee.borrow(), &account_retriever) diff --git a/programs/mango-v4/src/instructions/perp_liq_base_position.rs b/programs/mango-v4/src/instructions/perp_liq_base_position.rs index 311e0a18e..4cfbb4ca4 100644 --- a/programs/mango-v4/src/instructions/perp_liq_base_position.rs +++ b/programs/mango-v4/src/instructions/perp_liq_base_position.rs @@ -24,11 +24,11 @@ pub struct PerpLiqBasePosition<'info> { has_one = group // liqor_owner is checked at #1 )] - pub liqor: AccountLoaderDynamic<'info, MangoAccount>, + pub liqor: AccountLoader<'info, MangoAccountFixed>, pub liqor_owner: Signer<'info>, #[account(mut, has_one = group)] - pub liqee: AccountLoaderDynamic<'info, MangoAccount>, + pub liqee: AccountLoader<'info, MangoAccountFixed>, } pub fn perp_liq_base_position( @@ -37,7 +37,7 @@ pub fn perp_liq_base_position( ) -> Result<()> { let group_pk = &ctx.accounts.group.key(); - let mut liqor = ctx.accounts.liqor.load_mut()?; + let mut liqor = ctx.accounts.liqor.load_full_mut()?; // account constraint #1 require!( liqor @@ -47,7 +47,7 @@ pub fn perp_liq_base_position( ); require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated); - let mut liqee = ctx.accounts.liqee.load_mut()?; + let mut liqee = ctx.accounts.liqee.load_full_mut()?; // Initial liqee health check let mut liqee_health_cache = { diff --git a/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs b/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs index 2b6836b40..b33901a1d 100644 --- a/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs +++ b/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs @@ -10,7 +10,7 @@ pub struct PerpLiqForceCancelOrders<'info> { pub group: AccountLoader<'info, Group>, #[account(mut, has_one = group)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, #[account( mut, @@ -29,7 +29,7 @@ pub fn perp_liq_force_cancel_orders( ctx: Context, limit: u8, ) -> Result<()> { - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; // // Check liqee health if liquidation is allowed diff --git a/programs/mango-v4/src/instructions/perp_place_order.rs b/programs/mango-v4/src/instructions/perp_place_order.rs index ab96e8fa1..06794b872 100644 --- a/programs/mango-v4/src/instructions/perp_place_order.rs +++ b/programs/mango-v4/src/instructions/perp_place_order.rs @@ -3,9 +3,9 @@ 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::MangoAccount; use crate::state::{ - AccountLoaderDynamic, BookSide, EventQueue, Group, Order, Orderbook, PerpMarket, + BookSide, EventQueue, Group, MangoAccountFixed, MangoAccountLoader, Order, Orderbook, + PerpMarket, }; #[derive(Accounts)] @@ -13,7 +13,7 @@ pub struct PerpPlaceOrder<'info> { pub group: AccountLoader<'info, Group>, #[account(mut, has_one = group)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account( @@ -64,7 +64,7 @@ pub fn perp_place_order(ctx: Context, order: Order, limit: u8) - perp_market.update_funding_and_stable_price(&book, oracle_price, now_ts)?; } - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), MangoError::SomeError diff --git a/programs/mango-v4/src/instructions/perp_settle_fees.rs b/programs/mango-v4/src/instructions/perp_settle_fees.rs index f2edeae82..fd6e07941 100644 --- a/programs/mango-v4/src/instructions/perp_settle_fees.rs +++ b/programs/mango-v4/src/instructions/perp_settle_fees.rs @@ -6,8 +6,7 @@ use crate::accounts_zerocopy::*; use crate::error::*; use crate::health::{compute_health, new_fixed_order_account_retriever, HealthType}; use crate::state::Bank; -use crate::state::MangoAccount; -use crate::state::{AccountLoaderDynamic, Group, PerpMarket}; +use crate::state::{Group, MangoAccountFixed, MangoAccountLoader, PerpMarket}; use crate::logs::{emit_perp_balances, PerpSettleFeesLog, TokenBalanceLog}; @@ -20,7 +19,7 @@ pub struct PerpSettleFees<'info> { // This account MUST have a loss #[account(mut, has_one = group)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, /// CHECK: Oracle can have different account types, constrained by address in perp_market pub oracle: UncheckedAccount<'info>, @@ -40,7 +39,7 @@ pub fn perp_settle_fees(ctx: Context, max_settle_amount: u64) -> MangoError::MaxSettleAmountMustBeGreaterThanZero ); - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; let mut bank = ctx.accounts.settle_bank.load_mut()?; let mut perp_market = ctx.accounts.perp_market.load_mut()?; diff --git a/programs/mango-v4/src/instructions/perp_settle_pnl.rs b/programs/mango-v4/src/instructions/perp_settle_pnl.rs index d51fe8b8f..3b814ff53 100644 --- a/programs/mango-v4/src/instructions/perp_settle_pnl.rs +++ b/programs/mango-v4/src/instructions/perp_settle_pnl.rs @@ -7,8 +7,7 @@ use crate::error::*; use crate::health::{new_health_cache, HealthType, ScanningAccountRetriever}; use crate::logs::{emit_perp_balances, PerpSettlePnlLog, TokenBalanceLog}; use crate::state::Bank; -use crate::state::MangoAccount; -use crate::state::{AccountLoaderDynamic, Group, PerpMarket}; +use crate::state::{Group, MangoAccountFixed, MangoAccountLoader, PerpMarket}; #[derive(Accounts)] pub struct PerpSettlePnl<'info> { @@ -19,7 +18,7 @@ pub struct PerpSettlePnl<'info> { has_one = group, // settler_owner is checked at #1 )] - pub settler: AccountLoaderDynamic<'info, MangoAccount>, + pub settler: AccountLoader<'info, MangoAccountFixed>, pub settler_owner: Signer<'info>, #[account(has_one = group, has_one = oracle)] @@ -27,10 +26,10 @@ pub struct PerpSettlePnl<'info> { // This account MUST be profitable #[account(mut, has_one = group)] - pub account_a: AccountLoaderDynamic<'info, MangoAccount>, + pub account_a: AccountLoader<'info, MangoAccountFixed>, // This account MUST have a loss #[account(mut, has_one = group)] - pub account_b: AccountLoaderDynamic<'info, MangoAccount>, + pub account_b: AccountLoader<'info, MangoAccountFixed>, /// CHECK: Oracle can have different account types, constrained by address in perp_market pub oracle: UncheckedAccount<'info>, @@ -58,8 +57,8 @@ pub fn perp_settle_pnl(ctx: Context) -> Result<()> { ) }; - let mut account_a = ctx.accounts.account_a.load_mut()?; - let mut account_b = ctx.accounts.account_b.load_mut()?; + let mut account_a = ctx.accounts.account_a.load_full_mut()?; + let mut account_b = ctx.accounts.account_b.load_full_mut()?; // check positions exist, for nicer error messages { @@ -223,7 +222,7 @@ pub fn perp_settle_pnl(ctx: Context) -> Result<()> { drop(account_a); drop(account_b); - let mut settler = ctx.accounts.settler.load_mut()?; + let mut settler = ctx.accounts.settler.load_full_mut()?; // account constraint #1 require!( settler diff --git a/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs b/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs index 6ac6f95ae..9bb32086d 100644 --- a/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_cancel_all_orders.rs @@ -14,7 +14,7 @@ pub struct Serum3CancelAllOrders<'info> { has_one = group // owner is checked at #1 )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account(mut)] @@ -51,7 +51,7 @@ pub fn serum3_cancel_all_orders(ctx: Context, limit: u8) // Validation // { - let account = ctx.accounts.account.load()?; + let account = ctx.accounts.account.load_full()?; // account constraint #1 require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), diff --git a/programs/mango-v4/src/instructions/serum3_cancel_order.rs b/programs/mango-v4/src/instructions/serum3_cancel_order.rs index 89645c1f9..428cc7f8e 100644 --- a/programs/mango-v4/src/instructions/serum3_cancel_order.rs +++ b/programs/mango-v4/src/instructions/serum3_cancel_order.rs @@ -20,7 +20,7 @@ pub struct Serum3CancelOrder<'info> { has_one = group // owner is checked at #1 )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account(mut)] @@ -63,7 +63,7 @@ pub fn serum3_cancel_order( // Validation // { - let account = ctx.accounts.account.load()?; + let account = ctx.accounts.account.load_full()?; // account constraint #1 require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), diff --git a/programs/mango-v4/src/instructions/serum3_close_open_orders.rs b/programs/mango-v4/src/instructions/serum3_close_open_orders.rs index 7d0cebe91..d6da507a5 100644 --- a/programs/mango-v4/src/instructions/serum3_close_open_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_close_open_orders.rs @@ -12,7 +12,7 @@ pub struct Serum3CloseOpenOrders<'info> { has_one = group // owner is checked at #1 )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account( @@ -39,7 +39,7 @@ pub fn serum3_close_open_orders(ctx: Context) -> Result<( // // Validation // - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; // account constraint #1 require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), diff --git a/programs/mango-v4/src/instructions/serum3_create_open_orders.rs b/programs/mango-v4/src/instructions/serum3_create_open_orders.rs index ff6e34e97..fa40cdb14 100644 --- a/programs/mango-v4/src/instructions/serum3_create_open_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_create_open_orders.rs @@ -12,7 +12,7 @@ pub struct Serum3CreateOpenOrders<'info> { has_one = group // owner is checked at #1 )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account( @@ -51,7 +51,7 @@ pub fn serum3_create_open_orders(ctx: Context) -> Result let serum_market = ctx.accounts.serum_market.load()?; - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; // account constraint #1 require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), diff --git a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs index 93f00dd88..bd68ccb48 100644 --- a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs @@ -17,7 +17,7 @@ pub struct Serum3LiqForceCancelOrders<'info> { pub group: AccountLoader<'info, Group>, #[account(mut, has_one = group)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, #[account(mut)] /// CHECK: Validated inline by checking against the pubkey stored in the account at #2 @@ -77,7 +77,7 @@ pub fn serum3_liq_force_cancel_orders( // let serum_market = ctx.accounts.serum_market.load()?; { - let account = ctx.accounts.account.load()?; + let account = ctx.accounts.account.load_full()?; // Validate open_orders #2 require!( @@ -113,7 +113,7 @@ pub fn serum3_liq_force_cancel_orders( // Check liqee health if liquidation is allowed // let mut health_cache = { - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?; let health_cache = @@ -146,7 +146,7 @@ pub fn serum3_liq_force_cancel_orders( let before_oo = { let open_orders = load_open_orders_ref(ctx.accounts.open_orders.as_ref())?; let before_oo = OpenOrdersSlim::from_oo(&open_orders); - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; charge_loan_origination_fees( @@ -208,7 +208,7 @@ pub fn serum3_liq_force_cancel_orders( require_gte!(after_quote_vault, before_quote_vault); // Credit the difference in vault balances to the user's account - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; apply_vault_difference( diff --git a/programs/mango-v4/src/instructions/serum3_place_order.rs b/programs/mango-v4/src/instructions/serum3_place_order.rs index 5c89ba9ed..6a04fda64 100644 --- a/programs/mango-v4/src/instructions/serum3_place_order.rs +++ b/programs/mango-v4/src/instructions/serum3_place_order.rs @@ -142,7 +142,7 @@ pub struct Serum3PlaceOrder<'info> { has_one = group // owner is checked at #1 )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account(mut)] @@ -217,7 +217,7 @@ pub fn serum3_place_order( // Validation // { - let account = ctx.accounts.account.load()?; + let account = ctx.accounts.account.load_full()?; // account constraint #1 require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), @@ -246,7 +246,7 @@ pub fn serum3_place_order( // // Pre-health computation // - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; let pre_health_opt = if !account.fixed.is_in_health_region() { let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?; diff --git a/programs/mango-v4/src/instructions/serum3_settle_funds.rs b/programs/mango-v4/src/instructions/serum3_settle_funds.rs index d4f8fadc1..d4deab75d 100644 --- a/programs/mango-v4/src/instructions/serum3_settle_funds.rs +++ b/programs/mango-v4/src/instructions/serum3_settle_funds.rs @@ -20,7 +20,7 @@ pub struct Serum3SettleFunds<'info> { has_one = group // owner is checked at #1 )] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account(mut)] @@ -76,7 +76,7 @@ pub fn serum3_settle_funds(ctx: Context) -> Result<()> { // Validation // { - let account = ctx.accounts.account.load()?; + let account = ctx.accounts.account.load_full()?; // account constraint #1 require!( account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), @@ -119,7 +119,7 @@ pub fn serum3_settle_funds(ctx: Context) -> Result<()> { { let open_orders = load_open_orders_ref(ctx.accounts.open_orders.as_ref())?; let before_oo = OpenOrdersSlim::from_oo(&open_orders); - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; charge_loan_origination_fees( @@ -155,7 +155,7 @@ pub fn serum3_settle_funds(ctx: Context) -> Result<()> { require_gte!(after_quote_vault, before_quote_vault); // Credit the difference in vault balances to the user's account - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; apply_vault_difference( diff --git a/programs/mango-v4/src/instructions/token_deposit.rs b/programs/mango-v4/src/instructions/token_deposit.rs index f31d028e8..9ee258dfc 100644 --- a/programs/mango-v4/src/instructions/token_deposit.rs +++ b/programs/mango-v4/src/instructions/token_deposit.rs @@ -18,7 +18,7 @@ pub struct TokenDepositIntoExisting<'info> { pub group: AccountLoader<'info, Group>, #[account(mut, has_one = group)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, #[account( mut, @@ -48,7 +48,7 @@ pub struct TokenDeposit<'info> { pub group: AccountLoader<'info, Group>, #[account(mut, has_one = group, has_one = owner)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account( @@ -76,7 +76,7 @@ pub struct TokenDeposit<'info> { struct DepositCommon<'a, 'info> { pub group: &'a AccountLoader<'info, Group>, - pub account: &'a AccountLoaderDynamic<'info, MangoAccount>, + pub account: &'a AccountLoader<'info, MangoAccountFixed>, pub bank: &'a AccountLoader<'info, Bank>, pub vault: &'a Account<'info, TokenAccount>, pub oracle: &'a UncheckedAccount<'info>, @@ -107,7 +107,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> { let token_index = self.bank.load()?.token_index; // Get the account's position for that token index - let mut account = self.account.load_mut()?; + let mut account = self.account.load_full_mut()?; let (position, raw_token_index) = account.token_position_mut(token_index)?; @@ -190,7 +190,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> { pub fn token_deposit(ctx: Context, amount: u64) -> Result<()> { { let token_index = ctx.accounts.bank.load()?.token_index; - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; account.ensure_token_position(token_index)?; } diff --git a/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs b/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs index df07a4179..eef154b9c 100644 --- a/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs +++ b/programs/mango-v4/src/instructions/token_liq_bankruptcy.rs @@ -30,14 +30,14 @@ pub struct TokenLiqBankruptcy<'info> { has_one = group // liqor_owner is checked at #1 )] - pub liqor: AccountLoaderDynamic<'info, MangoAccount>, + pub liqor: AccountLoader<'info, MangoAccountFixed>, pub liqor_owner: Signer<'info>, #[account( mut, has_one = group )] - pub liqee: AccountLoaderDynamic<'info, MangoAccount>, + pub liqee: AccountLoader<'info, MangoAccountFixed>, #[account( has_one = group, @@ -81,7 +81,7 @@ pub fn token_liq_bankruptcy( let (bank_ais, health_ais) = &ctx.remaining_accounts.split_at(liab_mint_info.num_banks()); liab_mint_info.verify_banks_ais(bank_ais)?; - let mut liqor = ctx.accounts.liqor.load_mut()?; + let mut liqor = ctx.accounts.liqor.load_full_mut()?; // account constraint #1 require!( liqor @@ -93,7 +93,7 @@ pub fn token_liq_bankruptcy( let mut account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?; - let mut liqee = ctx.accounts.liqee.load_mut()?; + let mut liqee = ctx.accounts.liqee.load_full_mut()?; let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever) .context("create liqee health cache")?; require!( diff --git a/programs/mango-v4/src/instructions/token_liq_with_token.rs b/programs/mango-v4/src/instructions/token_liq_with_token.rs index a5c4760bc..f1c62d651 100644 --- a/programs/mango-v4/src/instructions/token_liq_with_token.rs +++ b/programs/mango-v4/src/instructions/token_liq_with_token.rs @@ -20,14 +20,14 @@ pub struct TokenLiqWithToken<'info> { has_one = group // liqor_owner is checked at #1 )] - pub liqor: AccountLoaderDynamic<'info, MangoAccount>, + pub liqor: AccountLoader<'info, MangoAccountFixed>, pub liqor_owner: Signer<'info>, #[account( mut, has_one = group )] - pub liqee: AccountLoaderDynamic<'info, MangoAccount>, + pub liqee: AccountLoader<'info, MangoAccountFixed>, } pub fn token_liq_with_token( @@ -42,7 +42,7 @@ pub fn token_liq_with_token( let mut account_retriever = ScanningAccountRetriever::new(ctx.remaining_accounts, group_pk) .context("create account retriever")?; - let mut liqor = ctx.accounts.liqor.load_mut()?; + let mut liqor = ctx.accounts.liqor.load_full_mut()?; // account constraint #1 require!( liqor @@ -52,7 +52,7 @@ pub fn token_liq_with_token( ); require!(!liqor.fixed.being_liquidated(), MangoError::BeingLiquidated); - let mut liqee = ctx.accounts.liqee.load_mut()?; + let mut liqee = ctx.accounts.liqee.load_full_mut()?; // Initial liqee health check let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever) diff --git a/programs/mango-v4/src/instructions/token_withdraw.rs b/programs/mango-v4/src/instructions/token_withdraw.rs index 657227763..8750d95ba 100644 --- a/programs/mango-v4/src/instructions/token_withdraw.rs +++ b/programs/mango-v4/src/instructions/token_withdraw.rs @@ -18,7 +18,7 @@ pub struct TokenWithdraw<'info> { pub group: AccountLoader<'info, Group>, #[account(mut, has_one = group, has_one = owner)] - pub account: AccountLoaderDynamic<'info, MangoAccount>, + pub account: AccountLoader<'info, MangoAccountFixed>, pub owner: Signer<'info>, #[account( @@ -62,7 +62,7 @@ pub fn token_withdraw(ctx: Context, amount: u64, allow_borrow: bo let token_index = ctx.accounts.bank.load()?.token_index; // Create the account's position for that token index - let mut account = ctx.accounts.account.load_mut()?; + let mut account = ctx.accounts.account.load_full_mut()?; let (_, raw_token_index, _) = account.ensure_token_position(token_index)?; // Health check _after_ the token position is guaranteed to exist diff --git a/programs/mango-v4/src/state/dynamic_account.rs b/programs/mango-v4/src/state/dynamic_account.rs index 0a5047b42..e4bc2e6a5 100644 --- a/programs/mango-v4/src/state/dynamic_account.rs +++ b/programs/mango-v4/src/state/dynamic_account.rs @@ -1,25 +1,15 @@ use std::cell::{Ref, RefMut}; -use std::marker::PhantomData; -use std::mem::size_of; - use anchor_lang::prelude::*; -use anchor_lang::Discriminator; -use arrayref::array_ref; -// Header is created by scanning and parsing dynamic portion of the account -// Header stores useful information e.g. offsets to easily seek into dynamic content +/// Header is created by scanning and parsing the dynamic portion of the account. +/// This stores useful information e.g. offsets to easily seek into dynamic content. pub trait DynamicHeader: Sized { - // build header by scanning and parsing dynamic portion of the account - fn from_bytes(data: &[u8]) -> Result; + /// Builds header by scanning and parsing the dynamic portion of the account. + fn from_bytes(dynamic_data: &[u8]) -> Result; - // initialize a header on a new account, if necessary - fn initialize(data: &mut [u8]) -> Result<()>; -} - -pub trait DynamicAccountType: Owner + Discriminator { - type Header: DynamicHeader; - type Fixed: bytemuck::Pod; + /// initializes a header on the dynamic portion of a new account + fn initialize(dynamic_data: &mut [u8]) -> Result<()>; } #[derive(Clone)] @@ -29,19 +19,6 @@ pub struct DynamicAccount { pub dynamic: Dynamic, } -pub type DynamicAccountValue = - DynamicAccount<::Header, ::Fixed, Vec>; -pub type DynamicAccountRef<'a, D> = DynamicAccount< - &'a ::Header, - &'a ::Fixed, - &'a [u8], ->; -pub type DynamicAccountRefMut<'a, D> = DynamicAccount< - &'a mut ::Header, - &'a mut ::Fixed, - &'a mut [u8], ->; - // Want to generalize over: // - T (which is Borrow) // - &T (which is Borrow and Deref) @@ -113,199 +90,3 @@ impl DerefOrBorrowMut<[T]> for Vec { self } } - -pub struct AccountLoaderDynamic<'info, D: DynamicAccountType> { - /// CHECK: is checked below - acc_info: AccountInfo<'info>, - phantom1: PhantomData<&'info D>, -} - -impl<'info, D: DynamicAccountType> AccountLoaderDynamic<'info, D> { - pub fn try_from(acc_info: &AccountInfo<'info>) -> Result { - if acc_info.owner != &D::owner() { - return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram) - .with_pubkeys((*acc_info.owner, D::owner()))); - } - - let data = acc_info.try_borrow_data()?; - if data.len() < D::discriminator().len() { - return Err(ErrorCode::AccountDiscriminatorNotFound.into()); - } - let disc_bytes = array_ref![data, 0, 8]; - if disc_bytes != &D::discriminator() { - return Err(ErrorCode::AccountDiscriminatorMismatch.into()); - } - - Ok(Self { - acc_info: acc_info.clone(), - phantom1: PhantomData, - }) - } - - pub fn try_from_unchecked(acc_info: &AccountInfo<'info>) -> Result { - if acc_info.owner != &D::owner() { - return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram) - .with_pubkeys((*acc_info.owner, D::owner()))); - } - Ok(Self { - acc_info: acc_info.clone(), - phantom1: PhantomData, - }) - } - - /// Returns a Ref to the account data structure for reading. - pub fn load_fixed(&self) -> Result> { - let data = self.acc_info.try_borrow_data()?; - let fixed = Ref::map(data, |d| { - bytemuck::from_bytes(&d[8..8 + size_of::()]) - }); - Ok(fixed) - } - - #[allow(clippy::type_complexity)] - /// Returns a Ref to the account data structure for reading. - pub fn load(&self) -> Result, Ref<[u8]>>> { - let data = self.acc_info.try_borrow_data()?; - let header = D::Header::from_bytes(&data[8 + size_of::()..])?; - let (_, data) = Ref::map_split(data, |d| d.split_at(8)); - let (fixed_bytes, dynamic) = Ref::map_split(data, |d| d.split_at(size_of::())); - Ok(DynamicAccount { - header, - fixed: Ref::map(fixed_bytes, |b| bytemuck::from_bytes(b)), - dynamic, - }) - } - - #[allow(clippy::type_complexity)] - pub fn load_init(&self) -> Result, RefMut<[u8]>>> { - if !self.acc_info.is_writable { - return Err(ErrorCode::AccountNotMutable.into()); - } - - let mut data = self.acc_info.try_borrow_mut_data()?; - let mut disc_bytes = [0u8; 8]; - disc_bytes.copy_from_slice(&data[..8]); - let discriminator = u64::from_le_bytes(disc_bytes); - if discriminator != 0 { - return Err(ErrorCode::AccountDiscriminatorAlreadySet.into()); - } - - let disc_bytes: &mut [u8] = &mut data[0..8]; - disc_bytes.copy_from_slice(bytemuck::bytes_of(&(D::discriminator()))); - - D::Header::initialize(&mut data[8 + size_of::()..])?; - - drop(data); - - self.load_mut() - } - - /// Returns a Ref to the account data structure for reading. - #[allow(clippy::type_complexity)] - pub fn load_mut(&self) -> Result, RefMut<[u8]>>> { - if !self.acc_info.is_writable { - return Err(ErrorCode::AccountNotMutable.into()); - } - - let data = self.acc_info.try_borrow_mut_data()?; - let header = D::Header::from_bytes(&data[8 + size_of::()..])?; - let (_, data) = RefMut::map_split(data, |d| d.split_at_mut(8)); - let (fixed_bytes, dynamic) = - RefMut::map_split(data, |d| d.split_at_mut(size_of::())); - Ok(DynamicAccount { - header, - fixed: RefMut::map(fixed_bytes, |b| bytemuck::from_bytes_mut(b)), - dynamic, - }) - } -} - -impl<'info, D: DynamicAccountType> anchor_lang::Accounts<'info> for AccountLoaderDynamic<'info, D> { - #[inline(never)] - fn try_accounts( - _program_id: &Pubkey, - accounts: &mut &[AccountInfo<'info>], - _ix_data: &[u8], - _bumps: &mut std::collections::BTreeMap, - _reallocs: &mut std::collections::BTreeSet, - ) -> Result { - if accounts.is_empty() { - return Err(ErrorCode::AccountNotEnoughKeys.into()); - } - let account = &accounts[0]; - *accounts = &accounts[1..]; - let l = AccountLoaderDynamic::try_from(account)?; - Ok(l) - } -} - -impl<'info, D: DynamicAccountType> anchor_lang::AccountsExit<'info> - for AccountLoaderDynamic<'info, D> -{ - fn exit(&self, _program_id: &Pubkey) -> Result<()> { - // Normally anchor writes the discriminator again here, but I don't see why - let data = self.acc_info.try_borrow_data()?; - if data.len() < D::discriminator().len() { - return Err(ErrorCode::AccountDiscriminatorNotFound.into()); - } - let disc_bytes = array_ref![data, 0, 8]; - if disc_bytes != &D::discriminator() { - return Err(ErrorCode::AccountDiscriminatorMismatch.into()); - } - Ok(()) - } -} - -impl<'info, D: DynamicAccountType> anchor_lang::AccountsClose<'info> - for AccountLoaderDynamic<'info, D> -{ - fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> { - close(self.to_account_info(), sol_destination) - } -} - -impl<'info, D: DynamicAccountType> anchor_lang::ToAccountMetas for AccountLoaderDynamic<'info, D> { - fn to_account_metas(&self, is_signer: Option) -> Vec { - let is_signer = is_signer.unwrap_or(self.acc_info.is_signer); - let meta = match self.acc_info.is_writable { - false => AccountMeta::new_readonly(*self.acc_info.key, is_signer), - true => AccountMeta::new(*self.acc_info.key, is_signer), - }; - vec![meta] - } -} - -impl<'info, D: DynamicAccountType> AsRef> for AccountLoaderDynamic<'info, D> { - fn as_ref(&self) -> &AccountInfo<'info> { - &self.acc_info - } -} - -impl<'info, D: DynamicAccountType> anchor_lang::ToAccountInfos<'info> - for AccountLoaderDynamic<'info, D> -{ - fn to_account_infos(&self) -> Vec> { - vec![self.acc_info.clone()] - } -} - -impl<'info, D: DynamicAccountType> anchor_lang::Key for AccountLoaderDynamic<'info, D> { - fn key(&self) -> Pubkey { - *self.acc_info.key - } -} - -// https://github.com/coral-xyz/anchor/blob/master/lang/src/common.rs#L8 -fn close<'info>(info: AccountInfo<'info>, sol_destination: AccountInfo<'info>) -> Result<()> { - // Transfer tokens from the account to the sol_destination. - let dest_starting_lamports = sol_destination.lamports(); - **sol_destination.lamports.borrow_mut() = - dest_starting_lamports.checked_add(info.lamports()).unwrap(); - **info.lamports.borrow_mut() = 0; - // Mark the account discriminator as closed. - let mut data = info.try_borrow_mut_data()?; - let dst: &mut [u8] = &mut data; - dst[0..8].copy_from_slice(&[255, 255, 255, 255, 255, 255, 255, 255]); - - Ok(()) -} diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index 71c236c34..9b82e4a53 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -1,6 +1,8 @@ +use std::cell::{Ref, RefMut}; use std::mem::size_of; use anchor_lang::prelude::*; +use anchor_lang::Discriminator; use arrayref::array_ref; use fixed::types::I80F48; @@ -248,11 +250,20 @@ impl MangoAccountFixed { } } -impl DynamicAccountType for MangoAccount { - type Header = MangoAccountDynamicHeader; - type Fixed = MangoAccountFixed; +impl Owner for MangoAccountFixed { + fn owner() -> Pubkey { + MangoAccount::owner() + } } +impl Discriminator for MangoAccountFixed { + fn discriminator() -> [u8; 8] { + MangoAccount::discriminator() + } +} + +impl anchor_lang::ZeroCopy for MangoAccountFixed {} + #[derive(Clone)] pub struct MangoAccountDynamicHeader { pub token_count: u8, @@ -262,34 +273,34 @@ pub struct MangoAccountDynamicHeader { } impl DynamicHeader for MangoAccountDynamicHeader { - fn from_bytes(data: &[u8]) -> Result { - let header_version = u8::from_le_bytes(*array_ref![data, 0, size_of::()]); + fn from_bytes(dynamic_data: &[u8]) -> Result { + let header_version = u8::from_le_bytes(*array_ref![dynamic_data, 0, size_of::()]); match header_version { 1 => { let token_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![ - data, + dynamic_data, MangoAccount::dynamic_token_vec_offset(), BORSH_VEC_SIZE_BYTES ])) .unwrap(); let serum3_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![ - data, + dynamic_data, MangoAccount::dynamic_serum3_vec_offset(token_count), BORSH_VEC_SIZE_BYTES ])) .unwrap(); let perp_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![ - data, + dynamic_data, MangoAccount::dynamic_perp_vec_offset(token_count, serum3_count), BORSH_VEC_SIZE_BYTES ])) .unwrap(); let perp_oo_count = u8::try_from(BorshVecLength::from_le_bytes(*array_ref![ - data, + dynamic_data, MangoAccount::dynamic_perp_oo_vec_offset(token_count, serum3_count, perp_count), BORSH_VEC_SIZE_BYTES ])) @@ -306,8 +317,8 @@ impl DynamicHeader for MangoAccountDynamicHeader { } } - fn initialize(data: &mut [u8]) -> Result<()> { - let dst: &mut [u8] = &mut data[0..1]; + fn initialize(dynamic_data: &mut [u8]) -> Result<()> { + let dst: &mut [u8] = &mut dynamic_data[0..1]; dst.copy_from_slice(&DEFAULT_MANGO_ACCOUNT_VERSION.to_le_bytes()); Ok(()) } @@ -368,11 +379,25 @@ impl MangoAccountDynamicHeader { } } -pub type MangoAccountValue = DynamicAccountValue; -pub type MangoAccountRef<'a> = DynamicAccountRef<'a, MangoAccount>; -pub type MangoAccountRefMut<'a> = DynamicAccountRefMut<'a, MangoAccount>; -pub type MangoAccountRefWithHeader<'a> = +/// Fully owned MangoAccount, useful for tests +pub type MangoAccountValue = DynamicAccount>; + +/// Full reference type, useful for borrows +pub type MangoAccountRef<'a> = + DynamicAccount<&'a MangoAccountDynamicHeader, &'a MangoAccountFixed, &'a [u8]>; +/// Full reference type, useful for borrows +pub type MangoAccountRefMut<'a> = + DynamicAccount<&'a mut MangoAccountDynamicHeader, &'a mut MangoAccountFixed, &'a mut [u8]>; + +/// Useful when loading from bytes +pub type MangoAccountLoadedRef<'a> = DynamicAccount; +/// Useful when loading from RefCell, like from AccountInfo +pub type MangoAccountLoadedRefCell<'a> = + DynamicAccount, Ref<'a, [u8]>>; +/// Useful when loading from RefCell, like from AccountInfo +pub type MangoAccountLoadedRefCellMut<'a> = + DynamicAccount, RefMut<'a, [u8]>>; impl MangoAccountValue { // bytes without discriminator @@ -386,7 +411,7 @@ impl MangoAccountValue { } } -impl<'a> MangoAccountRefWithHeader<'a> { +impl<'a> MangoAccountLoadedRef<'a> { // bytes without discriminator pub fn from_bytes(bytes: &'a [u8]) -> Result { let (fixed, dynamic) = bytes.split_at(size_of::()); @@ -399,7 +424,7 @@ impl<'a> MangoAccountRefWithHeader<'a> { } // This generic impl covers MangoAccountRef, MangoAccountRefMut and other -// DynamicAccountValue variants that allow read access. +// DynamicAccount variants that allow read access. impl< Header: DerefOrBorrow, Fixed: DerefOrBorrow, @@ -541,8 +566,8 @@ impl< self.fixed().being_liquidated() } - pub fn borrow(&self) -> DynamicAccountRef { - DynamicAccount { + pub fn borrow(&self) -> MangoAccountRef { + MangoAccountRef { header: self.header(), fixed: self.fixed(), dynamic: self.dynamic(), @@ -566,8 +591,8 @@ impl< self.dynamic.deref_or_borrow_mut() } - pub fn borrow_mut(&mut self) -> DynamicAccountRefMut { - DynamicAccount { + pub fn borrow_mut(&mut self) -> MangoAccountRefMut { + MangoAccountRefMut { header: self.header.deref_or_borrow_mut(), fixed: self.fixed.deref_or_borrow_mut(), dynamic: self.dynamic.deref_or_borrow_mut(), @@ -1086,6 +1111,65 @@ impl< } } +/// Trait to allow a AccountLoader to create an accessor for the full account. +pub trait MangoAccountLoader<'a> { + fn load_full(self) -> Result>; + fn load_full_mut(self) -> Result>; + fn load_full_init(self) -> Result>; +} + +impl<'a, 'info: 'a> MangoAccountLoader<'a> for &'a AccountLoader<'info, MangoAccountFixed> { + fn load_full(self) -> Result> { + // Error checking + self.load()?; + + let data = self.as_ref().try_borrow_data()?; + let header = + MangoAccountDynamicHeader::from_bytes(&data[8 + size_of::()..])?; + let (_, data) = Ref::map_split(data, |d| d.split_at(8)); + let (fixed_bytes, dynamic) = + Ref::map_split(data, |d| d.split_at(size_of::())); + Ok(MangoAccountLoadedRefCell { + header, + fixed: Ref::map(fixed_bytes, |b| bytemuck::from_bytes(b)), + dynamic, + }) + } + + fn load_full_mut(self) -> Result> { + // Error checking + self.load_mut()?; + + let data = self.as_ref().try_borrow_mut_data()?; + let header = + MangoAccountDynamicHeader::from_bytes(&data[8 + size_of::()..])?; + let (_, data) = RefMut::map_split(data, |d| d.split_at_mut(8)); + let (fixed_bytes, dynamic) = + RefMut::map_split(data, |d| d.split_at_mut(size_of::())); + Ok(MangoAccountLoadedRefCellMut { + header, + fixed: RefMut::map(fixed_bytes, |b| bytemuck::from_bytes_mut(b)), + dynamic, + }) + } + + fn load_full_init(self) -> Result> { + // Error checking + self.load_init()?; + + { + let mut data = self.as_ref().try_borrow_mut_data()?; + + let disc_bytes: &mut [u8] = &mut data[0..8]; + disc_bytes.copy_from_slice(bytemuck::bytes_of(&(MangoAccount::discriminator()))); + + MangoAccountDynamicHeader::initialize(&mut data[8 + size_of::()..])?; + } + + self.load_full_mut() + } +} + #[cfg(test)] mod tests { use super::*; From 7a8f31c6118b0577aa6375af4643e41efe85e057 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Fri, 30 Dec 2022 09:54:31 +0100 Subject: [PATCH 4/9] Perp: settlement applies no loan origination fee It's likely for perp pnl to oscillate and be settled in positive and negative direction repeatedly. If the user's USDC balance is <= 0, every settlement of negative pnl will increase borrows and cause loan origination fees. Over time the fees accumulated this way could be significant. Skipping loan origination fees for borrows created from perp settlement avoids that. --- programs/mango-v4/src/instructions/perp_settle_fees.rs | 2 +- programs/mango-v4/src/instructions/perp_settle_pnl.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/programs/mango-v4/src/instructions/perp_settle_fees.rs b/programs/mango-v4/src/instructions/perp_settle_fees.rs index fd6e07941..5d4fa5328 100644 --- a/programs/mango-v4/src/instructions/perp_settle_fees.rs +++ b/programs/mango-v4/src/instructions/perp_settle_fees.rs @@ -104,7 +104,7 @@ pub fn perp_settle_fees(ctx: Context, max_settle_amount: u64) -> let token_position = account .token_position_mut(perp_market.settle_token_index)? .0; - bank.withdraw_with_fee( + bank.withdraw_without_fee( token_position, settlement, Clock::get()?.unix_timestamp.try_into().unwrap(), diff --git a/programs/mango-v4/src/instructions/perp_settle_pnl.rs b/programs/mango-v4/src/instructions/perp_settle_pnl.rs index 3b814ff53..f2d020ea7 100644 --- a/programs/mango-v4/src/instructions/perp_settle_pnl.rs +++ b/programs/mango-v4/src/instructions/perp_settle_pnl.rs @@ -198,7 +198,10 @@ pub fn perp_settle_pnl(ctx: Context) -> Result<()> { let a_token_position = account_a.token_position_mut(settle_token_index)?.0; let b_token_position = account_b.token_position_mut(settle_token_index)?.0; bank.deposit(a_token_position, cm!(settlement - fee), now_ts)?; - bank.withdraw_with_fee(b_token_position, settlement, now_ts, oracle_price)?; + // Don't charge loan origination fees on borrows created via settling: + // Even small loan origination fees could accumulate if a perp position is + // settled back and forth repeatedly. + bank.withdraw_without_fee(b_token_position, settlement, now_ts, oracle_price)?; emit!(TokenBalanceLog { mango_group: ctx.accounts.group.key(), From 89c024e9df4d4ea25deb61b05b02923964eb9c43 Mon Sep 17 00:00:00 2001 From: riordanp Date: Mon, 2 Jan 2023 19:21:08 +0000 Subject: [PATCH 5/9] Setup MM for Heroku deployments (#358) * Setup mm for heroku * Update params * Update Procfile * Fix runtime dependencies * Run linter --- Procfile | 1 + package.json | 14 +++++++------- ts/client/src/scripts/mm/market-maker.ts | 7 ++++++- ts/client/src/scripts/mm/params/default.json | 13 +------------ tsconfig.json | 3 ++- yarn.lock | 8 ++++---- 6 files changed, 21 insertions(+), 25 deletions(-) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..b027e1d26 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +mm: node dist/cjs/src/scripts/mm/market-maker.js \ No newline at end of file diff --git a/package.json b/package.json index c4cf28492..fbbbc9904 100644 --- a/package.json +++ b/package.json @@ -37,18 +37,13 @@ "@types/bs58": "^4.0.1", "@types/chai": "^4.3.0", "@types/mocha": "^9.1.0", - "@types/node": "^14.14.37", + "@types/node": "^18.11.18", "@typescript-eslint/eslint-plugin": "^5.32.0", "@typescript-eslint/parser": "^5.32.0", - "binance-api-node": "^0.12.0", "chai": "^4.3.4", - "cross-fetch": "^3.1.5", - "dotenv": "^16.0.3", "eslint": "^7.28.0", "eslint-config-prettier": "^7.2.0", - "ftx-api": "^1.1.13", "mocha": "^9.1.3", - "node-kraken-api": "^2.2.2", "prettier": "^2.0.5", "ts-mocha": "^10.0.0", "ts-node": "^9.1.1", @@ -70,7 +65,12 @@ "@solana/web3.js": "^1.63.1", "@switchboard-xyz/sbv2-lite": "^0.1.6", "big.js": "^6.1.1", - "bs58": "^5.0.0" + "bs58": "^5.0.0", + "binance-api-node": "^0.12.0", + "node-kraken-api": "^2.2.2", + "ftx-api": "^1.1.13", + "cross-fetch": "^3.1.5", + "dotenv": "^16.0.3" }, "resolutions": { "@project-serum/anchor/@solana/web3.js": "1.63.1" diff --git a/ts/client/src/scripts/mm/market-maker.ts b/ts/client/src/scripts/mm/market-maker.ts index f63e9091c..cd39d5e67 100644 --- a/ts/client/src/scripts/mm/market-maker.ts +++ b/ts/client/src/scripts/mm/market-maker.ts @@ -28,6 +28,7 @@ import { makeInitSequenceEnforcerAccountIx, seqEnforcerProgramIds, } from './sequence-enforcer-util'; +import * as defaultParams from './params/default.json'; // Future // * use async nodejs logging @@ -229,7 +230,11 @@ async function fullMarketMaker() { const options = AnchorProvider.defaultOptions(); const connection = new Connection(CLUSTER_URL!, options); const user = Keypair.fromSecretKey( - Buffer.from(JSON.parse(fs.readFileSync(USER_KEYPAIR!, 'utf-8'))), + Buffer.from( + JSON.parse( + process.env.KEYPAIR || fs.readFileSync(USER_KEYPAIR!, 'utf-8'), + ), + ), ); const userWallet = new Wallet(user); const userProvider = new AnchorProvider(connection, userWallet, options); diff --git a/ts/client/src/scripts/mm/params/default.json b/ts/client/src/scripts/mm/params/default.json index e84ea7e2c..a681de704 100644 --- a/ts/client/src/scripts/mm/params/default.json +++ b/ts/client/src/scripts/mm/params/default.json @@ -5,7 +5,7 @@ "assets": { "BTC": { "perp": { - "sizePerc": 0.05, + "sizePerc": 0.75, "leanCoeff": 0.00025, "bias": 0.0, "requoteThresh": 0.0002, @@ -13,17 +13,6 @@ "spammerCharge": 2, "krakenCode": "XXBTZUSD" } - }, - "MNGO": { - "perp": { - "sizePerc": 0.05, - "leanCoeff": 0.00025, - "bias": 0.0, - "requoteThresh": 0.0002, - "takeSpammers": true, - "spammerCharge": 2, - "krakenCode": "MNGOUSD" - } } } } diff --git a/tsconfig.json b/tsconfig.json index 6e3534ec1..e5d8f227a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,8 @@ "esModuleInterop": true, "moduleResolution": "node", "lib": [ - "es2019" + "es2019", + "dom" ], "outDir": "./dist", "resolveJsonModule": true, diff --git a/yarn.lock b/yarn.lock index f881e3820..9310cbf16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -675,10 +675,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== -"@types/node@^14.14.37": - version "14.18.12" - resolved "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz" - integrity sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A== +"@types/node@^18.11.18": + version "18.11.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" + integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== "@types/promise-retry@^1.1.3": version "1.1.3" From b304ce09ec13bc4998d2c6bc67df34d12b281c01 Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Tue, 3 Jan 2023 14:15:06 +0100 Subject: [PATCH 6/9] Perp: Make settle limit robust against settle window size changes --- .../src/state/mango_account_components.rs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/programs/mango-v4/src/state/mango_account_components.rs b/programs/mango-v4/src/state/mango_account_components.rs index 5ee36b568..0a7024d9b 100644 --- a/programs/mango-v4/src/state/mango_account_components.rs +++ b/programs/mango-v4/src/state/mango_account_components.rs @@ -439,7 +439,10 @@ impl PerpPosition { pub fn update_settle_limit(&mut self, market: &PerpMarket, now_ts: u64) { assert_eq!(self.market_index, market.perp_market_index); let window_size = market.settle_pnl_limit_window_size_ts; - let new_window = now_ts >= cm!((self.settle_pnl_limit_window + 1) as u64 * window_size); + let window_start = cm!(self.settle_pnl_limit_window as u64 * window_size); + let window_end = cm!(window_start + window_size); + // now_ts < window_start can happen when window size is changed on the market + let new_window = now_ts >= window_end || now_ts < window_start; if new_window { self.settle_pnl_limit_window = cm!(now_ts / window_size).try_into().unwrap(); self.settle_pnl_limit_settled_in_current_window_native = 0; @@ -855,6 +858,34 @@ mod tests { assert_eq!(pos.realized_pnl_native, I80F48::from(0)); } + #[test] + fn test_perp_settle_limit_window() { + let mut market = PerpMarket::default_for_tests(); + let mut pos = create_perp_position(&market, 100, -50); + + market.settle_pnl_limit_window_size_ts = 100; + pos.settle_pnl_limit_settled_in_current_window_native = 10; + + pos.update_settle_limit(&market, 505); + assert_eq!(pos.settle_pnl_limit_settled_in_current_window_native, 0); + assert_eq!(pos.settle_pnl_limit_window, 5); + + pos.settle_pnl_limit_settled_in_current_window_native = 10; + pos.update_settle_limit(&market, 550); + assert_eq!(pos.settle_pnl_limit_settled_in_current_window_native, 10); + assert_eq!(pos.settle_pnl_limit_window, 5); + + pos.settle_pnl_limit_settled_in_current_window_native = 10; + pos.update_settle_limit(&market, 600); + assert_eq!(pos.settle_pnl_limit_settled_in_current_window_native, 0); + assert_eq!(pos.settle_pnl_limit_window, 6); + + market.settle_pnl_limit_window_size_ts = 400; + pos.update_settle_limit(&market, 605); + assert_eq!(pos.settle_pnl_limit_settled_in_current_window_native, 0); + assert_eq!(pos.settle_pnl_limit_window, 1); + } + #[test] fn test_perp_settle_limit() { let market = PerpMarket::default_for_tests(); From b174ee13cc026705145a5eedef96d07eae557cc4 Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 4 Jan 2023 07:42:19 +0100 Subject: [PATCH 7/9] update script for alts and new admin change Signed-off-by: microwavedcola1 --- ts/client/src/scripts/mb-admin.ts | 135 ++++++++++++++++-------------- 1 file changed, 72 insertions(+), 63 deletions(-) diff --git a/ts/client/src/scripts/mb-admin.ts b/ts/client/src/scripts/mb-admin.ts index b24d831dc..213167cf7 100644 --- a/ts/client/src/scripts/mb-admin.ts +++ b/ts/client/src/scripts/mb-admin.ts @@ -44,8 +44,11 @@ const MAINNET_SERUM3_MARKETS = new Map([ ['SOL/USDC', '8BnEgHoWFysVcuFFX7QztDmzuH8r5ZFvyP3sYwn1XTh6'], ]); -const { MB_CLUSTER_URL, MB_PAYER_KEYPAIR, MB_USER_KEYPAIR, MB_USER4_KEYPAIR } = - process.env; +const { + MB_CLUSTER_URL, + MB_PAYER_KEYPAIR, + MB_PAYER3_KEYPAIR: MB_PAYER2_KEYPAIR, +} = process.env; const MIN_VAULT_TO_DEPOSITS_RATIO = 0.2; const NET_BORROWS_WINDOW_SIZE_TS = 24 * 60 * 60; @@ -67,7 +70,7 @@ const defaultInterestRate = { async function buildAdminClient(): Promise<[MangoClient, Keypair]> { const admin = Keypair.fromSecretKey( - Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER_KEYPAIR!, 'utf-8'))), + Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER2_KEYPAIR!, 'utf-8'))), ); const options = AnchorProvider.defaultOptions(); @@ -76,17 +79,17 @@ async function buildAdminClient(): Promise<[MangoClient, Keypair]> { const adminWallet = new Wallet(admin); console.log(`Admin ${adminWallet.publicKey.toBase58()}`); const adminProvider = new AnchorProvider(connection, adminWallet, options); - return [ - await MangoClient.connect( - adminProvider, - 'mainnet-beta', - MANGO_V4_ID['mainnet-beta'], - { - idsSource: 'get-program-accounts', - }, - ), - admin, - ]; + + const client = await MangoClient.connect( + adminProvider, + 'mainnet-beta', + MANGO_V4_ID['mainnet-beta'], + { + idsSource: 'get-program-accounts', + }, + ); + + return [client, admin]; } async function buildUserClient( @@ -107,11 +110,11 @@ async function buildUserClient( MANGO_V4_ID['mainnet-beta'], ); - const admin = Keypair.fromSecretKey( + const creator = Keypair.fromSecretKey( Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER_KEYPAIR!, 'utf-8'))), ); - console.log(`Admin ${admin.publicKey.toBase58()}`); - const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM); + console.log(`Creator ${creator.publicKey.toBase58()}`); + const group = await client.getGroupForCreator(creator.publicKey, GROUP_NUM); return [client, group, user]; } @@ -436,56 +439,16 @@ async function registerPerpMarkets() { ); } -async function main() { - try { - // await createGroup(); - // await changeAdmin(); - } catch (error) { - console.log(error); - } - try { - // await registerTokens(); - } catch (error) { - console.log(error); - } - try { - // await registerSerum3Markets(); - } catch (error) { - console.log(error); - } - - try { - // await registerPerpMarkets(); - } catch (error) { - console.log(error); - } - try { - // await createUser(MB_USER_KEYPAIR!); - // depositForUser(MB_USER_KEYPAIR!); - } catch (error) { - console.log(error); - } - - try { - } catch (error) {} -} - -try { - main(); -} catch (error) { - console.log(error); -} - -//////////////////////////////////////////////////////////// -/// UNUSED ///////////////////////////////////////////////// -//////////////////////////////////////////////////////////// - async function createAndPopulateAlt() { const result = await buildAdminClient(); const client = result[0]; const admin = result[1]; - const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM); + const creator = Keypair.fromSecretKey( + Buffer.from(JSON.parse(fs.readFileSync(MB_PAYER_KEYPAIR!, 'utf-8'))), + ); + console.log(`Creator ${creator.publicKey.toBase58()}`); + const group = await client.getGroupForCreator(creator.publicKey, GROUP_NUM); const connection = client.program.provider.connection; @@ -594,12 +557,58 @@ async function createAndPopulateAlt() { await extendTable(bankAddresses); await extendTable(serum3MarketAddresses); await extendTable(serum3ExternalMarketAddresses); - await extendTable(perpMarketAddresses); + // TODO: dont extend for perps atm + // await extendTable(perpMarketAddresses); } catch (error) { console.log(error); } } +async function main() { + try { + // await createGroup(); + // await changeAdmin(); + } catch (error) { + console.log(error); + } + try { + // await registerTokens(); + } catch (error) { + console.log(error); + } + try { + // await registerSerum3Markets(); + } catch (error) { + console.log(error); + } + + try { + // await registerPerpMarkets(); + } catch (error) { + console.log(error); + } + try { + // await createUser(MB_USER_KEYPAIR!); + // depositForUser(MB_USER_KEYPAIR!); + } catch (error) { + console.log(error); + } + + try { + // createAndPopulateAlt(); + } catch (error) {} +} + +try { + main(); +} catch (error) { + console.log(error); +} + +//////////////////////////////////////////////////////////// +/// UNUSED ///////////////////////////////////////////////// +//////////////////////////////////////////////////////////// + async function expandMangoAccount(userKeypair: string) { const result = await buildUserClient(userKeypair); const client = result[0]; From 86c933164718c63c7a98e21ac13024b6b5779ec4 Mon Sep 17 00:00:00 2001 From: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com> Date: Wed, 4 Jan 2023 09:24:40 +0100 Subject: [PATCH 8/9] 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 * Fix from review Signed-off-by: microwavedcola1 * Fix from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * tests Signed-off-by: microwavedcola1 * remove token Signed-off-by: microwavedcola1 * fixes Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 * Fixes from review Signed-off-by: microwavedcola1 Signed-off-by: microwavedcola1 --- anchor | 2 +- cli/src/main.rs | 2 +- client/src/client.rs | 8 +- keeper/src/taker.rs | 2 +- programs/mango-v4/src/error.rs | 4 + .../mango-v4/src/instructions/flash_loan.rs | 20 +- programs/mango-v4/src/instructions/mod.rs | 2 + .../src/instructions/perp_create_market.rs | 3 +- .../src/instructions/perp_edit_market.rs | 5 + .../src/instructions/perp_place_order.rs | 34 +- .../src/instructions/serum3_edit_market.rs | 30 + .../src/instructions/serum3_place_order.rs | 4 + .../instructions/serum3_register_market.rs | 9 +- .../src/instructions/token_deposit.rs | 41 +- .../mango-v4/src/instructions/token_edit.rs | 5 + .../src/instructions/token_register.rs | 3 +- .../instructions/token_register_trustless.rs | 15 +- .../src/instructions/token_withdraw.rs | 3 + programs/mango-v4/src/lib.rs | 18 +- programs/mango-v4/src/state/bank.rs | 14 +- .../src/state/mango_account_components.rs | 5 + programs/mango-v4/src/state/perp_market.rs | 20 +- programs/mango-v4/src/state/serum3_market.rs | 9 +- .../tests/program_test/mango_client.rs | 149 ++++- .../tests/program_test/mango_setup.rs | 1 + .../mango-v4/tests/test_bankrupt_tokens.rs | 7 + programs/mango-v4/tests/test_basic.rs | 1 + .../mango-v4/tests/test_health_compute.rs | 3 + programs/mango-v4/tests/test_health_region.rs | 2 + programs/mango-v4/tests/test_liq_perps.rs | 5 + programs/mango-v4/tests/test_liq_tokens.rs | 3 + programs/mango-v4/tests/test_margin_trade.rs | 1 + programs/mango-v4/tests/test_perp.rs | 13 + programs/mango-v4/tests/test_perp_settle.rs | 6 + .../mango-v4/tests/test_perp_settle_fees.rs | 6 + .../mango-v4/tests/test_position_lifetime.rs | 5 + programs/mango-v4/tests/test_reduce_only.rs | 610 ++++++++++++++++++ ts/client/src/client.ts | 9 +- ts/client/src/mango_v4.ts | 162 ++++- ts/client/src/scripts/mb-admin.ts | 27 +- ts/client/src/scripts/mb-edit-perp-market.ts | 1 + .../src/scripts/mb-liqtest-make-candidates.ts | 4 + .../mb-liqtest-settle-and-close-all.ts | 4 +- 43 files changed, 1216 insertions(+), 61 deletions(-) create mode 100644 programs/mango-v4/src/instructions/serum3_edit_market.rs create mode 100644 programs/mango-v4/tests/test_reduce_only.rs diff --git a/anchor b/anchor index b3707b1fa..309c2c2f4 160000 --- a/anchor +++ b/anchor @@ -1 +1 @@ -Subproject commit b3707b1faaf6816cb3dd600074c81a39d373e952 +Subproject commit 309c2c2f4cce7c0a13d307fab3c7e2985bff3fa5 diff --git a/cli/src/main.rs b/cli/src/main.rs index d52488d6d..380e26802 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -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) => { diff --git a/client/src/client.rs b/client/src/client.rs index ca5606c2b..e65277bc3 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -302,7 +302,12 @@ impl MangoClient { ) } - pub async fn token_deposit(&self, mint: Pubkey, amount: u64) -> anyhow::Result { + pub async fn token_deposit( + &self, + mint: Pubkey, + amount: u64, + reduce_only: bool, + ) -> anyhow::Result { 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 diff --git a/keeper/src/taker.rs b/keeper/src/taker.rs index c9806ac5e..eaf24bf6d 100644 --- a/keeper/src/taker.rs +++ b/keeper/src/taker.rs @@ -117,7 +117,7 @@ async fn ensure_deposit(mango_client: &Arc) -> 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?; } diff --git a/programs/mango-v4/src/error.rs b/programs/mango-v4/src/error.rs index bfc5b8ae5..30fb55210 100644 --- a/programs/mango-v4/src/error.rs +++ b/programs/mango-v4/src/error.rs @@ -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 { diff --git a/programs/mango-v4/src/instructions/flash_loan.rs b/programs/mango-v4/src/instructions/flash_loan.rs index 5f8d1a56f..9701cf40f 100644 --- a/programs/mango-v4/src/instructions/flash_loan.rs +++ b/programs/mango-v4/src/instructions/flash_loan.rs @@ -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::()?; + 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), diff --git a/programs/mango-v4/src/instructions/mod.rs b/programs/mango-v4/src/instructions/mod.rs index a62bd0d73..ede7669da 100644 --- a/programs/mango-v4/src/instructions/mod.rs +++ b/programs/mango-v4/src/instructions/mod.rs @@ -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; diff --git a/programs/mango-v4/src/instructions/perp_create_market.rs b/programs/mango-v4/src/instructions/perp_create_market.rs index a68126c89..7573c403e 100644 --- a/programs/mango-v4/src/instructions/perp_create_market.rs +++ b/programs/mango-v4/src/instructions/perp_create_market.rs @@ -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 = diff --git a/programs/mango-v4/src/instructions/perp_edit_market.rs b/programs/mango-v4/src/instructions/perp_edit_market.rs index dc5d6bac2..3dbafc981 100644 --- a/programs/mango-v4/src/instructions/perp_edit_market.rs +++ b/programs/mango-v4/src/instructions/perp_edit_market.rs @@ -49,6 +49,7 @@ pub fn perp_edit_market( stable_price_growth_limit_opt: Option, settle_pnl_limit_factor_opt: Option, settle_pnl_limit_window_size_ts_opt: Option, + reduce_only_opt: Option, ) -> 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(), diff --git a/programs/mango-v4/src/instructions/perp_place_order.rs b/programs/mango-v4/src/instructions/perp_place_order.rs index 06794b872..cd66742af 100644 --- a/programs/mango-v4/src/instructions/perp_place_order.rs +++ b/programs/mango-v4/src/instructions/perp_place_order.rs @@ -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, order: Order, limit: u8) -> Result<()> { +pub fn perp_place_order(ctx: Context, 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, 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, diff --git a/programs/mango-v4/src/instructions/serum3_edit_market.rs b/programs/mango-v4/src/instructions/serum3_edit_market.rs new file mode 100644 index 000000000..fed566f34 --- /dev/null +++ b/programs/mango-v4/src/instructions/serum3_edit_market.rs @@ -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, + reduce_only_opt: Option, +) -> Result<()> { + if let Some(reduce_only) = reduce_only_opt { + ctx.accounts.market.load_mut()?.reduce_only = u8::from(reduce_only); + }; + Ok(()) +} diff --git a/programs/mango-v4/src/instructions/serum3_place_order.rs b/programs/mango-v4/src/instructions/serum3_place_order.rs index 6a04fda64..503508886 100644 --- a/programs/mango-v4/src/instructions/serum3_place_order.rs +++ b/programs/mango-v4/src/instructions/serum3_place_order.rs @@ -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 diff --git a/programs/mango-v4/src/instructions/serum3_register_market.rs b/programs/mango-v4/src/instructions/serum3_register_market.rs index ab1e34b43..36dc790ce 100644 --- a/programs/mango-v4/src/instructions/serum3_register_market.rs +++ b/programs/mango-v4/src/instructions/serum3_register_market.rs @@ -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], diff --git a/programs/mango-v4/src/instructions/token_deposit.rs b/programs/mango-v4/src/instructions/token_deposit.rs index 9ee258dfc..ce035600c 100644 --- a/programs/mango-v4/src/instructions/token_deposit.rs +++ b/programs/mango-v4/src/instructions/token_deposit.rs @@ -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::())?; 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, amount: u64) -> Result<()> { +pub fn token_deposit(ctx: Context, 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, 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, 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) } diff --git a/programs/mango-v4/src/instructions/token_edit.rs b/programs/mango-v4/src/instructions/token_edit.rs index bc485ee78..74afdeef3 100644 --- a/programs/mango-v4/src/instructions/token_edit.rs +++ b/programs/mango-v4/src/instructions/token_edit.rs @@ -56,6 +56,7 @@ pub fn token_edit( deposit_weight_scale_start_quote_opt: Option, reset_stable_price: bool, reset_net_borrow_limit: bool, + reduce_only_opt: Option, ) -> 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 diff --git a/programs/mango-v4/src/instructions/token_register.rs b/programs/mango-v4/src/instructions/token_register.rs index 36d63b0c0..4a0a7aade 100644 --- a/programs/mango-v4/src/instructions/token_register.rs +++ b/programs/mango-v4/src/instructions/token_register.rs @@ -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); diff --git a/programs/mango-v4/src/instructions/token_register_trustless.rs b/programs/mango-v4/src/instructions/token_register_trustless.rs index 2bba06c5f..85707e364 100644 --- a/programs/mango-v4/src/instructions/token_register_trustless.rs +++ b/programs/mango-v4/src/instructions/token_register_trustless.rs @@ -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); diff --git a/programs/mango-v4/src/instructions/token_withdraw.rs b/programs/mango-v4/src/instructions/token_withdraw.rs index 8750d95ba..9dfbc6319 100644 --- a/programs/mango-v4/src/instructions/token_withdraw.rs +++ b/programs/mango-v4/src/instructions/token_withdraw.rs @@ -96,6 +96,9 @@ pub fn token_withdraw(ctx: Context, 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); diff --git a/programs/mango-v4/src/lib.rs b/programs/mango-v4/src/lib.rs index 436ccd532..1a359c5cd 100644 --- a/programs/mango-v4/src/lib.rs +++ b/programs/mango-v4/src/lib.rs @@ -130,6 +130,7 @@ pub mod mango_v4 { deposit_weight_scale_start_quote_opt: Option, reset_stable_price: bool, reset_net_borrow_limit: bool, + reduce_only_opt: Option, ) -> 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, amount: u64) -> Result<()> { - instructions::token_deposit(ctx, amount) + pub fn token_deposit(ctx: Context, amount: u64, reduce_only: bool) -> Result<()> { + instructions::token_deposit(ctx, amount, reduce_only) } pub fn token_deposit_into_existing( ctx: Context, 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, + reduce_only_opt: Option, + ) -> 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, settle_pnl_limit_factor_opt: Option, settle_pnl_limit_window_size_ts: Option, + reduce_only_opt: Option, ) -> 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, ) } diff --git a/programs/mango-v4/src/state/bank.rs b/programs/mango-v4/src/state/bank.rs index 3eeb9bc06..91a74186b 100644 --- a/programs/mango-v4/src/state/bank.rs +++ b/programs/mango-v4/src/state/bank.rs @@ -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::(), @@ -155,7 +157,8 @@ const_assert_eq!( + 8 * 4 + 8 + 8 - + 2120 + + 1 + + 2119 ); const_assert_eq!(size_of::(), 3064); const_assert_eq!(size_of::() % 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) diff --git a/programs/mango-v4/src/state/mango_account_components.rs b/programs/mango-v4/src/state/mango_account_components.rs index 0a7024d9b..b089d60cd 100644 --- a/programs/mango-v4/src/state/mango_account_components.rs +++ b/programs/mango-v4/src/state/mango_account_components.rs @@ -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 } diff --git a/programs/mango-v4/src/state/perp_market.rs b/programs/mango-v4/src/state/perp_market.rs index 785f7865f..62c48ea6c 100644 --- a/programs/mango-v4/src/state/perp_market.rs +++ b/programs/mango-v4/src/state/perp_market.rs @@ -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::(), 2808); const_assert_eq!(size_of::() % 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], } } } diff --git a/programs/mango-v4/src/state/serum3_market.rs b/programs/mango-v4/src/state/serum3_market.rs index 623eb171a..a67d46a83 100644 --- a/programs/mango-v4/src/state/serum3_market.rs +++ b/programs/mango-v4/src/state/serum3_market.rs @@ -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::(), - 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::(), 264); const_assert_eq!(size_of::() % 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)] diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index 923199d03..26fd15f41 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -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 { + 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 { + 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, }; diff --git a/programs/mango-v4/tests/program_test/mango_setup.rs b/programs/mango-v4/tests/program_test/mango_setup.rs index f2e753762..837d3e247 100644 --- a/programs/mango-v4/tests/program_test/mango_setup.rs +++ b/programs/mango-v4/tests/program_test/mango_setup.rs @@ -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], diff --git a/programs/mango-v4/tests/test_bankrupt_tokens.rs b/programs/mango-v4/tests/test_bankrupt_tokens.rs index dcb2c3a2e..26db180f7 100644 --- a/programs/mango-v4/tests/test_bankrupt_tokens.rs +++ b/programs/mango-v4/tests/test_bankrupt_tokens.rs @@ -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], diff --git a/programs/mango-v4/tests/test_basic.rs b/programs/mango-v4/tests/test_basic.rs index 0db9978f1..2c59bc8de 100644 --- a/programs/mango-v4/tests/test_basic.rs +++ b/programs/mango-v4/tests/test_basic.rs @@ -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, diff --git a/programs/mango-v4/tests/test_health_compute.rs b/programs/mango-v4/tests/test_health_compute.rs index 5bae1523c..dff6d7cce 100644 --- a/programs/mango-v4/tests/test_health_compute.rs +++ b/programs/mango-v4/tests/test_health_compute.rs @@ -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], diff --git a/programs/mango-v4/tests/test_health_region.rs b/programs/mango-v4/tests/test_health_region.rs index 8f7853eff..f45408ae6 100644 --- a/programs/mango-v4/tests/test_health_region.rs +++ b/programs/mango-v4/tests/test_health_region.rs @@ -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], diff --git a/programs/mango-v4/tests/test_liq_perps.rs b/programs/mango-v4/tests/test_liq_perps.rs index e0bf8d858..78631dae6 100644 --- a/programs/mango-v4/tests/test_liq_perps.rs +++ b/programs/mango-v4/tests/test_liq_perps.rs @@ -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], diff --git a/programs/mango-v4/tests/test_liq_tokens.rs b/programs/mango-v4/tests/test_liq_tokens.rs index ab24be1f5..f7309e590 100644 --- a/programs/mango-v4/tests/test_liq_tokens.rs +++ b/programs/mango-v4/tests/test_liq_tokens.rs @@ -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], diff --git a/programs/mango-v4/tests/test_margin_trade.rs b/programs/mango-v4/tests/test_margin_trade.rs index 88d5fa104..d51a123f2 100644 --- a/programs/mango-v4/tests/test_margin_trade.rs +++ b/programs/mango-v4/tests/test_margin_trade.rs @@ -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, diff --git a/programs/mango-v4/tests/test_perp.rs b/programs/mango-v4/tests/test_perp.rs index 4c0f18596..1386c504d 100644 --- a/programs/mango-v4/tests/test_perp.rs +++ b/programs/mango-v4/tests/test_perp.rs @@ -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, }, ) diff --git a/programs/mango-v4/tests/test_perp_settle.rs b/programs/mango-v4/tests/test_perp_settle.rs index 776365d46..3c4edd021 100644 --- a/programs/mango-v4/tests/test_perp_settle.rs +++ b/programs/mango-v4/tests/test_perp_settle.rs @@ -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, }, ) diff --git a/programs/mango-v4/tests/test_perp_settle_fees.rs b/programs/mango-v4/tests/test_perp_settle_fees.rs index 8cf38b8a8..d4589c5b8 100644 --- a/programs/mango-v4/tests/test_perp_settle_fees.rs +++ b/programs/mango-v4/tests/test_perp_settle_fees.rs @@ -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, }, ) diff --git a/programs/mango-v4/tests/test_position_lifetime.rs b/programs/mango-v4/tests/test_position_lifetime.rs index 8913dd725..2d8cd5b06 100644 --- a/programs/mango-v4/tests/test_position_lifetime.rs +++ b/programs/mango-v4/tests/test_position_lifetime.rs @@ -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], diff --git a/programs/mango-v4/tests/test_reduce_only.rs b/programs/mango-v4/tests/test_reduce_only.rs new file mode 100644 index 000000000..cb4a539a3 --- /dev/null +++ b/programs/mango-v4/tests/test_reduce_only.rs @@ -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::(account_0).await; + let bank = solana.get_account::(tokens[0].bank).await; + let native = mango_account_0.tokens[0].native(&bank); + assert_eq!(native.to_num::(), 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::(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::(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::(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::(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::(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::(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::(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::(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::(account_1).await; + assert_eq!(mango_account_1.perps[0].bids_base_lots, 2); + + Ok(()) +} diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index 0908fa414..9cd81049d 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -316,6 +316,7 @@ export class MangoClient { depositWeightScaleStartQuote: number | null, resetStablePrice: boolean | null, resetNetBorrowLimit: boolean | null, + reduceOnly: boolean | null, ): Promise { 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 { 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 { 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 { const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex); @@ -1602,6 +1608,7 @@ export class MangoClient { settlePnlLimitWindowSize !== null ? new BN(settlePnlLimitWindowSize) : null, + reduceOnly, ) .accounts({ group: group.publicKey, diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index dcb2f1525..edb7403a4 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -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" } ] }; diff --git a/ts/client/src/scripts/mb-admin.ts b/ts/client/src/scripts/mb-admin.ts index 213167cf7..3904fb34a 100644 --- a/ts/client/src/scripts/mb-admin.ts +++ b/ts/client/src/scripts/mb-admin.ts @@ -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) {} } diff --git a/ts/client/src/scripts/mb-edit-perp-market.ts b/ts/client/src/scripts/mb-edit-perp-market.ts index d748737a3..c750acfa0 100644 --- a/ts/client/src/scripts/mb-edit-perp-market.ts +++ b/ts/client/src/scripts/mb-edit-perp-market.ts @@ -66,6 +66,7 @@ async function editPerpMarket(perpMarketName: string) { null, null, null, + null, ); console.log('Tx Successful:', signature); diff --git a/ts/client/src/scripts/mb-liqtest-make-candidates.ts b/ts/client/src/scripts/mb-liqtest-make-candidates.ts index 9f066e840..ef8d931e3 100644 --- a/ts/client/src/scripts/mb-liqtest-make-candidates.ts +++ b/ts/client/src/scripts/mb-liqtest-make-candidates.ts @@ -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, ); } } diff --git a/ts/client/src/scripts/mb-liqtest-settle-and-close-all.ts b/ts/client/src/scripts/mb-liqtest-settle-and-close-all.ts index 886938074..c1ba1d974 100644 --- a/ts/client/src/scripts/mb-liqtest-settle-and-close-all.ts +++ b/ts/client/src/scripts/mb-liqtest-settle-and-close-all.ts @@ -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 || ''; From 3ab8c6072ac4013de55a9c12ba3a35148a25f6ee Mon Sep 17 00:00:00 2001 From: microwavedcola1 Date: Wed, 4 Jan 2023 13:14:05 +0100 Subject: [PATCH 9/9] add reduce only field on ts classes Signed-off-by: microwavedcola1 --- ts/client/src/accounts/bank.ts | 3 +++ ts/client/src/accounts/perp.ts | 3 +++ ts/client/src/accounts/serum3.ts | 3 +++ 3 files changed, 9 insertions(+) diff --git a/ts/client/src/accounts/bank.ts b/ts/client/src/accounts/bank.ts index acccb5a75..aee5892b0 100644 --- a/ts/client/src/accounts/bank.ts +++ b/ts/client/src/accounts/bank.ts @@ -113,6 +113,7 @@ export class Bank implements BankForHealth { netBorrowsInWindow: BN; borrowWeightScaleStartQuote: number; depositWeightScaleStartQuote: number; + reduceOnly: number; }, ): Bank { return new Bank( @@ -158,6 +159,7 @@ export class Bank implements BankForHealth { obj.netBorrowsInWindow, obj.borrowWeightScaleStartQuote, obj.depositWeightScaleStartQuote, + obj.reduceOnly == 1, ); } @@ -204,6 +206,7 @@ export class Bank implements BankForHealth { public netBorrowsInWindow: BN, public borrowWeightScaleStartQuote: number, public depositWeightScaleStartQuote: number, + public reduceOnly: boolean, ) { this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0]; this.oracleConfig = { diff --git a/ts/client/src/accounts/perp.ts b/ts/client/src/accounts/perp.ts index 6636e8707..ebaf817dd 100644 --- a/ts/client/src/accounts/perp.ts +++ b/ts/client/src/accounts/perp.ts @@ -94,6 +94,7 @@ export class PerpMarket { settleFeeFractionLowHealth: number; settlePnlLimitFactor: number; settlePnlLimitWindowSizeTs: BN; + reduceOnly: number; }, ): PerpMarket { return new PerpMarket( @@ -137,6 +138,7 @@ export class PerpMarket { obj.settleFeeFractionLowHealth, obj.settlePnlLimitFactor, obj.settlePnlLimitWindowSizeTs, + obj.reduceOnly == 1, ); } @@ -181,6 +183,7 @@ export class PerpMarket { public settleFeeFractionLowHealth: number, settlePnlLimitFactor: number, settlePnlLimitWindowSizeTs: BN, + public reduceOnly: boolean, ) { this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0]; this.oracleConfig = { diff --git a/ts/client/src/accounts/serum3.ts b/ts/client/src/accounts/serum3.ts index a72c6dae3..554a79c83 100644 --- a/ts/client/src/accounts/serum3.ts +++ b/ts/client/src/accounts/serum3.ts @@ -24,6 +24,7 @@ export class Serum3Market { serumMarketExternal: PublicKey; marketIndex: number; registrationTime: BN; + reduceOnly: number; }, ): Serum3Market { return new Serum3Market( @@ -36,6 +37,7 @@ export class Serum3Market { obj.serumMarketExternal, obj.marketIndex as MarketIndex, obj.registrationTime, + obj.reduceOnly == 1, ); } @@ -49,6 +51,7 @@ export class Serum3Market { public serumMarketExternal: PublicKey, public marketIndex: MarketIndex, public registrationTime: BN, + public reduceOnly: boolean, ) { this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0]; }