From e60598d8b58b7e56c701dd8ba5a761b1c53d21bd Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Thu, 28 Nov 2024 09:30:17 -0600 Subject: [PATCH 01/14] program: protect maker oracle limit orders --- programs/drift/src/controller/orders.rs | 45 ++++++++++++++++--------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index f90e587c0..d0517f123 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1172,6 +1172,7 @@ pub fn fill_perp_order( let user_can_skip_duration: bool; let amm_can_skip_duration: bool; let amm_lp_allowed_to_jit_make: bool; + let oracle_limit_order_immediately_available: bool; let mut amm_is_available = !state.amm_paused()? && !fill_mode.is_rfq(); { @@ -1191,8 +1192,11 @@ pub fn fill_perp_order( market.get_max_confidence_interval_multiplier()?, )?; - amm_is_available &= - is_oracle_valid_for_action(_oracle_validity, Some(DriftAction::FillOrderAmm))?; + let oracle_valid_for_amm_fill = is_oracle_valid_for_action(_oracle_validity, Some(DriftAction::FillOrderAmm))?; + + oracle_limit_order_immediately_available = oracle_valid_for_amm_fill && oracle_price_data.delay == 0; + + amm_is_available &= oracle_valid_for_amm_fill; amm_is_available &= !market.is_operation_paused(PerpOperation::AmmFill); amm_is_available &= !market.has_too_much_drawdown()?; @@ -1203,19 +1207,15 @@ pub fn fill_perp_order( amm_can_skip_duration = market.can_skip_auction_duration(&state, amm_lp_allowed_to_jit_make)?; - user_can_skip_duration = if amm_can_skip_duration && amm_is_available { - user.can_skip_auction_duration( - user_stats, - order_auction_duration > 0, - fill_mode.is_ioc(), - order_direction, - order_price, - order_oracle_price_offset, - oracle_price_data.price, - )? - } else { - false - }; + user_can_skip_duration = user.can_skip_auction_duration( + user_stats, + order_auction_duration > 0, + fill_mode.is_ioc(), + order_direction, + order_price, + order_oracle_price_offset, + oracle_price_data.price, + )?; reserve_price_before = market.amm.reserve_price()?; oracle_price = oracle_price_data.price; @@ -1272,6 +1272,8 @@ pub fn fill_perp_order( now, slot, fill_mode, + oracle_limit_order_immediately_available, + user_can_skip_duration, )?; // no referrer bonus for liquidations @@ -1575,11 +1577,15 @@ fn get_maker_orders_info( now: i64, slot: u64, fill_mode: FillMode, + oracle_limit_order_immediately_available: bool, + user_can_skip_duration: bool, ) -> DriftResult> { let maker_direction = taker_order.direction.opposite(); let mut maker_orders_info = Vec::with_capacity(16); + let taker_order_age = slot.safe_sub(taker_order.slot)?; + for (maker_key, user_account_loader) in makers_and_referrer.0.iter() { if maker_key == taker_key { continue; @@ -1616,6 +1622,9 @@ fn get_maker_orders_info( drop(market); + // todo add flag + let is_protected_maker = false; + for (maker_order_index, maker_order_price) in maker_order_price_and_indexes.iter() { let maker_order_index = *maker_order_index; let maker_order_price = *maker_order_price; @@ -1698,6 +1707,12 @@ fn get_maker_orders_info( continue; } + if maker_order.has_oracle_price_offset() && is_protected_maker { + if !oracle_limit_order_immediately_available && !user_can_skip_duration && taker_order_age < 0 { + continue; + } + } + insert_maker_order_info( &mut maker_orders_info, (*maker_key, maker_order_index, maker_order_price), From 6154f0cc9c4917198ebe2e7ac7620d2ce2da3d0e Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 29 Nov 2024 11:58:51 -0600 Subject: [PATCH 02/14] add protected maker flag --- programs/drift/src/instructions/user.rs | 13 +++++++++++++ programs/drift/src/lib.rs | 8 ++++++++ programs/drift/src/state/user.rs | 18 ++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/programs/drift/src/instructions/user.rs b/programs/drift/src/instructions/user.rs index 35179e578..f3a52869d 100644 --- a/programs/drift/src/instructions/user.rs +++ b/programs/drift/src/instructions/user.rs @@ -2285,6 +2285,19 @@ pub fn handle_update_user_advanced_lp( Ok(()) } +pub fn handle_update_user_protected_maker_orders( + ctx: Context, + _sub_account_id: u16, + protected_maker_orders: bool, +) -> Result<()> { + let mut user = load_mut!(ctx.accounts.user)?; + + validate!(!user.is_being_liquidated(), ErrorCode::LiquidationsOngoing)?; + + user.update_protected_maker_orders_status(protected_maker_orders)?; + Ok(()) +} + pub fn handle_delete_user(ctx: Context) -> Result<()> { let user = &load!(ctx.accounts.user)?; let user_stats = &mut load_mut!(ctx.accounts.user_stats)?; diff --git a/programs/drift/src/lib.rs b/programs/drift/src/lib.rs index 7c74bdf66..c960af7d4 100644 --- a/programs/drift/src/lib.rs +++ b/programs/drift/src/lib.rs @@ -351,6 +351,14 @@ pub mod drift { handle_update_user_advanced_lp(ctx, _sub_account_id, advanced_lp) } + pub fn update_user_protected_maker_orders( + ctx: Context, + _sub_account_id: u16, + protected_maker_orders: bool, + ) -> Result<()> { + handle_update_user_protected_maker_orders(ctx, _sub_account_id, protected_maker_orders) + } + pub fn delete_user<'c: 'info, 'info>( ctx: Context<'_, '_, 'c, 'info, DeleteUser>, ) -> Result<()> { diff --git a/programs/drift/src/state/user.rs b/programs/drift/src/state/user.rs index 04a4e23c4..9db56a34f 100644 --- a/programs/drift/src/state/user.rs +++ b/programs/drift/src/state/user.rs @@ -54,6 +54,7 @@ pub enum UserStatus { Bankrupt = 0b00000010, ReduceOnly = 0b00000100, AdvancedLp = 0b00001000, + ProtectedMakerOrders = 0b00010000, } // implement SIZE const for User @@ -150,6 +151,10 @@ impl User { self.status & (UserStatus::AdvancedLp as u8) > 0 } + pub fn is_protected_maker(&self) -> bool { + self.status & (UserStatus::ProtectedMakerOrders as u8) > 0 + } + pub fn add_user_status(&mut self, status: UserStatus) { self.status |= status as u8; } @@ -411,6 +416,19 @@ impl User { Ok(()) } + pub fn update_protected_maker_orders_status( + &mut self, + protected_maker_orders: bool, + ) -> DriftResult { + if protected_maker_orders { + self.add_user_status(UserStatus::ProtectedMakerOrders); + } else { + self.remove_user_status(UserStatus::ProtectedMakerOrders); + } + + Ok(()) + } + pub fn has_room_for_new_order(&self) -> bool { for order in self.orders.iter() { if order.status == OrderStatus::Init { From 946e304c0773c3d85c6117a9647e3915e09e4f57 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 29 Nov 2024 12:01:05 -0600 Subject: [PATCH 03/14] use maker flag in get makers order info --- programs/drift/src/controller/orders.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index d0517f123..e84545bcb 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1622,8 +1622,7 @@ fn get_maker_orders_info( drop(market); - // todo add flag - let is_protected_maker = false; + let is_protected_maker = maker.is_protected_maker(); for (maker_order_index, maker_order_price) in maker_order_price_and_indexes.iter() { let maker_order_index = *maker_order_index; From 9d371e1f4d51fa9874af91a64c57d759465bacf6 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 29 Nov 2024 12:15:12 -0600 Subject: [PATCH 04/14] add protected maker mode config --- programs/drift/src/error.rs | 2 + programs/drift/src/instructions/admin.rs | 63 +++++++++++++++++++ programs/drift/src/lib.rs | 15 +++++ programs/drift/src/state/mod.rs | 3 +- .../src/state/protected_maker_mode_config.rs | 37 +++++++++++ 5 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 programs/drift/src/state/protected_maker_mode_config.rs diff --git a/programs/drift/src/error.rs b/programs/drift/src/error.rs index 47c65e1a0..1bdd1c571 100644 --- a/programs/drift/src/error.rs +++ b/programs/drift/src/error.rs @@ -611,6 +611,8 @@ pub enum ErrorCode { InvalidSwiftOrderId, #[msg("Invalid pool id")] InvalidPoolId, + #[msg("Invalid Protected Maker Mode Config")] + InvalidProtectedMakerModeConfig, } #[macro_export] diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index d1feb9054..727f14dba 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -55,6 +55,7 @@ use crate::state::perp_market::{ ContractTier, ContractType, InsuranceClaim, MarketStatus, PerpMarket, PoolBalance, AMM, }; use crate::state::perp_market_map::get_writable_perp_market_set; +use crate::state::protected_maker_mode_config::ProtectedMakerModeConfig; use crate::state::spot_market::{ AssetTier, InsuranceFund, SpotBalanceType, SpotFulfillmentConfigStatus, SpotMarket, }; @@ -4198,6 +4199,32 @@ pub fn handle_update_high_leverage_mode_config( Ok(()) } +pub fn handle_initialize_protected_maker_mode_config( + ctx: Context, + max_users: u32, +) -> Result<()> { + let mut config = ctx.accounts.protected_maker_mode_config.load_init()?; + + config.max_users = max_users; + + Ok(()) +} + +pub fn handle_update_protected_maker_mode_config( + ctx: Context, + max_users: u32, + reduce_only: bool, +) -> Result<()> { + let mut config = load_mut!(ctx.accounts.protected_maker_mode_config)?; + + config.max_users = max_users; + config.reduce_only = reduce_only as u8; + + config.validate()?; + + Ok(()) +} + #[derive(Accounts)] pub struct Initialize<'info> { #[account(mut)] @@ -4848,3 +4875,39 @@ pub struct UpdateHighLeverageModeConfig<'info> { )] pub state: Box>, } + +#[derive(Accounts)] +pub struct InitializeProtectedMakerModeConfig<'info> { + #[account(mut)] + pub admin: Signer<'info>, + #[account( + init, + seeds = [b"protected_maker_mode_config".as_ref()], + space = ProtectedMakerModeConfig::SIZE, + bump, + payer = admin + )] + pub protected_maker_mode_config: AccountLoader<'info, ProtectedMakerModeConfig>, + #[account( + has_one = admin + )] + pub state: Box>, + pub rent: Sysvar<'info, Rent>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct UpdateProtectedMakerModeConfig<'info> { + #[account(mut)] + pub admin: Signer<'info>, + #[account( + mut, + seeds = [b"protected_maker_mode_config".as_ref()], + bump, + )] + pub protected_maker_mode_config: AccountLoader<'info, ProtectedMakerModeConfig>, + #[account( + has_one = admin + )] + pub state: Box>, +} \ No newline at end of file diff --git a/programs/drift/src/lib.rs b/programs/drift/src/lib.rs index c960af7d4..d3a093bd6 100644 --- a/programs/drift/src/lib.rs +++ b/programs/drift/src/lib.rs @@ -1568,6 +1568,21 @@ pub mod drift { ) -> Result<()> { handle_update_high_leverage_mode_config(ctx, max_users, reduce_only) } + + pub fn initialize_protected_maker_mode_config( + ctx: Context, + max_users: u32, + ) -> Result<()> { + handle_initialize_protected_maker_mode_config(ctx, max_users) + } + + pub fn update_protected_maker_mode_config( + ctx: Context, + max_users: u32, + reduce_only: bool, + ) -> Result<()> { + handle_update_protected_maker_mode_config(ctx, max_users, reduce_only) + } } #[cfg(not(feature = "no-entrypoint"))] diff --git a/programs/drift/src/state/mod.rs b/programs/drift/src/state/mod.rs index 13973ff64..a6020ea8c 100644 --- a/programs/drift/src/state/mod.rs +++ b/programs/drift/src/state/mod.rs @@ -12,6 +12,7 @@ pub mod order_params; pub mod paused_operations; pub mod perp_market; pub mod perp_market_map; +pub mod protected_maker_mode_config; pub mod rfq_user; pub mod settle_pnl_mode; pub mod spot_fulfillment_params; @@ -22,4 +23,4 @@ pub mod state; pub mod swift_user; pub mod traits; pub mod user; -pub mod user_map; +pub mod user_map; \ No newline at end of file diff --git a/programs/drift/src/state/protected_maker_mode_config.rs b/programs/drift/src/state/protected_maker_mode_config.rs new file mode 100644 index 000000000..b12bb28b5 --- /dev/null +++ b/programs/drift/src/state/protected_maker_mode_config.rs @@ -0,0 +1,37 @@ +use crate::error::DriftResult; +use crate::error::ErrorCode; +use crate::state::traits::Size; +use crate::validate; +use anchor_lang::prelude::*; + +#[account(zero_copy(unsafe))] +#[derive(Default, Eq, PartialEq, Debug)] +#[repr(C)] +pub struct ProtectedMakerModeConfig { + pub max_users: u32, + pub current_users: u32, + pub reduce_only: u8, + pub padding: [u8; 31], +} + +impl Size for ProtectedMakerModeConfig { + const SIZE: usize = 48; +} + +impl ProtectedMakerModeConfig { + pub fn validate(&self) -> DriftResult { + validate!( + self.current_users <= self.max_users, + ErrorCode::InvalidProtectedMakerModeConfig, + "current users ({}) > max users ({})", + self.current_users, + self.max_users + )?; + + Ok(()) + } + + pub fn is_reduce_only(&self) -> bool { + self.reduce_only > 0 + } +} From a10366a8c09e31f8ba1546f2fe0a4d8668f28290 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 29 Nov 2024 12:22:38 -0600 Subject: [PATCH 05/14] look at config in ix to turn on mode --- programs/drift/src/instructions/user.rs | 38 ++++++++++++++++++++++++- programs/drift/src/lib.rs | 2 +- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/programs/drift/src/instructions/user.rs b/programs/drift/src/instructions/user.rs index f3a52869d..f1058cce8 100644 --- a/programs/drift/src/instructions/user.rs +++ b/programs/drift/src/instructions/user.rs @@ -63,6 +63,7 @@ use crate::state::paused_operations::{PerpOperation, SpotOperation}; use crate::state::perp_market::ContractType; use crate::state::perp_market::MarketStatus; use crate::state::perp_market_map::{get_writable_perp_market_set, MarketSet}; +use crate::state::protected_maker_mode_config::ProtectedMakerModeConfig; use crate::state::rfq_user::{load_rfq_user_account_map, RFQUser, RFQ_PDA_SEED}; use crate::state::spot_fulfillment_params::SpotFulfillmentParams; use crate::state::spot_market::SpotBalanceType; @@ -2286,7 +2287,7 @@ pub fn handle_update_user_advanced_lp( } pub fn handle_update_user_protected_maker_orders( - ctx: Context, + ctx: Context, _sub_account_id: u16, protected_maker_orders: bool, ) -> Result<()> { @@ -2295,6 +2296,23 @@ pub fn handle_update_user_protected_maker_orders( validate!(!user.is_being_liquidated(), ErrorCode::LiquidationsOngoing)?; user.update_protected_maker_orders_status(protected_maker_orders)?; + + let mut config = load_mut!(ctx.accounts.protected_maker_mode_config)?; + + if protected_maker_orders { + validate!( + !config.is_reduce_only(), + ErrorCode::DefaultError, + "protected maker mode config reduce only" + )?; + + config.current_users = config.current_users.safe_add(1)?; + } else { + config.current_users = config.current_users.safe_sub(1)?; + } + + config.validate()?; + Ok(()) } @@ -2976,6 +2994,24 @@ pub struct EnableUserHighLeverageMode<'info> { pub high_leverage_mode_config: AccountLoader<'info, HighLeverageModeConfig>, } +#[derive(Accounts)] +#[instruction( + sub_account_id: u16, +)] +pub struct UpdateUserProtectedMakerMode<'info> { + pub state: Box>, + #[account( + mut, + seeds = [b"user", authority.key.as_ref(), sub_account_id.to_le_bytes().as_ref()], + bump, + )] + pub user: AccountLoader<'info, User>, + pub authority: Signer<'info>, + #[account(mut)] + pub protected_maker_mode_config: AccountLoader<'info, ProtectedMakerModeConfig>, +} + + #[access_control( fill_not_paused(&ctx.accounts.state) )] diff --git a/programs/drift/src/lib.rs b/programs/drift/src/lib.rs index d3a093bd6..dffd566a1 100644 --- a/programs/drift/src/lib.rs +++ b/programs/drift/src/lib.rs @@ -352,7 +352,7 @@ pub mod drift { } pub fn update_user_protected_maker_orders( - ctx: Context, + ctx: Context, _sub_account_id: u16, protected_maker_orders: bool, ) -> Result<()> { From 07fd2cf0491ec2b353b56c9acdf2afbec458a8da Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 29 Nov 2024 12:31:08 -0600 Subject: [PATCH 06/14] fix taker_order_age --- programs/drift/src/controller/orders.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index e84545bcb..d4a5ce64e 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1707,7 +1707,7 @@ fn get_maker_orders_info( } if maker_order.has_oracle_price_offset() && is_protected_maker { - if !oracle_limit_order_immediately_available && !user_can_skip_duration && taker_order_age < 0 { + if !oracle_limit_order_immediately_available && !user_can_skip_duration && taker_order_age < 10 { continue; } } From 898d49a6ceb589ae6b285733eb625d2faa0a4b45 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 29 Nov 2024 12:39:57 -0600 Subject: [PATCH 07/14] use state min perp auction duration --- programs/drift/src/controller/orders.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index d4a5ce64e..f69a2a4e0 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1274,6 +1274,7 @@ pub fn fill_perp_order( fill_mode, oracle_limit_order_immediately_available, user_can_skip_duration, + state.min_perp_auction_duration as u64, )?; // no referrer bonus for liquidations @@ -1579,6 +1580,7 @@ fn get_maker_orders_info( fill_mode: FillMode, oracle_limit_order_immediately_available: bool, user_can_skip_duration: bool, + protected_maker_min_age: u64, ) -> DriftResult> { let maker_direction = taker_order.direction.opposite(); @@ -1707,7 +1709,7 @@ fn get_maker_orders_info( } if maker_order.has_oracle_price_offset() && is_protected_maker { - if !oracle_limit_order_immediately_available && !user_can_skip_duration && taker_order_age < 10 { + if !oracle_limit_order_immediately_available && !user_can_skip_duration && taker_order_age < protected_maker_min_age { continue; } } From 68ce1fde1ce3a569c3c263bb110ecbfec6c7862f Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 29 Nov 2024 17:04:24 -0500 Subject: [PATCH 08/14] more strict logic and fix tests --- programs/drift/src/controller/orders.rs | 16 +++++++------ programs/drift/src/controller/orders/tests.rs | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index f69a2a4e0..d89f0915d 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1167,12 +1167,13 @@ pub fn fill_perp_order( let reserve_price_before: u64; let oracle_validity: OracleValidity; let oracle_price: i64; + let oracle_delay: i64; let oracle_twap_5min: i64; let perp_market_index: u16; let user_can_skip_duration: bool; let amm_can_skip_duration: bool; let amm_lp_allowed_to_jit_make: bool; - let oracle_limit_order_immediately_available: bool; + let oracle_valid_for_amm_fill: bool; let mut amm_is_available = !state.amm_paused()? && !fill_mode.is_rfq(); { @@ -1192,9 +1193,7 @@ pub fn fill_perp_order( market.get_max_confidence_interval_multiplier()?, )?; - let oracle_valid_for_amm_fill = is_oracle_valid_for_action(_oracle_validity, Some(DriftAction::FillOrderAmm))?; - - oracle_limit_order_immediately_available = oracle_valid_for_amm_fill && oracle_price_data.delay == 0; + oracle_valid_for_amm_fill = is_oracle_valid_for_action(_oracle_validity, Some(DriftAction::FillOrderAmm))?; amm_is_available &= oracle_valid_for_amm_fill; amm_is_available &= !market.is_operation_paused(PerpOperation::AmmFill); @@ -1224,6 +1223,7 @@ pub fn fill_perp_order( .historical_oracle_data .last_oracle_price_twap_5min; oracle_validity = _oracle_validity; + oracle_delay = oracle_price_data.delay; perp_market_index = market.market_index; } @@ -1272,7 +1272,8 @@ pub fn fill_perp_order( now, slot, fill_mode, - oracle_limit_order_immediately_available, + oracle_valid_for_amm_fill, + oracle_delay, user_can_skip_duration, state.min_perp_auction_duration as u64, )?; @@ -1578,7 +1579,8 @@ fn get_maker_orders_info( now: i64, slot: u64, fill_mode: FillMode, - oracle_limit_order_immediately_available: bool, + oracle_valid_for_amm_fill: bool, + oracle_delay: i64, user_can_skip_duration: bool, protected_maker_min_age: u64, ) -> DriftResult> { @@ -1709,7 +1711,7 @@ fn get_maker_orders_info( } if maker_order.has_oracle_price_offset() && is_protected_maker { - if !oracle_limit_order_immediately_available && !user_can_skip_duration && taker_order_age < protected_maker_min_age { + if !oracle_valid_for_amm_fill || (oracle_delay > 0 && !user_can_skip_duration && taker_order_age < protected_maker_min_age) { continue; } } diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index e6899479e..713088926 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -9977,6 +9977,10 @@ pub mod get_maker_orders_info { clock.unix_timestamp, clock.slot, FillMode::Fill, + true, + 0, + true, + 10, ) .unwrap(); @@ -10167,6 +10171,10 @@ pub mod get_maker_orders_info { clock.unix_timestamp, clock.slot, FillMode::Fill, + true, + 0, + true, + 10, ) .unwrap(); @@ -10346,6 +10354,10 @@ pub mod get_maker_orders_info { clock.unix_timestamp, clock.slot, FillMode::Fill, + true, + 0, + true, + 10, ) .unwrap(); @@ -10588,6 +10600,10 @@ pub mod get_maker_orders_info { clock.unix_timestamp, clock.slot, FillMode::Fill, + true, + 0, + true, + 10, ) .unwrap(); @@ -10785,6 +10801,10 @@ pub mod get_maker_orders_info { clock.unix_timestamp, clock.slot, FillMode::PlaceAndTake(false, 0), + true, + 0, + true, + 10, ) .unwrap(); @@ -11004,6 +11024,10 @@ pub mod get_maker_orders_info { clock.unix_timestamp, clock.slot, FillMode::Fill, + true, + 0, + true, + 10, ) .unwrap(); From 34ca52b78d59cc2e0097479879fb202c4a1263df Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Fri, 29 Nov 2024 17:20:51 -0500 Subject: [PATCH 09/14] add test --- programs/drift/src/controller/orders.rs | 13 ++++++++++++- programs/drift/src/controller/orders/tests.rs | 14 ++++++++++++++ programs/drift/src/math/auction.rs | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index d89f0915d..14057af88 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1711,7 +1711,7 @@ fn get_maker_orders_info( } if maker_order.has_oracle_price_offset() && is_protected_maker { - if !oracle_valid_for_amm_fill || (oracle_delay > 0 && !user_can_skip_duration && taker_order_age < protected_maker_min_age) { + if !protected_maker_oracle_limit_can_fill(oracle_valid_for_amm_fill, oracle_delay, user_can_skip_duration, taker_order_age, protected_maker_min_age) { continue; } } @@ -1727,6 +1727,17 @@ fn get_maker_orders_info( Ok(maker_orders_info) } +#[inline(always)] +fn protected_maker_oracle_limit_can_fill( + oracle_valid_for_amm_fill: bool, + oracle_delay: i64, + user_can_skip_duration: bool, + taker_order_age: u64, + protected_maker_min_age: u64, +) -> bool { + oracle_valid_for_amm_fill && (oracle_delay == 0 || user_can_skip_duration || taker_order_age > protected_maker_min_age) +} + #[inline(always)] fn insert_maker_order_info( maker_orders_info: &mut Vec<(Pubkey, usize, u64)>, diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index 713088926..13a370ec8 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -12226,3 +12226,17 @@ mod update_maker_fills_map { assert_eq!(*map.get(&maker_key).unwrap(), -2 * fill as i64); } } + +pub mod protected_maker_oracle_limit_can_fill { + use crate::controller::orders::protected_maker_oracle_limit_can_fill; + + #[test] + fn test() { + assert!(protected_maker_oracle_limit_can_fill(true, 0, true, 10, 10)); // all cases + assert!(protected_maker_oracle_limit_can_fill(true, 0, false, 9, 10)); // oracle delay is 0 + assert!(protected_maker_oracle_limit_can_fill(true, 1, false, 10, 9)); // min age passed + assert!(protected_maker_oracle_limit_can_fill(true, 1, true, 9, 10)); // user exempt + assert!(!protected_maker_oracle_limit_can_fill(true, 1, false, 10, 11)); // no condition met + assert!(!protected_maker_oracle_limit_can_fill(false, 0, true, 10, 10)); // oracle valid for amm fill is false + } +} \ No newline at end of file diff --git a/programs/drift/src/math/auction.rs b/programs/drift/src/math/auction.rs index de33cc088..f5343b14a 100644 --- a/programs/drift/src/math/auction.rs +++ b/programs/drift/src/math/auction.rs @@ -230,7 +230,7 @@ pub fn can_fill_with_amm( slot: u64, fill_mode: FillMode, ) -> DriftResult { - Ok(!(amm_availability == AMMAvailability::Unavailable) + Ok(amm_availability != AMMAvailability::Unavailable && valid_oracle_price.is_some() && (amm_availability == AMMAvailability::Immediate || is_amm_available_liquidity_source(order, min_auction_duration, slot, fill_mode)?)) From 86f9979138794b4b87ae9dda698a18fc34c3067c Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Sat, 30 Nov 2024 10:33:32 -0500 Subject: [PATCH 10/14] add protected maker to dlob --- sdk/src/dlob/DLOB.ts | 24 ++++++++++++----- sdk/src/dlob/DLOBNode.ts | 31 ++++++++++++++++------ sdk/src/dlob/NodeList.ts | 11 ++++++-- sdk/src/math/userStatus.ts | 5 ++++ sdk/src/orderSubscriber/OrderSubscriber.ts | 4 ++- sdk/src/types.ts | 1 + 6 files changed, 58 insertions(+), 18 deletions(-) create mode 100644 sdk/src/math/userStatus.ts diff --git a/sdk/src/dlob/DLOB.ts b/sdk/src/dlob/DLOB.ts index 370d44d39..00b5ec0b6 100644 --- a/sdk/src/dlob/DLOB.ts +++ b/sdk/src/dlob/DLOB.ts @@ -46,6 +46,7 @@ import { L3OrderBook, mergeL2LevelGenerators, } from './orderBookLevels'; +import { isUserProtectedMaker } from '../math/userStatus'; export type MarketNodeLists = { restingLimit: { @@ -158,9 +159,10 @@ export class DLOB { const userAccount = user.getUserAccount(); const userAccountPubkey = user.getUserAccountPublicKey(); const userAccountPubkeyString = userAccountPubkey.toString(); + const protectedMaker = isUserProtectedMaker(userAccount); for (const order of userAccount.orders) { - this.insertOrder(order, userAccountPubkeyString, slot); + this.insertOrder(order, userAccountPubkeyString, slot, protectedMaker); } } @@ -174,7 +176,7 @@ export class DLOB { } for (const { user, order } of dlobOrders) { - this.insertOrder(order, user.toString(), slot); + this.insertOrder(order, user.toString(), slot, false); } this.initialized = true; @@ -182,7 +184,7 @@ export class DLOB { } public handleOrderRecord(record: OrderRecord, slot: number): void { - this.insertOrder(record.order, record.user.toString(), slot); + this.insertOrder(record.order, record.user.toString(), slot, false); } public handleOrderActionRecord( @@ -197,14 +199,14 @@ export class DLOB { if (record.taker !== null) { const takerOrder = this.getOrder(record.takerOrderId, record.taker); if (takerOrder) { - this.trigger(takerOrder, record.taker, slot); + this.trigger(takerOrder, record.taker, slot, false); } } if (record.maker !== null) { const makerOrder = this.getOrder(record.makerOrderId, record.maker); if (makerOrder) { - this.trigger(makerOrder, record.maker, slot); + this.trigger(makerOrder, record.maker, slot, false); } } } else if (isVariant(record.action, 'fill')) { @@ -252,6 +254,7 @@ export class DLOB { order: Order, userAccount: string, slot: number, + isUserProtectedMaker: boolean, onInsert?: OrderBookCallback ): void { if (isVariant(order.status, 'init')) { @@ -273,7 +276,12 @@ export class DLOB { .get(marketType) .add(getOrderSignature(order.orderId, userAccount)); } - this.getListForOrder(order, slot)?.insert(order, marketType, userAccount); + this.getListForOrder(order, slot)?.insert( + order, + marketType, + userAccount, + isUserProtectedMaker + ); if (onInsert) { onInsert(); @@ -339,6 +347,7 @@ export class DLOB { order: Order, userAccount: PublicKey, slot: number, + isUserProtectedMaker: boolean, onTrigger?: OrderBookCallback ): void { if (isVariant(order.status, 'init')) { @@ -360,7 +369,8 @@ export class DLOB { this.getListForOrder(order, slot)?.insert( order, marketType, - userAccount.toString() + userAccount.toString(), + isUserProtectedMaker ); if (onTrigger) { onTrigger(); diff --git a/sdk/src/dlob/DLOBNode.ts b/sdk/src/dlob/DLOBNode.ts index bab644acb..db0592cac 100644 --- a/sdk/src/dlob/DLOBNode.ts +++ b/sdk/src/dlob/DLOBNode.ts @@ -19,6 +19,7 @@ export interface DLOBNode { isBaseFilled(): boolean; haveFilled: boolean; userAccount: string | undefined; + isUserProtectedMaker: boolean; } export abstract class OrderNode implements DLOBNode { @@ -27,12 +28,17 @@ export abstract class OrderNode implements DLOBNode { sortValue: BN; haveFilled = false; haveTrigger = false; - - constructor(order: Order, userAccount: string) { + isUserProtectedMaker: boolean; + constructor( + order: Order, + userAccount: string, + isUserProtectedMaker: boolean + ) { // Copy the order over to the node this.order = { ...order }; this.userAccount = userAccount; this.sortValue = this.getSortValue(order); + this.isUserProtectedMaker = isUserProtectedMaker; } abstract getSortValue(order: Order): BN; @@ -140,19 +146,28 @@ export type DLOBNodeType = export function createNode( nodeType: T, order: Order, - userAccount: string + userAccount: string, + isUserProtectedMaker: boolean ): DLOBNodeMap[T] { switch (nodeType) { case 'floatingLimit': - return new FloatingLimitOrderNode(order, userAccount); + return new FloatingLimitOrderNode( + order, + userAccount, + isUserProtectedMaker + ); case 'restingLimit': - return new RestingLimitOrderNode(order, userAccount); + return new RestingLimitOrderNode( + order, + userAccount, + isUserProtectedMaker + ); case 'takingLimit': - return new TakingLimitOrderNode(order, userAccount); + return new TakingLimitOrderNode(order, userAccount, isUserProtectedMaker); case 'market': - return new MarketOrderNode(order, userAccount); + return new MarketOrderNode(order, userAccount, isUserProtectedMaker); case 'trigger': - return new TriggerOrderNode(order, userAccount); + return new TriggerOrderNode(order, userAccount, isUserProtectedMaker); default: throw Error(`Unknown DLOBNode type ${nodeType}`); } diff --git a/sdk/src/dlob/NodeList.ts b/sdk/src/dlob/NodeList.ts index 7add26756..ab2dd235d 100644 --- a/sdk/src/dlob/NodeList.ts +++ b/sdk/src/dlob/NodeList.ts @@ -36,13 +36,19 @@ export class NodeList public insert( order: Order, marketType: MarketTypeStr, - userAccount: string + userAccount: string, + isUserProtectedMaker: boolean ): void { if (isVariant(order.status, 'init')) { return; } - const newNode = createNode(this.nodeType, order, userAccount); + const newNode = createNode( + this.nodeType, + order, + userAccount, + isUserProtectedMaker + ); const orderSignature = getOrderSignature(order.orderId, userAccount); if (this.nodeMap.has(orderSignature)) { @@ -178,6 +184,7 @@ export function* getVammNodeGenerator( isVammNode: () => true, order: undefined, userAccount: undefined, + isUserProtectedMaker: false, isBaseFilled: () => false, haveFilled: false, }; diff --git a/sdk/src/math/userStatus.ts b/sdk/src/math/userStatus.ts new file mode 100644 index 000000000..9a957e0a3 --- /dev/null +++ b/sdk/src/math/userStatus.ts @@ -0,0 +1,5 @@ +import { UserAccount, UserStatus } from '..'; + +export function isUserProtectedMaker(userAccount: UserAccount): boolean { + return (userAccount.status & UserStatus.PROTECTED_MAKER) > 0; +} diff --git a/sdk/src/orderSubscriber/OrderSubscriber.ts b/sdk/src/orderSubscriber/OrderSubscriber.ts index 401d06f80..6694fe4a6 100644 --- a/sdk/src/orderSubscriber/OrderSubscriber.ts +++ b/sdk/src/orderSubscriber/OrderSubscriber.ts @@ -12,6 +12,7 @@ import { EventEmitter } from 'events'; import { BN } from '../index'; import { decodeUser } from '../decode/user'; import { grpcSubscription } from './grpcSubscription'; +import { isUserProtectedMaker } from '../math/userStatus'; export class OrderSubscriber { driftClient: DriftClient; @@ -229,8 +230,9 @@ export class OrderSubscriber { public async getDLOB(slot: number): Promise { const dlob = this.createDLOB(); for (const [key, { userAccount }] of this.usersAccounts.entries()) { + const protectedMaker = isUserProtectedMaker(userAccount); for (const order of userAccount.orders) { - dlob.insertOrder(order, key, slot); + dlob.insertOrder(order, key, slot, protectedMaker); } } return dlob; diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 6a48ac5ca..7c9849e32 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -67,6 +67,7 @@ export enum UserStatus { BANKRUPT = 2, REDUCE_ONLY = 4, ADVANCED_LP = 8, + PROTECTED_MAKER = 16, } export class MarginMode { From dd4c96f19b117572cc65eefde33eafbdda22130e Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Wed, 4 Dec 2024 19:41:40 -0500 Subject: [PATCH 11/14] smol tweak and formmating --- programs/drift/src/controller/orders.rs | 19 +++++++++++++++---- programs/drift/src/controller/orders/tests.rs | 10 +++++++--- programs/drift/src/instructions/admin.rs | 2 +- programs/drift/src/instructions/user.rs | 3 +-- programs/drift/src/state/mod.rs | 2 +- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 14057af88..9f918640e 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -1193,7 +1193,8 @@ pub fn fill_perp_order( market.get_max_confidence_interval_multiplier()?, )?; - oracle_valid_for_amm_fill = is_oracle_valid_for_action(_oracle_validity, Some(DriftAction::FillOrderAmm))?; + oracle_valid_for_amm_fill = + is_oracle_valid_for_action(_oracle_validity, Some(DriftAction::FillOrderAmm))?; amm_is_available &= oracle_valid_for_amm_fill; amm_is_available &= !market.is_operation_paused(PerpOperation::AmmFill); @@ -1710,8 +1711,15 @@ fn get_maker_orders_info( continue; } - if maker_order.has_oracle_price_offset() && is_protected_maker { - if !protected_maker_oracle_limit_can_fill(oracle_valid_for_amm_fill, oracle_delay, user_can_skip_duration, taker_order_age, protected_maker_min_age) { + if is_protected_maker && maker_order.has_oracle_price_offset() { + if !protected_maker_oracle_limit_can_fill( + oracle_valid_for_amm_fill, + oracle_delay, + user_can_skip_duration, + taker_order_age, + protected_maker_min_age, + ) { + msg!("Protected maker oracle limit cannot fill. oracle delay = {}, user can skip duration = {}, taker order age = {}", oracle_delay, user_can_skip_duration, taker_order_age); continue; } } @@ -1735,7 +1743,10 @@ fn protected_maker_oracle_limit_can_fill( taker_order_age: u64, protected_maker_min_age: u64, ) -> bool { - oracle_valid_for_amm_fill && (oracle_delay == 0 || user_can_skip_duration || taker_order_age > protected_maker_min_age) + oracle_valid_for_amm_fill + && (oracle_delay == 0 + || user_can_skip_duration + || taker_order_age > protected_maker_min_age) } #[inline(always)] diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index 13a370ec8..52bea8e19 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -12236,7 +12236,11 @@ pub mod protected_maker_oracle_limit_can_fill { assert!(protected_maker_oracle_limit_can_fill(true, 0, false, 9, 10)); // oracle delay is 0 assert!(protected_maker_oracle_limit_can_fill(true, 1, false, 10, 9)); // min age passed assert!(protected_maker_oracle_limit_can_fill(true, 1, true, 9, 10)); // user exempt - assert!(!protected_maker_oracle_limit_can_fill(true, 1, false, 10, 11)); // no condition met - assert!(!protected_maker_oracle_limit_can_fill(false, 0, true, 10, 10)); // oracle valid for amm fill is false + assert!(!protected_maker_oracle_limit_can_fill( + true, 1, false, 10, 11 + )); // no condition met + assert!(!protected_maker_oracle_limit_can_fill( + false, 0, true, 10, 10 + )); // oracle valid for amm fill is false } -} \ No newline at end of file +} diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index 727f14dba..e431d95d1 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -4910,4 +4910,4 @@ pub struct UpdateProtectedMakerModeConfig<'info> { has_one = admin )] pub state: Box>, -} \ No newline at end of file +} diff --git a/programs/drift/src/instructions/user.rs b/programs/drift/src/instructions/user.rs index f1058cce8..2f39556a5 100644 --- a/programs/drift/src/instructions/user.rs +++ b/programs/drift/src/instructions/user.rs @@ -2305,7 +2305,7 @@ pub fn handle_update_user_protected_maker_orders( ErrorCode::DefaultError, "protected maker mode config reduce only" )?; - + config.current_users = config.current_users.safe_add(1)?; } else { config.current_users = config.current_users.safe_sub(1)?; @@ -3011,7 +3011,6 @@ pub struct UpdateUserProtectedMakerMode<'info> { pub protected_maker_mode_config: AccountLoader<'info, ProtectedMakerModeConfig>, } - #[access_control( fill_not_paused(&ctx.accounts.state) )] diff --git a/programs/drift/src/state/mod.rs b/programs/drift/src/state/mod.rs index a6020ea8c..ad87a8d88 100644 --- a/programs/drift/src/state/mod.rs +++ b/programs/drift/src/state/mod.rs @@ -23,4 +23,4 @@ pub mod state; pub mod swift_user; pub mod traits; pub mod user; -pub mod user_map; \ No newline at end of file +pub mod user_map; From ff48a9658688ab8000fd4f7159c16acf671441cc Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Wed, 4 Dec 2024 19:42:47 -0500 Subject: [PATCH 12/14] let hot wallet init config --- programs/drift/src/instructions/admin.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index e431d95d1..e6fc03a58 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -4878,7 +4878,9 @@ pub struct UpdateHighLeverageModeConfig<'info> { #[derive(Accounts)] pub struct InitializeProtectedMakerModeConfig<'info> { - #[account(mut)] + #[account( + constraint = admin.key() == admin_hot_wallet::id() || admin.key() == state.admin + )] pub admin: Signer<'info>, #[account( init, From 6735ff0a63cf1454f6ace667133e4b60ff0c2186 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Wed, 4 Dec 2024 19:47:26 -0500 Subject: [PATCH 13/14] fix build --- programs/drift/src/instructions/admin.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index e6fc03a58..034fc2d3d 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -4879,6 +4879,7 @@ pub struct UpdateHighLeverageModeConfig<'info> { #[derive(Accounts)] pub struct InitializeProtectedMakerModeConfig<'info> { #[account( + mut, constraint = admin.key() == admin_hot_wallet::id() || admin.key() == state.admin )] pub admin: Signer<'info>, From f11a7eda670d597b848ef34eda7585e47aaa5e61 Mon Sep 17 00:00:00 2001 From: Chris Heaney Date: Wed, 4 Dec 2024 19:50:32 -0500 Subject: [PATCH 14/14] cargo fmt --- programs/drift/src/instructions/user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/drift/src/instructions/user.rs b/programs/drift/src/instructions/user.rs index 5660e814f..64e1a13ba 100644 --- a/programs/drift/src/instructions/user.rs +++ b/programs/drift/src/instructions/user.rs @@ -3230,7 +3230,7 @@ pub fn handle_begin_swap<'c: 'info, 'info>( "instructions after swap end must not have writable accounts" )?; } - } else { + } else { let mut whitelisted_programs = vec![ serum_program::id(), AssociatedToken::id(),