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}