levana_perpswap_cosmos/
max_gains.rs1use schemars::{
3 schema::{InstanceType, SchemaObject},
4 JsonSchema,
5};
6
7use crate::prelude::*;
8
9const POS_INF_STR: &str = "+Inf";
11
12#[derive(Debug, Clone, Copy, Eq, PartialEq)]
20#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
21pub enum MaxGainsInQuote {
22 Finite(NonZero<Decimal256>),
24 PosInfinity,
26}
27
28impl Display for MaxGainsInQuote {
29 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
30 match self {
31 MaxGainsInQuote::Finite(val) => val.fmt(f),
32 MaxGainsInQuote::PosInfinity => write!(f, "{}", POS_INF_STR),
33 }
34 }
35}
36
37impl FromStr for MaxGainsInQuote {
38 type Err = PerpError;
39 fn from_str(src: &str) -> Result<Self, PerpError> {
40 match src {
41 POS_INF_STR => Ok(MaxGainsInQuote::PosInfinity),
42 _ => match src.parse() {
43 Ok(number) => Ok(MaxGainsInQuote::Finite(number)),
44 Err(err) => Err(PerpError::new(
45 ErrorId::Conversion,
46 ErrorDomain::Default,
47 format!("error converting {} to MaxGainsInQuote, {}", src, err),
48 )),
49 },
50 }
51 }
52}
53
54impl TryFrom<&str> for MaxGainsInQuote {
55 type Error = anyhow::Error;
56
57 fn try_from(val: &str) -> Result<Self, Self::Error> {
58 Self::from_str(val).map_err(|err| err.into())
59 }
60}
61
62impl serde::Serialize for MaxGainsInQuote {
63 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
64 where
65 S: serde::Serializer,
66 {
67 match self {
68 MaxGainsInQuote::Finite(number) => number.serialize(serializer),
69 MaxGainsInQuote::PosInfinity => serializer.serialize_str(POS_INF_STR),
70 }
71 }
72}
73
74impl<'de> serde::Deserialize<'de> for MaxGainsInQuote {
75 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
76 where
77 D: serde::Deserializer<'de>,
78 {
79 deserializer.deserialize_str(MaxGainsInQuoteVisitor)
80 }
81}
82
83impl JsonSchema for MaxGainsInQuote {
84 fn schema_name() -> String {
85 "MaxGainsInQuote".to_owned()
86 }
87
88 fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
89 SchemaObject {
90 instance_type: Some(InstanceType::String.into()),
91 format: Some("leverage".to_owned()),
92 ..Default::default()
93 }
94 .into()
95 }
96}
97
98struct MaxGainsInQuoteVisitor;
99
100impl<'de> serde::de::Visitor<'de> for MaxGainsInQuoteVisitor {
101 type Value = MaxGainsInQuote;
102
103 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
104 formatter.write_str("MaxGainsInQuote")
105 }
106
107 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
108 where
109 E: serde::de::Error,
110 {
111 v.parse()
112 .map_err(|_| E::custom(format!("Invalid MaxGainsInQuote: {v}")))
113 }
114}
115
116impl MaxGainsInQuote {
117 pub fn calculate_counter_collateral(
119 self,
120 market_type: MarketType,
121 collateral: NonZero<Collateral>,
122 notional_size_in_collateral: Signed<Collateral>,
123 leverage_to_notional: SignedLeverageToNotional,
124 ) -> Result<NonZero<Collateral>> {
125 let direction_to_base = leverage_to_notional.direction().into_base(market_type);
126 Ok(match market_type {
127 MarketType::CollateralIsQuote => match self {
128 MaxGainsInQuote::Finite(max_gains_in_collateral) => {
129 collateral.checked_mul_non_zero(max_gains_in_collateral)?
130 }
131 MaxGainsInQuote::PosInfinity => {
132 return Err(MarketError::InvalidInfiniteMaxGains {
133 market_type,
134 direction: direction_to_base,
135 }
136 .into_anyhow());
137 }
138 },
139 MarketType::CollateralIsBase => {
140 match self {
141 MaxGainsInQuote::PosInfinity => {
142 if leverage_to_notional.direction() == DirectionToNotional::Long {
148 return Err(MarketError::InvalidInfiniteMaxGains {
149 market_type,
150 direction: direction_to_base,
151 }
152 .into_anyhow());
153 }
154
155 NonZero::new(notional_size_in_collateral.abs_unsigned())
156 .context("notional_size_in_collateral is zero")?
157 }
158 MaxGainsInQuote::Finite(max_gains_in_notional) => {
159 let max_gains_multiple = (Number::ONE
160 - (max_gains_in_notional.into_number() + Number::ONE)?
161 .checked_div(leverage_to_notional.into_number())?)?;
162
163 if max_gains_multiple.approx_lt_relaxed(Number::ZERO)? {
164 return Err(MarketError::MaxGainsTooLarge {}.into());
165 }
166
167 let counter_collateral = collateral
168 .into_number()
169 .checked_mul(max_gains_in_notional.into_number())?
170 .checked_div(max_gains_multiple)?;
171 NonZero::<Collateral>::try_from_number(counter_collateral).with_context(|| format!("Calculated an invalid counter_collateral: {counter_collateral}"))?
172 }
173 }
174 }
175 })
176 }
177}