1use crate::models::Point;
2
3#[derive(Clone, Debug)]
12#[cfg_attr(
13 feature = "serde",
14 derive(serde::Serialize, serde::Deserialize),
15 serde(try_from = "ConstantCurveDto", into = "ConstantCurveDto")
16)]
17pub struct ConstantCurve {
18 min_rate: f64,
19 max_rate: f64,
20 price: f64,
21}
22
23impl ConstantCurve {
24 pub unsafe fn new_unchecked(min_rate: f64, max_rate: f64, price: f64) -> Self {
30 Self {
31 min_rate: min_rate,
32 max_rate: max_rate,
33 price: price,
34 }
35 }
36
37 pub fn new(
39 min_rate: Option<f64>,
40 max_rate: Option<f64>,
41 price: f64,
42 ) -> Result<Self, ConstantCurveError> {
43 let dto = ConstantCurveDto {
44 min_rate,
45 max_rate,
46 price,
47 };
48 Self::try_from(dto)
49 }
50
51 pub fn domain(&self) -> (f64, f64) {
53 (self.min_rate, self.max_rate)
54 }
55
56 pub fn points(self) -> Vec<Point> {
62 let mut response = Vec::with_capacity(2);
63 response.push(Point {
64 rate: self.min_rate,
65 price: self.price,
66 });
67 if self.min_rate != self.max_rate {
68 response.push(Point {
69 rate: self.max_rate,
70 price: self.price,
71 });
72 }
73 response
74 }
75}
76
77#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema), schemars(inline))]
79#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
80#[derive(Debug)]
81pub struct ConstantCurveDto {
82 pub min_rate: Option<f64>,
84 pub max_rate: Option<f64>,
86 pub price: f64,
88}
89
90impl Into<ConstantCurveDto> for ConstantCurve {
91 fn into(self) -> ConstantCurveDto {
92 ConstantCurveDto {
93 min_rate: if self.min_rate.is_finite() {
94 Some(self.min_rate)
95 } else {
96 None
97 },
98 max_rate: if self.max_rate.is_finite() {
99 Some(self.max_rate)
100 } else {
101 None
102 },
103 price: self.price,
104 }
105 }
106}
107
108impl TryFrom<ConstantCurveDto> for ConstantCurve {
109 type Error = ConstantCurveError;
110
111 fn try_from(value: ConstantCurveDto) -> Result<Self, Self::Error> {
112 let min_rate = value.min_rate.unwrap_or(f64::NEG_INFINITY);
113 let max_rate = value.max_rate.unwrap_or(f64::INFINITY);
114 let price = value.price;
115
116 if min_rate.is_nan() || max_rate.is_nan() || price.is_nan() {
117 return Err(ConstantCurveError::NaN);
118 }
119 if price.is_infinite() {
120 return Err(ConstantCurveError::InfinitePrice);
121 }
122 if !(min_rate <= 0.0 && 0.0 <= max_rate) {
123 return Err(ConstantCurveError::ZeroTrade);
124 }
125
126 Ok(Self {
127 min_rate,
128 max_rate,
129 price,
130 })
131 }
132}
133
134#[derive(Debug, PartialEq, thiserror::Error)]
136pub enum ConstantCurveError {
137 #[error("NaN value encountered")]
139 NaN,
140 #[error("Domain excludes rate=0")]
142 ZeroTrade,
143 #[error("Price cannot be infinite")]
145 InfinitePrice,
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_domain_contains_zero() {
154 let result = ConstantCurve::new(Some(-5.0), Some(5.0), 10.0);
156 assert!(result.is_ok());
157
158 let result = ConstantCurve::new(Some(0.0), Some(5.0), 10.0);
160 assert!(result.is_ok());
161
162 let result = ConstantCurve::new(Some(-5.0), Some(0.0), 10.0);
164 assert!(result.is_ok());
165
166 let result = ConstantCurve::new(None, None, 10.0);
168 assert!(result.is_ok());
169
170 let result = ConstantCurve::new(None, Some(5.0), 10.0);
172 assert!(result.is_ok());
173
174 let result = ConstantCurve::new(Some(-5.0), None, 10.0);
176 assert!(result.is_ok());
177 }
178
179 #[test]
180 fn test_neg_infinity() {
181 let (min, max) = ConstantCurve::new(None, Some(5.0), 10.0).unwrap().domain();
182 assert!(min.is_infinite() && min < 0.0 && max == 5.0);
183 }
184
185 #[test]
186 fn test_pos_infinity() {
187 let (min, max) = ConstantCurve::new(Some(0.0), None, 10.0).unwrap().domain();
188 assert!(min == 0.0 && max.is_infinite() && max > 0.0);
189 }
190
191 #[test]
192 fn test_full_infinity() {
193 let (min, max) = ConstantCurve::new(None, None, 10.0).unwrap().domain();
194 assert!(min.is_infinite() && min < 0.0 && max.is_infinite() && max > 0.0);
195 }
196
197 #[test]
198 fn test_infinite_price() {
199 assert_eq!(
200 ConstantCurve::new(None, None, f64::INFINITY).unwrap_err(),
201 ConstantCurveError::InfinitePrice
202 );
203 }
204
205 #[test]
206 fn test_nans() {
207 assert_eq!(
208 ConstantCurve::new(Some(f64::NAN), None, 10.0).unwrap_err(),
209 ConstantCurveError::NaN
210 );
211 assert_eq!(
212 ConstantCurve::new(None, Some(f64::NAN), 10.0).unwrap_err(),
213 ConstantCurveError::NaN
214 );
215 assert_eq!(
216 ConstantCurve::new(None, None, f64::NAN).unwrap_err(),
217 ConstantCurveError::NaN
218 );
219 }
220
221 #[test]
222 fn test_bad_domain_reversed() {
223 assert_eq!(
224 ConstantCurve::new(Some(1.0), Some(-1.0), 10.0).unwrap_err(),
225 ConstantCurveError::ZeroTrade
226 );
227 }
228
229 #[test]
230 fn test_bad_domain_strict_positive() {
231 assert_eq!(
232 ConstantCurve::new(Some(1.0), Some(3.0), 10.0).unwrap_err(),
233 ConstantCurveError::ZeroTrade
234 );
235 }
236
237 #[test]
238 fn test_bad_domain_strict_negative() {
239 assert_eq!(
240 ConstantCurve::new(Some(-3.0), Some(-1.0), 10.0).unwrap_err(),
241 ConstantCurveError::ZeroTrade
242 );
243 }
244}