levana_perpswap_cosmos/contracts/market/
order.rs

1//! Data types for limit orders
2use crate::prelude::*;
3use cosmwasm_std::{Addr, StdResult};
4use cw_storage_plus::{IntKey, Key, KeyDeserialize, Prefixer, PrimaryKey};
5use std::fmt;
6use std::hash::Hash;
7use std::num::ParseIntError;
8
9/// A limit order
10#[cw_serde]
11pub struct LimitOrder {
12    /// ID of the order
13    pub order_id: OrderId,
14    /// Owner of the order
15    pub owner: Addr,
16    /// Price where the order will trigger
17    pub trigger_price: PriceBaseInQuote,
18    /// Deposit collateral
19    pub collateral: NonZero<Collateral>,
20    /// Leverage
21    pub leverage: LeverageToBase,
22    /// Direction of the position
23    pub direction: DirectionToNotional,
24    /// Maximum gains
25    #[deprecated(note = "Use take_profit instead")]
26    pub max_gains: Option<MaxGainsInQuote>,
27    /// Stop loss price
28    pub stop_loss_override: Option<PriceBaseInQuote>,
29    /// Take profit price
30    // TODO - this should eventually become non-optional, but that would require a migration
31    // it is, however, non-optional in LimitOrderResp
32    #[serde(alias = "take_profit_override")]
33    pub take_profit: Option<TakeProfitTrader>,
34    /// Crank fee charged during deferred execution and placing the limit order
35    #[serde(default)]
36    pub crank_fee_collateral: Collateral,
37    /// Same as [Self::crank_fee_collateral] but cost-basis expressed in USD.
38    #[serde(default)]
39    pub crank_fee_usd: Usd,
40}
41
42/// A unique numeric ID for each order in the protocol.
43#[cw_serde]
44#[derive(Copy, PartialOrd, Ord, Eq)]
45pub struct OrderId(Uint64);
46
47#[cfg(feature = "arbitrary")]
48impl<'a> arbitrary::Arbitrary<'a> for OrderId {
49    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
50        u64::arbitrary(u).map(|x| OrderId(Uint64::new(x)))
51    }
52}
53
54#[allow(clippy::derived_hash_with_manual_eq)]
55impl Hash for OrderId {
56    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
57        self.u64().hash(state);
58    }
59}
60
61impl OrderId {
62    /// Construct a new value from a [u64].
63    pub fn new(x: u64) -> Self {
64        OrderId(x.into())
65    }
66
67    /// Get the underlying `u64` representation of the order ID.
68    pub fn u64(self) -> u64 {
69        self.0.u64()
70    }
71}
72
73impl<'a> PrimaryKey<'a> for OrderId {
74    type Prefix = ();
75    type SubPrefix = ();
76    type Suffix = Self;
77    type SuperSuffix = Self;
78
79    fn key(&self) -> Vec<Key> {
80        vec![Key::Val64(self.0.u64().to_cw_bytes())]
81    }
82}
83
84impl<'a> Prefixer<'a> for OrderId {
85    fn prefix(&self) -> Vec<Key> {
86        vec![Key::Val64(self.0.u64().to_cw_bytes())]
87    }
88}
89
90impl KeyDeserialize for OrderId {
91    type Output = OrderId;
92
93    const KEY_ELEMS: u16 = 1;
94
95    #[inline(always)]
96    fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
97        u64::from_vec(value).map(|x| OrderId(Uint64::new(x)))
98    }
99}
100
101impl fmt::Display for OrderId {
102    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103        write!(f, "{}", self.0)
104    }
105}
106
107impl FromStr for OrderId {
108    type Err = ParseIntError;
109    fn from_str(src: &str) -> Result<Self, ParseIntError> {
110        src.parse().map(|x| OrderId(Uint64::new(x)))
111    }
112}
113
114/// Events
115pub mod events {
116    use crate::constants::{event_key, event_val};
117    use crate::contracts::market::order::OrderId;
118    use crate::contracts::market::position::PositionId;
119    use crate::prelude::MarketType::{CollateralIsBase, CollateralIsQuote};
120    use crate::prelude::*;
121
122    /// Event when a limit order is placed
123    pub struct PlaceLimitOrderEvent {
124        /// Unique order ID
125        pub order_id: OrderId,
126        /// Owner of the order
127        pub owner: Addr,
128        /// Trigger price
129        pub trigger_price: PriceBaseInQuote,
130        /// Market type of the contract
131        pub market_type: MarketType,
132        /// Deposit collateral
133        pub collateral: NonZero<Collateral>,
134        /// Deposit collateral in USD at current exchange rate
135        pub collateral_usd: NonZero<Usd>,
136        /// Signed leverage to base (negative == short, positive == long)
137        pub leverage: SignedLeverageToBase,
138        /// Direction of the position
139        pub direction: DirectionToBase,
140        /// Maximum gains
141        #[deprecated(note = "Use take_profit_override instead")]
142        pub max_gains: Option<MaxGainsInQuote>,
143        /// Stop loss price
144        pub stop_loss_override: Option<PriceBaseInQuote>,
145        /// Take profit price
146        pub take_profit_override: Option<TakeProfitTrader>,
147    }
148
149    impl From<PlaceLimitOrderEvent> for Event {
150        fn from(src: PlaceLimitOrderEvent) -> Self {
151            let mut event = Event::new(event_key::PLACE_LIMIT_ORDER)
152                .add_attribute(
153                    event_key::MARKET_TYPE,
154                    match src.market_type {
155                        CollateralIsQuote => event_val::NOTIONAL_BASE,
156                        CollateralIsBase => event_val::COLLATERAL_BASE,
157                    },
158                )
159                .add_attribute(event_key::ORDER_ID, src.order_id.to_string())
160                .add_attribute(event_key::POS_OWNER, src.owner.to_string())
161                .add_attribute(event_key::TRIGGER_PRICE, src.trigger_price.to_string())
162                .add_attribute(event_key::DEPOSIT_COLLATERAL, src.collateral.to_string())
163                .add_attribute(
164                    event_key::DEPOSIT_COLLATERAL_USD,
165                    src.collateral_usd.to_string(),
166                )
167                .add_attribute(event_key::LEVERAGE_TO_BASE, src.leverage.to_string())
168                .add_attribute(event_key::DIRECTION, src.direction.as_str());
169
170            if let Some(stop_loss_override) = src.stop_loss_override {
171                event = event.add_attribute(
172                    event_key::STOP_LOSS_OVERRIDE,
173                    stop_loss_override.to_string(),
174                );
175            }
176
177            if let Some(take_profit_override) = src.take_profit_override {
178                event = event.add_attribute(
179                    event_key::TAKE_PROFIT_OVERRIDE,
180                    take_profit_override.to_string(),
181                );
182            }
183            #[allow(deprecated)]
184            if let Some(max_gains) = src.max_gains {
185                event = event.add_attribute(event_key::MAX_GAINS, max_gains.to_string());
186            }
187
188            event
189        }
190    }
191    impl TryFrom<Event> for PlaceLimitOrderEvent {
192        type Error = anyhow::Error;
193
194        fn try_from(evt: Event) -> Result<Self, Self::Error> {
195            #[allow(deprecated)]
196            Ok(Self {
197                market_type: evt.map_attr_result(event_key::MARKET_TYPE, |s| match s {
198                    event_val::NOTIONAL_BASE => Ok(CollateralIsQuote),
199                    event_val::COLLATERAL_BASE => Ok(CollateralIsBase),
200                    _ => Err(PerpError::unimplemented().into()),
201                })?,
202                collateral: evt
203                    .string_attr(event_key::DEPOSIT_COLLATERAL)?
204                    .as_str()
205                    .try_into()?,
206                collateral_usd: evt
207                    .string_attr(event_key::DEPOSIT_COLLATERAL_USD)?
208                    .as_str()
209                    .try_into()?,
210                leverage: SignedLeverageToBase::from_str(
211                    &(evt.string_attr(event_key::LEVERAGE_TO_BASE)?),
212                )?,
213                direction: evt.direction_attr(event_key::DIRECTION)?,
214                order_id: OrderId::new(evt.u64_attr(event_key::ORDER_ID)?),
215                owner: evt.unchecked_addr_attr(event_key::POS_OWNER)?,
216                trigger_price: PriceBaseInQuote::try_from_number(
217                    evt.number_attr(event_key::TRIGGER_PRICE)?,
218                )?,
219                stop_loss_override: match evt.try_number_attr(event_key::STOP_LOSS_OVERRIDE)? {
220                    None => None,
221                    Some(stop_loss_override) => {
222                        Some(PriceBaseInQuote::try_from_number(stop_loss_override)?)
223                    }
224                },
225                take_profit_override: evt
226                    .try_map_attr(event_key::TAKE_PROFIT_OVERRIDE, |s| {
227                        TakeProfitTrader::try_from(s)
228                    })
229                    .transpose()?,
230
231                max_gains: evt
232                    .try_map_attr(event_key::MAX_GAINS, MaxGainsInQuote::from_str)
233                    .transpose()?,
234            })
235        }
236    }
237
238    /// A limit order was canceled
239    pub struct CancelLimitOrderEvent {
240        /// ID of the canceled order
241        pub order_id: OrderId,
242    }
243
244    impl From<CancelLimitOrderEvent> for Event {
245        fn from(src: CancelLimitOrderEvent) -> Self {
246            Event::new(event_key::PLACE_LIMIT_ORDER)
247                .add_attribute(event_key::ORDER_ID, src.order_id.to_string())
248        }
249    }
250    impl TryFrom<Event> for CancelLimitOrderEvent {
251        type Error = anyhow::Error;
252
253        fn try_from(evt: Event) -> Result<Self, Self::Error> {
254            Ok(Self {
255                order_id: OrderId::new(evt.u64_attr(event_key::ORDER_ID)?),
256            })
257        }
258    }
259
260    /// A limit order was triggered
261    pub struct ExecuteLimitOrderEvent {
262        /// ID of the order
263        pub order_id: OrderId,
264        /// ID of the position, if it successfully opened
265        pub pos_id: Option<PositionId>,
266        /// The error message for a failed limit order, if it failed
267        pub error: Option<String>,
268    }
269
270    impl From<ExecuteLimitOrderEvent> for Event {
271        fn from(src: ExecuteLimitOrderEvent) -> Self {
272            let mut event = Event::new(event_key::EXECUTE_LIMIT_ORDER)
273                .add_attribute(event_key::ORDER_ID, src.order_id.to_string());
274
275            if let Some(pos_id) = src.pos_id {
276                event = event.add_attribute(event_key::POS_ID, pos_id.to_string());
277            }
278
279            if let Some(error) = src.error {
280                event = event.add_attribute(event_key::EXECUTE_LIMIT_ORDER_ERROR, error);
281            }
282
283            event
284        }
285    }
286    impl TryFrom<Event> for ExecuteLimitOrderEvent {
287        type Error = anyhow::Error;
288
289        fn try_from(evt: Event) -> Result<Self, Self::Error> {
290            Ok(Self {
291                order_id: OrderId::new(evt.u64_attr(event_key::ORDER_ID)?),
292                pos_id: evt.try_u64_attr(event_key::POS_ID)?.map(PositionId::new),
293                error: evt.try_map_attr(event_key::EXECUTE_LIMIT_ORDER_ERROR, |x| x.to_owned()),
294            })
295        }
296    }
297}