diff --git a/CHANGELOG.md b/CHANGELOG.md index 858b54113..91eb51a29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking +- program: modify_order and modify_order_by_id now expect a ModifyOrderPolicy ([#461](https://github.com/drift-labs/protocol-v2/pull/461)) +- program: cancel_order does not fail if order does not exist ([#461](https://github.com/drift-labs/protocol-v2/pull/461)) + ## [2.28.0] - 2023-05-11 ### Features diff --git a/programs/drift/src/controller/orders.rs b/programs/drift/src/controller/orders.rs index 2e5813f3f..ecf76510f 100644 --- a/programs/drift/src/controller/orders.rs +++ b/programs/drift/src/controller/orders.rs @@ -22,7 +22,6 @@ use crate::controller::spot_position::{ }; use crate::error::DriftResult; use crate::error::ErrorCode; -use crate::get_then_update_id; use crate::instructions::OrderParams; use crate::load_mut; use crate::math::auction::calculate_auction_prices; @@ -48,6 +47,7 @@ use crate::math::stats::calculate_new_twap; use crate::math::{amm, fees, margin::*, orders::*}; use crate::{controller, PostOnlyParam}; use crate::{get_struct_values, ModifyOrderParams}; +use crate::{get_then_update_id, ModifyOrderPolicy}; use crate::math::amm::calculate_amm_available_liquidity; use crate::math::safe_unwrap::SafeUnwrap; @@ -494,7 +494,7 @@ pub fn cancel_order_by_order_id( Ok(order_index) => order_index, Err(_) => { msg!("could not find order id {}", order_id); - return Err(ErrorCode::InvalidOrder); + return Ok(()); } }; @@ -536,7 +536,7 @@ pub fn cancel_order_by_user_order_id( Some(order_index) => order_index, None => { msg!("could not find user order id {}", user_order_id); - return Err(ErrorCode::InvalidOrder); + return Ok(()); } }; @@ -677,18 +677,34 @@ pub fn modify_order( ) -> DriftResult { let user_key = user_loader.key(); let mut user = load_mut!(user_loader)?; + let order_index = match order_id { - ModifyOrderId::UserOrderId(user_order_id) => user - .get_order_index_by_user_order_id(user_order_id) - .map_err(|e| { - msg!("User order id {} not found", user_order_id); - e - })?, - ModifyOrderId::OrderId(order_id) => user.get_order_index(order_id).map_err(|e| { - msg!("Order id {} not found", order_id); - e - })?, + ModifyOrderId::UserOrderId(user_order_id) => { + match user.get_order_index_by_user_order_id(user_order_id) { + Ok(order_index) => order_index, + Err(e) => { + msg!("User order id {} not found", user_order_id); + if modify_order_params.policy == Some(ModifyOrderPolicy::MustModify) { + return Err(e); + } else { + return Ok(()); + } + } + } + } + ModifyOrderId::OrderId(order_id) => match user.get_order_index(order_id) { + Ok(order_index) => order_index, + Err(e) => { + msg!("Order id {} not found", order_id); + if modify_order_params.policy == Some(ModifyOrderPolicy::MustModify) { + return Err(e); + } else { + return Ok(()); + } + } + }, }; + let existing_order = user.orders[order_index]; cancel_order( diff --git a/programs/drift/src/instructions/user.rs b/programs/drift/src/instructions/user.rs index 6a518ac98..42f1d64d8 100644 --- a/programs/drift/src/instructions/user.rs +++ b/programs/drift/src/instructions/user.rs @@ -918,6 +918,19 @@ pub struct ModifyOrderParams { pub auction_duration: Option, pub auction_start_price: Option, pub auction_end_price: Option, + pub policy: Option, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Eq, PartialEq)] +pub enum ModifyOrderPolicy { + TryModify, + MustModify, +} + +impl Default for ModifyOrderPolicy { + fn default() -> Self { + Self::TryModify + } } #[access_control( diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 817d14843..cfa179cf7 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -3807,22 +3807,54 @@ export class DriftClient { /** * Modifies an open order (spot or perp) by closing it and replacing it with a new order. - * @param orderId: The open order to modify - * @param newDirection: The new direction for the order - * @param newBaseAmount: The new base amount for the order - * @param newLimitPice: The new limit price for the order - * @param newOraclePriceOffset: The new oracle price offset for the order - * @param newTriggerPrice: Optional - Thew new trigger price for the order. - * @param auctionDuration: - * @param auctionStartPrice: - * @param auctionEndPrice: - * @param reduceOnly: - * @param postOnly: - * @param immediateOrCancel: - * @param maxTs: + * @param orderParams.orderId: The open order to modify + * @param orderParams.newDirection: The new direction for the order + * @param orderParams.newBaseAmount: The new base amount for the order + * @param orderParams.newLimitPice: The new limit price for the order + * @param orderParams.newOraclePriceOffset: The new oracle price offset for the order + * @param orderParams.newTriggerPrice: Optional - Thew new trigger price for the order. + * @param orderParams.auctionDuration: + * @param orderParams.auctionStartPrice: + * @param orderParams.auctionEndPrice: + * @param orderParams.reduceOnly: + * @param orderParams.postOnly: + * @param orderParams.immediateOrCancel: + * @param orderParams.policy: + * @param orderParams.maxTs: * @returns */ - public async modifyOrder({ + public async modifyOrder( + orderParams: { + orderId: number; + newDirection?: PositionDirection; + newBaseAmount?: BN; + newLimitPrice?: BN; + newOraclePriceOffset?: number; + newTriggerPrice?: BN; + newTriggerCondition?: OrderTriggerCondition; + auctionDuration?: number; + auctionStartPrice?: BN; + auctionEndPrice?: BN; + reduceOnly?: boolean; + postOnly?: boolean; + immediateOrCancel?: boolean; + maxTs?: BN; + policy?: ModifyOrderParams; + }, + txParams?: TxParams + ): Promise { + const { txSig } = await this.sendTransaction( + await this.buildTransaction( + await this.getModifyOrderIx(orderParams), + txParams + ), + [], + this.opts + ); + return txSig; + } + + public async getModifyOrderIx({ orderId, newDirection, newBaseAmount, @@ -3837,7 +3869,7 @@ export class DriftClient { postOnly, immediateOrCancel, maxTs, - txParams, + policy, }: { orderId: number; newDirection?: PositionDirection; @@ -3853,8 +3885,15 @@ export class DriftClient { postOnly?: boolean; immediateOrCancel?: boolean; maxTs?: BN; - txParams?: TxParams; - }): Promise { + policy?: ModifyOrderParams; + }): Promise { + const userAccountPublicKey = await this.getUserAccountPublicKey(); + + const remainingAccounts = this.getRemainingAccounts({ + userAccounts: [this.getUserAccount()], + useMarketLastSlotCache: true, + }); + const orderParams: ModifyOrderParams = { baseAssetAmount: newBaseAmount || null, direction: newDirection || null, @@ -3868,31 +3907,10 @@ export class DriftClient { reduceOnly: reduceOnly || null, postOnly: postOnly || null, immediateOrCancel: immediateOrCancel || null, + policy: policy || null, maxTs: maxTs || null, }; - const { txSig } = await this.sendTransaction( - await this.buildTransaction( - await this.getModifyOrderIx(orderId, orderParams), - txParams - ), - [], - this.opts - ); - return txSig; - } - - public async getModifyOrderIx( - orderId: number, - orderParams: ModifyOrderParams - ): Promise { - const userAccountPublicKey = await this.getUserAccountPublicKey(); - - const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount()], - useMarketLastSlotCache: true, - }); - return await this.program.instruction.modifyOrder(orderId, orderParams, { accounts: { state: await this.getStatePublicKey(), @@ -3906,22 +3924,54 @@ export class DriftClient { /** * Modifies an open order by closing it and replacing it with a new order. - * @param userOrderId: The open order to modify - * @param newDirection: The new direction for the order - * @param newBaseAmount: The new base amount for the order - * @param newLimitPice: The new limit price for the order - * @param newOraclePriceOffset: The new oracle price offset for the order - * @param newTriggerPrice: Optional - Thew new trigger price for the order. - * @param auctionDuration: Only required if order type changed to market from something else - * @param auctionStartPrice: Only required if order type changed to market from something else - * @param auctionEndPrice: Only required if order type changed to market from something else - * @param reduceOnly: - * @param postOnly: - * @param immediateOrCancel: - * @param maxTs: + * @param orderParams.userOrderId: The open order to modify + * @param orderParams.newDirection: The new direction for the order + * @param orderParams.newBaseAmount: The new base amount for the order + * @param orderParams.newLimitPice: The new limit price for the order + * @param orderParams.newOraclePriceOffset: The new oracle price offset for the order + * @param orderParams.newTriggerPrice: Optional - Thew new trigger price for the order. + * @param orderParams.auctionDuration: Only required if order type changed to market from something else + * @param orderParams.auctionStartPrice: Only required if order type changed to market from something else + * @param orderParams.auctionEndPrice: Only required if order type changed to market from something else + * @param orderParams.reduceOnly: + * @param orderParams.postOnly: + * @param orderParams.immediateOrCancel: + * @param orderParams.policy: + * @param orderParams.maxTs: * @returns */ - public async modifyOrderByUserOrderId({ + public async modifyOrderByUserOrderId( + orderParams: { + userOrderId: number; + newDirection?: PositionDirection; + newBaseAmount?: BN; + newLimitPrice?: BN; + newOraclePriceOffset?: number; + newTriggerPrice?: BN; + newTriggerCondition?: OrderTriggerCondition; + auctionDuration?: number; + auctionStartPrice?: BN; + auctionEndPrice?: BN; + reduceOnly?: boolean; + postOnly?: boolean; + immediateOrCancel?: boolean; + policy?: ModifyOrderParams; + maxTs?: BN; + }, + txParams?: TxParams + ): Promise { + const { txSig } = await this.sendTransaction( + await this.buildTransaction( + await this.getModifyOrderByUserIdIx(orderParams), + txParams + ), + [], + this.opts + ); + return txSig; + } + + public async getModifyOrderByUserIdIx({ userOrderId, newDirection, newBaseAmount, @@ -3936,7 +3986,7 @@ export class DriftClient { postOnly, immediateOrCancel, maxTs, - txParams, + policy, }: { userOrderId: number; newDirection?: PositionDirection; @@ -3951,9 +4001,17 @@ export class DriftClient { reduceOnly?: boolean; postOnly?: boolean; immediateOrCancel?: boolean; + policy?: ModifyOrderParams; maxTs?: BN; txParams?: TxParams; - }): Promise { + }): Promise { + const userAccountPublicKey = await this.getUserAccountPublicKey(); + + const remainingAccounts = this.getRemainingAccounts({ + userAccounts: [this.getUserAccount()], + useMarketLastSlotCache: true, + }); + const orderParams: ModifyOrderParams = { baseAssetAmount: newBaseAmount || null, direction: newDirection || null, @@ -3967,31 +4025,10 @@ export class DriftClient { reduceOnly: reduceOnly || null, postOnly: postOnly || null, immediateOrCancel: immediateOrCancel || null, + policy: policy || null, maxTs: maxTs || null, }; - const { txSig } = await this.sendTransaction( - await this.buildTransaction( - await this.getModifyOrderByUserIdIx(userOrderId, orderParams), - txParams - ), - [], - this.opts - ); - return txSig; - } - - public async getModifyOrderByUserIdIx( - userOrderId: number, - orderParams: ModifyOrderParams - ): Promise { - const userAccountPublicKey = await this.getUserAccountPublicKey(); - - const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount()], - useMarketLastSlotCache: true, - }); - return await this.program.instruction.modifyOrderByUserId( userOrderId, orderParams, diff --git a/sdk/src/idl/drift.json b/sdk/src/idl/drift.json index 35735ae4c..63e8acc12 100644 --- a/sdk/src/idl/drift.json +++ b/sdk/src/idl/drift.json @@ -4736,7 +4736,7 @@ "name": "orderTickSize", "docs": [ "Spot orders must be a multiple of the tick size", - "precision: QUOTE_PRECISION" + "precision: PRICE_PRECISION" ], "type": "u64" }, @@ -5565,6 +5565,14 @@ "type": { "option": "i64" } + }, + { + "name": "policy", + "type": { + "option": { + "defined": "ModifyOrderPolicy" + } + } } ] } @@ -7237,6 +7245,20 @@ ] } }, + { + "name": "ModifyOrderPolicy", + "type": { + "kind": "enum", + "variants": [ + { + "name": "TryModify" + }, + { + "name": "MustModify" + } + ] + } + }, { "name": "TwapPeriod", "type": { @@ -10067,4 +10089,4 @@ "msg": "InvalidPhoenixMarket" } ] -} +} \ No newline at end of file diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 3eaa1fb65..8c4ed595e 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -883,7 +883,12 @@ export type OptionalOrderParams = { export type ModifyOrderParams = { [Property in keyof OrderParams]?: OrderParams[Property] | null; -}; +} & { policy?: ModifyOrderPolicy }; + +export class ModifyOrderPolicy { + static readonly MUST_MODIFY = { mustModify: {} }; + static readonly TRY_MODIFY = { tryModify: {} }; +} export const DefaultOrderParams: OrderParams = { orderType: OrderType.MARKET,