levana_perpswap_cosmos/contracts/market/
entry.rs

1//! Entrypoint messages for the market
2use super::deferred_execution::DeferredExecId;
3use super::order::LimitOrder;
4use super::position::{ClosedPosition, PositionId};
5use super::spot_price::SpotPriceConfigInit;
6use super::{config::ConfigUpdate, crank::CrankWorkInfo};
7use crate::contracts::market::order::OrderId;
8use crate::prelude::*;
9use crate::{contracts::liquidity_token::LiquidityTokenKind, token::TokenInit};
10use cosmwasm_schema::{cw_serde, QueryResponses};
11use cosmwasm_std::{Binary, BlockInfo, Decimal256, Uint128};
12use pyth_sdk_cw::PriceIdentifier;
13use schemars::schema::{InstanceType, SchemaObject};
14use schemars::JsonSchema;
15use std::collections::BTreeMap;
16use std::fmt::Formatter;
17
18/// The InstantiateMsg comes from Factory only
19#[cw_serde]
20pub struct InstantiateMsg {
21    /// The factory address
22    pub factory: RawAddr,
23    /// Modifications to the default config value
24    pub config: Option<ConfigUpdate>,
25    /// Mandatory spot price config
26    pub spot_price: SpotPriceConfigInit,
27    /// Initial price to use in the contract
28    ///
29    /// This is required when doing manual price updates, and prohibited for oracle based price updates. It would make more sense to include this in [SpotPriceConfigInit], but that will create more complications in config update logic.
30    pub initial_price: Option<InitialPrice>,
31    /// Base, quote, and market type
32    pub market_id: MarketId,
33    /// The token used for collateral
34    pub token: TokenInit,
35    /// Initial borrow fee rate when launching the protocol, annualized
36    pub initial_borrow_fee_rate: Decimal256,
37}
38
39/// Initial price when instantiating a contract
40#[cw_serde]
41#[derive(Copy)]
42pub struct InitialPrice {
43    /// Price of base in terms of quote
44    pub price: PriceBaseInQuote,
45    /// Price of collateral in terms of USD
46    pub price_usd: PriceCollateralInUsd,
47}
48
49/// Config info passed on to all sub-contracts in order to
50/// add a new market.
51#[cw_serde]
52pub struct NewMarketParams {
53    /// Base, quote, and market type
54    pub market_id: MarketId,
55
56    /// The token used for collateral
57    pub token: TokenInit,
58
59    /// config
60    pub config: Option<ConfigUpdate>,
61
62    /// mandatory spot price config
63    pub spot_price: SpotPriceConfigInit,
64
65    /// Initial borrow fee rate, annualized
66    pub initial_borrow_fee_rate: Decimal256,
67
68    /// Initial price, only provided for manual price updates
69    pub initial_price: Option<InitialPrice>,
70}
71
72/// Parameter for the copy trading parameter
73#[cw_serde]
74pub struct NewCopyTradingParams {
75    /// Name of the copy trading pool
76    pub name: String,
77
78    /// Description of the copy_trading pool. Not more than 128
79    /// characters.
80    pub description: String,
81}
82
83/// Parameters passed as part of factory contract
84#[cw_serde]
85pub struct NewCounterTradeParams {
86    /// Market ID where the trading is allowed
87    pub market_id: MarketId,
88}
89
90/// There are two sources of slippage in the protocol:
91/// - Change in the oracle price from creation of the message to execution of the message.
92/// - Change in delta neutrality fee from creation of the message to execution of the message.
93/// Slippage assert tolerance is the tolerance to the sum of the two sources of slippage.
94#[cw_serde]
95#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
96#[derive(Eq)]
97pub struct SlippageAssert {
98    /// Expected effective price from the sender. To incorporate tolerance on delta neutrality fee,
99    /// the expected price should be modified by expected fee rate:
100    /// `price = oracle_price * (1 + fee_rate)`
101    /// `fee_rate` here is the ratio between the delta neutrality fee amount and notional size delta (in collateral asset).
102    pub price: PriceBaseInQuote,
103    /// Max ratio tolerance of actual trade price differing in an unfavorable direction from expected price.
104    /// Tolerance of 0.01 means max 1% difference.
105    pub tolerance: Number,
106}
107
108/// Sudo message for the market contract
109#[cw_serde]
110pub enum SudoMsg {
111    /// Update the config
112    ConfigUpdate {
113        /// New configuration parameters
114        update: Box<ConfigUpdate>,
115    },
116}
117
118/// Execute message for the market contract
119#[allow(clippy::large_enum_variant)]
120#[cw_serde]
121pub enum ExecuteMsg {
122    /// Owner-only executions
123    Owner(ExecuteOwnerMsg),
124
125    /// cw20
126    Receive {
127        /// Owner of funds sent to the contract
128        sender: RawAddr,
129        /// Amount of funds sent
130        amount: Uint128,
131        /// Must parse to a [ExecuteMsg]
132        msg: Binary,
133    },
134
135    /// Open a new position
136    OpenPosition {
137        /// Assertion that the price has not moved too far
138        slippage_assert: Option<SlippageAssert>,
139        /// Leverage of new position
140        leverage: LeverageToBase,
141        /// Direction of new position
142        direction: DirectionToBase,
143        /// Stop loss price of new position
144        stop_loss_override: Option<PriceBaseInQuote>,
145        /// Take profit price of new position
146        #[serde(alias = "take_profit_override")]
147        take_profit: TakeProfitTrader,
148    },
149
150    /// Add collateral to a position, causing leverage to decrease
151    ///
152    /// The amount of collateral to add must be attached as funds
153    UpdatePositionAddCollateralImpactLeverage {
154        /// ID of position to update
155        id: PositionId,
156    },
157
158    /// Add collateral to a position, causing notional size to increase
159    ///
160    /// The amount of collateral to add must be attached as funds
161    UpdatePositionAddCollateralImpactSize {
162        /// ID of position to update
163        id: PositionId,
164        /// Assertion that the price has not moved too far
165        slippage_assert: Option<SlippageAssert>,
166    },
167
168    /// Remove collateral from a position, causing leverage to increase
169    UpdatePositionRemoveCollateralImpactLeverage {
170        /// ID of position to update
171        id: PositionId,
172        /// Amount of funds to remove from the position
173        amount: NonZero<Collateral>,
174    },
175    /// Remove collateral from a position, causing notional size to decrease
176    UpdatePositionRemoveCollateralImpactSize {
177        /// ID of position to update
178        id: PositionId,
179        /// Amount of funds to remove from the position
180        amount: NonZero<Collateral>,
181        /// Assertion that the price has not moved too far
182        slippage_assert: Option<SlippageAssert>,
183    },
184
185    /// Modify the leverage of the position
186    ///
187    /// This will impact the notional size of the position
188    UpdatePositionLeverage {
189        /// ID of position to update
190        id: PositionId,
191        /// New leverage of the position
192        leverage: LeverageToBase,
193        /// Assertion that the price has not moved too far
194        slippage_assert: Option<SlippageAssert>,
195    },
196
197    /// Modify the max gains of a position
198    UpdatePositionMaxGains {
199        /// ID of position to update
200        id: PositionId,
201        /// New max gains of the position
202        max_gains: MaxGainsInQuote,
203    },
204
205    /// Modify the take profit price of a position
206    UpdatePositionTakeProfitPrice {
207        /// ID of position to update
208        id: PositionId,
209        /// New take profit price of the position
210        price: TakeProfitTrader,
211    },
212
213    /// Update the stop loss price of a position
214    UpdatePositionStopLossPrice {
215        /// ID of position to update
216        id: PositionId,
217        /// New stop loss price of the position, or remove
218        stop_loss: StopLoss,
219    },
220
221    /// Set a stop loss or take profit override.
222    /// Deprecated, use UpdatePositionStopLossPrice instead
223    // not sure why this causes a warning here...
224    // #[deprecated(note = "Use UpdatePositionStopLossPrice instead")]
225    SetTriggerOrder {
226        /// ID of position to modify
227        id: PositionId,
228        /// New stop loss price of the position
229        /// Passing None will remove the override.
230        stop_loss_override: Option<PriceBaseInQuote>,
231        /// New take profit price of the position, merely as a trigger.
232        /// Passing None will bypass changing this
233        /// This does not affect the locked up counter collateral (or borrow fees etc.).
234        /// if this override is further away than the position's take profit price, the position's will be triggered first
235        /// if you want to update the position itself, use [ExecuteMsg::UpdatePositionTakeProfitPrice]
236        #[serde(alias = "take_profit_override")]
237        take_profit: Option<TakeProfitTrader>,
238    },
239
240    /// Set a limit order to open a position when the price of the asset hits
241    /// the specified trigger price.
242    PlaceLimitOrder {
243        /// Price when the order should trigger
244        trigger_price: PriceBaseInQuote,
245        /// Leverage of new position
246        leverage: LeverageToBase,
247        /// Direction of new position
248        direction: DirectionToBase,
249        /// Stop loss price of new position
250        stop_loss_override: Option<PriceBaseInQuote>,
251        /// Take profit price of new position
252        #[serde(alias = "take_profit_override")]
253        take_profit: TakeProfitTrader,
254    },
255
256    /// Cancel an open limit order
257    CancelLimitOrder {
258        /// ID of the order
259        order_id: OrderId,
260    },
261
262    /// Close a position
263    ClosePosition {
264        /// ID of position to close
265        id: PositionId,
266        /// Assertion that the price has not moved too far
267        slippage_assert: Option<SlippageAssert>,
268    },
269
270    /// Deposits send funds into the unlocked liquidity fund
271    DepositLiquidity {
272        /// Should we stake the resulting LP tokens into xLP?
273        ///
274        /// Defaults to `false`.
275        #[serde(default)]
276        stake_to_xlp: bool,
277    },
278
279    /// Like [ExecuteMsg::DepositLiquidity], but reinvests pending yield instead of receiving new funds.
280    ReinvestYield {
281        /// Should we stake the resulting LP tokens into xLP?
282        ///
283        /// Defaults to `false`.
284        #[serde(default)]
285        stake_to_xlp: bool,
286        /// Amount of rewards to reinvest.
287        ///
288        /// If `None`, reinvests all pending rewards.
289        amount: Option<NonZero<Collateral>>,
290    },
291
292    /// Withdraw liquidity calculated from specified `lp_amount`
293    WithdrawLiquidity {
294        /// Amount of LP tokens to burn
295        lp_amount: Option<NonZero<LpToken>>,
296        /// Claim yield as well?
297        #[serde(default)]
298        claim_yield: bool,
299    },
300
301    /// Claims accrued yield based on LP share allocation
302    ClaimYield {},
303
304    /// Stake some existing LP tokens into xLP
305    ///
306    /// [None] means stake all LP tokens.
307    StakeLp {
308        /// Amount of LP tokens to convert into xLP.
309        amount: Option<NonZero<LpToken>>,
310    },
311
312    /// Begin unstaking xLP into LP
313    ///
314    /// [None] means unstake all xLP tokens.
315    UnstakeXlp {
316        /// Amount of xLP tokens to convert into LP
317        amount: Option<NonZero<LpToken>>,
318    },
319
320    /// Stop an ongoing xLP unstaking process.
321    StopUnstakingXlp {},
322
323    /// Collect any LP tokens that have been unstaked from xLP.
324    CollectUnstakedLp {},
325
326    /// Crank a number of times
327    Crank {
328        /// Total number of crank executions to do
329        /// None: config default
330        execs: Option<u32>,
331        /// Which wallet receives crank rewards.
332        ///
333        /// If unspecified, sender receives the rewards.
334        rewards: Option<RawAddr>,
335    },
336
337    /// Nft proxy messages.
338    /// Only allowed to be called by this market's position_token contract
339    NftProxy {
340        /// Original caller of the NFT proxy.
341        sender: RawAddr,
342        /// Message sent to the NFT proxy
343        msg: crate::contracts::position_token::entry::ExecuteMsg,
344    },
345
346    /// liquidity token cw20 proxy messages.
347    /// Only allowed to be called by this market's liquidity_token contract
348    LiquidityTokenProxy {
349        /// Original caller of the liquidity token proxy.
350        sender: RawAddr,
351        /// Whether this was the LP or xLP proxy.
352        kind: LiquidityTokenKind,
353        /// Message sent to the liquidity token proxy.
354        msg: crate::contracts::liquidity_token::entry::ExecuteMsg,
355    },
356
357    /// Transfer all available protocol fees to the dao account
358    TransferDaoFees {},
359
360    /// Begin force-closing all positions in the protocol.
361    ///
362    /// This can only be performed by the market wind down wallet.
363    CloseAllPositions {},
364
365    /// Provide funds directly to the crank fees.
366    ///
367    /// The person who calls this receives no benefits. It's intended for the
368    /// DAO to use to incentivize cranking.
369    ProvideCrankFunds {},
370
371    /// Set manual price (mostly for testing)
372    SetManualPrice {
373        /// Price of the base asset in terms of the quote.
374        price: PriceBaseInQuote,
375        /// Price of the collateral asset in terms of USD.
376        ///
377        /// This is generally used for reporting of values like PnL and trade
378        /// volume.
379        price_usd: PriceCollateralInUsd,
380    },
381
382    /// Perform a deferred exec
383    ///
384    /// This should only ever be called from the market contract itself, any
385    /// other call is guaranteed to fail.
386    PerformDeferredExec {
387        /// Which ID to execute
388        id: DeferredExecId,
389        /// Which price point to use for this execution.
390        price_point_timestamp: Timestamp,
391    },
392}
393
394/// Owner-only messages
395#[cw_serde]
396#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
397pub enum ExecuteOwnerMsg {
398    /// Update the config
399    ConfigUpdate {
400        /// New configuration parameters
401        update: Box<ConfigUpdate>,
402    },
403}
404
405/// Fees held within the market contract.
406#[cw_serde]
407pub struct Fees {
408    /// Fees available for individual wallets to withdraw.
409    pub wallets: Collateral,
410    /// Fees available for the protocol overall to withdraw.
411    pub protocol: Collateral,
412    /// Crank fees collected and waiting to be allocated to crankers.
413    pub crank: Collateral,
414    /// Referral fees collected and waiting to be allocated to crankers.
415    #[serde(default)]
416    pub referral: Collateral,
417}
418
419/// Return value from [QueryMsg::ClosedPositionHistory]
420#[cw_serde]
421pub struct ClosedPositionsResp {
422    /// Closed positions
423    pub positions: Vec<ClosedPosition>,
424    /// the next cursor to start from
425    /// if we've reached the end, it's a None
426    pub cursor: Option<ClosedPositionCursor>,
427}
428
429/// A cursor used for paginating
430/// the closed position history
431#[cw_serde]
432#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
433pub struct ClosedPositionCursor {
434    /// Last close timestamp
435    pub time: Timestamp,
436    /// Last closed position ID
437    pub position: PositionId,
438}
439
440/// Use this price as the current price during a query.
441#[cw_serde]
442#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
443#[derive(Eq, Copy)]
444pub struct PriceForQuery {
445    /// Price of the base asset in terms of quote
446    pub base: PriceBaseInQuote,
447    /// Price of the collateral asset in terms of USD
448    ///
449    /// This is optional if the notional asset is USD and required otherwise.
450    pub collateral: PriceCollateralInUsd,
451}
452
453impl PriceForQuery {
454    /// Create a PriceForQuery with a base price and USD-market, without specifying the USD price
455    pub fn from_usd_market(base: PriceBaseInQuote, market_id: &MarketId) -> Result<Self> {
456        Ok(Self {
457            base,
458            collateral: base
459                .try_into_usd(market_id)
460                .context("cannot derive price query for non-USD market")?,
461        })
462    }
463}
464
465/// Query messages on the market contract
466#[cw_serde]
467#[derive(QueryResponses)]
468pub enum QueryMsg {
469    /// * returns [cw2::ContractVersion]
470    #[returns(cw2::ContractVersion)]
471    Version {},
472
473    /// Provides overall information about this market.
474    ///
475    /// This is intended as catch-all for protocol wide information, both static
476    /// (like market ID) and dynamic (like notional interest). The goal is to
477    /// limit the total number of queries callers have to make to get relevant
478    /// information.
479    ///
480    /// * returns [StatusResp]
481    #[returns(StatusResp)]
482    Status {
483        /// Price to be used as the current price
484        price: Option<PriceForQuery>,
485    },
486
487    /// * returns [crate::prelude::PricePoint]
488    ///
489    /// Gets the spot price, if no time is supplied, then it's current
490    /// This is the spot price as seen by the contract storage
491    /// i.e. the price that was pushed via execution messages
492    #[returns(crate::prelude::PricePoint)]
493    SpotPrice {
494        /// Timestamp when the price should be effective.
495        ///
496        /// [None] means "give the most recent price."
497        timestamp: Option<Timestamp>,
498    },
499
500    /// * returns [SpotPriceHistoryResp]
501    ///
502    /// Gets a collection of historical spot prices
503    #[returns(SpotPriceHistoryResp)]
504    SpotPriceHistory {
505        /// Last timestamp we saw
506        start_after: Option<Timestamp>,
507        /// How many prices to query
508        limit: Option<u32>,
509        /// Order to sort by, if None then it will be descending
510        order: Option<OrderInMessage>,
511    },
512
513    /// * returns [OraclePriceResp]
514    ///
515    /// Gets the current price from the oracle (for markets configured with an oracle)
516    ///
517    /// Also returns prices for each feed used to compose the final price
518    ///
519    /// This may be more up-to-date than the spot price which was
520    /// validated and pushed into the contract storage via execution messages
521    #[returns(OraclePriceResp)]
522    OraclePrice {
523        /// If true then it will validate the publish_time age as though it were
524        /// used to push a new spot_price update
525        /// Otherwise, it just returns the oracle price as-is, even if it's old
526        #[serde(default)]
527        validate_age: bool,
528    },
529
530    /// * returns [super::position::PositionsResp]
531    ///
532    /// Maps the given PositionIds into Positions
533    #[returns(super::position::PositionsResp)]
534    Positions {
535        /// Positions to query.
536        position_ids: Vec<PositionId>,
537        /// Should we skip calculating pending fees?
538        ///
539        /// This field is ignored if `fees` is set.
540        ///
541        /// The default for this field is `false`. The behavior of this field is:
542        ///
543        /// * `true`: the same as [PositionsQueryFeeApproach::NoFees]
544        ///
545        /// * `false`: the same as [PositionsQueryFeeApproach::AllFees] (though see note on that variant, this default will likely change in the future).
546        ///
547        /// It is recommended _not_ to use this field going forward, and to instead use `fees`.
548        skip_calc_pending_fees: Option<bool>,
549        /// How do we calculate fees for this position?
550        ///
551        /// Any value here will override the `skip_calc_pending_fees` field.
552        fees: Option<PositionsQueryFeeApproach>,
553        /// Price to be used as the current price
554        price: Option<PriceForQuery>,
555    },
556
557    /// * returns [LimitOrderResp]
558    ///
559    /// Returns the specified Limit Order
560    #[returns(LimitOrderResp)]
561    LimitOrder {
562        /// Limit order ID to query
563        order_id: OrderId,
564    },
565
566    /// * returns [LimitOrdersResp]
567    ///
568    /// Returns the Limit Orders for the specified addr
569    #[returns(LimitOrdersResp)]
570    LimitOrders {
571        /// Owner of limit orders
572        owner: RawAddr,
573        /// Last limit order seen
574        start_after: Option<OrderId>,
575        /// Number of order to return
576        limit: Option<u32>,
577        /// Whether to return ascending or descending
578        order: Option<OrderInMessage>,
579    },
580
581    /// * returns [ClosedPositionsResp]
582    #[returns(ClosedPositionsResp)]
583    ClosedPositionHistory {
584        /// Owner of the positions to get history for
585        owner: RawAddr,
586        /// Cursor to start from, for pagination
587        cursor: Option<ClosedPositionCursor>,
588        /// limit pagination
589        limit: Option<u32>,
590        /// order is default Descending
591        order: Option<OrderInMessage>,
592    },
593
594    /// * returns [cosmwasm_std::QueryResponse]
595    ///
596    /// Nft proxy messages. Not meant to be called directly
597    /// but rather for internal cross-contract calls
598    ///
599    /// however, these are merely queries, and can be called by anyone
600    /// and clients may take advantage of this to save query gas
601    /// by calling the market directly
602    #[returns(cosmwasm_std::QueryResponse)]
603    NftProxy {
604        /// NFT message to process
605        nft_msg: crate::contracts::position_token::entry::QueryMsg,
606    },
607
608    /// * returns [cosmwasm_std::QueryResponse]
609    ///
610    /// Liquidity token cw20 proxy messages. Not meant to be called directly
611    /// but rather for internal cross-contract calls
612    ///
613    /// however, these are merely queries, and can be called by anyone
614    /// and clients may take advantage of this to save query gas
615    /// by calling the market directly
616    #[returns(cosmwasm_std::QueryResponse)]
617    LiquidityTokenProxy {
618        /// Whether to query LP or xLP tokens
619        kind: LiquidityTokenKind,
620        /// Query to run
621        msg: crate::contracts::liquidity_token::entry::QueryMsg,
622    },
623
624    /// * returns [TradeHistorySummary] for a given wallet addr
625    #[returns(TradeHistorySummary)]
626    TradeHistorySummary {
627        /// Which wallet's history are we querying?
628        addr: RawAddr,
629    },
630
631    /// * returns [PositionActionHistoryResp]
632    #[returns(PositionActionHistoryResp)]
633    PositionActionHistory {
634        /// Which position's history are we querying?
635        id: PositionId,
636        /// Last action ID we saw
637        start_after: Option<String>,
638        /// How many actions to query
639        limit: Option<u32>,
640        /// Order to sort by
641        order: Option<OrderInMessage>,
642    },
643
644    /// Actions taken by a trader.
645    ///
646    /// Similar to [Self::PositionActionHistory], but provides all details for
647    /// an individual trader, not an individual position.
648    ///
649    /// * returns [TraderActionHistoryResp]
650    #[returns(TraderActionHistoryResp)]
651    TraderActionHistory {
652        /// Which trader's history are we querying?
653        owner: RawAddr,
654        /// Last action ID we saw
655        start_after: Option<String>,
656        /// How many actions to query
657        limit: Option<u32>,
658        /// Order to sort by
659        order: Option<OrderInMessage>,
660    },
661
662    /// * returns [LpActionHistoryResp]
663    #[returns(LpActionHistoryResp)]
664    LpActionHistory {
665        /// Which provider's history are we querying?
666        addr: RawAddr,
667        /// Last action ID we saw
668        start_after: Option<String>,
669        /// How many actions to query
670        limit: Option<u32>,
671        /// Order to sort by
672        order: Option<OrderInMessage>,
673    },
674
675    /// * returns [LimitOrderHistoryResp]
676    ///
677    /// Provides information on triggered limit orders.
678    #[returns(LimitOrderHistoryResp)]
679    LimitOrderHistory {
680        /// Trader's address for history we are querying
681        addr: RawAddr,
682        /// Last order ID we saw
683        start_after: Option<String>,
684        /// How many orders to query
685        limit: Option<u32>,
686        /// Order to sort the order IDs by
687        order: Option<OrderInMessage>,
688    },
689
690    /// * returns [LpInfoResp]
691    ///
692    /// Provides the data needed by the earn page.
693    #[returns(LpInfoResp)]
694    LpInfo {
695        /// Which provider's information are we querying?
696        liquidity_provider: RawAddr,
697    },
698
699    /// * returns [ReferralStatsResp]
700    ///
701    /// Returns the referral rewards generated and received by this wallet.
702    #[returns(ReferralStatsResp)]
703    ReferralStats {
704        /// Which address to check
705        addr: RawAddr,
706    },
707
708    /// * returns [DeltaNeutralityFeeResp]
709    ///
710    /// Gets the delta neutrality fee
711    /// at the current price, for a given change in terms of net notional
712    #[returns(DeltaNeutralityFeeResp)]
713    DeltaNeutralityFee {
714        /// the amount of notional that would be changed
715        notional_delta: Signed<Notional>,
716        /// for real delta neutrality fees, this is calculated internally
717        /// should only be supplied if querying the fee for close or update
718        pos_delta_neutrality_fee_margin: Option<Collateral>,
719    },
720
721    /// Check if a price update would trigger a liquidation/take profit/etc.
722    ///
723    /// * returns [PriceWouldTriggerResp]
724    #[returns(PriceWouldTriggerResp)]
725    PriceWouldTrigger {
726        /// The new price of the base asset in terms of quote
727        price: PriceBaseInQuote,
728    },
729
730    /// Enumerate deferred execution work items for the given trader.
731    ///
732    /// Always begins enumeration from the most recent.
733    ///
734    /// * returns [crate::contracts::market::deferred_execution::ListDeferredExecsResp]
735    #[returns(crate::contracts::market::deferred_execution::ListDeferredExecsResp)]
736    ListDeferredExecs {
737        /// Trader wallet address
738        addr: RawAddr,
739        /// Previously seen final ID.
740        start_after: Option<DeferredExecId>,
741        /// How many items to request per batch.
742        limit: Option<u32>,
743    },
744
745    /// Get a single deferred execution item, if available.
746    ///
747    /// * returns [crate::contracts::market::deferred_execution::GetDeferredExecResp]
748    #[returns(crate::contracts::market::deferred_execution::GetDeferredExecResp)]
749    GetDeferredExec {
750        /// ID
751        id: DeferredExecId,
752    },
753}
754
755/// Response for [QueryMsg::OraclePrice]
756#[cw_serde]
757pub struct OraclePriceResp {
758    /// A map of each pyth id used in this market to the price and publish time
759    pub pyth: BTreeMap<PriceIdentifier, OraclePriceFeedPythResp>,
760    /// A map of each sei denom used in this market to the price
761    pub sei: BTreeMap<String, OraclePriceFeedSeiResp>,
762    /// A map of each rujira used in this market to the redemption price
763    #[serde(default)]
764    pub rujira: BTreeMap<String, OraclePriceFeedRujiraResp>,
765    /// A map of each stride denom used in this market to the redemption price
766    pub stride: BTreeMap<String, OraclePriceFeedStrideResp>,
767    /// A map of each simple contract used in this market to the contract price
768    #[serde(default)]
769    pub simple: BTreeMap<RawAddr, OraclePriceFeedSimpleResp>,
770    /// The final, composed price. See [QueryMsg::OraclePrice] for more information about this value
771    pub composed_price: PricePoint,
772}
773
774/// Part of [OraclePriceResp]
775#[cw_serde]
776pub struct OraclePriceFeedPythResp {
777    /// The pyth price
778    pub price: NumberGtZero,
779    /// The pyth publish time
780    pub publish_time: Timestamp,
781    /// Is this considered a volatile feed?
782    pub volatile: bool,
783}
784
785/// Part of [OraclePriceResp]
786#[cw_serde]
787pub struct OraclePriceFeedSeiResp {
788    /// The Sei price
789    pub price: NumberGtZero,
790    /// The Sei publish time
791    pub publish_time: Timestamp,
792    /// Is this considered a volatile feed?
793    pub volatile: bool,
794}
795
796/// Part of [OraclePriceResp]
797#[cw_serde]
798pub struct OraclePriceFeedRujiraResp {
799    /// The Rujira price
800    pub price: NumberGtZero,
801    /// Is this considered a volatile feed?
802    pub volatile: bool,
803}
804
805/// Part of [OraclePriceResp]
806#[cw_serde]
807pub struct OraclePriceFeedStrideResp {
808    /// The redemption rate
809    pub redemption_rate: NumberGtZero,
810    /// The redemption price publish time
811    pub publish_time: Timestamp,
812    /// Is this considered a volatile feed?
813    pub volatile: bool,
814}
815
816/// Part of [OraclePriceResp]
817#[cw_serde]
818pub struct OraclePriceFeedSimpleResp {
819    /// The price value
820    pub value: NumberGtZero,
821    /// The block info when this price was set
822    pub block_info: BlockInfo,
823    /// Optional timestamp for the price, independent of block_info.time
824    pub timestamp: Option<Timestamp>,
825    /// Is this considered a volatile feed?
826    #[serde(default)]
827    pub volatile: bool,
828}
829
830/// When querying an open position, how do we calculate PnL vis-a-vis fees?
831#[cw_serde]
832#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
833#[derive(Copy, Eq)]
834pub enum PositionsQueryFeeApproach {
835    /// Do not include any pending fees
836    NoFees,
837    /// Include accumulated fees (borrow and funding rates), but do not include future fees (specifically DNF).
838    Accumulated,
839    /// Include the DNF fee in addition to accumulated fees.
840    ///
841    /// This gives an idea of "what will be my PnL if I close my position right
842    /// now." To keep compatibility with previous contract APIs, this is the
843    /// default behavior. However, going forward, `Accumulated` should be
844    /// preferred, and will eventually become the default.
845    AllFees,
846}
847
848/// Placeholder migration message
849#[cw_serde]
850pub struct MigrateMsg {}
851
852/// The summary for trade history
853#[cw_serde]
854#[derive(Default)]
855pub struct TradeHistorySummary {
856    /// Given in usd
857    pub trade_volume: Usd,
858    /// Given in usd
859    pub realized_pnl: Signed<Usd>,
860}
861
862/// Response for [QueryMsg::PositionActionHistory]
863#[cw_serde]
864pub struct PositionActionHistoryResp {
865    /// list of position actions that happened historically
866    pub actions: Vec<PositionAction>,
867    /// Next start_after value to continue pagination
868    ///
869    /// None means no more pagination
870    pub next_start_after: Option<String>,
871}
872
873/// Response for [QueryMsg::TraderActionHistory]
874#[cw_serde]
875pub struct TraderActionHistoryResp {
876    /// list of position actions that this trader performed
877    pub actions: Vec<PositionAction>,
878    /// Next start_after value to continue pagination
879    ///
880    /// None means no more pagination
881    pub next_start_after: Option<String>,
882}
883
884/// A distinct position history action
885#[cw_serde]
886pub struct PositionAction {
887    /// ID of the position impacted
888    ///
889    /// For ease of migration, we allow for a missing position ID.
890    pub id: Option<PositionId>,
891    /// Kind of action taken by the trader
892    pub kind: PositionActionKind,
893    /// Timestamp when the action occurred
894    pub timestamp: Timestamp,
895    /// Timestamp of the PricePoint used for this action, if relevant
896    pub price_timestamp: Option<Timestamp>,
897    /// the amount of collateral at the time of the action
898    #[serde(alias = "active_collateral")]
899    pub collateral: Collateral,
900    /// The amount of collateral transferred to or from the trader
901    #[serde(default)]
902    pub transfer_collateral: Signed<Collateral>,
903    /// Leverage of the position at the time of the action, if relevant
904    pub leverage: Option<LeverageToBase>,
905    /// max gains in quote
906    pub max_gains: Option<MaxGainsInQuote>,
907    /// the trade fee in USD
908    pub trade_fee: Option<Usd>,
909    /// The delta neutrality fee paid (or, if negative, received) in USD
910    pub delta_neutrality_fee: Option<Signed<Usd>>,
911    /// If this is a position transfer, the previous owner.
912    pub old_owner: Option<Addr>,
913    /// If this is a position transfer, the new owner.
914    pub new_owner: Option<Addr>,
915    /// The take profit price set by the trader.
916    /// For historical reasons this is optional, i.e. if the trader had set max gains price instead
917    #[serde(rename = "take_profit_override")]
918    pub take_profit_trader: Option<TakeProfitTrader>,
919    /// The stop loss override, if set.
920    pub stop_loss_override: Option<PriceBaseInQuote>,
921}
922
923/// Action taken by trader for a [PositionAction]
924#[cw_serde]
925pub enum PositionActionKind {
926    /// Open a new position
927    Open,
928    /// Updated an existing position
929    Update,
930    /// Close a position
931    Close,
932    /// Position was transferred between wallets
933    //FIXME need distinct "Received Position" and "Sent Position" cases
934    Transfer,
935}
936
937//todo does it make sense for PositionActionKind to impl Display
938impl Display for PositionActionKind {
939    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
940        let str = match self {
941            PositionActionKind::Open => "Open",
942            PositionActionKind::Update => "Update",
943            PositionActionKind::Close => "Close",
944            PositionActionKind::Transfer => "Transfer",
945        };
946
947        f.write_str(str)
948    }
949}
950
951/// Returned by [QueryMsg::LpInfo]
952#[cw_serde]
953pub struct LpInfoResp {
954    /// This LP amount includes both actual LP tokens and xLP unstaked to LP but
955    /// not yet collected.
956    pub lp_amount: LpToken,
957    /// Collateral backing the LP tokens
958    pub lp_collateral: Collateral,
959    /// This shows the balance of xLP minus any xLP already unstaked.
960    pub xlp_amount: LpToken,
961    /// Collateral backing the xLP tokens
962    pub xlp_collateral: Collateral,
963    /// Total available yield, sum of the available LP, xLP, crank rewards, and referral rewards.
964    pub available_yield: Collateral,
965    /// Available yield from LP tokens
966    pub available_yield_lp: Collateral,
967    /// Available yield from xLP tokens
968    pub available_yield_xlp: Collateral,
969    /// Available crank rewards
970    pub available_crank_rewards: Collateral,
971    #[serde(default)]
972    /// Available referrer rewards
973    pub available_referrer_rewards: Collateral,
974    /// Current status of an unstaking, if under way
975    ///
976    /// This will return `Some` from the time the provider begins an unstaking process until either:
977    ///
978    /// 1. They either cancel it, _or_
979    /// 2. They unstake all request xLP into LP _and_ collect that LP within the contract.
980    pub unstaking: Option<UnstakingStatus>,
981    /// Historical information on LP activity
982    pub history: LpHistorySummary,
983    /// Liquidity cooldown information, if active.
984    pub liquidity_cooldown: Option<LiquidityCooldown>,
985}
986
987/// Returned by [QueryMsg::ReferralStats]
988#[cw_serde]
989#[derive(Default)]
990pub struct ReferralStatsResp {
991    /// Rewards generated by this wallet, in collateral.
992    pub generated: Collateral,
993    /// Rewards generated by this wallet, converted to USD at time of generation.
994    pub generated_usd: Usd,
995    /// Rewards received by this wallet, in collateral.
996    pub received: Collateral,
997    /// Rewards received by this wallet, converted to USD at time of generation.
998    pub received_usd: Usd,
999    /// Total number of referees associated with this wallet.
1000    ///
1001    /// Note that this is a factory-wide value. It will be identical
1002    /// across all markets for a given address.
1003    pub referees: u32,
1004    /// Who referred this account, if anyone.
1005    pub referrer: Option<Addr>,
1006}
1007
1008/// When a liquidity cooldown period will end
1009#[cw_serde]
1010pub struct LiquidityCooldown {
1011    /// Timestamp when it will end
1012    pub at: Timestamp,
1013    /// Number of seconds until it will end
1014    pub seconds: u64,
1015}
1016
1017/// Status of an ongoing unstaking process.
1018#[cw_serde]
1019pub struct UnstakingStatus {
1020    /// When the unstaking began
1021    pub start: Timestamp,
1022    /// This will be in the future if unstaking is incomplete
1023    pub end: Timestamp,
1024    /// Total amount requested to be unstaked
1025    ///
1026    /// Note that this value must be the sum of collected, available, and pending.
1027    pub xlp_unstaking: NonZero<LpToken>,
1028    /// Collateral, at current exchange rate, underlying the [UnstakingStatus::xlp_unstaking]
1029    pub xlp_unstaking_collateral: Collateral,
1030    /// Total amount of LP tokens that have been unstaked and collected
1031    pub collected: LpToken,
1032    /// Total amount of LP tokens that have been unstaked and not yet collected
1033    pub available: LpToken,
1034    /// Total amount of xLP tokens that are still pending unstaking
1035    pub pending: LpToken,
1036}
1037
1038/// The summary for LP history
1039#[cw_serde]
1040#[derive(Default)]
1041pub struct LpHistorySummary {
1042    /// How much collateral was deposited in total
1043    pub deposit: Collateral,
1044    /// Value of the collateral in USD at time of deposit
1045    #[serde(alias = "deposit_in_usd")]
1046    pub deposit_usd: Usd,
1047    /// Cumulative yield claimed by the provider
1048    ///
1049    /// Note that this field includes crank and referral rewards.
1050    pub r#yield: Collateral,
1051    /// Cumulative yield expressed in USD at time of claiming
1052    ///
1053    /// Note that this field includes crank and referral rewards.
1054    #[serde(alias = "yield_in_usd")]
1055    pub yield_usd: Usd,
1056}
1057
1058/// Response for [QueryMsg::LpActionHistory]
1059#[cw_serde]
1060pub struct LpActionHistoryResp {
1061    /// list of earn actions that happened historically
1062    pub actions: Vec<LpAction>,
1063    /// Next start_after value to continue pagination
1064    ///
1065    /// None means no more pagination
1066    pub next_start_after: Option<String>,
1067}
1068
1069/// A distinct lp history action
1070#[cw_serde]
1071pub struct LpAction {
1072    /// Kind of action
1073    pub kind: LpActionKind,
1074    /// When the action happened
1075    pub timestamp: Timestamp,
1076    /// How many tokens were involved, if relevant
1077    pub tokens: Option<LpToken>,
1078    /// Amount of collateral
1079    pub collateral: Collateral,
1080    /// Value of that collateral in USD at the time
1081    #[serde(alias = "collateral_in_usd")]
1082    pub collateral_usd: Usd,
1083}
1084
1085/// Kind of action for a [LpAction].
1086#[cw_serde]
1087pub enum LpActionKind {
1088    /// via [ExecuteMsg::DepositLiquidity]
1089    DepositLp,
1090    /// via [ExecuteMsg::DepositLiquidity]
1091    DepositXlp,
1092    /// via [ExecuteMsg::ReinvestYield]
1093    ReinvestYieldLp,
1094    /// via [ExecuteMsg::ReinvestYield]
1095    ReinvestYieldXlp,
1096    /// via [ExecuteMsg::UnstakeXlp]
1097    /// the amount of collateral is determined by the time they send their message
1098    /// [ExecuteMsg::CollectUnstakedLp] is *not* accounted for here
1099    UnstakeXlp,
1100    /// Some amount of unstaked LP has been collected into actual LP.
1101    CollectLp,
1102    /// via [ExecuteMsg::WithdrawLiquidity]
1103    Withdraw,
1104    /// via [ExecuteMsg::ClaimYield]
1105    ClaimYield,
1106}
1107
1108#[cw_serde]
1109/// Return value from [QueryMsg::LimitOrder].
1110pub struct LimitOrderResp {
1111    /// The order identifier
1112    pub order_id: OrderId,
1113    /// The price at which the order will trigger
1114    pub trigger_price: PriceBaseInQuote,
1115    /// Amount of deposit collateral on the order
1116    pub collateral: NonZero<Collateral>,
1117    /// Leverage to open the position at
1118    pub leverage: LeverageToBase,
1119    /// Direction of the new position
1120    pub direction: DirectionToBase,
1121    /// Max gains of the new position
1122    #[deprecated(note = "Use take_profit instead")]
1123    pub max_gains: Option<MaxGainsInQuote>,
1124    /// Stop loss of the new position
1125    pub stop_loss_override: Option<PriceBaseInQuote>,
1126    #[serde(alias = "take_profit_override")]
1127    /// Take profit of the new position
1128    pub take_profit: TakeProfitTrader,
1129}
1130
1131/// Response for [QueryMsg::LimitOrders]
1132#[cw_serde]
1133pub struct LimitOrdersResp {
1134    /// The list of limit orders
1135    pub orders: Vec<LimitOrderResp>,
1136    /// Next start_after value to continue pagination
1137    ///
1138    /// None means no more pagination
1139    pub next_start_after: Option<OrderId>,
1140}
1141
1142/// Response for [QueryMsg::DeltaNeutralityFee]
1143#[cw_serde]
1144pub struct DeltaNeutralityFeeResp {
1145    /// the amount charged
1146    pub amount: Signed<Collateral>,
1147    /// the amount in the fund currently
1148    pub fund_total: Collateral,
1149    /// Expected effective price after slippage, can be used for the slippage assert.
1150    pub slippage_assert_price: PriceBaseInQuote,
1151}
1152
1153#[cfg(feature = "arbitrary")]
1154impl<'a> arbitrary::Arbitrary<'a> for QueryMsg {
1155    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1156        Self::arbitrary_with_user(u, None)
1157    }
1158}
1159
1160#[cfg(feature = "arbitrary")]
1161impl QueryMsg {
1162    /// Generate an arbitrary [QueryMsg] using the given default user address.
1163    pub fn arbitrary_with_user(
1164        u: &mut arbitrary::Unstructured<'_>,
1165        user: Option<RawAddr>,
1166    ) -> arbitrary::Result<Self> {
1167        let user_arb = |u: &mut arbitrary::Unstructured<'_>| -> arbitrary::Result<RawAddr> {
1168            match user {
1169                Some(user) => Ok(user),
1170                None => u.arbitrary(),
1171            }
1172        };
1173        // only allow messages for *this* contract - no proxies or cw20 submessages
1174
1175        // prior art for this approach: https://github.com/rust-fuzz/arbitrary/blob/061ca86be699faf1fb584dd7a7843b3541cd5f2c/src/lib.rs#L724
1176        match u.int_in_range::<u8>(0..=11)? {
1177            0 => Ok(Self::Version {}),
1178            1 => Ok(Self::Status {
1179                price: u.arbitrary()?,
1180            }),
1181            2 => Ok(Self::SpotPrice {
1182                timestamp: u.arbitrary()?,
1183            }),
1184            3 => Ok(Self::Positions {
1185                position_ids: u.arbitrary()?,
1186                skip_calc_pending_fees: u.arbitrary()?,
1187                fees: u.arbitrary()?,
1188                price: u.arbitrary()?,
1189            }),
1190
1191            4 => Ok(Self::LimitOrder {
1192                order_id: u.arbitrary()?,
1193            }),
1194
1195            5 => Ok(Self::LimitOrders {
1196                owner: user_arb(u)?,
1197                start_after: u.arbitrary()?,
1198                limit: u.arbitrary()?,
1199                order: u.arbitrary()?,
1200            }),
1201
1202            6 => Ok(Self::ClosedPositionHistory {
1203                owner: user_arb(u)?,
1204                cursor: u.arbitrary()?,
1205                limit: u.arbitrary()?,
1206                order: u.arbitrary()?,
1207            }),
1208
1209            7 => Ok(Self::TradeHistorySummary { addr: user_arb(u)? }),
1210
1211            8 => Ok(Self::PositionActionHistory {
1212                id: u.arbitrary()?,
1213                start_after: u.arbitrary()?,
1214                limit: u.arbitrary()?,
1215                order: u.arbitrary()?,
1216            }),
1217
1218            9 => Ok(Self::LpActionHistory {
1219                addr: user_arb(u)?,
1220                start_after: u.arbitrary()?,
1221                limit: u.arbitrary()?,
1222                order: u.arbitrary()?,
1223            }),
1224
1225            10 => Ok(Self::LpInfo {
1226                liquidity_provider: user_arb(u)?,
1227            }),
1228
1229            11 => Ok(Self::DeltaNeutralityFee {
1230                notional_delta: u.arbitrary()?,
1231                pos_delta_neutrality_fee_margin: u.arbitrary()?,
1232            }),
1233
1234            _ => unreachable!(),
1235        }
1236    }
1237}
1238
1239#[cfg(feature = "arbitrary")]
1240impl<'a> arbitrary::Arbitrary<'a> for SudoMsg {
1241    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1242        match u.int_in_range::<u8>(0..=0)? {
1243            0 => Ok(SudoMsg::ConfigUpdate {
1244                update: Box::<ConfigUpdate>::default(),
1245            }),
1246            _ => unreachable!(),
1247        }
1248    }
1249}
1250
1251#[cfg(feature = "arbitrary")]
1252impl<'a> arbitrary::Arbitrary<'a> for ExecuteMsg {
1253    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1254        // only allow messages for *this* contract - no proxies or cw20 submessages
1255
1256        // prior art for this approach: https://github.com/rust-fuzz/arbitrary/blob/061ca86be699faf1fb584dd7a7843b3541cd5f2c/src/lib.rs#L724
1257        match u.int_in_range::<u8>(0..=23)? {
1258            //0 => Ok(ExecuteMsg::Owner(u.arbitrary()?)),
1259            0 => Ok(ExecuteMsg::Owner(ExecuteOwnerMsg::ConfigUpdate {
1260                update: Box::<ConfigUpdate>::default(),
1261            })),
1262            1 => Ok(ExecuteMsg::OpenPosition {
1263                slippage_assert: u.arbitrary()?,
1264                leverage: u.arbitrary()?,
1265                direction: u.arbitrary()?,
1266                stop_loss_override: u.arbitrary()?,
1267                take_profit: u.arbitrary()?,
1268            }),
1269            2 => Ok(ExecuteMsg::UpdatePositionAddCollateralImpactLeverage { id: u.arbitrary()? }),
1270            3 => Ok(ExecuteMsg::UpdatePositionAddCollateralImpactSize {
1271                id: u.arbitrary()?,
1272                slippage_assert: u.arbitrary()?,
1273            }),
1274            4 => Ok(ExecuteMsg::UpdatePositionRemoveCollateralImpactLeverage {
1275                id: u.arbitrary()?,
1276                amount: u.arbitrary()?,
1277            }),
1278            5 => Ok(ExecuteMsg::UpdatePositionRemoveCollateralImpactSize {
1279                id: u.arbitrary()?,
1280                amount: u.arbitrary()?,
1281                slippage_assert: u.arbitrary()?,
1282            }),
1283            6 => Ok(ExecuteMsg::UpdatePositionLeverage {
1284                id: u.arbitrary()?,
1285                leverage: u.arbitrary()?,
1286                slippage_assert: u.arbitrary()?,
1287            }),
1288            7 => Ok(ExecuteMsg::UpdatePositionMaxGains {
1289                id: u.arbitrary()?,
1290                max_gains: u.arbitrary()?,
1291            }),
1292            #[allow(deprecated)]
1293            8 => Ok(ExecuteMsg::SetTriggerOrder {
1294                id: u.arbitrary()?,
1295                stop_loss_override: u.arbitrary()?,
1296                take_profit: u.arbitrary()?,
1297            }),
1298            9 => Ok(ExecuteMsg::PlaceLimitOrder {
1299                trigger_price: u.arbitrary()?,
1300                leverage: u.arbitrary()?,
1301                direction: u.arbitrary()?,
1302                stop_loss_override: u.arbitrary()?,
1303                take_profit: u.arbitrary()?,
1304            }),
1305            10 => Ok(ExecuteMsg::CancelLimitOrder {
1306                order_id: u.arbitrary()?,
1307            }),
1308            11 => Ok(ExecuteMsg::ClosePosition {
1309                id: u.arbitrary()?,
1310                slippage_assert: u.arbitrary()?,
1311            }),
1312            12 => Ok(ExecuteMsg::DepositLiquidity {
1313                stake_to_xlp: u.arbitrary()?,
1314            }),
1315            13 => Ok(ExecuteMsg::ReinvestYield {
1316                stake_to_xlp: u.arbitrary()?,
1317                amount: None,
1318            }),
1319            14 => Ok(ExecuteMsg::WithdrawLiquidity {
1320                lp_amount: u.arbitrary()?,
1321                claim_yield: false,
1322            }),
1323            15 => Ok(ExecuteMsg::ClaimYield {}),
1324            16 => Ok(ExecuteMsg::StakeLp {
1325                amount: u.arbitrary()?,
1326            }),
1327            17 => Ok(ExecuteMsg::UnstakeXlp {
1328                amount: u.arbitrary()?,
1329            }),
1330            18 => Ok(ExecuteMsg::StopUnstakingXlp {}),
1331            19 => Ok(ExecuteMsg::CollectUnstakedLp {}),
1332            20 => Ok(ExecuteMsg::Crank {
1333                execs: u.arbitrary()?,
1334                rewards: None,
1335            }),
1336
1337            21 => Ok(ExecuteMsg::TransferDaoFees {}),
1338
1339            22 => Ok(ExecuteMsg::CloseAllPositions {}),
1340            23 => Ok(ExecuteMsg::ProvideCrankFunds {}),
1341
1342            _ => unreachable!(),
1343        }
1344    }
1345}
1346
1347/// Overall market status information
1348///
1349/// Returned from [QueryMsg::Status]
1350#[cw_serde]
1351pub struct StatusResp {
1352    /// This market's identifier
1353    pub market_id: MarketId,
1354    /// Base asset
1355    pub base: String,
1356    /// Quote asset
1357    pub quote: String,
1358    /// Type of market
1359    pub market_type: MarketType,
1360    /// The asset used for collateral within the system
1361    pub collateral: crate::token::Token,
1362    /// Config for this market
1363    pub config: super::config::Config,
1364    /// Current status of the liquidity pool
1365    pub liquidity: super::liquidity::LiquidityStats,
1366    /// Next bit of crank work available, if any
1367    pub next_crank: Option<CrankWorkInfo>,
1368    /// Timestamp of the last completed crank
1369    pub last_crank_completed: Option<Timestamp>,
1370    /// Earliest deferred execution price timestamp needed
1371    pub next_deferred_execution: Option<Timestamp>,
1372    /// Latest deferred execution price timestamp needed
1373    pub newest_deferred_execution: Option<Timestamp>,
1374    /// Next liquifunding work item timestamp
1375    pub next_liquifunding: Option<Timestamp>,
1376    /// Number of work items sitting in the deferred execution queue
1377    pub deferred_execution_items: u32,
1378    /// Last processed deferred execution ID, if any
1379    pub last_processed_deferred_exec_id: Option<DeferredExecId>,
1380    /// Overall borrow fee rate (annualized), combining LP and xLP
1381    pub borrow_fee: Decimal256,
1382    /// LP component of [Self::borrow_fee]
1383    pub borrow_fee_lp: Decimal256,
1384    /// xLP component of [Self::borrow_fee]
1385    pub borrow_fee_xlp: Decimal256,
1386    /// Long funding rate (annualized)
1387    pub long_funding: Number,
1388    /// Short funding rate (annualized)
1389    pub short_funding: Number,
1390
1391    /// Total long interest, given in the notional asset.
1392    pub long_notional: Notional,
1393    /// Total short interest, given in the notional asset.
1394    pub short_notional: Notional,
1395
1396    /// Total long interest, given in USD, converted at the current exchange rate.
1397    pub long_usd: Usd,
1398    /// Total short interest, given in USD, converted at the current exchange rate.
1399    pub short_usd: Usd,
1400
1401    /// Instant delta neutrality fee value
1402    ///
1403    /// This is based on net notional and the sensitivity parameter
1404    pub instant_delta_neutrality_fee_value: Signed<Decimal256>,
1405
1406    /// Amount of collateral in the delta neutrality fee fund.
1407    pub delta_neutrality_fee_fund: Collateral,
1408
1409    /// Fees held by the market contract
1410    pub fees: Fees,
1411}
1412
1413/// Response for [QueryMsg::LimitOrderHistory]
1414#[cw_serde]
1415pub struct LimitOrderHistoryResp {
1416    /// list of triggered limit orders that happened historically
1417    pub orders: Vec<ExecutedLimitOrder>,
1418    /// Next start_after value to continue pagination
1419    ///
1420    /// None means no more pagination
1421    pub next_start_after: Option<String>,
1422}
1423
1424/// History information on a limit order which was triggered.
1425#[cw_serde]
1426pub struct ExecutedLimitOrder {
1427    /// The order itself
1428    pub order: LimitOrder,
1429    /// The result of triggering the order
1430    pub result: LimitOrderResult,
1431    /// When the order was triggered
1432    pub timestamp: Timestamp,
1433}
1434
1435/// The result of triggering a limit order
1436#[cw_serde]
1437pub enum LimitOrderResult {
1438    /// Position was opened successfully
1439    Success {
1440        /// New position ID
1441        position: PositionId,
1442    },
1443    /// Position failed to open
1444    Failure {
1445        /// Error message
1446        reason: String,
1447    },
1448}
1449
1450/// Response for [QueryMsg::SpotPriceHistory]
1451#[cw_serde]
1452pub struct SpotPriceHistoryResp {
1453    /// list of historical price points
1454    pub price_points: Vec<PricePoint>,
1455}
1456
1457/// Would a price update trigger a liquidation/take profit/etc?
1458#[cw_serde]
1459pub struct PriceWouldTriggerResp {
1460    /// Would a price update trigger a liquidation/take profit/etc?
1461    pub would_trigger: bool,
1462}
1463
1464/// String representation of remove.
1465const REMOVE_STR: &str = "remove";
1466/// Stop loss configuration
1467#[derive(Debug, Clone, Copy, PartialEq)]
1468pub enum StopLoss {
1469    /// Remove stop loss price for the position
1470    Remove,
1471    /// Set the stop loss price for the position
1472    Price(PriceBaseInQuote),
1473}
1474
1475impl FromStr for StopLoss {
1476    type Err = PerpError;
1477    fn from_str(src: &str) -> Result<StopLoss, PerpError> {
1478        match src {
1479            REMOVE_STR => Ok(StopLoss::Remove),
1480            _ => match src.parse() {
1481                Ok(number) => Ok(StopLoss::Price(number)),
1482                Err(err) => Err(PerpError::new(
1483                    ErrorId::Conversion,
1484                    ErrorDomain::Default,
1485                    format!("error converting {} to StopLoss , {}", src, err),
1486                )),
1487            },
1488        }
1489    }
1490}
1491
1492impl TryFrom<&str> for StopLoss {
1493    type Error = PerpError;
1494
1495    fn try_from(val: &str) -> Result<Self, Self::Error> {
1496        Self::from_str(val)
1497    }
1498}
1499
1500impl serde::Serialize for StopLoss {
1501    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1502    where
1503        S: serde::Serializer,
1504    {
1505        match self {
1506            StopLoss::Price(price) => price.serialize(serializer),
1507            StopLoss::Remove => serializer.serialize_str(REMOVE_STR),
1508        }
1509    }
1510}
1511
1512impl<'de> serde::Deserialize<'de> for StopLoss {
1513    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1514    where
1515        D: serde::Deserializer<'de>,
1516    {
1517        deserializer.deserialize_any(StopLossVisitor)
1518    }
1519}
1520
1521impl JsonSchema for StopLoss {
1522    fn schema_name() -> String {
1523        "StopLoss".to_owned()
1524    }
1525
1526    fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
1527        SchemaObject {
1528            instance_type: Some(InstanceType::String.into()),
1529            format: Some("stop-loss".to_owned()),
1530            ..Default::default()
1531        }
1532        .into()
1533    }
1534}
1535
1536struct StopLossVisitor;
1537impl<'de> serde::de::Visitor<'de> for StopLossVisitor {
1538    type Value = StopLoss;
1539
1540    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
1541        formatter.write_str("StopLoss")
1542    }
1543
1544    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
1545    where
1546        E: serde::de::Error,
1547    {
1548        v.parse()
1549            .map_err(|_| E::custom(format!("Invalid StopLoss: {v}")))
1550    }
1551
1552    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1553    where
1554        A: serde::de::MapAccess<'de>,
1555    {
1556        if let Some((key, value)) = map.next_entry()? {
1557            match key {
1558                REMOVE_STR => Ok(Self::Value::Remove),
1559                "price" => Ok(Self::Value::Price(value)),
1560                _ => Err(serde::de::Error::custom(format!(
1561                    "Invalid StopLoss field: {key}"
1562                ))),
1563            }
1564        } else {
1565            Err(serde::de::Error::custom("Empty StopLoss Object"))
1566        }
1567    }
1568}
1569
1570#[cfg(test)]
1571mod tests {
1572    use super::StopLoss;
1573
1574    #[test]
1575    fn deserialize_stop_loss() {
1576        let go = serde_json::from_str::<StopLoss>;
1577
1578        go(r#"{"price": "2.2"}"#).unwrap();
1579        go("\"remove\"").unwrap();
1580        go("\"2.2\"").unwrap();
1581        go("\"-2.2\"").unwrap_err();
1582        go(r#"{}"#).unwrap_err();
1583        go(r#"{"error-field": "2.2"}"#).unwrap_err();
1584        go("").unwrap_err();
1585    }
1586}