diff --git a/CHANGELOG.md b/CHANGELOG.md index 2365b267d..335f1762a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- program: add reduce only atomic taking against amm option [#2037](https://github.com/drift-labs/protocol-v2/pull/2037) + ### Fixes ### Breaking diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index bc8b3aec4..76407a7da 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -996,8 +996,13 @@ pub fn fill_perp_order( .position(|order| order.order_id == order_id && order.status == OrderStatus::Open) .ok_or_else(print_error!(ErrorCode::OrderDoesNotExist))?; - let (order_status, market_index, order_market_type) = - get_struct_values!(user.orders[order_index], status, market_index, market_type); + let (order_status, market_index, order_market_type, order_reduce_only) = get_struct_values!( + user.orders[order_index], + status, + market_index, + market_type, + reduce_only + ); validate!( order_market_type == MarketType::Perp, @@ -1095,7 +1100,7 @@ pub fn fill_perp_order( market.amm.oracle_low_risk_slot_delay_override, )?; - user_can_skip_duration = user.can_skip_auction_duration(user_stats)?; + user_can_skip_duration = user.can_skip_auction_duration(user_stats, order_reduce_only)?; amm_is_available &= market.amm_can_fill_order( &user.orders[order_index], slot, diff --git a/programs/drift/src/controller/orders/amm_jit_tests.rs b/programs/drift/src/controller/orders/amm_jit_tests.rs index 87c160b78..4ab526d1f 100644 --- a/programs/drift/src/controller/orders/amm_jit_tests.rs +++ b/programs/drift/src/controller/orders/amm_jit_tests.rs @@ -332,7 +332,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -535,7 +537,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -749,7 +753,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -959,7 +965,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -1172,7 +1180,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -1393,7 +1403,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -1813,7 +1825,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -2013,7 +2027,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -2226,7 +2242,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -2778,8 +2796,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = - taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -3022,7 +3041,9 @@ pub mod amm_jit { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, diff --git a/programs/drift/src/controller/orders/fuel_tests.rs b/programs/drift/src/controller/orders/fuel_tests.rs index 24959f9e3..5684875e6 100644 --- a/programs/drift/src/controller/orders/fuel_tests.rs +++ b/programs/drift/src/controller/orders/fuel_tests.rs @@ -270,7 +270,9 @@ pub mod fuel_scoring { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, diff --git a/programs/drift/src/controller/orders/tests.rs b/programs/drift/src/controller/orders/tests.rs index 21667d9ef..931c0edee 100644 --- a/programs/drift/src/controller/orders/tests.rs +++ b/programs/drift/src/controller/orders/tests.rs @@ -100,7 +100,8 @@ pub mod fill_order_protected_maker { use crate::state::spot_market_map::SpotMarketMap; use crate::state::state::State; use crate::state::user::{ - MarketType, OrderStatus, OrderType, SpotPosition, User, UserStats, UserStatus, + MarketType, OrderStatus, OrderType, SpotPosition, User, UserStats, + UserStatsPausedOperations, UserStatus, }; use crate::test_utils::*; use crate::test_utils::{ @@ -231,7 +232,7 @@ pub mod fill_order_protected_maker { AccountLoader::try_from(&user_account_info).unwrap(); let mut taker_stats = UserStats { - disable_update_perp_bid_ask_twap: true, + paused_operations: UserStatsPausedOperations::AmmAtomicFill as u8, ..UserStats::default() }; @@ -323,7 +324,7 @@ pub mod fill_order_protected_maker { // user exempt, no 10 bps applied for pmm let mut taker_stats = UserStats { - disable_update_perp_bid_ask_twap: false, + paused_operations: 0, ..UserStats::default() }; @@ -3367,7 +3368,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -3625,7 +3628,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -3831,7 +3836,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -4050,7 +4057,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -4229,7 +4238,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -4440,7 +4451,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -4640,7 +4653,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -4793,7 +4808,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -4973,7 +4990,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -5576,7 +5595,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 10; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, @@ -5833,7 +5854,9 @@ pub mod fulfill_order { let order_index = 0; let min_auction_duration = 0; - let user_can_skip_auction_duration = taker.can_skip_auction_duration(&taker_stats).unwrap(); + let user_can_skip_auction_duration = taker + .can_skip_auction_duration(&taker_stats, false) + .unwrap(); let is_amm_available = get_amm_is_available( &taker.orders[order_index], min_auction_duration, diff --git a/programs/drift/src/instructions/admin.rs b/programs/drift/src/instructions/admin.rs index 110c43b8a..3feeabb79 100644 --- a/programs/drift/src/instructions/admin.rs +++ b/programs/drift/src/instructions/admin.rs @@ -4484,19 +4484,19 @@ pub fn handle_update_spot_auction_duration( Ok(()) } -pub fn handle_admin_disable_update_perp_bid_ask_twap( +pub fn handle_admin_update_user_stats_paused_operations( ctx: Context, - disable: bool, + paused_operations: u8, ) -> Result<()> { let mut user_stats = load_mut!(ctx.accounts.user_stats)?; msg!( - "disable_update_perp_bid_ask_twap: {:?} -> {:?}", - user_stats.disable_update_perp_bid_ask_twap, - disable + "user_stats.paused_operations: {:?} -> {:?}", + user_stats.paused_operations, + paused_operations ); - user_stats.disable_update_perp_bid_ask_twap = disable; + user_stats.paused_operations = paused_operations; Ok(()) } diff --git a/programs/drift/src/instructions/keeper.rs b/programs/drift/src/instructions/keeper.rs index d6f920811..dffdd81d6 100644 --- a/programs/drift/src/instructions/keeper.rs +++ b/programs/drift/src/instructions/keeper.rs @@ -2624,9 +2624,9 @@ pub fn handle_update_perp_bid_ask_twap<'c: 'info, 'info>( let keeper_stats = load!(ctx.accounts.keeper_stats)?; validate!( - !keeper_stats.disable_update_perp_bid_ask_twap, + keeper_stats.can_update_bid_ask_twap(), ErrorCode::CantUpdatePerpBidAskTwap, - "Keeper stats disable_update_perp_bid_ask_twap is true" + "Keeper stats can_update_bid_ask_twap is false" )?; let min_if_stake = 1000 * QUOTE_PRECISION_U64; diff --git a/programs/drift/src/lib.rs b/programs/drift/src/lib.rs index cc30448b0..ae6d90d0a 100644 --- a/programs/drift/src/lib.rs +++ b/programs/drift/src/lib.rs @@ -528,11 +528,11 @@ pub mod drift { // handle_update_user_open_orders_count(ctx) // } - pub fn admin_disable_update_perp_bid_ask_twap( + pub fn admin_update_user_stats_paused_operations( ctx: Context, - disable: bool, + paused_operations: u8, ) -> Result<()> { - handle_admin_disable_update_perp_bid_ask_twap(ctx, disable) + handle_admin_update_user_stats_paused_operations(ctx, paused_operations) } pub fn settle_pnl<'c: 'info, 'info>( diff --git a/programs/drift/src/state/user.rs b/programs/drift/src/state/user.rs index 017f5aeb2..8932a7245 100644 --- a/programs/drift/src/state/user.rs +++ b/programs/drift/src/state/user.rs @@ -613,8 +613,21 @@ impl User { Ok(true) } - pub fn can_skip_auction_duration(&self, user_stats: &UserStats) -> DriftResult { - if user_stats.disable_update_perp_bid_ask_twap { + pub fn can_skip_auction_duration( + &self, + user_stats: &UserStats, + reduce_only_order: bool, + ) -> DriftResult { + let atomic_fill_paused = UserStatsPausedOperations::is_operation_paused( + user_stats.paused_operations, + UserStatsPausedOperations::AmmAtomicFill, + ); + let atomic_risk_increasing_fill_paused = UserStatsPausedOperations::is_operation_paused( + user_stats.paused_operations, + UserStatsPausedOperations::AmmAtomicRiskIncreasingFill, + ); + + if atomic_fill_paused || (atomic_risk_increasing_fill_paused && !reduce_only_order) { return Ok(false); } @@ -1688,8 +1701,8 @@ pub struct UserStats { /// First bit (LSB): 1 if user is a referrer, 0 otherwise /// Second bit: 1 if user was referred, 0 otherwise pub referrer_status: u8, - pub disable_update_perp_bid_ask_twap: bool, - pub padding1: [u8; 1], + pub disable_update_perp_bid_ask_twap: u8, + pub paused_operations: u8, /// whether the user has a FuelOverflow account pub fuel_overflow_status: u8, /// accumulated fuel for token amounts of insurance @@ -2039,6 +2052,14 @@ impl UserStats { } } } + + pub fn can_update_bid_ask_twap(&self) -> bool { + let update_mark_twap_paused = UserStatsPausedOperations::is_operation_paused( + self.paused_operations, + UserStatsPausedOperations::UpdateBidAskTwap, + ); + !update_mark_twap_paused + } } pub trait FuelOverflowProvider<'a> { @@ -2152,3 +2173,17 @@ impl FuelOverflow { .safe_add(self.fuel_maker) } } + +#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)] +#[repr(u8)] +pub enum UserStatsPausedOperations { + UpdateBidAskTwap = 0b00000001, + AmmAtomicFill = 0b00000010, + AmmAtomicRiskIncreasingFill = 0b00000100, +} + +impl UserStatsPausedOperations { + pub fn is_operation_paused(current: u8, operation: UserStatsPausedOperations) -> bool { + current & operation as u8 != 0 + } +} diff --git a/sdk/src/idl/drift.json b/sdk/src/idl/drift.json index b55a69fc4..fcd488872 100644 --- a/sdk/src/idl/drift.json +++ b/sdk/src/idl/drift.json @@ -2042,7 +2042,7 @@ "args": [] }, { - "name": "adminDisableUpdatePerpBidAskTwap", + "name": "adminUpdateUserStatsPausedOperations", "accounts": [ { "name": "admin", @@ -2062,8 +2062,8 @@ ], "args": [ { - "name": "disable", - "type": "bool" + "name": "pausedOperations", + "type": "u8" } ] }, @@ -11587,13 +11587,8 @@ "type": "bool" }, { - "name": "padding1", - "type": { - "array": [ - "u8", - 1 - ] - } + "name": "pausedOperations", + "type": "u8" }, { "name": "fuelOverflowStatus", @@ -15960,6 +15955,23 @@ ] } }, + { + "name": "UserStatsPausedOperations", + "type": { + "kind": "enum", + "variants": [ + { + "name": "UpdateBidAskTwap" + }, + { + "name": "AmmAtomicFill" + }, + { + "name": "AmmAtomicRiskIncreasingFill" + } + ] + } + }, { "name": "SignatureVerificationError", "type": { @@ -19580,5 +19592,8 @@ "name": "MarketIndexNotFoundAmmCache", "msg": "MarketIndexNotFoundAmmCache" } - ] + ], + "metadata": { + "address": "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH" + } } \ No newline at end of file