From c8efabec594969f5d462afe39f0dccd08783e079 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Mon, 18 Nov 2024 12:32:33 -0500 Subject: [PATCH 01/10] Add resize drift user orders --- crates/drift-idl-gen/src/lib.rs | 75 +++++--- crates/src/drift_idl.rs | 313 ++++++++++++++++++++++++++++++-- crates/src/ffi.rs | 17 +- crates/src/math/liquidation.rs | 12 +- res/drift.json | 158 +++++++++++++++- 5 files changed, 518 insertions(+), 57 deletions(-) diff --git a/crates/drift-idl-gen/src/lib.rs b/crates/drift-idl-gen/src/lib.rs index 04b5094..5023647 100644 --- a/crates/drift-idl-gen/src/lib.rs +++ b/crates/drift-idl-gen/src/lib.rs @@ -264,34 +264,70 @@ fn generate_idl_types(idl: &Idl) -> String { for account in &idl.accounts { let struct_name = Ident::new(&account.name, proc_macro2::Span::call_site()); - let struct_fields = account.account_type.fields.iter().map(|field| { - let field_name = - Ident::new(&to_snake_case(&field.name), proc_macro2::Span::call_site()); + let mut has_vec_field = false; + let struct_fields: Vec = account + .account_type + .fields + .iter() + .map(|field| { + let field_name = + Ident::new(&to_snake_case(&field.name), proc_macro2::Span::call_site()); + if let ArgType::Vec { .. } = field.field_type { + dbg!(&field.name, "has vec"); + has_vec_field = true; + } + let mut serde_decorator = TokenStream::new(); + let mut field_type: Type = + syn::parse_str(&field.field_type.to_rust_type()).unwrap(); + // workaround for padding types preventing outertype from deriving 'Default' + if field_name == "padding" { + if let ArgType::Array { array: (_t, len) } = &field.field_type { + field_type = syn::parse_str(&format!("Padding<{len}>")).unwrap(); + serde_decorator = quote! { + #[serde(skip)] + }; + } + } - let mut serde_decorator = TokenStream::new(); - let mut field_type: Type = syn::parse_str(&field.field_type.to_rust_type()).unwrap(); - // workaround for padding types preventing outertype from deriving 'Default' - if field_name == "padding" { - if let ArgType::Array { array: (_t, len) } = &field.field_type { - field_type = syn::parse_str(&format!("Padding<{len}>")).unwrap(); - serde_decorator = quote! { - #[serde(skip)] - }; + quote! { + #serde_decorator + pub #field_name: #field_type, } + }) + .collect(); + + let derive_tokens = if !has_vec_field { + quote! { + #[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Serialize, Deserialize, Copy, Clone, Default, Debug, PartialEq)] + } + } else { + // can't derive `Copy` on accounts with `Vec` field + // `InitSpace` requires a 'max_len' but no point enforcing here if unset on program side + quote! { + #[derive(AnchorSerialize, AnchorDeserialize, Serialize, Deserialize, Clone, Default, Debug, PartialEq)] } + }; + let zc_tokens = if !has_vec_field { + // without copy can't derive the ZeroCopy trait quote! { - #serde_decorator - pub #field_name: #field_type, + #[automatically_derived] + unsafe impl anchor_lang::__private::bytemuck::Pod for #struct_name {} + #[automatically_derived] + unsafe impl anchor_lang::__private::bytemuck::Zeroable for #struct_name {} + #[automatically_derived] + impl anchor_lang::ZeroCopy for #struct_name {} } - }); + } else { + Default::default() + }; let discriminator: TokenStream = format!("{:?}", sighash("account", &account.name)) .parse() .unwrap(); let struct_def = quote! { #[repr(C)] - #[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Serialize, Deserialize, Copy, Clone, Default, Debug, PartialEq)] + #derive_tokens pub struct #struct_name { #(#struct_fields)* } @@ -299,12 +335,7 @@ fn generate_idl_types(idl: &Idl) -> String { impl anchor_lang::Discriminator for #struct_name { const DISCRIMINATOR: [u8; 8] = #discriminator; } - #[automatically_derived] - unsafe impl anchor_lang::__private::bytemuck::Pod for #struct_name {} - #[automatically_derived] - unsafe impl anchor_lang::__private::bytemuck::Zeroable for #struct_name {} - #[automatically_derived] - impl anchor_lang::ZeroCopy for #struct_name {} + #zc_tokens #[automatically_derived] impl anchor_lang::AccountSerialize for #struct_name { fn try_serialize(&self, writer: &mut W) -> anchor_lang::Result<()> { diff --git a/crates/src/drift_idl.rs b/crates/src/drift_idl.rs index edcb4ab..ae29ea4 100644 --- a/crates/src/drift_idl.rs +++ b/crates/src/drift_idl.rs @@ -52,7 +52,9 @@ pub mod instructions { #[automatically_derived] impl anchor_lang::InstructionData for InitializeRfqUser {} #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] - pub struct InitializeSwiftUserOrders {} + pub struct InitializeSwiftUserOrders { + pub num_orders: u16, + } #[automatically_derived] impl anchor_lang::Discriminator for InitializeSwiftUserOrders { const DISCRIMINATOR: [u8; 8] = [26, 91, 2, 246, 96, 153, 117, 194]; @@ -60,6 +62,16 @@ pub mod instructions { #[automatically_derived] impl anchor_lang::InstructionData for InitializeSwiftUserOrders {} #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] + pub struct ResizeSwiftUserOrders { + pub num_orders: u16, + } + #[automatically_derived] + impl anchor_lang::Discriminator for ResizeSwiftUserOrders { + const DISCRIMINATOR: [u8; 8] = [36, 57, 40, 90, 193, 150, 249, 53]; + } + #[automatically_derived] + impl anchor_lang::InstructionData for ResizeSwiftUserOrders {} + #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] pub struct InitializeReferrerName { pub name: [u8; 32], } @@ -409,6 +421,14 @@ pub mod instructions { #[automatically_derived] impl anchor_lang::InstructionData for DeleteUser {} #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] + pub struct DeleteSwiftUserOrders {} + #[automatically_derived] + impl anchor_lang::Discriminator for DeleteSwiftUserOrders { + const DISCRIMINATOR: [u8; 8] = [83, 157, 116, 215, 177, 177, 158, 20]; + } + #[automatically_derived] + impl anchor_lang::InstructionData for DeleteSwiftUserOrders {} + #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] pub struct ReclaimRent {} #[automatically_derived] impl anchor_lang::Discriminator for ReclaimRent { @@ -500,6 +520,14 @@ pub mod instructions { #[automatically_derived] impl anchor_lang::InstructionData for UpdateUserFuelBonus {} #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] + pub struct UpdateUserStatsReferrerStatus {} + #[automatically_derived] + impl anchor_lang::Discriminator for UpdateUserStatsReferrerStatus { + const DISCRIMINATOR: [u8; 8] = [174, 154, 72, 42, 191, 148, 145, 205]; + } + #[automatically_derived] + impl anchor_lang::InstructionData for UpdateUserStatsReferrerStatus {} + #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] pub struct UpdateUserOpenOrdersCount {} #[automatically_derived] impl anchor_lang::Discriminator for UpdateUserOpenOrdersCount { @@ -2698,6 +2726,25 @@ pub mod types { pub uuid: [u8; 8], pub max_slot: u64, pub order_id: u32, + pub padding: u32, + } + #[repr(C)] + #[derive( + AnchorSerialize, + AnchorDeserialize, + InitSpace, + Serialize, + Deserialize, + Copy, + Clone, + Default, + Debug, + PartialEq, + )] + pub struct SwiftUserOrdersFixed { + pub user_pubkey: Pubkey, + pub padding: u32, + pub len: u32, } #[repr(C)] #[derive( @@ -3360,6 +3407,7 @@ pub mod types { SettlePnl, SettlePnlWithPosition, Liquidation, + AmmImmediateFill, } #[derive( AnchorSerialize, @@ -3493,6 +3541,24 @@ pub mod types { Debug, PartialEq, )] + pub enum AMMAvailability { + #[default] + Immediate, + AfterMinDuration, + Unavailable, + } + #[derive( + AnchorSerialize, + AnchorDeserialize, + InitSpace, + Serialize, + Deserialize, + Copy, + Clone, + Default, + Debug, + PartialEq, + )] pub enum SettlePnlMode { #[default] MustSettle, @@ -3573,6 +3639,7 @@ pub mod types { LiqPaused, FundingPaused, SettlePnlPaused, + AmmImmediateFillPaused, } #[derive( AnchorSerialize, @@ -4518,32 +4585,18 @@ pub mod accounts { } #[repr(C)] #[derive( - AnchorSerialize, - AnchorDeserialize, - InitSpace, - Serialize, - Deserialize, - Copy, - Clone, - Default, - Debug, - PartialEq, + AnchorSerialize, AnchorDeserialize, Serialize, Deserialize, Clone, Default, Debug, PartialEq, )] pub struct SwiftUserOrders { pub user_pubkey: Pubkey, - pub swift_order_data: [SwiftOrderId; 32], + pub padding: u32, + pub swift_order_data: Vec, } #[automatically_derived] impl anchor_lang::Discriminator for SwiftUserOrders { const DISCRIMINATOR: [u8; 8] = [67, 121, 127, 98, 21, 50, 57, 193]; } #[automatically_derived] - unsafe impl anchor_lang::__private::bytemuck::Pod for SwiftUserOrders {} - #[automatically_derived] - unsafe impl anchor_lang::__private::bytemuck::Zeroable for SwiftUserOrders {} - #[automatically_derived] - impl anchor_lang::ZeroCopy for SwiftUserOrders {} - #[automatically_derived] impl anchor_lang::AccountSerialize for SwiftUserOrders { fn try_serialize(&self, writer: &mut W) -> anchor_lang::Result<()> { if writer.write_all(&Self::DISCRIMINATOR).is_err() { @@ -5104,7 +5157,7 @@ pub mod accounts { AccountMeta { pubkey: self.user, is_signer: false, - is_writable: true, + is_writable: false, }, AccountMeta { pubkey: self.payer, @@ -5155,6 +5208,82 @@ pub mod accounts { } #[repr(C)] #[derive(Copy, Clone, Default, AnchorSerialize, AnchorDeserialize, Serialize, Deserialize)] + pub struct ResizeSwiftUserOrders { + pub swift_user_orders: Pubkey, + pub authority: Pubkey, + pub user: Pubkey, + pub system_program: Pubkey, + } + #[automatically_derived] + impl anchor_lang::Discriminator for ResizeSwiftUserOrders { + const DISCRIMINATOR: [u8; 8] = [237, 41, 225, 39, 0, 209, 116, 228]; + } + #[automatically_derived] + unsafe impl anchor_lang::__private::bytemuck::Pod for ResizeSwiftUserOrders {} + #[automatically_derived] + unsafe impl anchor_lang::__private::bytemuck::Zeroable for ResizeSwiftUserOrders {} + #[automatically_derived] + impl anchor_lang::ZeroCopy for ResizeSwiftUserOrders {} + #[automatically_derived] + impl anchor_lang::InstructionData for ResizeSwiftUserOrders {} + #[automatically_derived] + impl ToAccountMetas for ResizeSwiftUserOrders { + fn to_account_metas(&self) -> Vec { + vec![ + AccountMeta { + pubkey: self.swift_user_orders, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: self.authority, + is_signer: true, + is_writable: true, + }, + AccountMeta { + pubkey: self.user, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: self.system_program, + is_signer: false, + is_writable: false, + }, + ] + } + } + #[automatically_derived] + impl anchor_lang::AccountSerialize for ResizeSwiftUserOrders { + fn try_serialize(&self, writer: &mut W) -> anchor_lang::Result<()> { + if writer.write_all(&Self::DISCRIMINATOR).is_err() { + return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into()); + } + if AnchorSerialize::serialize(self, writer).is_err() { + return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into()); + } + Ok(()) + } + } + #[automatically_derived] + impl anchor_lang::AccountDeserialize for ResizeSwiftUserOrders { + fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result { + let given_disc = &buf[..8]; + if Self::DISCRIMINATOR != given_disc { + return Err(anchor_lang::error!( + anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch + )); + } + Self::try_deserialize_unchecked(buf) + } + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + let mut data: &[u8] = &buf[8..]; + AnchorDeserialize::deserialize(&mut data) + .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into()) + } + } + #[repr(C)] + #[derive(Copy, Clone, Default, AnchorSerialize, AnchorDeserialize, Serialize, Deserialize)] pub struct InitializeReferrerName { pub referrer_name: Pubkey, pub user: Pubkey, @@ -7653,6 +7782,82 @@ pub mod accounts { } #[repr(C)] #[derive(Copy, Clone, Default, AnchorSerialize, AnchorDeserialize, Serialize, Deserialize)] + pub struct DeleteSwiftUserOrders { + pub user: Pubkey, + pub swift_user_orders: Pubkey, + pub state: Pubkey, + pub authority: Pubkey, + } + #[automatically_derived] + impl anchor_lang::Discriminator for DeleteSwiftUserOrders { + const DISCRIMINATOR: [u8; 8] = [183, 16, 243, 132, 133, 172, 85, 107]; + } + #[automatically_derived] + unsafe impl anchor_lang::__private::bytemuck::Pod for DeleteSwiftUserOrders {} + #[automatically_derived] + unsafe impl anchor_lang::__private::bytemuck::Zeroable for DeleteSwiftUserOrders {} + #[automatically_derived] + impl anchor_lang::ZeroCopy for DeleteSwiftUserOrders {} + #[automatically_derived] + impl anchor_lang::InstructionData for DeleteSwiftUserOrders {} + #[automatically_derived] + impl ToAccountMetas for DeleteSwiftUserOrders { + fn to_account_metas(&self) -> Vec { + vec![ + AccountMeta { + pubkey: self.user, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: self.swift_user_orders, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: self.state, + is_signer: false, + is_writable: true, + }, + AccountMeta { + pubkey: self.authority, + is_signer: true, + is_writable: false, + }, + ] + } + } + #[automatically_derived] + impl anchor_lang::AccountSerialize for DeleteSwiftUserOrders { + fn try_serialize(&self, writer: &mut W) -> anchor_lang::Result<()> { + if writer.write_all(&Self::DISCRIMINATOR).is_err() { + return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into()); + } + if AnchorSerialize::serialize(self, writer).is_err() { + return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into()); + } + Ok(()) + } + } + #[automatically_derived] + impl anchor_lang::AccountDeserialize for DeleteSwiftUserOrders { + fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result { + let given_disc = &buf[..8]; + if Self::DISCRIMINATOR != given_disc { + return Err(anchor_lang::error!( + anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch + )); + } + Self::try_deserialize_unchecked(buf) + } + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + let mut data: &[u8] = &buf[8..]; + AnchorDeserialize::deserialize(&mut data) + .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into()) + } + } + #[repr(C)] + #[derive(Copy, Clone, Default, AnchorSerialize, AnchorDeserialize, Serialize, Deserialize)] pub struct ReclaimRent { pub user: Pubkey, pub user_stats: Pubkey, @@ -8443,6 +8648,76 @@ pub mod accounts { } #[repr(C)] #[derive(Copy, Clone, Default, AnchorSerialize, AnchorDeserialize, Serialize, Deserialize)] + pub struct UpdateUserStatsReferrerStatus { + pub state: Pubkey, + pub authority: Pubkey, + pub user_stats: Pubkey, + } + #[automatically_derived] + impl anchor_lang::Discriminator for UpdateUserStatsReferrerStatus { + const DISCRIMINATOR: [u8; 8] = [88, 125, 77, 90, 13, 11, 141, 158]; + } + #[automatically_derived] + unsafe impl anchor_lang::__private::bytemuck::Pod for UpdateUserStatsReferrerStatus {} + #[automatically_derived] + unsafe impl anchor_lang::__private::bytemuck::Zeroable for UpdateUserStatsReferrerStatus {} + #[automatically_derived] + impl anchor_lang::ZeroCopy for UpdateUserStatsReferrerStatus {} + #[automatically_derived] + impl anchor_lang::InstructionData for UpdateUserStatsReferrerStatus {} + #[automatically_derived] + impl ToAccountMetas for UpdateUserStatsReferrerStatus { + fn to_account_metas(&self) -> Vec { + vec![ + AccountMeta { + pubkey: self.state, + is_signer: false, + is_writable: false, + }, + AccountMeta { + pubkey: self.authority, + is_signer: true, + is_writable: false, + }, + AccountMeta { + pubkey: self.user_stats, + is_signer: false, + is_writable: true, + }, + ] + } + } + #[automatically_derived] + impl anchor_lang::AccountSerialize for UpdateUserStatsReferrerStatus { + fn try_serialize(&self, writer: &mut W) -> anchor_lang::Result<()> { + if writer.write_all(&Self::DISCRIMINATOR).is_err() { + return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into()); + } + if AnchorSerialize::serialize(self, writer).is_err() { + return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into()); + } + Ok(()) + } + } + #[automatically_derived] + impl anchor_lang::AccountDeserialize for UpdateUserStatsReferrerStatus { + fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result { + let given_disc = &buf[..8]; + if Self::DISCRIMINATOR != given_disc { + return Err(anchor_lang::error!( + anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch + )); + } + Self::try_deserialize_unchecked(buf) + } + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + let mut data: &[u8] = &buf[8..]; + AnchorDeserialize::deserialize(&mut data) + .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into()) + } + } + #[repr(C)] + #[derive(Copy, Clone, Default, AnchorSerialize, AnchorDeserialize, Serialize, Deserialize)] pub struct UpdateUserOpenOrdersCount { pub state: Pubkey, pub authority: Pubkey, diff --git a/crates/src/ffi.rs b/crates/src/ffi.rs index f5d4978..6f4a5c2 100644 --- a/crates/src/ffi.rs +++ b/crates/src/ffi.rs @@ -54,6 +54,7 @@ extern "C" { market: &accounts::PerpMarket, size: u128, margin_type: MarginRequirementType, + high_leverage_mode: bool, ) -> FfiResult; #[allow(improper_ctypes)] pub fn perp_market_get_open_interest(market: &accounts::PerpMarket) -> u128; @@ -246,8 +247,11 @@ impl accounts::PerpMarket { &self, size: u128, margin_requirement_type: MarginRequirementType, + high_leverage_mode: bool, ) -> SdkResult { - to_sdk_result(unsafe { perp_market_get_margin_ratio(self, size, margin_requirement_type) }) + to_sdk_result(unsafe { + perp_market_get_margin_ratio(self, size, margin_requirement_type, high_leverage_mode) + }) } pub fn get_open_interest(&self) -> u128 { unsafe { perp_market_get_open_interest(self) } @@ -651,22 +655,29 @@ mod tests { margin_ratio_initial: 1_000 * MARGIN_PRECISION, // 10% margin_ratio_maintenance: 500, // 5% imf_factor: 0, // No impact for simplicity + high_leverage_margin_ratio_maintenance: 1_234, ..Default::default() }; let size = 1_000 * MARGIN_PRECISION as u128; // Assuming MARGIN_PRECISION is defined // Test initial margin ratio - let result = perp_market.get_margin_ratio(size, MarginRequirementType::Initial); + let result = perp_market.get_margin_ratio(size, MarginRequirementType::Initial, false); assert!(result.is_ok()); let initial_margin_ratio = result.unwrap(); assert_eq!(initial_margin_ratio, 1_000 * MARGIN_PRECISION); // 10% // Test maintenance margin ratio - let result = perp_market.get_margin_ratio(size, MarginRequirementType::Maintenance); + let result = perp_market.get_margin_ratio(size, MarginRequirementType::Maintenance, false); assert!(result.is_ok()); let maintenance_margin_ratio = result.unwrap(); assert_eq!(maintenance_margin_ratio, 500); // 5% + + // HL mode + let result = perp_market.get_margin_ratio(size, MarginRequirementType::Maintenance, true); + assert!(result.is_ok()); + let maintenance_margin_ratio = result.unwrap(); + assert_eq!(maintenance_margin_ratio, 1_234); // 5% } #[test] diff --git a/crates/src/math/liquidation.rs b/crates/src/math/liquidation.rs index 2d7704e..0f1704d 100644 --- a/crates/src/math/liquidation.rs +++ b/crates/src/math/liquidation.rs @@ -19,7 +19,7 @@ use crate::{ accounts::{PerpMarket, SpotMarket, User}, MarginRequirementType, PerpPosition, }, - DriftClient, MarketId, SdkError, SdkResult, SpotPosition, + DriftClient, MarginMode, MarketId, SdkError, SdkResult, SpotPosition, }; /// Info on a position's liquidation price and unrealized PnL @@ -178,8 +178,12 @@ pub fn calculate_liquidation_price_inner( let perp_position_with_lp = perp_position.simulate_settled_lp_position(perp_market, oracle_price)?; - let perp_free_collateral_delta = - calculate_perp_free_collateral_delta(&perp_position_with_lp, perp_market, oracle_price); + let perp_free_collateral_delta = calculate_perp_free_collateral_delta( + &perp_position_with_lp, + perp_market, + oracle_price, + user.margin_mode, + ); // user holding spot asset case let mut spot_free_collateral_delta = 0; @@ -210,6 +214,7 @@ fn calculate_perp_free_collateral_delta( position: &PerpPosition, market: &PerpMarket, oracle_price: i64, + margin_mode: MarginMode, ) -> i64 { let current_base_asset_amount = position.base_asset_amount; @@ -220,6 +225,7 @@ fn calculate_perp_free_collateral_delta( .get_margin_ratio( worst_case_base_amount.unsigned_abs(), MarginRequirementType::Maintenance, + margin_mode == MarginMode::HighLeverage, ) .unwrap(); let margin_ratio = (margin_ratio as i64 * QUOTE_PRECISION_I64) / MARGIN_PRECISION as i64; diff --git a/res/drift.json b/res/drift.json index 2374bf4..c72e535 100644 --- a/res/drift.json +++ b/res/drift.json @@ -1,5 +1,5 @@ { - "version": "2.97.0", + "version": "2.100.0", "name": "drift", "instructions": [ { @@ -144,7 +144,7 @@ }, { "name": "user", - "isMut": true, + "isMut": false, "isSigner": false }, { @@ -163,7 +163,43 @@ "isSigner": false } ], - "args": [] + "args": [ + { + "name": "numOrders", + "type": "u16" + } + ] + }, + { + "name": "resizeSwiftUserOrders", + "accounts": [ + { + "name": "swiftUserOrders", + "isMut": true, + "isSigner": false + }, + { + "name": "authority", + "isMut": true, + "isSigner": true + }, + { + "name": "user", + "isMut": false, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "numOrders", + "type": "u16" + } + ] }, { "name": "initializeReferrerName", @@ -1422,6 +1458,32 @@ ], "args": [] }, + { + "name": "deleteSwiftUserOrders", + "accounts": [ + { + "name": "user", + "isMut": true, + "isSigner": false + }, + { + "name": "swiftUserOrders", + "isMut": true, + "isSigner": false + }, + { + "name": "state", + "isMut": true, + "isSigner": false + }, + { + "name": "authority", + "isMut": false, + "isSigner": true + } + ], + "args": [] + }, { "name": "reclaimRent", "accounts": [ @@ -1751,6 +1813,27 @@ ], "args": [] }, + { + "name": "updateUserStatsReferrerStatus", + "accounts": [ + { + "name": "state", + "isMut": false, + "isSigner": false + }, + { + "name": "authority", + "isMut": false, + "isSigner": true + }, + { + "name": "userStats", + "isMut": true, + "isSigner": false + } + ], + "args": [] + }, { "name": "updateUserOpenOrdersCount", "accounts": [ @@ -7568,6 +7651,9 @@ }, { "name": "SwiftUserOrders", + "docs": [ + "* This struct is a duplicate of SwiftUserOrdersZeroCopy\n * It is used to give anchor an struct to generate the idl for clients\n * The struct SwiftUserOrdersZeroCopy is used to load the data in efficiently" + ], "type": { "kind": "struct", "fields": [ @@ -7575,15 +7661,16 @@ "name": "userPubkey", "type": "publicKey" }, + { + "name": "padding", + "type": "u32" + }, { "name": "swiftOrderData", "type": { - "array": [ - { - "defined": "SwiftOrderId" - }, - 32 - ] + "vec": { + "defined": "SwiftOrderId" + } } } ] @@ -9789,6 +9876,30 @@ { "name": "orderId", "type": "u32" + }, + { + "name": "padding", + "type": "u32" + } + ] + } + }, + { + "name": "SwiftUserOrdersFixed", + "type": { + "kind": "struct", + "fields": [ + { + "name": "userPubkey", + "type": "publicKey" + }, + { + "name": "padding", + "type": "u32" + }, + { + "name": "len", + "type": "u32" } ] } @@ -10692,7 +10803,10 @@ "name": "PlaceAndMake" }, { - "name": "PlaceAndTake" + "name": "PlaceAndTake", + "fields": [ + "bool" + ] }, { "name": "Liquidation" @@ -10888,6 +11002,9 @@ }, { "name": "Liquidation" + }, + { + "name": "AmmImmediateFill" } ] } @@ -11030,6 +11147,23 @@ ] } }, + { + "name": "AMMAvailability", + "type": { + "kind": "enum", + "variants": [ + { + "name": "Immediate" + }, + { + "name": "AfterMinDuration" + }, + { + "name": "Unavailable" + } + ] + } + }, { "name": "SettlePnlMode", "type": { @@ -11120,6 +11254,9 @@ }, { "name": "SettlePnlPaused" + }, + { + "name": "AmmImmediateFillPaused" } ] } @@ -11681,6 +11818,7 @@ }, { "name": "hash", + "doc": "capitalized to (S)tring for drift-idl-gen to work easier", "type": "String", "index": false }, From a322c7b993daf2fd43fe7156b296ea0bc49340d9 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Mon, 18 Nov 2024 13:25:58 -0500 Subject: [PATCH 02/10] high maintenance mode --- build.rs | 86 ++++++++++++++++++--------------- crates/drift-ffi-sys | 2 +- crates/drift-idl-gen/src/lib.rs | 1 - crates/src/ffi.rs | 2 + 4 files changed, 49 insertions(+), 42 deletions(-) diff --git a/build.rs b/build.rs index dca5a95..bd6bdee 100644 --- a/build.rs +++ b/build.rs @@ -59,51 +59,57 @@ fn main() { fail_build(); } - // Build ffi crate and link - let mut ffi_build = std::process::Command::new("rustup"); - ffi_build - .env_clear() - .envs(ffi_build_envs) - .current_dir(drift_ffi_sys_crate.clone()) - .args(["run", &ffi_toolchain, "cargo", "build"]); - - match profile.as_str() { - "debug" => (), - "release" => { - ffi_build.arg("--release"); + // install the dylib to system path + let lib_major_v = std::env::var("CARGO_PKG_VERSION_MAJOR").unwrap(); + let lib_minor_v = std::env::var("CARGO_PKG_VERSION_MINOR").unwrap(); + let libffi_out_path = drift_ffi_sys_crate.join(Path::new(&format!( + "target/{profile}/{LIB}.{lib_ext}.{lib_major_v}.{lib_minor_v}" + ))); + + if !libffi_out_path.exists() { + // Build ffi crate and link + let mut ffi_build = std::process::Command::new("rustup"); + ffi_build + .env_clear() + .envs(ffi_build_envs) + .current_dir(drift_ffi_sys_crate.clone()) + .args(["run", &ffi_toolchain, "cargo", "build"]); + + match profile.as_str() { + "debug" => (), + "release" => { + ffi_build.arg("--release"); + } + custom => { + ffi_build.arg(format!("--profile={custom}")); + } } - custom => { - ffi_build.arg(format!("--profile={custom}")); + + let output = ffi_build.output().expect("drift-ffi-sys built"); + if !output.status.success() { + eprintln!(" {}", String::from_utf8_lossy(output.stderr.as_slice())); + fail_build(); } - } - let output = ffi_build.output().expect("drift-ffi-sys built"); - if !output.status.success() { - eprintln!(" {}", String::from_utf8_lossy(output.stderr.as_slice())); - fail_build(); - } + if !output.status.success() { + eprintln!( + "{LIB} could not be installed: {}", + String::from_utf8_lossy(output.stderr.as_slice()) + ); + } - if !output.status.success() { - eprintln!( - "{LIB} could not be installed: {}", - String::from_utf8_lossy(output.stderr.as_slice()) - ); - } - // install the dylib to system path - let libffi_out_path = - drift_ffi_sys_crate.join(Path::new(&format!("target/{profile}/{LIB}.{lib_ext}"))); + if let Ok(out_dir) = std::env::var("OUT_DIR") { + let _output = std::process::Command::new("cp") + .args([ + libffi_out_path.to_str().expect("ffi build path"), + out_dir.as_str(), + ]) + .output() + .expect("install ok"); + println!("{LIB}: searching for lib at: {out_dir}"); + println!("cargo:rustc-link-search=native={out_dir}"); + } - if let Ok(out_dir) = std::env::var("OUT_DIR") { - let _output = std::process::Command::new("cp") - .args([ - libffi_out_path.to_str().expect("ffi build path"), - out_dir.as_str(), - ]) - .output() - .expect("install ok"); - println!("{LIB}: searching for lib at: {out_dir}"); - println!("cargo:rustc-link-search=native={out_dir}"); - } else { let _output = std::process::Command::new("ln") .args([ "-sf", diff --git a/crates/drift-ffi-sys b/crates/drift-ffi-sys index 719a0f9..bdbc10f 160000 --- a/crates/drift-ffi-sys +++ b/crates/drift-ffi-sys @@ -1 +1 @@ -Subproject commit 719a0f9511a5bb49228841ff1e3c500025dfcc40 +Subproject commit bdbc10f2895f8ac362cfe80af19ece3e43f72d86 diff --git a/crates/drift-idl-gen/src/lib.rs b/crates/drift-idl-gen/src/lib.rs index 5023647..393b46f 100644 --- a/crates/drift-idl-gen/src/lib.rs +++ b/crates/drift-idl-gen/src/lib.rs @@ -273,7 +273,6 @@ fn generate_idl_types(idl: &Idl) -> String { let field_name = Ident::new(&to_snake_case(&field.name), proc_macro2::Span::call_site()); if let ArgType::Vec { .. } = field.field_type { - dbg!(&field.name, "has vec"); has_vec_field = true; } let mut serde_decorator = TokenStream::new(); diff --git a/crates/src/ffi.rs b/crates/src/ffi.rs index 6f4a5c2..eaf6c2b 100644 --- a/crates/src/ffi.rs +++ b/crates/src/ffi.rs @@ -655,7 +655,9 @@ mod tests { margin_ratio_initial: 1_000 * MARGIN_PRECISION, // 10% margin_ratio_maintenance: 500, // 5% imf_factor: 0, // No impact for simplicity + // enable HL mode for this market high_leverage_margin_ratio_maintenance: 1_234, + high_leverage_margin_ratio_initial: 4_321, ..Default::default() }; From cc260b740566c451d0864635e807342ed4f159c2 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Mon, 18 Nov 2024 16:18:22 -0500 Subject: [PATCH 03/10] CI use built ffi lib --- .github/workflows/build.yml | 14 +++++++------- build.rs | 7 ++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9efb74f..2dbd5f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,13 +30,13 @@ jobs: run: | rustup show active-toolchain rustup component add clippy rustfmt - - name: install libdrift_ffi_sys - run: | - curl -L https://github.com/user-attachments/files/17160233/libdrift_ffi_sys.so.zip > ffi.zip - unzip ffi.zip - ldd libdrift_ffi_sys.so - sudo cp libdrift_ffi_sys.so /usr/lib - ldconfig -p + # - name: install libdrift_ffi_sys + # run: | + # curl -L https://github.com/user-attachments/files/17160233/libdrift_ffi_sys.so.zip > ffi.zip + # unzip ffi.zip + # ldd libdrift_ffi_sys.so + # sudo cp libdrift_ffi_sys.so /usr/lib + # ldconfig -p - name: Format run: cargo fmt --all -- --check - name: Build diff --git a/build.rs b/build.rs index bd6bdee..d82067e 100644 --- a/build.rs +++ b/build.rs @@ -60,11 +60,8 @@ fn main() { } // install the dylib to system path - let lib_major_v = std::env::var("CARGO_PKG_VERSION_MAJOR").unwrap(); - let lib_minor_v = std::env::var("CARGO_PKG_VERSION_MINOR").unwrap(); - let libffi_out_path = drift_ffi_sys_crate.join(Path::new(&format!( - "target/{profile}/{LIB}.{lib_ext}.{lib_major_v}.{lib_minor_v}" - ))); + let libffi_out_path = + drift_ffi_sys_crate.join(Path::new(&format!("target/{profile}/{LIB}.{lib_ext}"))); if !libffi_out_path.exists() { // Build ffi crate and link From 08df3c27f7d1a2356e2e56a3e4674fd545d7c853 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Mon, 18 Nov 2024 16:23:24 -0500 Subject: [PATCH 04/10] CI use built ffi lib --- .github/workflows/build.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2dbd5f1..c1fc1d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,13 +30,6 @@ jobs: run: | rustup show active-toolchain rustup component add clippy rustfmt - # - name: install libdrift_ffi_sys - # run: | - # curl -L https://github.com/user-attachments/files/17160233/libdrift_ffi_sys.so.zip > ffi.zip - # unzip ffi.zip - # ldd libdrift_ffi_sys.so - # sudo cp libdrift_ffi_sys.so /usr/lib - # ldconfig -p - name: Format run: cargo fmt --all -- --check - name: Build @@ -59,5 +52,4 @@ jobs: RUST_LOG: info TEST_DEVNET_RPC_ENDPOINT: ${{ secrets.DEVNET_RPC_ENDPOINT }} TEST_MAINNET_RPC_ENDPOINT: ${{ secrets.MAINNET_RPC_ENDPOINT }} - TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }} - CARGO_DRIFT_FFI_PATH: "/usr/lib" \ No newline at end of file + TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }} \ No newline at end of file From 94d4b4c31dd634d5fb7a8c8fc6224bb202ff7ebd Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Mon, 18 Nov 2024 16:54:16 -0500 Subject: [PATCH 05/10] update ffi lib for ci --- .github/workflows/build.yml | 10 +++++++++- crates/drift-ffi-sys | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1fc1d0..43c6676 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,6 +30,13 @@ jobs: run: | rustup show active-toolchain rustup component add clippy rustfmt + - name: install libdrift_ffi_sys + run: | + curl -L https://github.com/user-attachments/files/17806677/libdrift_ffi_sys.so.zip > ffi.zip + unzip ffi.zip + ldd libdrift_ffi_sys.so + sudo cp libdrift_ffi_sys.so /usr/lib + ldconfig -p - name: Format run: cargo fmt --all -- --check - name: Build @@ -52,4 +59,5 @@ jobs: RUST_LOG: info TEST_DEVNET_RPC_ENDPOINT: ${{ secrets.DEVNET_RPC_ENDPOINT }} TEST_MAINNET_RPC_ENDPOINT: ${{ secrets.MAINNET_RPC_ENDPOINT }} - TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }} \ No newline at end of file + TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }} + CARGO_DRIFT_FFI_PATH: "/usr/lib" \ No newline at end of file diff --git a/crates/drift-ffi-sys b/crates/drift-ffi-sys index bdbc10f..0d5df4c 160000 --- a/crates/drift-ffi-sys +++ b/crates/drift-ffi-sys @@ -1 +1 @@ -Subproject commit bdbc10f2895f8ac362cfe80af19ece3e43f72d86 +Subproject commit 0d5df4c857466cb9132763bb925d94c9a1b96f5c From 7ee18c33d4e693da867928c5ebab57c7b92afba6 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Mon, 18 Nov 2024 17:17:03 -0500 Subject: [PATCH 06/10] tidy up --- build.rs | 88 +++++++++++++++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/build.rs b/build.rs index d82067e..e2b7eb4 100644 --- a/build.rs +++ b/build.rs @@ -63,62 +63,60 @@ fn main() { let libffi_out_path = drift_ffi_sys_crate.join(Path::new(&format!("target/{profile}/{LIB}.{lib_ext}"))); - if !libffi_out_path.exists() { - // Build ffi crate and link - let mut ffi_build = std::process::Command::new("rustup"); - ffi_build - .env_clear() - .envs(ffi_build_envs) - .current_dir(drift_ffi_sys_crate.clone()) - .args(["run", &ffi_toolchain, "cargo", "build"]); - - match profile.as_str() { - "debug" => (), - "release" => { - ffi_build.arg("--release"); - } - custom => { - ffi_build.arg(format!("--profile={custom}")); - } + // Build ffi crate and link + let mut ffi_build = std::process::Command::new("rustup"); + ffi_build + .env_clear() + .envs(ffi_build_envs) + .current_dir(drift_ffi_sys_crate.clone()) + .args(["run", &ffi_toolchain, "cargo", "build"]); + + match profile.as_str() { + "debug" => (), + "release" => { + ffi_build.arg("--release"); } - - let output = ffi_build.output().expect("drift-ffi-sys built"); - if !output.status.success() { - eprintln!(" {}", String::from_utf8_lossy(output.stderr.as_slice())); - fail_build(); + custom => { + ffi_build.arg(format!("--profile={custom}")); } + } - if !output.status.success() { - eprintln!( - "{LIB} could not be installed: {}", - String::from_utf8_lossy(output.stderr.as_slice()) - ); - } + let output = ffi_build.output().expect("drift-ffi-sys built"); + if !output.status.success() { + eprintln!(" {}", String::from_utf8_lossy(output.stderr.as_slice())); + fail_build(); + } - if let Ok(out_dir) = std::env::var("OUT_DIR") { - let _output = std::process::Command::new("cp") - .args([ - libffi_out_path.to_str().expect("ffi build path"), - out_dir.as_str(), - ]) - .output() - .expect("install ok"); - println!("{LIB}: searching for lib at: {out_dir}"); - println!("cargo:rustc-link-search=native={out_dir}"); - } + if !output.status.success() { + eprintln!( + "{LIB} could not be installed: {}", + String::from_utf8_lossy(output.stderr.as_slice()) + ); + } - let _output = std::process::Command::new("ln") + if let Ok(out_dir) = std::env::var("OUT_DIR") { + let _output = std::process::Command::new("cp") .args([ - "-sf", libffi_out_path.to_str().expect("ffi build path"), - "/usr/local/lib/", + out_dir.as_str(), ]) .output() .expect("install ok"); - - println!("{LIB}: searching for lib at: /usr/local/lib"); - println!("cargo:rustc-link-search=native=/usr/local/lib"); + println!("{LIB}: searching for lib at: {out_dir}"); + println!("cargo:rustc-link-search=native={out_dir}"); } + + let _output = std::process::Command::new("ln") + .args([ + "-sf", + libffi_out_path.to_str().expect("ffi build path"), + "/usr/local/lib/", + ]) + .output() + .expect("install ok"); + + println!("{LIB}: searching for lib at: /usr/local/lib"); + println!("cargo:rustc-link-search=native=/usr/local/lib"); } if let Ok(lib_path) = std::env::var("CARGO_DRIFT_FFI_PATH") { From cde570d0a1854b83f0d63f3e0e7681033f6e7abf Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Wed, 20 Nov 2024 13:22:04 -0500 Subject: [PATCH 07/10] add max trade size calculations --- crates/src/drift_idl.rs | 6 +- crates/src/math/leverage.rs | 155 +++++++++++++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 6 deletions(-) diff --git a/crates/src/drift_idl.rs b/crates/src/drift_idl.rs index ae29ea4..024c170 100644 --- a/crates/src/drift_idl.rs +++ b/crates/src/drift_idl.rs @@ -2,7 +2,6 @@ #![doc = r""] #![doc = r" Auto-generated IDL types, manual edits do not persist (see `crates/drift-idl-gen`)"] #![doc = r""] -use self::traits::ToAccountMetas; use anchor_lang::{ prelude::{ account, @@ -13,6 +12,8 @@ use anchor_lang::{ }; use serde::{Deserialize, Serialize}; use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey}; + +use self::traits::ToAccountMetas; pub mod traits { use solana_sdk::instruction::AccountMeta; #[doc = r" This is distinct from the anchor_lang version of the trait"] @@ -1918,8 +1919,9 @@ pub mod instructions { } pub mod types { #![doc = r" IDL types"] - use super::*; use std::ops::Mul; + + use super::*; #[doc = ""] #[doc = " backwards compatible u128 deserializing data from rust <=1.76.0 when u/i128 was 8-byte aligned"] #[doc = " https://solana.stackexchange.com/questions/7720/using-u128-without-sacrificing-alignment-8"] diff --git a/crates/src/math/leverage.rs b/crates/src/math/leverage.rs index 629c4a5..dfe0ec8 100644 --- a/crates/src/math/leverage.rs +++ b/crates/src/math/leverage.rs @@ -1,10 +1,18 @@ -use super::{account_map_builder::AccountsListBuilder, constants::PRICE_PRECISION}; +use solana_sdk::pubkey::Pubkey; + +use super::{ + account_map_builder::AccountsListBuilder, + constants::{AMM_RESERVE_PRECISION, BASE_PRECISION, MARGIN_PRECISION, PRICE_PRECISION}, +}; use crate::{ + accounts::PerpMarket, ffi::{ - calculate_margin_requirement_and_total_collateral_and_liability_info, MarginContextMode, + calculate_margin_requirement_and_total_collateral_and_liability_info, MarginCalculation, + MarginContextMode, }, types::accounts::User, - DriftClient, SdkError, SdkResult, + ContractType, DriftClient, MarginMode, MarginRequirementType, MarketId, PositionDirection, + SdkError, SdkResult, }; pub fn get_leverage(client: &DriftClient, user: &User) -> SdkResult { @@ -72,8 +80,147 @@ fn calculate_leverage(total_liability_value: u128, net_asset_value: i128) -> u12 sign as u128 * (leverage * PRICE_PRECISION as f64) as u128 } -#[cfg(feature = "rpc_tests")] +/// Provides margin calculation helpers for User accounts +/// +/// sync, requires client is subscribed to necessary markets beforehand +pub trait UserMargin { + /// Calculate user's max. trade size in USDC for a given market and direction + /// + /// * `user` - the user account + /// * `market` - the market to trade + /// * `trade_side` - the direction of the trade + /// + /// Returns max USDC trade size (PRICE_PRECISION) + fn max_trade_size( + &self, + user: &Pubkey, + market: MarketId, + trade_side: PositionDirection, + ) -> SdkResult; + fn calculate_perp_buying_power( + &self, + user: &User, + market: &PerpMarket, + oracle_price: i64, + collateral_buffer: u64, + ) -> SdkResult; + /// Calculate the user's live margin information + fn calculate_margin_info(&self, user: &User) -> SdkResult; +} + +impl UserMargin for DriftClient { + fn calculate_margin_info(&self, user: &User) -> SdkResult { + let mut builder = AccountsListBuilder::default(); + let mut accounts = builder.try_build(self, user)?; + calculate_margin_requirement_and_total_collateral_and_liability_info( + user, + &mut accounts, + MarginContextMode::StandardMaintenance, + ) + } + fn max_trade_size( + &self, + user: &Pubkey, + market: MarketId, + trade_side: PositionDirection, + ) -> SdkResult { + // TODO: implement for spot + let oracle = self + .try_get_oracle_price_data_and_slot(market) + .ok_or(SdkError::NoMarketData(market))?; + let oracle_price = oracle.data.price; + let market_account = self.try_get_perp_market_account(market.index())?; + let user_account = self.try_get_account::(user)?; + + let position = user_account + .get_perp_position(market_account.market_index) + .map_err(|_| SdkError::NoMarketData(MarketId::perp(market_account.market_index)))?; + // add any position we have on the opposite side of the current trade + // because we can "flip" the size of this position without taking any extra leverage. + let is_reduce_only = position.base_asset_amount.is_negative() as u8 != trade_side as u8; + let opposite_side_liability_value = calculate_perp_liability_value( + position.base_asset_amount, + oracle_price, + market_account.contract_type == ContractType::Prediction, + ); + + let lp_buffer = ((oracle_price as u64 * market_account.amm.order_step_size) + / AMM_RESERVE_PRECISION as u64) + * position.lp_shares.max(1); + + let max_position_size = self.calculate_perp_buying_power( + &user_account, + &market_account, + oracle_price, + lp_buffer as u64, + )?; + + Ok(max_position_size as u64 + opposite_side_liability_value * is_reduce_only as u64) + } + /// Calculate buying power = free collateral / initial margin ratio + /// + /// Returns buying power in `QUOTE_PRECISION` units + fn calculate_perp_buying_power( + &self, + user: &User, + market: &PerpMarket, + oracle_price: i64, + collateral_buffer: u64, + ) -> SdkResult { + let position = user + .get_perp_position(market.market_index) + .map_err(|_| SdkError::NoMarketData(MarketId::perp(market.market_index)))?; + let position_with_lp_settle = + position.simulate_settled_lp_position(market, oracle_price)?; + + let worst_case_base_amount = position_with_lp_settle + .worst_case_base_asset_amount(oracle_price, market.contract_type)?; + + let margin_info = self.calculate_margin_info(user)?; + let free_collateral = margin_info.get_free_collateral() - collateral_buffer as u128; + + let margin_ratio = market + .get_margin_ratio( + worst_case_base_amount.unsigned_abs(), + crate::MarginRequirementType::Initial, + user.margin_mode == MarginMode::HighLeverage, + ) + .expect("got margin ratio"); + let margin_ratio = margin_ratio.max(user.max_margin_ratio); + + return Ok((free_collateral * MARGIN_PRECISION as u128) / margin_ratio as u128); + } +} + +#[inline] +pub fn calculate_perp_liability_value( + base_asset_amount: i64, + price: i64, + is_prediction_market: bool, +) -> u64 { + let max_prediction_price = PRICE_PRECISION as i64; + let max_price = + max_prediction_price * base_asset_amount.is_negative() as i64 * is_prediction_market as i64; + (base_asset_amount * (max_price - price) / BASE_PRECISION as i64).unsigned_abs() +} + +#[cfg(test)] mod tests { + use super::calculate_perp_liability_value; + + #[test] + fn calculate_perp_liability_value_works() { + use crate::math::constants::{BASE_PRECISION_I64, PRICE_PRECISION_I64}; + + calculate_perp_liability_value(1 * BASE_PRECISION_I64, 5 * PRICE_PRECISION_I64, false); + calculate_perp_liability_value(-1 * BASE_PRECISION_I64, 5 * PRICE_PRECISION_I64, false); + calculate_perp_liability_value(-1 * BASE_PRECISION_I64, 10_000, true); + calculate_perp_liability_value(1 * BASE_PRECISION_I64, 90_000, true); + } +} + +#[cfg(feature = "rpc_tests")] +mod rpc_tests { use solana_sdk::signature::Keypair; use super::*; From 279d1a0840546bf627145dc5b43b1b07fbb7cf5a Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Wed, 20 Nov 2024 13:27:35 -0500 Subject: [PATCH 08/10] clippy --- crates/src/math/leverage.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/src/math/leverage.rs b/crates/src/math/leverage.rs index dfe0ec8..9368c86 100644 --- a/crates/src/math/leverage.rs +++ b/crates/src/math/leverage.rs @@ -152,7 +152,7 @@ impl UserMargin for DriftClient { &user_account, &market_account, oracle_price, - lp_buffer as u64, + lp_buffer, )?; Ok(max_position_size as u64 + opposite_side_liability_value * is_reduce_only as u64) @@ -182,13 +182,13 @@ impl UserMargin for DriftClient { let margin_ratio = market .get_margin_ratio( worst_case_base_amount.unsigned_abs(), - crate::MarginRequirementType::Initial, + MarginRequirementType::Initial, user.margin_mode == MarginMode::HighLeverage, ) .expect("got margin ratio"); let margin_ratio = margin_ratio.max(user.max_margin_ratio); - return Ok((free_collateral * MARGIN_PRECISION as u128) / margin_ratio as u128); + Ok((free_collateral * MARGIN_PRECISION as u128) / margin_ratio as u128) } } From 5a3bb8c20980d168be3e958424a0c22db40d18d6 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Wed, 20 Nov 2024 13:44:34 -0500 Subject: [PATCH 09/10] error on spot --- crates/src/math/leverage.rs | 57 ++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/crates/src/math/leverage.rs b/crates/src/math/leverage.rs index 9368c86..a41e6bc 100644 --- a/crates/src/math/leverage.rs +++ b/crates/src/math/leverage.rs @@ -124,38 +124,43 @@ impl UserMargin for DriftClient { market: MarketId, trade_side: PositionDirection, ) -> SdkResult { - // TODO: implement for spot let oracle = self .try_get_oracle_price_data_and_slot(market) .ok_or(SdkError::NoMarketData(market))?; let oracle_price = oracle.data.price; - let market_account = self.try_get_perp_market_account(market.index())?; let user_account = self.try_get_account::(user)?; - let position = user_account - .get_perp_position(market_account.market_index) - .map_err(|_| SdkError::NoMarketData(MarketId::perp(market_account.market_index)))?; - // add any position we have on the opposite side of the current trade - // because we can "flip" the size of this position without taking any extra leverage. - let is_reduce_only = position.base_asset_amount.is_negative() as u8 != trade_side as u8; - let opposite_side_liability_value = calculate_perp_liability_value( - position.base_asset_amount, - oracle_price, - market_account.contract_type == ContractType::Prediction, - ); - - let lp_buffer = ((oracle_price as u64 * market_account.amm.order_step_size) - / AMM_RESERVE_PRECISION as u64) - * position.lp_shares.max(1); - - let max_position_size = self.calculate_perp_buying_power( - &user_account, - &market_account, - oracle_price, - lp_buffer, - )?; - - Ok(max_position_size as u64 + opposite_side_liability_value * is_reduce_only as u64) + if market.is_perp() { + let market_account = self.try_get_perp_market_account(market.index())?; + + let position = user_account + .get_perp_position(market_account.market_index) + .map_err(|_| SdkError::NoMarketData(MarketId::perp(market_account.market_index)))?; + // add any position we have on the opposite side of the current trade + // because we can "flip" the size of this position without taking any extra leverage. + let is_reduce_only = position.base_asset_amount.is_negative() as u8 != trade_side as u8; + let opposite_side_liability_value = calculate_perp_liability_value( + position.base_asset_amount, + oracle_price, + market_account.contract_type == ContractType::Prediction, + ); + + let lp_buffer = ((oracle_price as u64 * market_account.amm.order_step_size) + / AMM_RESERVE_PRECISION as u64) + * position.lp_shares.max(1); + + let max_position_size = self.calculate_perp_buying_power( + &user_account, + &market_account, + oracle_price, + lp_buffer, + )?; + + Ok(max_position_size as u64 + opposite_side_liability_value * is_reduce_only as u64) + } else { + // TODO: implement for spot + Err(SdkError::Generic("spot market unimplemented".to_string())) + } } /// Calculate buying power = free collateral / initial margin ratio /// From a34f2caec92f2c6730327392d99cf9657ec82a4c Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Wed, 20 Nov 2024 13:47:49 -0500 Subject: [PATCH 10/10] add asserts --- crates/src/drift_idl.rs | 6 ++---- crates/src/math/leverage.rs | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/crates/src/drift_idl.rs b/crates/src/drift_idl.rs index 024c170..ae29ea4 100644 --- a/crates/src/drift_idl.rs +++ b/crates/src/drift_idl.rs @@ -2,6 +2,7 @@ #![doc = r""] #![doc = r" Auto-generated IDL types, manual edits do not persist (see `crates/drift-idl-gen`)"] #![doc = r""] +use self::traits::ToAccountMetas; use anchor_lang::{ prelude::{ account, @@ -12,8 +13,6 @@ use anchor_lang::{ }; use serde::{Deserialize, Serialize}; use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey}; - -use self::traits::ToAccountMetas; pub mod traits { use solana_sdk::instruction::AccountMeta; #[doc = r" This is distinct from the anchor_lang version of the trait"] @@ -1919,9 +1918,8 @@ pub mod instructions { } pub mod types { #![doc = r" IDL types"] - use std::ops::Mul; - use super::*; + use std::ops::Mul; #[doc = ""] #[doc = " backwards compatible u128 deserializing data from rust <=1.76.0 when u/i128 was 8-byte aligned"] #[doc = " https://solana.stackexchange.com/questions/7720/using-u128-without-sacrificing-alignment-8"] diff --git a/crates/src/math/leverage.rs b/crates/src/math/leverage.rs index a41e6bc..17a331f 100644 --- a/crates/src/math/leverage.rs +++ b/crates/src/math/leverage.rs @@ -216,11 +216,23 @@ mod tests { #[test] fn calculate_perp_liability_value_works() { use crate::math::constants::{BASE_PRECISION_I64, PRICE_PRECISION_I64}; - - calculate_perp_liability_value(1 * BASE_PRECISION_I64, 5 * PRICE_PRECISION_I64, false); - calculate_perp_liability_value(-1 * BASE_PRECISION_I64, 5 * PRICE_PRECISION_I64, false); - calculate_perp_liability_value(-1 * BASE_PRECISION_I64, 10_000, true); - calculate_perp_liability_value(1 * BASE_PRECISION_I64, 90_000, true); + // test values taken from TS sdk + assert_eq!( + calculate_perp_liability_value(1 * BASE_PRECISION_I64, 5 * PRICE_PRECISION_I64, false), + 5_000_000 + ); + assert_eq!( + calculate_perp_liability_value(-1 * BASE_PRECISION_I64, 5 * PRICE_PRECISION_I64, false), + 5_000_000 + ); + assert_eq!( + calculate_perp_liability_value(-1 * BASE_PRECISION_I64, 10_000, true), + 990_000 + ); + assert_eq!( + calculate_perp_liability_value(1 * BASE_PRECISION_I64, 90_000, true), + 90_000 + ); } }