1use std::{fmt, num::ParseIntError};
5
6use crate::constants::event_key;
7use crate::prelude::*;
8use cosmwasm_std::StdResult;
9use cw_storage_plus::{IntKey, Key, KeyDeserialize, Prefixer, PrimaryKey};
10
11use super::{
12 entry::{SlippageAssert, StopLoss},
13 order::OrderId,
14 position::PositionId,
15};
16
17#[cw_serde]
19#[derive(Copy, PartialOrd, Ord, Eq)]
20pub struct DeferredExecId(Uint64);
21
22impl std::hash::Hash for DeferredExecId {
23 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
24 self.0.u64().hash(state);
25 }
26}
27
28impl DeferredExecId {
29 pub fn first() -> Self {
31 DeferredExecId(Uint64::one())
32 }
33
34 pub fn next(self) -> Self {
36 DeferredExecId((self.0.u64() + 1).into())
37 }
38
39 pub fn u64(self) -> u64 {
41 self.0.u64()
42 }
43
44 pub fn from_u64(x: u64) -> Self {
46 DeferredExecId(x.into())
47 }
48}
49
50impl<'a> PrimaryKey<'a> for DeferredExecId {
51 type Prefix = ();
52 type SubPrefix = ();
53 type Suffix = Self;
54 type SuperSuffix = Self;
55
56 fn key(&self) -> Vec<Key> {
57 vec![Key::Val64(self.0.u64().to_cw_bytes())]
58 }
59}
60
61impl<'a> Prefixer<'a> for DeferredExecId {
62 fn prefix(&self) -> Vec<Key> {
63 vec![Key::Val64(self.0.u64().to_cw_bytes())]
64 }
65}
66
67impl KeyDeserialize for DeferredExecId {
68 type Output = DeferredExecId;
69
70 const KEY_ELEMS: u16 = 1;
71
72 #[inline(always)]
73 fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
74 u64::from_vec(value).map(|x| DeferredExecId(Uint64::new(x)))
75 }
76}
77
78impl fmt::Display for DeferredExecId {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 write!(f, "{}", self.0)
81 }
82}
83
84impl FromStr for DeferredExecId {
85 type Err = ParseIntError;
86 fn from_str(src: &str) -> Result<Self, ParseIntError> {
87 src.parse().map(|x| DeferredExecId(Uint64::new(x)))
88 }
89}
90
91#[cw_serde]
93pub struct ListDeferredExecsResp {
94 pub items: Vec<DeferredExecWithStatus>,
96 pub next_start_after: Option<DeferredExecId>,
98}
99
100#[cw_serde]
102pub enum GetDeferredExecResp {
103 Found {
105 item: Box<DeferredExecWithStatus>,
107 },
108 NotFound {},
110}
111
112#[cw_serde]
114pub struct DeferredExecWithStatus {
115 pub id: DeferredExecId,
117 pub created: Timestamp,
119 pub status: DeferredExecStatus,
121 pub owner: Addr,
123 pub item: DeferredExecItem,
125}
126
127#[cw_serde]
129pub enum DeferredExecStatus {
130 Pending,
132 Success {
134 target: DeferredExecCompleteTarget,
136 executed: Timestamp,
138 },
139 Failure {
141 reason: String,
143 executed: Timestamp,
145 crank_price: Option<PricePoint>,
147 },
148}
149
150impl DeferredExecStatus {
151 pub fn is_pending(&self) -> bool {
153 match self {
154 DeferredExecStatus::Pending => true,
155 DeferredExecStatus::Success { .. } => false,
156 DeferredExecStatus::Failure { .. } => false,
157 }
158 }
159 pub fn is_failure(&self) -> bool {
161 match self {
162 DeferredExecStatus::Pending => false,
163 DeferredExecStatus::Success { .. } => false,
164 DeferredExecStatus::Failure { .. } => true,
165 }
166 }
167
168 pub fn is_success(&self) -> bool {
170 match self {
171 DeferredExecStatus::Pending => false,
172 DeferredExecStatus::Success { .. } => true,
173 DeferredExecStatus::Failure { .. } => false,
174 }
175 }
176}
177
178#[cw_serde]
180#[allow(clippy::large_enum_variant)]
181pub enum DeferredExecItem {
182 OpenPosition {
184 slippage_assert: Option<SlippageAssert>,
186 leverage: LeverageToBase,
188 direction: DirectionToBase,
190 #[deprecated(note = "use take_profit instead")]
192 max_gains: Option<MaxGainsInQuote>,
193 stop_loss_override: Option<PriceBaseInQuote>,
195 #[serde(alias = "take_profit_override")]
197 take_profit: Option<TakeProfitTrader>,
198 amount: NonZero<Collateral>,
200 crank_fee: Collateral,
206 crank_fee_usd: Usd,
208 },
209 UpdatePositionAddCollateralImpactLeverage {
213 id: PositionId,
215 amount: NonZero<Collateral>,
217 },
218 UpdatePositionAddCollateralImpactSize {
222 id: PositionId,
224 slippage_assert: Option<SlippageAssert>,
226 amount: NonZero<Collateral>,
228 },
229
230 UpdatePositionRemoveCollateralImpactLeverage {
232 id: PositionId,
234 amount: NonZero<Collateral>,
236 },
237 UpdatePositionRemoveCollateralImpactSize {
239 id: PositionId,
241 amount: NonZero<Collateral>,
243 slippage_assert: Option<SlippageAssert>,
245 },
246
247 UpdatePositionLeverage {
251 id: PositionId,
253 leverage: LeverageToBase,
255 slippage_assert: Option<SlippageAssert>,
257 },
258
259 UpdatePositionMaxGains {
261 id: PositionId,
263 max_gains: MaxGainsInQuote,
265 },
266
267 UpdatePositionTakeProfitPrice {
269 id: PositionId,
271 price: TakeProfitTrader,
273 },
274
275 UpdatePositionStopLossPrice {
277 id: PositionId,
279 stop_loss: StopLoss,
281 },
282
283 ClosePosition {
285 id: PositionId,
287 slippage_assert: Option<SlippageAssert>,
289 },
290
291 SetTriggerOrder {
293 id: PositionId,
295 stop_loss_override: Option<PriceBaseInQuote>,
298 #[serde(alias = "take_profit_override")]
301 take_profit: Option<TakeProfitTrader>,
302 },
303
304 PlaceLimitOrder {
307 trigger_price: PriceBaseInQuote,
309 leverage: LeverageToBase,
311 direction: DirectionToBase,
313 #[deprecated(note = "use take_profit instead")]
315 max_gains: Option<MaxGainsInQuote>,
316 stop_loss_override: Option<PriceBaseInQuote>,
318 #[serde(alias = "take_profit_override")]
320 take_profit: Option<TakeProfitTrader>,
321 amount: NonZero<Collateral>,
323 crank_fee: Collateral,
325 crank_fee_usd: Usd,
327 },
328
329 CancelLimitOrder {
331 order_id: OrderId,
333 },
334}
335
336#[cw_serde]
338#[derive(Copy)]
339pub enum DeferredExecTarget {
340 DoesNotExist,
342 Position(PositionId),
344 Order(OrderId),
346}
347
348#[cw_serde]
352#[derive(Copy)]
353pub enum DeferredExecCompleteTarget {
354 Position(PositionId),
356 Order(OrderId),
358}
359
360impl DeferredExecTarget {
361 pub fn position_id(&self) -> Option<PositionId> {
363 match self {
364 DeferredExecTarget::DoesNotExist | DeferredExecTarget::Order(_) => None,
365 DeferredExecTarget::Position(pos_id) => Some(*pos_id),
366 }
367 }
368
369 pub fn order_id(&self) -> Option<OrderId> {
371 match self {
372 DeferredExecTarget::DoesNotExist | DeferredExecTarget::Position(_) => None,
373 DeferredExecTarget::Order(order_id) => Some(*order_id),
374 }
375 }
376}
377
378impl DeferredExecItem {
379 pub fn target(&self) -> DeferredExecTarget {
381 match self {
382 DeferredExecItem::OpenPosition { .. } => DeferredExecTarget::DoesNotExist,
383 DeferredExecItem::UpdatePositionAddCollateralImpactLeverage { id, .. } => {
384 DeferredExecTarget::Position(*id)
385 }
386 DeferredExecItem::UpdatePositionAddCollateralImpactSize { id, .. } => {
387 DeferredExecTarget::Position(*id)
388 }
389 DeferredExecItem::UpdatePositionRemoveCollateralImpactLeverage { id, .. } => {
390 DeferredExecTarget::Position(*id)
391 }
392 DeferredExecItem::UpdatePositionRemoveCollateralImpactSize { id, .. } => {
393 DeferredExecTarget::Position(*id)
394 }
395 DeferredExecItem::UpdatePositionLeverage { id, .. } => {
396 DeferredExecTarget::Position(*id)
397 }
398 DeferredExecItem::UpdatePositionMaxGains { id, .. } => {
399 DeferredExecTarget::Position(*id)
400 }
401 DeferredExecItem::UpdatePositionTakeProfitPrice { id, .. } => {
402 DeferredExecTarget::Position(*id)
403 }
404 DeferredExecItem::UpdatePositionStopLossPrice { id, .. } => {
405 DeferredExecTarget::Position(*id)
406 }
407 DeferredExecItem::ClosePosition { id, .. } => DeferredExecTarget::Position(*id),
408 DeferredExecItem::SetTriggerOrder { id, .. } => DeferredExecTarget::Position(*id),
409 DeferredExecItem::PlaceLimitOrder { .. } => DeferredExecTarget::DoesNotExist,
410 DeferredExecItem::CancelLimitOrder { order_id } => DeferredExecTarget::Order(*order_id),
411 }
412 }
413
414 pub fn deposited_amount(&self) -> Collateral {
416 match self {
417 DeferredExecItem::OpenPosition { amount, .. }
418 | DeferredExecItem::UpdatePositionAddCollateralImpactLeverage { amount, .. }
419 | DeferredExecItem::UpdatePositionAddCollateralImpactSize { amount, .. }
420 | DeferredExecItem::PlaceLimitOrder { amount, .. } => amount.raw(),
421 DeferredExecItem::UpdatePositionRemoveCollateralImpactLeverage { .. }
422 | DeferredExecItem::UpdatePositionRemoveCollateralImpactSize { .. }
423 | DeferredExecItem::UpdatePositionLeverage { .. }
424 | DeferredExecItem::UpdatePositionMaxGains { .. }
425 | DeferredExecItem::UpdatePositionTakeProfitPrice { .. }
426 | DeferredExecItem::UpdatePositionStopLossPrice { .. }
427 | DeferredExecItem::ClosePosition { .. }
428 | DeferredExecItem::SetTriggerOrder { .. }
429 | DeferredExecItem::CancelLimitOrder { .. } => Collateral::zero(),
430 }
431 }
432}
433
434#[derive(Clone, Debug)]
436pub struct DeferredExecQueuedEvent {
437 pub deferred_exec_id: DeferredExecId,
439 pub target: DeferredExecTarget,
441 pub owner: Addr,
443}
444
445impl From<DeferredExecQueuedEvent> for Event {
446 fn from(
447 DeferredExecQueuedEvent {
448 deferred_exec_id,
449 target,
450 owner,
451 }: DeferredExecQueuedEvent,
452 ) -> Self {
453 let mut event = Event::new("deferred-exec-queued")
454 .add_attribute(event_key::DEFERRED_EXEC_ID, deferred_exec_id.to_string())
455 .add_attribute(event_key::DEFERRED_EXEC_OWNER, owner);
456 match target {
457 DeferredExecTarget::DoesNotExist => {
458 event = event.add_attribute(event_key::DEFERRED_EXEC_TARGET, "does-not-exist");
459 }
460 DeferredExecTarget::Position(position_id) => {
461 event = event
462 .add_attribute(event_key::POS_ID, position_id.to_string())
463 .add_attribute(event_key::DEFERRED_EXEC_TARGET, "position");
464 }
465 DeferredExecTarget::Order(order_id) => {
466 event = event
467 .add_attribute(event_key::ORDER_ID, order_id.to_string())
468 .add_attribute(event_key::DEFERRED_EXEC_TARGET, "order");
469 }
470 }
471 event
472 }
473}
474
475impl TryFrom<Event> for DeferredExecQueuedEvent {
476 type Error = anyhow::Error;
477
478 fn try_from(evt: Event) -> anyhow::Result<Self> {
479 Ok(Self {
480 deferred_exec_id: evt
481 .u64_attr(event_key::DEFERRED_EXEC_ID)
482 .map(DeferredExecId::from_u64)?,
483 owner: evt.unchecked_addr_attr(event_key::DEFERRED_EXEC_OWNER)?,
484 target: match evt.string_attr(event_key::DEFERRED_EXEC_TARGET)?.as_str() {
485 "does-not-exist" => DeferredExecTarget::DoesNotExist,
486 "position" => DeferredExecTarget::Position(
487 evt.u64_attr(event_key::POS_ID).map(PositionId::new)?,
488 ),
489 "order" => {
490 DeferredExecTarget::Order(evt.u64_attr(event_key::ORDER_ID).map(OrderId::new)?)
491 }
492 _ => anyhow::bail!("invalid deferred exec target"),
493 },
494 })
495 }
496}
497
498#[derive(Debug)]
500pub struct DeferredExecExecutedEvent {
501 pub deferred_exec_id: DeferredExecId,
503 pub target: DeferredExecTarget,
505 pub owner: Addr,
507 pub success: bool,
509 pub desc: String,
511}
512
513impl From<DeferredExecExecutedEvent> for Event {
514 fn from(
515 DeferredExecExecutedEvent {
516 deferred_exec_id,
517 target,
518 owner,
519 success,
520 desc,
521 }: DeferredExecExecutedEvent,
522 ) -> Self {
523 let mut event = Event::new("deferred-exec-executed")
524 .add_attribute(event_key::DEFERRED_EXEC_ID, deferred_exec_id.to_string())
525 .add_attribute(event_key::DEFERRED_EXEC_OWNER, owner)
526 .add_attribute(event_key::SUCCESS, if success { "true" } else { "false" })
527 .add_attribute(event_key::DESC, desc);
528
529 match target {
530 DeferredExecTarget::DoesNotExist => {
531 event = event.add_attribute(event_key::DEFERRED_EXEC_TARGET, "does-not-exist");
532 }
533 DeferredExecTarget::Position(position_id) => {
534 event = event
535 .add_attribute(event_key::POS_ID, position_id.to_string())
536 .add_attribute(event_key::DEFERRED_EXEC_TARGET, "position");
537 }
538 DeferredExecTarget::Order(order_id) => {
539 event = event
540 .add_attribute(event_key::ORDER_ID, order_id.to_string())
541 .add_attribute(event_key::DEFERRED_EXEC_TARGET, "order");
542 }
543 }
544 event
545 }
546}
547
548impl TryFrom<Event> for DeferredExecExecutedEvent {
549 type Error = anyhow::Error;
550
551 fn try_from(evt: Event) -> anyhow::Result<Self> {
552 Ok(Self {
553 deferred_exec_id: evt
554 .u64_attr(event_key::DEFERRED_EXEC_ID)
555 .map(DeferredExecId::from_u64)?,
556 owner: evt.unchecked_addr_attr(event_key::DEFERRED_EXEC_OWNER)?,
557 success: evt.bool_attr(event_key::SUCCESS)?,
558 desc: evt.string_attr(event_key::DESC)?,
559 target: match evt.string_attr(event_key::DEFERRED_EXEC_TARGET)?.as_str() {
560 "does-not-exist" => DeferredExecTarget::DoesNotExist,
561 "position" => DeferredExecTarget::Position(
562 evt.u64_attr(event_key::POS_ID).map(PositionId::new)?,
563 ),
564 "order" => {
565 DeferredExecTarget::Order(evt.u64_attr(event_key::ORDER_ID).map(OrderId::new)?)
566 }
567 _ => anyhow::bail!("invalid deferred exec target"),
568 },
569 })
570 }
571}
572
573pub struct FeesReturnedEvent {
575 pub recipient: Addr,
577 pub amount: NonZero<Collateral>,
579 pub amount_usd: NonZero<Usd>,
581}
582
583impl From<FeesReturnedEvent> for Event {
584 fn from(
585 FeesReturnedEvent {
586 recipient,
587 amount,
588 amount_usd,
589 }: FeesReturnedEvent,
590 ) -> Self {
591 Event::new("fees-returned")
592 .add_attribute("recipient", recipient.into_string())
593 .add_attribute("amount", amount.to_string())
594 .add_attribute("amount_usd", amount_usd.to_string())
595 }
596}