pyth_sdk/price.rs
1use borsh::{
2 BorshDeserialize,
3 BorshSerialize,
4};
5
6use std::convert::TryFrom;
7
8use schemars::JsonSchema;
9
10use crate::{
11 utils,
12 UnixTimestamp,
13};
14
15// Constants for working with pyth's number representation
16const PD_EXPO: i32 = -9;
17const PD_SCALE: u64 = 1_000_000_000;
18const MAX_PD_V_U64: u64 = (1 << 28) - 1;
19
20/// A price with a degree of uncertainty at a certain time, represented as a price +- a confidence
21/// interval.
22///
23/// Please refer to the documentation at https://docs.pyth.network/consumers/best-practices for
24/// using this price safely.
25///
26/// The confidence interval roughly corresponds to the standard error of a normal distribution.
27/// Both the price and confidence are stored in a fixed-point numeric representation, `x *
28/// 10^expo`, where `expo` is the exponent. For example:
29///
30/// ```
31/// use pyth_sdk::Price;
32/// Price { price: 12345, conf: 267, expo: -2, publish_time: 100 }; // represents 123.45 +- 2.67 published at UnixTimestamp 100
33/// Price { price: 123, conf: 1, expo: 2, publish_time: 100 }; // represents 12300 +- 100 published at UnixTimestamp 100
34/// ```
35///
36/// `Price` supports a limited set of mathematical operations. All of these operations will
37/// propagate any uncertainty in the arguments into the result. However, the uncertainty in the
38/// result may overestimate the true uncertainty (by at most a factor of `sqrt(2)`) due to
39/// computational limitations. Furthermore, all of these operations may return `None` if their
40/// result cannot be represented within the numeric representation (e.g., the exponent is so
41/// small that the price does not fit into an i64). Users of these methods should (1) select
42/// their exponents to avoid this problem, and (2) handle the `None` case gracefully.
43#[derive(
44 Clone,
45 Copy,
46 Default,
47 Debug,
48 PartialEq,
49 Eq,
50 BorshSerialize,
51 BorshDeserialize,
52 serde::Serialize,
53 serde::Deserialize,
54 JsonSchema,
55)]
56pub struct Price {
57 /// Price.
58 #[serde(with = "utils::as_string")] // To ensure accuracy on conversion to json.
59 #[schemars(with = "String")]
60 pub price: i64,
61 /// Confidence interval.
62 #[serde(with = "utils::as_string")]
63 #[schemars(with = "String")]
64 pub conf: u64,
65 /// Exponent.
66 pub expo: i32,
67 /// Publish time.
68 pub publish_time: UnixTimestamp,
69}
70
71impl Price {
72 /// Get the current price of this account in a different quote currency.
73 ///
74 /// If this account represents the price of the product X/Z, and `quote` represents the price
75 /// of the product Y/Z, this method returns the price of X/Y. Use this method to get the
76 /// price of e.g., mSOL/SOL from the mSOL/USD and SOL/USD accounts.
77 ///
78 /// `result_expo` determines the exponent of the result, i.e., the number of digits below the
79 /// decimal point. This method returns `None` if either the price or confidence are too
80 /// large to be represented with the requested exponent.
81 ///
82 /// Example:
83 /// ```ignore
84 /// let btc_usd: Price = ...;
85 /// let eth_usd: Price = ...;
86 /// // -8 is the desired exponent for the result
87 /// let btc_eth: Price = btc_usd.get_price_in_quote(ð_usd, -8);
88 /// println!("BTC/ETH price: ({} +- {}) x 10^{}", price.price, price.conf, price.expo);
89 /// ```
90 pub fn get_price_in_quote(&self, quote: &Price, result_expo: i32) -> Option<Price> {
91 self.div(quote)?.scale_to_exponent(result_expo)
92 }
93
94 /// Get the valuation of a collateral position according to:
95 /// 1. the net amount currently deposited (across the protocol)
96 /// 2. the deposits endpoint for the affine combination (across the protocol)
97 /// 3. the initial (at 0 deposits) and final (at the deposits endpoint) valuation discount rates
98 ///
99 /// We use a linear interpolation between the the initial and final discount rates,
100 /// scaled by the proportion of the deposits endpoint that has been deposited.
101 /// This essentially assumes a linear liquidity cumulative density function,
102 /// which has been shown to be a reasonable assumption for many crypto tokens in literature.
103 /// For more detail on this: https://pythnetwork.medium.com/improving-lending-protocols-with-liquidity-oracles-fd1ea4f96f37
104 ///
105 /// If the assumptions of the liquidity curve hold true, we are obtaining a lower bound for the
106 /// net price at which one can sell the quantity of token specified by deposits in the open
107 /// markets. We value collateral according to the total deposits in the protocol due to the
108 /// present intractability of assessing collateral at risk by price range.
109 ///
110 /// Args
111 /// deposits: u64, quantity of token deposited in the protocol
112 /// deposits_endpoint: u64, deposits right endpoint for the affine combination
113 /// rate_discount_initial: u64, initial discounted rate at 0 deposits (units given by
114 /// discount_exponent) rate_discount_final: u64, final discounted rate at deposits_endpoint
115 /// deposits (units given by discount_exponent) discount_exponent: u64, the exponent to
116 /// apply to the discounts above (e.g. if discount_final is 10 but meant to express 0.1/10%,
117 /// exponent would be -2) note that if discount_initial is bigger than 100% per the discount
118 /// exponent scale, then the initial valuation of the collateral will be higher than the oracle
119 /// price
120 pub fn get_collateral_valuation_price(
121 &self,
122 deposits: u64,
123 deposits_endpoint: u64,
124 rate_discount_initial: u64,
125 rate_discount_final: u64,
126 discount_exponent: i32,
127 ) -> Option<Price> {
128 // valuation price should not increase as amount of collateral grows, so
129 // rate_discount_initial should >= rate_discount_final
130 if rate_discount_initial < rate_discount_final {
131 return None;
132 }
133
134 // get price versions of discounts
135 let initial_percentage = Price {
136 price: i64::try_from(rate_discount_initial).ok()?,
137 conf: 0,
138 expo: discount_exponent,
139 publish_time: 0,
140 };
141 let final_percentage = Price {
142 price: i64::try_from(rate_discount_final).ok()?,
143 conf: 0,
144 expo: discount_exponent,
145 publish_time: 0,
146 };
147
148 // get the interpolated discount as a price
149 let discount_interpolated = Price::affine_combination(
150 0,
151 initial_percentage,
152 i64::try_from(deposits_endpoint).ok()?,
153 final_percentage,
154 i64::try_from(deposits).ok()?,
155 -9,
156 )?;
157
158 let conf_orig = self.conf;
159 let expo_orig = self.expo;
160
161 // get price discounted, convert back to the original exponents we received the price in
162 let price_discounted = self
163 .mul(&discount_interpolated)?
164 .scale_to_exponent(expo_orig)?;
165
166 return Some(Price {
167 price: price_discounted.price,
168 conf: conf_orig,
169 expo: price_discounted.expo,
170 publish_time: self.publish_time,
171 });
172 }
173
174 /// Get the valuation of a borrow position according to:
175 /// 1. the net amount currently borrowed (across the protocol)
176 /// 2. the borrowed endpoint for the affine combination (across the protocol)
177 /// 3. the initial (at 0 borrows) and final (at the borrow endpoint) valuation premiums
178 ///
179 /// We use a linear interpolation between the the initial and final premiums,
180 /// scaled by the proportion of the borrows endpoint that has been borrowed out.
181 /// This essentially assumes a linear liquidity cumulative density function,
182 /// which has been shown to be a reasonable assumption for many crypto tokens in literature.
183 /// For more detail on this: https://pythnetwork.medium.com/improving-lending-protocols-with-liquidity-oracles-fd1ea4f96f37
184 ///
185 /// If the assumptions of the liquidity curve hold true, we are obtaining an upper bound for the
186 /// net price at which one can buy the quantity of token specified by borrows in the open
187 /// markets. We value the borrows according to the total borrows out of the protocol due to
188 /// the present intractability of assessing collateral at risk and repayment likelihood by
189 /// price range.
190 ///
191 /// Args
192 /// borrows: u64, quantity of token borrowed from the protocol
193 /// borrows_endpoint: u64, borrows right endpoint for the affine combination
194 /// rate_premium_initial: u64, initial premium at 0 borrows (units given by premium_exponent)
195 /// rate_premium_final: u64, final premium at borrows_endpoint borrows (units given by
196 /// premium_exponent) premium_exponent: u64, the exponent to apply to the premiums above
197 /// (e.g. if premium_final is 50 but meant to express 0.05/5%, exponent would be -3)
198 /// note that if premium_initial is less than 100% per the premium exponent scale, then the
199 /// initial valuation of the borrow will be lower than the oracle price
200 pub fn get_borrow_valuation_price(
201 &self,
202 borrows: u64,
203 borrows_endpoint: u64,
204 rate_premium_initial: u64,
205 rate_premium_final: u64,
206 premium_exponent: i32,
207 ) -> Option<Price> {
208 // valuation price should not decrease as amount of borrow grows, so rate_premium_initial
209 // should <= rate_premium_final
210 if rate_premium_initial > rate_premium_final {
211 return None;
212 }
213
214 // get price versions of premiums
215 let initial_percentage = Price {
216 price: i64::try_from(rate_premium_initial).ok()?,
217 conf: 0,
218 expo: premium_exponent,
219 publish_time: 0,
220 };
221 let final_percentage = Price {
222 price: i64::try_from(rate_premium_final).ok()?,
223 conf: 0,
224 expo: premium_exponent,
225 publish_time: 0,
226 };
227
228 // get the interpolated premium as a price
229 let premium_interpolated = Price::affine_combination(
230 0,
231 initial_percentage,
232 i64::try_from(borrows_endpoint).ok()?,
233 final_percentage,
234 i64::try_from(borrows).ok()?,
235 -9,
236 )?;
237
238 let conf_orig = self.conf;
239 let expo_orig = self.expo;
240
241 // get price premium, convert back to the original exponents we received the price in
242 let price_premium = self
243 .mul(&premium_interpolated)?
244 .scale_to_exponent(expo_orig)?;
245
246 return Some(Price {
247 price: price_premium.price,
248 conf: conf_orig,
249 expo: price_premium.expo,
250 publish_time: self.publish_time,
251 });
252 }
253
254 /// affine_combination performs an affine combination of two prices located at x coordinates x1
255 /// and x2, for query x coordinate x_query Takes in 2 points and a 3rd "query" x coordinate,
256 /// to compute the value at x_query Effectively draws a line between the 2 points and then
257 /// proceeds to interpolate/extrapolate to find the value at the query coordinate according
258 /// to that line
259 ///
260 /// affine_combination gives you the Price, scaled to a specified exponent, closest to y2 *
261 /// ((xq-x1)/(x2-x1)) + y1 * ((x2-x3)/(x2-x1)) If the numerators and denominators of the
262 /// fractions there are both representable within 8 digits of precision and the fraction
263 /// itself is also representable within 8 digits of precision, there is no loss due to taking
264 /// the fractions. If the prices are normalized, then there is no loss in taking the
265 /// products via mul. Otherwise, the prices will be converted to a form representable within
266 /// 8 digits of precision. The scaling to the specified expo pre_add_expo introduces a max
267 /// error of 2*10^pre_add_expo. If pre_add_expo is small enough relative to the products,
268 /// then there is no loss due to scaling. If the fractions are expressable within 8 digits
269 /// of precision, the ys are normalized, and the exponent is sufficiently small,
270 /// then you get an exact result. Otherwise, your error is bounded as given below.
271 ///
272 /// Args
273 /// x1: i64, the x coordinate of the first point
274 /// y1: Price, the y coordinate of the first point, represented as a Price struct
275 /// x2: i64, the x coordinate of the second point, must be greater than x1
276 /// y2: Price, the y coordinate of the second point, represented as a Price struct
277 /// x_query: i64, the query x coordinate, at which we wish to impute a y value
278 /// pre_add_expo: i32, the exponent to scale to, before final addition; essentially the final
279 /// precision you want
280 ///
281 /// Logic
282 /// imputed y value = y2 * ((xq-x1)/(x2-x1)) + y1 * ((x2-x3)/(x2-x1))
283 /// 1. compute A = xq-x1
284 /// 2. compute B = x2-xq
285 /// 3. compute C = x2-x1
286 /// 4. compute D = A/C
287 /// 5. compute E = B/C
288 /// 6. compute F = y2 * D
289 /// 7. compute G = y1 * E
290 /// 8. compute H = F + G
291 ///
292 /// Bounds due to precision loss
293 /// x = 10^(PD_EXPO+2)
294 /// fraction (due to normalization & division) incurs max loss of x
295 /// Thus, max loss here: Err(D), Err(E) <= x
296 /// If y1, y2 already normalized, no additional error. O/w, Err(y1), Err(y2) with normalization
297 /// <= x Err(F), Err(G) <= (1+x)^2 - 1 (in fractional terms) ~= 2x
298 /// Err(H) <= 2*2x = 4x, when PD_EXPO = -9 ==> Err(H) <= 4*10^-7
299 ///
300 /// Scaling this back has error bounded by the expo (10^pre_add_expo).
301 /// This is because reverting a potentially finer expo to a coarser grid has the potential to be
302 /// off by the order of the atomic unit of the coarser grid.
303 /// This scaling error combines with the previous error additively: Err <= 4x +
304 /// 2*10^pre_add_expo But if pre_add_expo is reasonably small (<= -9), then other term will
305 /// dominate
306 ///
307 /// Note that if the ys are unnormalized due to the confidence but not the price, the
308 /// normalization could zero out the price fields. Based on this, it is recommended that
309 /// input prices are normalized, or at least do not contain huge discrepancies between price and
310 /// confidence.
311 pub fn affine_combination(
312 x1: i64,
313 y1: Price,
314 x2: i64,
315 y2: Price,
316 x_query: i64,
317 pre_add_expo: i32,
318 ) -> Option<Price> {
319 if x2 <= x1 {
320 return None;
321 }
322
323 // get the deltas for the x coordinates
324 // 1. compute A = xq-x1
325 let delta_q1 = x_query.checked_sub(x1)?;
326 // 2. compute B = x2-xq
327 let delta_2q = x2.checked_sub(x_query)?;
328 // 3. compute C = x2-x1
329 let delta_21 = x2.checked_sub(x1)?;
330
331 // get the relevant fractions of the deltas, with scaling
332 // 4. compute D = A/C, Err(D) <= x
333 let frac_q1 = Price::fraction(delta_q1, delta_21)?;
334 // 5. compute E = B/C, Err(E) <= x
335 let frac_2q = Price::fraction(delta_2q, delta_21)?;
336
337 // calculate products for left and right
338 // 6. compute F = y2 * D, Err(F) <= (1+x)^2 - 1 ~= 2x
339 let mut left = y2.mul(&frac_q1)?;
340 // 7. compute G = y1 * E, Err(G) <= (1+x)^2 - 1 ~= 2x
341 let mut right = y1.mul(&frac_2q)?;
342
343 // Err(scaling) += 2*10^pre_add_expo
344 left = left.scale_to_exponent(pre_add_expo)?;
345 right = right.scale_to_exponent(pre_add_expo)?;
346
347 // 8. compute H = F + G, Err(H) ~= 4x + 2*10^pre_add_expo
348 return left.add(&right);
349 }
350
351 /// Get the price of a basket of currencies.
352 ///
353 /// Each entry in `amounts` is of the form `(price, qty, qty_expo)`, and the result is the sum
354 /// of `price * qty * 10^qty_expo`. The result is returned with exponent `result_expo`.
355 ///
356 /// An example use case for this function is to get the value of an LP token.
357 ///
358 /// Example:
359 /// ```ignore
360 /// let btc_usd: Price = ...;
361 /// let eth_usd: Price = ...;
362 /// // Quantity of each asset in fixed-point a * 10^e.
363 /// // This represents 0.1 BTC and .05 ETH.
364 /// // -8 is desired exponent for result
365 /// let basket_price: Price = Price::price_basket(&[
366 /// (btc_usd, 10, -2),
367 /// (eth_usd, 5, -2)
368 /// ], -8);
369 /// println!("0.1 BTC and 0.05 ETH are worth: ({} +- {}) x 10^{} USD",
370 /// basket_price.price, basket_price.conf, basket_price.expo);
371 /// ```
372 pub fn price_basket(amounts: &[(Price, i64, i32)], result_expo: i32) -> Option<Price> {
373 if amounts.is_empty() {
374 return None;
375 }
376
377 let mut res = Price {
378 price: 0,
379 conf: 0,
380 expo: result_expo,
381 publish_time: amounts[0].0.publish_time,
382 };
383 for amount in amounts {
384 res = res.add(
385 &amount
386 .0
387 .cmul(amount.1, amount.2)?
388 .scale_to_exponent(result_expo)?,
389 )?
390 }
391 Some(res)
392 }
393
394 /// Divide this price by `other` while propagating the uncertainty in both prices into the
395 /// result.
396 ///
397 /// This method will automatically select a reasonable exponent for the result. If both
398 /// `self` and `other` are normalized, the exponent is `self.expo + PD_EXPO - other.expo`
399 /// (i.e., the fraction has `PD_EXPO` digits of additional precision). If they are not
400 /// normalized, this method will normalize them, resulting in an unpredictable result
401 /// exponent. If the result is used in a context that requires a specific exponent,
402 /// please call `scale_to_exponent` on it.
403 pub fn div(&self, other: &Price) -> Option<Price> {
404 // Price is not guaranteed to store its price/confidence in normalized form.
405 // Normalize them here to bound the range of price/conf, which is required to perform
406 // arithmetic operations.
407
408 let base = self.normalize()?;
409 let other = other.normalize()?;
410
411 if other.price == 0 {
412 return None;
413 }
414
415 // These use at most 27 bits each
416 let (base_price, base_sign) = Price::to_unsigned(base.price);
417 let (other_price, other_sign) = Price::to_unsigned(other.price);
418
419 // Compute the midprice, base in terms of other.
420 // Uses at most 57 bits
421 let midprice = base_price.checked_mul(PD_SCALE)?.checked_div(other_price)?;
422 let midprice_expo = base.expo.checked_sub(other.expo)?.checked_add(PD_EXPO)?;
423
424 // Compute the confidence interval.
425 // This code uses the 1-norm instead of the 2-norm for computational reasons.
426 // Let p +- a and q +- b be the two arguments to this method. The correct
427 // formula is p/q * sqrt( (a/p)^2 + (b/q)^2 ). This quantity
428 // is difficult to compute due to the sqrt and overflow/underflow considerations.
429 //
430 // This code instead computes p/q * (a/p + b/q) = a/q + pb/q^2 .
431 // This quantity is at most a factor of sqrt(2) greater than the correct result, which
432 // shouldn't matter considering that confidence intervals are typically ~0.1% of the price.
433
434 // This uses 57 bits and has an exponent of PD_EXPO.
435 let other_confidence_pct: u64 =
436 other.conf.checked_mul(PD_SCALE)?.checked_div(other_price)?;
437
438 // first term is 57 bits, second term is 57 + 58 - 29 = 86 bits. Same exponent as the
439 // midprice. Note: the computation of the 2nd term consumes about 3k ops. We may
440 // want to optimize this.
441 let conf = (base.conf.checked_mul(PD_SCALE)?.checked_div(other_price)? as u128)
442 .checked_add(
443 (other_confidence_pct as u128)
444 .checked_mul(midprice as u128)?
445 .checked_div(PD_SCALE as u128)?,
446 )?;
447
448 // Note that this check only fails if an argument's confidence interval was >> its price,
449 // in which case None is a reasonable result, as we have essentially 0 information about the
450 // price.
451 if conf < (u64::MAX as u128) {
452 Some(Price {
453 price: (midprice as i64)
454 .checked_mul(base_sign)?
455 .checked_mul(other_sign)?,
456 conf: conf as u64,
457 expo: midprice_expo,
458 publish_time: self.publish_time.min(other.publish_time),
459 })
460 } else {
461 None
462 }
463 }
464
465 /// Add `other` to this, propagating uncertainty in both prices.
466 ///
467 /// Requires both `Price`s to have the same exponent -- use `scale_to_exponent` on
468 /// the arguments if necessary.
469 ///
470 /// TODO: could generalize this method to support different exponents.
471 pub fn add(&self, other: &Price) -> Option<Price> {
472 assert_eq!(self.expo, other.expo);
473
474 let price = self.price.checked_add(other.price)?;
475 // The conf should technically be sqrt(a^2 + b^2), but that's harder to compute.
476 let conf = self.conf.checked_add(other.conf)?;
477 Some(Price {
478 price,
479 conf,
480 expo: self.expo,
481 publish_time: self.publish_time.min(other.publish_time),
482 })
483 }
484
485 /// Multiply this `Price` by a constant `c * 10^e`.
486 pub fn cmul(&self, c: i64, e: i32) -> Option<Price> {
487 self.mul(&Price {
488 price: c,
489 conf: 0,
490 expo: e,
491 publish_time: self.publish_time,
492 })
493 }
494
495 /// Multiply this `Price` by `other`, propagating any uncertainty.
496 pub fn mul(&self, other: &Price) -> Option<Price> {
497 // Price is not guaranteed to store its price/confidence in normalized form.
498 // Normalize them here to bound the range of price/conf, which is required to perform
499 // arithmetic operations.
500 let base = self.normalize()?;
501 let other = other.normalize()?;
502
503 // These use at most 27 bits each
504 let (base_price, base_sign) = Price::to_unsigned(base.price);
505 let (other_price, other_sign) = Price::to_unsigned(other.price);
506
507 // Uses at most 27*2 = 54 bits
508 let midprice = base_price.checked_mul(other_price)?;
509 let midprice_expo = base.expo.checked_add(other.expo)?;
510
511 // Compute the confidence interval.
512 // This code uses the 1-norm instead of the 2-norm for computational reasons.
513 // Note that this simplifies: pq * (a/p + b/q) = qa + pb
514 // 27*2 + 1 bits
515 let conf = base
516 .conf
517 .checked_mul(other_price)?
518 .checked_add(other.conf.checked_mul(base_price)?)?;
519
520 Some(Price {
521 price: (midprice as i64)
522 .checked_mul(base_sign)?
523 .checked_mul(other_sign)?,
524 conf,
525 expo: midprice_expo,
526 publish_time: self.publish_time.min(other.publish_time),
527 })
528 }
529
530 /// Get a copy of this struct where the price and confidence
531 /// have been normalized to be between `MIN_PD_V_I64` and `MAX_PD_V_I64`.
532 pub fn normalize(&self) -> Option<Price> {
533 // signed division is very expensive in op count
534 let (mut p, s) = Price::to_unsigned(self.price);
535 let mut c = self.conf;
536 let mut e = self.expo;
537
538 while p > MAX_PD_V_U64 || c > MAX_PD_V_U64 {
539 p = p.checked_div(10)?;
540 c = c.checked_div(10)?;
541 e = e.checked_add(1)?;
542 }
543
544 Some(Price {
545 price: (p as i64).checked_mul(s)?,
546 conf: c,
547 expo: e,
548 publish_time: self.publish_time,
549 })
550 }
551
552 /// Scale this price/confidence so that its exponent is `target_expo`.
553 ///
554 /// Return `None` if this number is outside the range of numbers representable in `target_expo`,
555 /// which will happen if `target_expo` is too small.
556 ///
557 /// Warning: if `target_expo` is significantly larger than the current exponent, this
558 /// function will return 0 +- 0.
559 pub fn scale_to_exponent(&self, target_expo: i32) -> Option<Price> {
560 let mut delta = target_expo.checked_sub(self.expo)?;
561 if delta >= 0 {
562 let mut p = self.price;
563 let mut c = self.conf;
564 // 2nd term is a short-circuit to bound op consumption
565 while delta > 0 && (p != 0 || c != 0) {
566 p = p.checked_div(10)?;
567 c = c.checked_div(10)?;
568 delta = delta.checked_sub(1)?;
569 }
570
571 Some(Price {
572 price: p,
573 conf: c,
574 expo: target_expo,
575 publish_time: self.publish_time,
576 })
577 } else {
578 let mut p = self.price;
579 let mut c = self.conf;
580
581 // Either p or c == None will short-circuit to bound op consumption
582 while delta < 0 {
583 p = p.checked_mul(10)?;
584 c = c.checked_mul(10)?;
585 delta = delta.checked_add(1)?;
586 }
587
588 Some(Price {
589 price: p,
590 conf: c,
591 expo: target_expo,
592 publish_time: self.publish_time,
593 })
594 }
595 }
596
597 /// Helper function to convert signed integers to unsigned and a sign bit, which simplifies
598 /// some of the computations above.
599 fn to_unsigned(x: i64) -> (u64, i64) {
600 if x == i64::MIN {
601 // special case because i64::MIN == -i64::MIN
602 (i64::MAX as u64 + 1, -1)
603 } else if x < 0 {
604 (-x as u64, -1)
605 } else {
606 (x as u64, 1)
607 }
608 }
609
610 /// Helper function to create fraction
611 ///
612 /// fraction(x, y) gives you the unnormalized Price closest to x/y.
613 /// This output could have arbitrary exponent due to the div, so you may need to call
614 /// scale_to_exponent to scale to your desired expo. If you cannot represent x/y exactly
615 /// within 8 digits of precision, it may zero out the remainder. In particular, if x and/or
616 /// y cannot be represented within 8 digits of precision, potential for precision error.
617 /// If x and y can both be represented within 8 digits of precision AND x/y can be represented
618 /// within 8 digits, no precision loss.
619 ///
620 /// Error of normalizing x, y <= 10^(PD_EXPO+2) = 10^-7
621 /// Inherits any bounded errors from normalization and div
622 fn fraction(x: i64, y: i64) -> Option<Price> {
623 // convert x and y to Prices
624 let x_as_price = Price {
625 price: x,
626 conf: 0,
627 expo: 0,
628 publish_time: 0,
629 };
630 let y_as_price = Price {
631 price: y,
632 conf: 0,
633 expo: 0,
634 publish_time: 0,
635 };
636
637 // get the relevant fraction
638 let frac = x_as_price.div(&y_as_price)?;
639
640 return Some(frac);
641 }
642}
643
644#[cfg(test)]
645mod test {
646 use quickcheck::TestResult;
647 use quickcheck_macros::quickcheck;
648 use std::convert::TryFrom;
649
650 use crate::price::{
651 Price,
652 MAX_PD_V_U64,
653 PD_EXPO,
654 PD_SCALE,
655 };
656
657 const MAX_PD_V_I64: i64 = MAX_PD_V_U64 as i64;
658 const MIN_PD_V_I64: i64 = -MAX_PD_V_I64;
659
660 fn pc(price: i64, conf: u64, expo: i32) -> Price {
661 Price {
662 price,
663 conf,
664 expo,
665 publish_time: 0,
666 }
667 }
668
669 fn pc_scaled(price: i64, conf: u64, cur_expo: i32, expo: i32) -> Price {
670 Price {
671 price,
672 conf,
673 expo: cur_expo,
674 publish_time: 0,
675 }
676 .scale_to_exponent(expo)
677 .unwrap()
678 }
679
680
681 #[test]
682 fn test_normalize() {
683 fn succeeds(price1: Price, expected: Price) {
684 assert_eq!(price1.normalize().unwrap(), expected);
685 }
686
687 fn fails(price1: Price) {
688 assert_eq!(price1.normalize(), None);
689 }
690
691 succeeds(
692 pc(2 * (PD_SCALE as i64), 3 * PD_SCALE, 0),
693 pc(2 * (PD_SCALE as i64) / 100, 3 * PD_SCALE / 100, 2),
694 );
695
696 succeeds(
697 pc(-2 * (PD_SCALE as i64), 3 * PD_SCALE, 0),
698 pc(-2 * (PD_SCALE as i64) / 100, 3 * PD_SCALE / 100, 2),
699 );
700
701 // the i64 / u64 max values are a factor of 10^11 larger than MAX_PD_V
702 let expo = -(PD_EXPO - 2);
703 let scale_i64 = (PD_SCALE as i64) * 100;
704 let scale_u64 = scale_i64 as u64;
705 succeeds(pc(i64::MAX, 1, 0), pc(i64::MAX / scale_i64, 0, expo));
706 succeeds(pc(i64::MIN, 1, 0), pc(i64::MIN / scale_i64, 0, expo));
707 succeeds(pc(1, u64::MAX, 0), pc(0, u64::MAX / scale_u64, expo));
708
709 // exponent overflows
710 succeeds(
711 pc(i64::MAX, 1, i32::MAX - expo),
712 pc(i64::MAX / scale_i64, 0, i32::MAX),
713 );
714 fails(pc(i64::MAX, 1, i32::MAX - expo + 1));
715 succeeds(
716 pc(i64::MAX, 1, i32::MIN),
717 pc(i64::MAX / scale_i64, 0, i32::MIN + expo),
718 );
719
720 succeeds(
721 pc(1, u64::MAX, i32::MAX - expo),
722 pc(0, u64::MAX / scale_u64, i32::MAX),
723 );
724 fails(pc(1, u64::MAX, i32::MAX - expo + 1));
725
726 // Check timestamp won't change after normalize
727 let p = Price {
728 publish_time: 100,
729 ..Default::default()
730 };
731
732 assert_eq!(p.normalize().unwrap().publish_time, 100);
733 }
734
735 #[test]
736 fn test_scale_to_exponent() {
737 fn succeeds(price1: Price, target: i32, expected: Price) {
738 assert_eq!(price1.scale_to_exponent(target).unwrap(), expected);
739 }
740
741 fn fails(price1: Price, target: i32) {
742 assert_eq!(price1.scale_to_exponent(target), None);
743 }
744
745 succeeds(pc(1234, 1234, 0), 0, pc(1234, 1234, 0));
746 succeeds(pc(1234, 1234, 0), 1, pc(123, 123, 1));
747 succeeds(pc(1234, 1234, 0), 2, pc(12, 12, 2));
748 succeeds(pc(-1234, 1234, 0), 2, pc(-12, 12, 2));
749 succeeds(pc(1234, 1234, 0), 4, pc(0, 0, 4));
750 succeeds(pc(1234, 1234, 0), -1, pc(12340, 12340, -1));
751 succeeds(pc(1234, 1234, 0), -2, pc(123400, 123400, -2));
752 succeeds(pc(1234, 1234, 0), -8, pc(123400000000, 123400000000, -8));
753 // insufficient precision to represent the result in this exponent
754 fails(pc(1234, 1234, 0), -20);
755 fails(pc(1234, 0, 0), -20);
756 fails(pc(0, 1234, 0), -20);
757
758 // fails because exponent delta overflows
759 fails(pc(1, 1, i32::MIN), i32::MAX);
760
761 // Check timestamp won't change after scale to exponent
762 let p = Price {
763 publish_time: 100,
764 ..pc(1234, 1234, 0)
765 };
766
767 assert_eq!(p.scale_to_exponent(2).unwrap().publish_time, 100);
768 }
769
770 #[test]
771 fn test_div() {
772 fn succeeds(price1: Price, price2: Price, expected: Price) {
773 assert_eq!(price1.div(&price2).unwrap(), expected);
774 }
775
776 fn fails(price1: Price, price2: Price) {
777 let result = price1.div(&price2);
778 assert_eq!(result, None);
779 }
780
781 succeeds(pc(1, 1, 0), pc(1, 1, 0), pc_scaled(1, 2, 0, PD_EXPO));
782 succeeds(pc(1, 1, -8), pc(1, 1, -8), pc_scaled(1, 2, 0, PD_EXPO));
783 succeeds(pc(10, 1, 0), pc(1, 1, 0), pc_scaled(10, 11, 0, PD_EXPO));
784 succeeds(pc(1, 1, 1), pc(1, 1, 0), pc_scaled(10, 20, 0, PD_EXPO + 1));
785 succeeds(pc(1, 1, 0), pc(5, 1, 0), pc_scaled(20, 24, -2, PD_EXPO));
786
787 // Negative numbers
788 succeeds(pc(-1, 1, 0), pc(1, 1, 0), pc_scaled(-1, 2, 0, PD_EXPO));
789 succeeds(pc(1, 1, 0), pc(-1, 1, 0), pc_scaled(-1, 2, 0, PD_EXPO));
790 succeeds(pc(-1, 1, 0), pc(-1, 1, 0), pc_scaled(1, 2, 0, PD_EXPO));
791
792 // Different exponents in the two inputs
793 succeeds(
794 pc(100, 10, -8),
795 pc(2, 1, -7),
796 pc_scaled(500_000_000, 300_000_000, -8, PD_EXPO - 1),
797 );
798 succeeds(
799 pc(100, 10, -4),
800 pc(2, 1, 0),
801 pc_scaled(500_000, 300_000, -8, PD_EXPO + -4),
802 );
803
804 // Test with end range of possible inputs where the output should not lose precision.
805 succeeds(
806 pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
807 pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
808 pc_scaled(1, 2, 0, PD_EXPO),
809 );
810 succeeds(
811 pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
812 pc(1, 1, 0),
813 pc_scaled(MAX_PD_V_I64, 2 * MAX_PD_V_U64, 0, PD_EXPO),
814 );
815 succeeds(
816 pc(1, 1, 0),
817 pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
818 pc(
819 (PD_SCALE as i64) / MAX_PD_V_I64,
820 2 * (PD_SCALE / MAX_PD_V_U64),
821 PD_EXPO,
822 ),
823 );
824
825 succeeds(
826 pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
827 pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
828 pc_scaled(1, 2, 0, PD_EXPO),
829 );
830 succeeds(
831 pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
832 pc(1, 1, 0),
833 pc_scaled(MIN_PD_V_I64, 2 * MAX_PD_V_U64, 0, PD_EXPO),
834 );
835 succeeds(
836 pc(1, 1, 0),
837 pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
838 pc(
839 (PD_SCALE as i64) / MIN_PD_V_I64,
840 2 * (PD_SCALE / MAX_PD_V_U64),
841 PD_EXPO,
842 ),
843 );
844
845 succeeds(
846 pc(1, MAX_PD_V_U64, 0),
847 pc(1, MAX_PD_V_U64, 0),
848 pc_scaled(1, 2 * MAX_PD_V_U64, 0, PD_EXPO),
849 );
850 // This fails because the confidence interval is too large to be represented in PD_EXPO
851 fails(pc(MAX_PD_V_I64, MAX_PD_V_U64, 0), pc(1, MAX_PD_V_U64, 0));
852
853 // Unnormalized tests below here
854
855 // More realistic inputs (get BTC price in ETH)
856 let ten_e7: i64 = 10000000;
857 let uten_e7: u64 = 10000000;
858 succeeds(
859 pc(520010 * ten_e7, 310 * uten_e7, -8),
860 pc(38591 * ten_e7, 18 * uten_e7, -8),
861 pc(1347490347, 1431804, -8),
862 );
863
864 // Test with end range of possible inputs to identify overflow
865 // These inputs will lose precision due to the initial normalization.
866 // Get the rounded versions of these inputs in order to compute the expected results.
867 let normed = pc(i64::MAX, u64::MAX, 0).normalize().unwrap();
868
869 succeeds(
870 pc(i64::MAX, u64::MAX, 0),
871 pc(i64::MAX, u64::MAX, 0),
872 pc_scaled(1, 4, 0, PD_EXPO),
873 );
874 succeeds(
875 pc(i64::MAX, u64::MAX, 0),
876 pc(1, 1, 0),
877 pc_scaled(
878 normed.price,
879 3 * (normed.price as u64),
880 normed.expo,
881 normed.expo + PD_EXPO,
882 ),
883 );
884 succeeds(
885 pc(1, 1, 0),
886 pc(i64::MAX, u64::MAX, 0),
887 pc(
888 (PD_SCALE as i64) / normed.price,
889 3 * (PD_SCALE / (normed.price as u64)),
890 PD_EXPO - normed.expo,
891 ),
892 );
893
894 succeeds(
895 pc(i64::MAX, 1, 0),
896 pc(i64::MAX, 1, 0),
897 pc_scaled(1, 0, 0, PD_EXPO),
898 );
899 succeeds(
900 pc(i64::MAX, 1, 0),
901 pc(1, 1, 0),
902 pc_scaled(
903 normed.price,
904 normed.price as u64,
905 normed.expo,
906 normed.expo + PD_EXPO,
907 ),
908 );
909 succeeds(
910 pc(1, 1, 0),
911 pc(i64::MAX, 1, 0),
912 pc(
913 (PD_SCALE as i64) / normed.price,
914 PD_SCALE / (normed.price as u64),
915 PD_EXPO - normed.expo,
916 ),
917 );
918
919 let normed = pc(i64::MIN, u64::MAX, 0).normalize().unwrap();
920 let normed_c = (-normed.price) as u64;
921
922 succeeds(
923 pc(i64::MIN, u64::MAX, 0),
924 pc(i64::MIN, u64::MAX, 0),
925 pc_scaled(1, 4, 0, PD_EXPO),
926 );
927 succeeds(
928 pc(i64::MIN, u64::MAX, 0),
929 pc(i64::MAX, u64::MAX, 0),
930 pc_scaled(-1, 4, 0, PD_EXPO),
931 );
932 succeeds(
933 pc(i64::MIN, u64::MAX, 0),
934 pc(1, 1, 0),
935 pc_scaled(
936 normed.price,
937 3 * normed_c,
938 normed.expo,
939 normed.expo + PD_EXPO,
940 ),
941 );
942 succeeds(
943 pc(1, 1, 0),
944 pc(i64::MIN, u64::MAX, 0),
945 pc(
946 (PD_SCALE as i64) / normed.price,
947 3 * (PD_SCALE / normed_c),
948 PD_EXPO - normed.expo,
949 ),
950 );
951
952 succeeds(
953 pc(i64::MIN, 1, 0),
954 pc(i64::MIN, 1, 0),
955 pc_scaled(1, 0, 0, PD_EXPO),
956 );
957 succeeds(
958 pc(i64::MIN, 1, 0),
959 pc(1, 1, 0),
960 pc_scaled(normed.price, normed_c, normed.expo, normed.expo + PD_EXPO),
961 );
962 succeeds(
963 pc(1, 1, 0),
964 pc(i64::MIN, 1, 0),
965 pc(
966 (PD_SCALE as i64) / normed.price,
967 PD_SCALE / (normed_c),
968 PD_EXPO - normed.expo,
969 ),
970 );
971
972 // Price is zero pre-normalization
973 succeeds(pc(0, 1, 0), pc(1, 1, 0), pc_scaled(0, 1, 0, PD_EXPO));
974 succeeds(pc(0, 1, 0), pc(100, 1, 0), pc_scaled(0, 1, -2, PD_EXPO));
975 fails(pc(1, 1, 0), pc(0, 1, 0));
976
977 // Normalizing the input when the confidence is >> price produces a price of 0.
978 fails(pc(1, 1, 0), pc(1, u64::MAX, 0));
979 succeeds(
980 pc(1, u64::MAX, 0),
981 pc(1, 1, 0),
982 pc_scaled(0, normed.conf, normed.expo, normed.expo + PD_EXPO),
983 );
984
985 // Exponent under/overflow.
986 succeeds(
987 pc(1, 1, i32::MAX),
988 pc(1, 1, 0),
989 pc(PD_SCALE as i64, 2 * PD_SCALE, i32::MAX + PD_EXPO),
990 );
991 fails(pc(1, 1, i32::MAX), pc(1, 1, -1));
992
993 succeeds(
994 pc(1, 1, i32::MIN - PD_EXPO),
995 pc(1, 1, 0),
996 pc(PD_SCALE as i64, 2 * PD_SCALE, i32::MIN),
997 );
998 succeeds(
999 pc(1, 1, i32::MIN),
1000 pc(1, 1, PD_EXPO),
1001 pc(PD_SCALE as i64, 2 * PD_SCALE, i32::MIN),
1002 );
1003 fails(pc(1, 1, i32::MIN - PD_EXPO), pc(1, 1, 1));
1004
1005 // Check timestamp will be the minimum after div
1006 let p1 = Price {
1007 publish_time: 100,
1008 ..pc(1234, 1234, 0)
1009 };
1010
1011 let p2 = Price {
1012 publish_time: 200,
1013 ..pc(1234, 1234, 0)
1014 };
1015
1016 assert_eq!(p1.div(&p2).unwrap().publish_time, 100);
1017 assert_eq!(p2.div(&p1).unwrap().publish_time, 100);
1018 }
1019
1020 #[test]
1021 fn test_mul() {
1022 fn succeeds(price1: Price, price2: Price, expected: Price) {
1023 assert_eq!(price1.mul(&price2).unwrap(), expected);
1024 }
1025
1026 fn fails(price1: Price, price2: Price) {
1027 let result = price1.mul(&price2);
1028 assert_eq!(result, None);
1029 }
1030
1031 succeeds(pc(1, 1, 0), pc(1, 1, 0), pc(1, 2, 0));
1032 succeeds(pc(1, 1, -8), pc(1, 1, -8), pc(1, 2, -16));
1033 succeeds(pc(10, 1, 0), pc(1, 1, 0), pc(10, 11, 0));
1034 succeeds(pc(1, 1, 1), pc(1, 1, 0), pc(1, 2, 1));
1035 succeeds(pc(1, 1, 0), pc(5, 1, 0), pc(5, 6, 0));
1036
1037 // Different exponents in the two inputs
1038 succeeds(pc(100, 10, -8), pc(2, 1, -7), pc(200, 120, -15));
1039 succeeds(pc(100, 10, -4), pc(2, 1, 0), pc(200, 120, -4));
1040
1041 // Zero
1042 succeeds(pc(0, 10, -4), pc(2, 1, 0), pc(0, 20, -4));
1043 succeeds(pc(2, 1, 0), pc(0, 10, -4), pc(0, 20, -4));
1044
1045 // Test with end range of possible inputs where the output should not lose precision.
1046 succeeds(
1047 pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
1048 pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
1049 pc(
1050 MAX_PD_V_I64 * MAX_PD_V_I64,
1051 2 * MAX_PD_V_U64 * MAX_PD_V_U64,
1052 0,
1053 ),
1054 );
1055 succeeds(
1056 pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
1057 pc(1, 1, 0),
1058 pc(MAX_PD_V_I64, 2 * MAX_PD_V_U64, 0),
1059 );
1060 succeeds(
1061 pc(1, MAX_PD_V_U64, 0),
1062 pc(3, 1, 0),
1063 pc(3, 1 + 3 * MAX_PD_V_U64, 0),
1064 );
1065
1066 succeeds(
1067 pc(1, MAX_PD_V_U64, 0),
1068 pc(1, MAX_PD_V_U64, 0),
1069 pc(1, 2 * MAX_PD_V_U64, 0),
1070 );
1071 succeeds(
1072 pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
1073 pc(1, MAX_PD_V_U64, 0),
1074 pc(MAX_PD_V_I64, MAX_PD_V_U64 + MAX_PD_V_U64 * MAX_PD_V_U64, 0),
1075 );
1076
1077 succeeds(
1078 pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
1079 pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
1080 pc(
1081 MIN_PD_V_I64 * MIN_PD_V_I64,
1082 2 * MAX_PD_V_U64 * MAX_PD_V_U64,
1083 0,
1084 ),
1085 );
1086 succeeds(
1087 pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
1088 pc(MAX_PD_V_I64, MAX_PD_V_U64, 0),
1089 pc(
1090 MIN_PD_V_I64 * MAX_PD_V_I64,
1091 2 * MAX_PD_V_U64 * MAX_PD_V_U64,
1092 0,
1093 ),
1094 );
1095 succeeds(
1096 pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
1097 pc(1, 1, 0),
1098 pc(MIN_PD_V_I64, 2 * MAX_PD_V_U64, 0),
1099 );
1100 succeeds(
1101 pc(MIN_PD_V_I64, MAX_PD_V_U64, 0),
1102 pc(1, MAX_PD_V_U64, 0),
1103 pc(MIN_PD_V_I64, MAX_PD_V_U64 + MAX_PD_V_U64 * MAX_PD_V_U64, 0),
1104 );
1105
1106 // Unnormalized tests below here
1107 let ten_e7: i64 = 10000000;
1108 let uten_e7: u64 = 10000000;
1109 succeeds(
1110 pc(3 * (PD_SCALE as i64), 3 * PD_SCALE, PD_EXPO),
1111 pc(2 * (PD_SCALE as i64), 4 * PD_SCALE, PD_EXPO),
1112 pc(6 * ten_e7 * ten_e7, 18 * uten_e7 * uten_e7, -14),
1113 );
1114
1115 // Test with end range of possible inputs to identify overflow
1116 // These inputs will lose precision due to the initial normalization.
1117 // Get the rounded versions of these inputs in order to compute the expected results.
1118 let normed = pc(i64::MAX, u64::MAX, 0).normalize().unwrap();
1119
1120 succeeds(
1121 pc(i64::MAX, u64::MAX, 0),
1122 pc(i64::MAX, u64::MAX, 0),
1123 pc(
1124 normed.price * normed.price,
1125 4 * ((normed.price * normed.price) as u64),
1126 normed.expo * 2,
1127 ),
1128 );
1129 succeeds(
1130 pc(i64::MAX, u64::MAX, 0),
1131 pc(1, 1, 0),
1132 pc(normed.price, 3 * (normed.price as u64), normed.expo),
1133 );
1134
1135 succeeds(
1136 pc(i64::MAX, 1, 0),
1137 pc(i64::MAX, 1, 0),
1138 pc(normed.price * normed.price, 0, normed.expo * 2),
1139 );
1140 succeeds(
1141 pc(i64::MAX, 1, 0),
1142 pc(1, 1, 0),
1143 pc(normed.price, normed.price as u64, normed.expo),
1144 );
1145
1146 let normed = pc(i64::MIN, u64::MAX, 0).normalize().unwrap();
1147 let normed_c = (-normed.price) as u64;
1148
1149 succeeds(
1150 pc(i64::MIN, u64::MAX, 0),
1151 pc(i64::MIN, u64::MAX, 0),
1152 pc(
1153 normed.price * normed.price,
1154 4 * (normed_c * normed_c),
1155 normed.expo * 2,
1156 ),
1157 );
1158 succeeds(
1159 pc(i64::MIN, u64::MAX, 0),
1160 pc(1, 1, 0),
1161 pc(normed.price, 3 * normed_c, normed.expo),
1162 );
1163
1164 succeeds(
1165 pc(i64::MIN, 1, 0),
1166 pc(i64::MIN, 1, 0),
1167 pc(normed.price * normed.price, 0, normed.expo * 2),
1168 );
1169 succeeds(
1170 pc(i64::MIN, 1, 0),
1171 pc(1, 1, 0),
1172 pc(normed.price, normed_c, normed.expo),
1173 );
1174
1175 // Exponent under/overflow.
1176 succeeds(pc(1, 1, i32::MAX), pc(1, 1, 0), pc(1, 2, i32::MAX));
1177 succeeds(pc(1, 1, i32::MAX), pc(1, 1, -1), pc(1, 2, i32::MAX - 1));
1178 fails(pc(1, 1, i32::MAX), pc(1, 1, 1));
1179
1180 succeeds(pc(1, 1, i32::MIN), pc(1, 1, 0), pc(1, 2, i32::MIN));
1181 succeeds(pc(1, 1, i32::MIN), pc(1, 1, 1), pc(1, 2, i32::MIN + 1));
1182 fails(pc(1, 1, i32::MIN), pc(1, 1, -1));
1183
1184 // Check timestamp will be the minimum after mul
1185 let p1 = Price {
1186 publish_time: 100,
1187 ..pc(1234, 1234, 0)
1188 };
1189
1190 let p2 = Price {
1191 publish_time: 200,
1192 ..pc(1234, 1234, 0)
1193 };
1194
1195 assert_eq!(p1.mul(&p2).unwrap().publish_time, 100);
1196 assert_eq!(p2.mul(&p1).unwrap().publish_time, 100);
1197 }
1198
1199 #[test]
1200 fn test_get_collateral_valuation_price() {
1201 fn succeeds(
1202 price: Price,
1203 deposits: u64,
1204 deposits_endpoint: u64,
1205 discount_initial: u64,
1206 discount_final: u64,
1207 discount_exponent: i32,
1208 expected: Price,
1209 ) {
1210 let price_collat = price
1211 .get_collateral_valuation_price(
1212 deposits,
1213 deposits_endpoint,
1214 discount_initial,
1215 discount_final,
1216 discount_exponent,
1217 )
1218 .unwrap();
1219
1220 assert_eq!(price_collat, expected);
1221 }
1222
1223 fn fails(
1224 price: Price,
1225 deposits: u64,
1226 deposits_endpoint: u64,
1227 discount_initial: u64,
1228 discount_final: u64,
1229 discount_exponent: i32,
1230 ) {
1231 let result = price.get_collateral_valuation_price(
1232 deposits,
1233 deposits_endpoint,
1234 discount_initial,
1235 discount_final,
1236 discount_exponent,
1237 );
1238 assert_eq!(result, None);
1239 }
1240
1241 // 0 deposits
1242 succeeds(
1243 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1244 0,
1245 100,
1246 100,
1247 90,
1248 -2,
1249 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1250 );
1251
1252 // half deposits
1253 succeeds(
1254 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1255 50,
1256 100,
1257 100,
1258 90,
1259 -2,
1260 pc(95 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1261 );
1262
1263 // full deposits
1264 succeeds(
1265 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1266 100,
1267 100,
1268 100,
1269 90,
1270 -2,
1271 pc(90 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1272 );
1273
1274 // 0 deposits, diff precision
1275 succeeds(
1276 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1277 0,
1278 100,
1279 1000,
1280 900,
1281 -3,
1282 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1283 );
1284
1285 // half deposits, diff precision
1286 succeeds(
1287 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1288 50,
1289 100,
1290 1000,
1291 900,
1292 -3,
1293 pc(95 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1294 );
1295
1296 // full deposits, diff precision
1297 succeeds(
1298 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1299 100,
1300 100,
1301 1000,
1302 900,
1303 -3,
1304 pc(90 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1305 );
1306
1307 // beyond final endpoint deposits
1308 succeeds(
1309 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1310 150,
1311 100,
1312 100,
1313 90,
1314 -2,
1315 pc(85 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1316 );
1317
1318 // 0 deposits, staggered initial discount
1319 succeeds(
1320 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1321 0,
1322 100,
1323 98,
1324 90,
1325 -2,
1326 pc(98 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1327 );
1328
1329 // half deposits, staggered initial discount
1330 succeeds(
1331 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1332 50,
1333 100,
1334 98,
1335 90,
1336 -2,
1337 pc(94 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1338 );
1339
1340 // full deposits, staggered initial discount
1341 succeeds(
1342 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1343 100,
1344 100,
1345 98,
1346 90,
1347 -2,
1348 pc(90 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1349 );
1350
1351 // test precision limits
1352 succeeds(
1353 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1354 0,
1355 1_000_000_000_000_000_000,
1356 100,
1357 90,
1358 -2,
1359 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1360 );
1361 succeeds(
1362 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1363 1,
1364 1_000_000_000_000_000_000,
1365 100,
1366 90,
1367 -2,
1368 pc(100 * (PD_SCALE as i64) - 1000, 2 * PD_SCALE, -9),
1369 );
1370 succeeds(
1371 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1372 100_000_000,
1373 1_000_000_000_000_000_000,
1374 100,
1375 90,
1376 -2,
1377 pc(100 * (PD_SCALE as i64) - 1000, 2 * PD_SCALE, -9),
1378 );
1379 succeeds(
1380 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1381 1_000_000_000,
1382 1_000_000_000_000_000_000,
1383 100,
1384 90,
1385 -2,
1386 pc(100 * (PD_SCALE as i64) - 1000, 2 * PD_SCALE, -9),
1387 );
1388 succeeds(
1389 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1390 10_000_000_000,
1391 1_000_000_000_000_000_000,
1392 100,
1393 90,
1394 -2,
1395 pc(100 * (PD_SCALE as i64) - 1000, 2 * PD_SCALE, -9),
1396 );
1397 succeeds(
1398 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1399 100_000_000_000,
1400 1_000_000_000_000_000_000,
1401 100,
1402 90,
1403 -2,
1404 pc(100 * (PD_SCALE as i64) - 1000, 2 * PD_SCALE, -9),
1405 );
1406 succeeds(
1407 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1408 200_000_000_000,
1409 1_000_000_000_000_000_000,
1410 100,
1411 90,
1412 -2,
1413 pc(100 * (PD_SCALE as i64) - 2000, 2 * PD_SCALE, -9),
1414 );
1415 succeeds(
1416 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1417 1_000_000_000_000,
1418 1_000_000_000_000_000_000,
1419 100,
1420 90,
1421 -2,
1422 pc(100 * (PD_SCALE as i64) - 10000, 2 * PD_SCALE, -9),
1423 );
1424
1425 // fails bc initial discount lower than final discount
1426 fails(
1427 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1428 50,
1429 100,
1430 89,
1431 90,
1432 -2,
1433 );
1434 }
1435
1436 #[test]
1437 fn test_get_borrow_valuation_price() {
1438 fn succeeds(
1439 price: Price,
1440 borrows: u64,
1441 borrows_endpoint: u64,
1442 premium_initial: u64,
1443 premium_final: u64,
1444 premium_exponent: i32,
1445 expected: Price,
1446 ) {
1447 let price_borrow = price
1448 .get_borrow_valuation_price(
1449 borrows,
1450 borrows_endpoint,
1451 premium_initial,
1452 premium_final,
1453 premium_exponent,
1454 )
1455 .unwrap();
1456
1457 assert_eq!(price_borrow, expected);
1458 }
1459
1460 fn fails(
1461 price: Price,
1462 borrows: u64,
1463 borrows_endpoint: u64,
1464 premium_initial: u64,
1465 premium_final: u64,
1466 premium_exponent: i32,
1467 ) {
1468 let result = price.get_borrow_valuation_price(
1469 borrows,
1470 borrows_endpoint,
1471 premium_initial,
1472 premium_final,
1473 premium_exponent,
1474 );
1475 assert_eq!(result, None);
1476 }
1477
1478 // 0 borrows
1479 succeeds(
1480 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1481 0,
1482 100,
1483 100,
1484 110,
1485 -2,
1486 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1487 );
1488
1489 // half borrows
1490 succeeds(
1491 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1492 50,
1493 100,
1494 100,
1495 110,
1496 -2,
1497 pc(105 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1498 );
1499
1500 // full borrows
1501 succeeds(
1502 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1503 100,
1504 100,
1505 100,
1506 110,
1507 -2,
1508 pc(110 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1509 );
1510
1511 // 0 borrows, diff precision
1512 succeeds(
1513 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1514 0,
1515 100,
1516 1000,
1517 1100,
1518 -3,
1519 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1520 );
1521
1522 // half borrows, diff precision
1523 succeeds(
1524 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1525 50,
1526 100,
1527 1000,
1528 1100,
1529 -3,
1530 pc(105 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1531 );
1532
1533 // full borrows, diff precision
1534 succeeds(
1535 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1536 100,
1537 100,
1538 1000,
1539 1100,
1540 -3,
1541 pc(110 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1542 );
1543
1544 // beyond final endpoint borrows
1545 succeeds(
1546 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1547 150,
1548 100,
1549 100,
1550 110,
1551 -2,
1552 pc(115 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1553 );
1554
1555 // 0 borrows, staggered initial premium
1556 succeeds(
1557 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1558 0,
1559 100,
1560 102,
1561 110,
1562 -2,
1563 pc(102 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1564 );
1565
1566 // half borrows, staggered initial premium
1567 succeeds(
1568 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1569 50,
1570 100,
1571 102,
1572 110,
1573 -2,
1574 pc(106 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1575 );
1576
1577 // full borrows, staggered initial premium
1578 succeeds(
1579 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1580 100,
1581 100,
1582 102,
1583 110,
1584 -2,
1585 pc(110 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1586 );
1587
1588 // test precision limits
1589 succeeds(
1590 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1591 1,
1592 1_000_000_000_000_000_000,
1593 100,
1594 110,
1595 -2,
1596 pc(100 * (PD_SCALE as i64 - 10), 2 * PD_SCALE, -9),
1597 );
1598 succeeds(
1599 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1600 100_000_000,
1601 1_000_000_000_000_000_000,
1602 100,
1603 110,
1604 -2,
1605 pc(100 * (PD_SCALE as i64 - 10), 2 * PD_SCALE, -9),
1606 );
1607 succeeds(
1608 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1609 1_000_000_000,
1610 1_000_000_000_000_000_000,
1611 100,
1612 110,
1613 -2,
1614 pc(100 * (PD_SCALE as i64 - 10), 2 * PD_SCALE, -9),
1615 );
1616 // interpolation now doesn't lose precision, but normalize in final multiply loses precision
1617 succeeds(
1618 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1619 10_000_000_000,
1620 1_000_000_000_000_000_000,
1621 100,
1622 110,
1623 -2,
1624 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1625 );
1626 succeeds(
1627 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1628 20_000_000_000,
1629 1_000_000_000_000_000_000,
1630 100,
1631 110,
1632 -2,
1633 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1634 );
1635 // precision no longer lost
1636 succeeds(
1637 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1638 100_000_000_000,
1639 1_000_000_000_000_000_000,
1640 100,
1641 110,
1642 -2,
1643 pc(100 * (PD_SCALE as i64 + 10), 2 * PD_SCALE, -9),
1644 );
1645 succeeds(
1646 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1647 200_000_000_000,
1648 1_000_000_000_000_000_000,
1649 100,
1650 110,
1651 -2,
1652 pc(100 * (PD_SCALE as i64 + 20), 2 * PD_SCALE, -9),
1653 );
1654 succeeds(
1655 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1656 1_000_000_000_000,
1657 1_000_000_000_000_000_000,
1658 100,
1659 110,
1660 -2,
1661 pc(100 * (PD_SCALE as i64 + 100), 2 * PD_SCALE, -9),
1662 );
1663
1664 // fails bc initial premium exceeds final premium
1665 fails(
1666 pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, -9),
1667 50,
1668 100,
1669 111,
1670 110,
1671 -2,
1672 );
1673 }
1674
1675 #[test]
1676 fn test_affine_combination() {
1677 fn succeeds(
1678 x1: i64,
1679 y1: Price,
1680 x2: i64,
1681 y2: Price,
1682 x_query: i64,
1683 pre_add_expo: i32,
1684 expected: Price,
1685 ) {
1686 let y_query = Price::affine_combination(x1, y1, x2, y2, x_query, pre_add_expo).unwrap();
1687
1688 assert_eq!(y_query, expected);
1689 }
1690
1691 fn fails(x1: i64, y1: Price, x2: i64, y2: Price, x_query: i64, pre_add_expo: i32) {
1692 let result = Price::affine_combination(x1, y1, x2, y2, x_query, pre_add_expo);
1693 assert_eq!(result, None);
1694 }
1695
1696 // constant, in the bounds [x1, x2]
1697 succeeds(
1698 0,
1699 pc(100, 10, -4),
1700 10,
1701 pc(100, 10, -4),
1702 5,
1703 -9,
1704 pc(10_000_000, 1_000_000, -9),
1705 );
1706
1707 // constant, outside the bounds
1708 succeeds(
1709 0,
1710 pc(100, 10, -4),
1711 10,
1712 pc(100, 10, -4),
1713 15,
1714 -9,
1715 pc(10_000_000, 2_000_000, -9),
1716 );
1717
1718 // increasing, in the bounds
1719 succeeds(
1720 0,
1721 pc(90, 9, -4),
1722 10,
1723 pc(100, 10, -4),
1724 5,
1725 -9,
1726 pc(9_500_000, 950_000, -9),
1727 );
1728
1729 // increasing, out of bounds
1730 succeeds(
1731 0,
1732 pc(90, 9, -4),
1733 10,
1734 pc(100, 10, -4),
1735 15,
1736 -9,
1737 pc(10_500_000, 1_950_000, -9),
1738 );
1739
1740 // decreasing, in the bounds
1741 succeeds(
1742 0,
1743 pc(100, 10, -4),
1744 10,
1745 pc(80, 8, -4),
1746 5,
1747 -9,
1748 pc(9_000_000, 900_000, -9),
1749 );
1750
1751 // decreasing, out of bounds
1752 succeeds(
1753 0,
1754 pc(100, 10, -4),
1755 10,
1756 pc(80, 8, -4),
1757 15,
1758 -9,
1759 pc(7_000_000, 1_700_000, -9),
1760 );
1761
1762 // test with different pre_add_expos than -9
1763 succeeds(
1764 0,
1765 pc(100, 10, -2),
1766 100,
1767 pc(8000, 800, -4),
1768 50,
1769 -3,
1770 pc(900, 90, -3),
1771 );
1772 succeeds(
1773 100_000,
1774 pc(200_000, 20_000, -6),
1775 200_000,
1776 pc(-20_000_000_000, 2_000_000_000, -11),
1777 175_000,
1778 -4,
1779 pc(-1_000, 200, -4),
1780 );
1781 succeeds(
1782 2000,
1783 pc(75, 7, 3),
1784 10000,
1785 pc(675_000_000, 67_500_000, -3),
1786 6000,
1787 -2,
1788 pc(37_500_000, 3_725_000, -2),
1789 );
1790 succeeds(
1791 0,
1792 pc(100, 10, 2),
1793 100,
1794 pc(0, 0, -12),
1795 200,
1796 -12,
1797 pc(-10_000_000_000_000_000, 1_000_000_000_000_000, -12),
1798 );
1799 succeeds(
1800 0,
1801 pc(10, 1, 9),
1802 1000,
1803 pc(2, 0, 10),
1804 6000,
1805 6,
1806 pc(70_000, 5_000, 6),
1807 );
1808
1809 // test loss due to scaling
1810 // lose more bc scaling to higher expo
1811 succeeds(
1812 0,
1813 pc(0, 0, -2),
1814 13,
1815 pc(10, 1, -2),
1816 1,
1817 -8,
1818 pc(769230, 76923, -8),
1819 );
1820 // lose less bc scaling to lower expo
1821 succeeds(
1822 0,
1823 pc(0, 0, -2),
1824 13,
1825 pc(10, 1, -2),
1826 1,
1827 -9,
1828 pc(7692307, 769230, -9),
1829 );
1830 // lose more bc need to increment expo more in scaling from original inputs
1831 succeeds(
1832 0,
1833 pc(0, 0, -3),
1834 13,
1835 pc(100, 10, -3),
1836 1,
1837 -9,
1838 pc(7692307, 769230, -9),
1839 );
1840 // lose less bc need to increment expo less in scaling from original inputs
1841 succeeds(
1842 0,
1843 pc(0, 0, -2),
1844 13,
1845 pc(100, 10, -2),
1846 1,
1847 -9,
1848 pc(76923076, 7692307, -9),
1849 );
1850
1851 // Test with end range of possible inputs on endpoint xs
1852 succeeds(
1853 0,
1854 pc(100, 10, -9),
1855 i64::MAX,
1856 pc(0, 0, -9),
1857 i64::MAX / 10,
1858 -9,
1859 pc(90, 9, -9),
1860 );
1861 succeeds(
1862 i64::MIN,
1863 pc(100, 10, -9),
1864 i64::MIN / 2,
1865 pc(0, 0, -9),
1866 (i64::MIN / 4) * 3,
1867 -9,
1868 pc(50, 5, -9),
1869 );
1870 // test with xs that yield fractions with significantly different expos
1871 succeeds(
1872 0,
1873 pc(100_000_000, 10_000_000, -9),
1874 1_000_000_000_000_000,
1875 pc(0, 0, -9),
1876 10_000_000,
1877 -9,
1878 pc(99_999_999, 9_999_999, -9),
1879 );
1880
1881 // Test with end range of possible inputs in prices to identify precision inaccuracy
1882 // precision inaccuracy due to loss in scaling
1883 succeeds(
1884 0,
1885 pc(MAX_PD_V_I64 - 10, 1000, -9),
1886 10,
1887 pc(MAX_PD_V_I64, 997, -9),
1888 5,
1889 -9,
1890 pc(MAX_PD_V_I64 - 6, 998, -9),
1891 );
1892 // precision inaccruacy due to loss in scaling
1893 succeeds(
1894 0,
1895 pc(MAX_PD_V_I64 - 1, 200, -9),
1896 10,
1897 pc(MAX_PD_V_I64, 191, -9),
1898 9,
1899 -9,
1900 pc(MAX_PD_V_I64 - 1, 191, -9),
1901 );
1902 // // test with max u64 in conf
1903 // // normalization to first price causes loss of price; loss in conf precision, only
1904 // preserve 8 digits of precision
1905 succeeds(
1906 0,
1907 pc(1000, u64::MAX, -9),
1908 1000,
1909 pc(-1000, 0, -9),
1910 500,
1911 -9,
1912 pc(-500, 92_23_372_000_000_000_000, -9),
1913 );
1914 // test with MAX_PD_V_U64 in conf--no loss in precision unlike above
1915 succeeds(
1916 0,
1917 pc(1000, MAX_PD_V_U64, -9),
1918 1000,
1919 pc(-1000, 0, -9),
1920 500,
1921 -9,
1922 pc(0, MAX_PD_V_U64 / 2, -9),
1923 );
1924
1925
1926 // Test with combinations of (in)exact fractions + (un)normalized ys; making pre_add_expo
1927 // very small to abstract away scaling error exact fraction, normalized ys --> exact
1928 // result
1929 succeeds(
1930 0,
1931 pc(0, 0, -9),
1932 512,
1933 pc(MAX_PD_V_I64 - 511, 512, -9),
1934 1,
1935 -18,
1936 pc(524_287_000_000_000, 1_000_000_000, -18),
1937 );
1938 // exact fraction, unnormalized ys, should be 524_289_000_000_000 exactly, but due to
1939 // normalization lose <= 2*10^(PD_EXPO+2) we see the actual result is off by <
1940 // 16_000_000, which corresponds to loss of ~= 1.6*10^-8 < 2*10^-7 as can be seen,
1941 // the normalization also messes with the final confidence precision
1942 succeeds(
1943 0,
1944 pc(0, 0, -9),
1945 512,
1946 pc(MAX_PD_V_I64 + 513, 512, -9),
1947 1,
1948 -18,
1949 pc(524_288_984_375_000, 996_093_750, -18),
1950 );
1951 // inexact fraciton, normalized ys, should be 262_143_000_000_000 exactly, but due to
1952 // fraction imprecision lose <= 2*10^(PD_EXPO+2) 1/1024 = 0.0009765625, but due to
1953 // imprecision --> 0.00976562; similar for 1023/1024 we see the actual result is off
1954 // by < 140_000_000, which corresponds to loss of 1.4*10^-7 < 2*10^-7
1955 // inexact fraction also messes with the final confidence precision
1956 succeeds(
1957 0,
1958 pc(0, 0, -9),
1959 1024,
1960 pc(MAX_PD_V_I64 - 1023, 1024, -9),
1961 1,
1962 -18,
1963 pc(262_142_865_782_784, 999_999_488, -18),
1964 );
1965 // inexact fraction, unnormalized ys, should be 262_145_000_000_000 exactly, but due to
1966 // normalization and fraction imprecision lose <= 4*10^(PD_EXPO+2) 1/1024 and
1967 // 1023/1024 precision losses described above + normalization of y2 actual result
1968 // off by < 140_000_000, which corresponds to loss of 1.4*10^-7 < 2*10^-7
1969 succeeds(
1970 0,
1971 pc(0, 0, -9),
1972 1024,
1973 pc(MAX_PD_V_I64 + 1025, 1024, -9),
1974 1,
1975 -18,
1976 pc(262_144_865_781_760, 996_093_240, -18),
1977 );
1978 // should be -267_912_190_000_000_000 exactly, but due to normalization and fraction
1979 // imprecision lose <= 4^10^(PD_EXPO+2) actual result off by < 2_000_000_000, which
1980 // corresponds to loss of 2*10^-7 < 4*10^-7 (counting figures from the start of the number)
1981 succeeds(
1982 0,
1983 pc(MIN_PD_V_I64 - 1025, 0, -9),
1984 1024,
1985 pc(MAX_PD_V_I64 + 1025, 0, -9),
1986 1,
1987 -18,
1988 pc(-267_912_188_120_944_640, 0, -18),
1989 );
1990
1991
1992 // test w confidence (same at both endpoints)--expect linear change btwn x1 and x2 and
1993 // growth in conf as distance from interval [x1, x2] increases
1994 succeeds(
1995 0,
1996 pc(90, 10, -4),
1997 10,
1998 pc(100, 10, -4),
1999 5,
2000 -9,
2001 pc(9_500_000, 1_000_000, -9),
2002 );
2003
2004 // test w confidence (different at the endpoints)
2005 succeeds(
2006 0,
2007 pc(90, 10, -4),
2008 10,
2009 pc(100, 15, -4),
2010 5,
2011 -9,
2012 pc(9_500_000, 1_250_000, -9),
2013 );
2014 succeeds(
2015 0,
2016 pc(90, 10, -4),
2017 10,
2018 pc(100, 15, -4),
2019 8,
2020 -9,
2021 pc(9_800_000, 1_400_000, -9),
2022 );
2023 succeeds(
2024 0,
2025 pc(90, 10, -4),
2026 10,
2027 pc(100, 15, -4),
2028 15,
2029 -9,
2030 pc(10_500_000, 2_750_000, -9),
2031 );
2032
2033 // fails bc x1 > x2
2034 fails(20, pc(100, 10, -4), 10, pc(100, 20, -4), 15, -9);
2035 // fails bc x1 is MIN, x2-x1 --> overflow in delta
2036 fails(i64::MIN, pc(100, 20, -5), 10, pc(1000, 40, -5), 5, -9);
2037 // fails bc x2 is MAX, x1 is negative --> overflow in delta
2038 fails(-5, pc(100, 40, -4), i64::MAX, pc(1000, 10, -4), 5, -9);
2039 // fails bc of overflow in the checked_sub for x2-x1
2040 fails(
2041 i64::MIN / 2,
2042 pc(100, 20, -4),
2043 i64::MAX / 2 + 1,
2044 pc(100, 30, -4),
2045 5,
2046 -9,
2047 );
2048 // fails bc output price too small to be realized, cannot be scaled to fit with specified
2049 // pre_add_expo
2050 fails(0, pc(100, 0, -4), 10, pc(5, 50, -4), i64::MAX - 100, -9);
2051 // fails bc 0-i64::MIN > i64::MAX, so overflow
2052 fails(i64::MIN, pc(100, 10, -9), 0, pc(0, 12, -9), 0, -9);
2053 }
2054
2055 pub fn construct_quickcheck_affine_combination_price(price: i64) -> Price {
2056 return Price {
2057 price: price,
2058 conf: 0,
2059 expo: -9,
2060 publish_time: 0,
2061 };
2062 }
2063
2064 // quickcheck to confirm affine_combination introduces no error if normalization done
2065 // explicitly on prices first this quickcheck calls affine_combination with two sets of
2066 // almost identical inputs: the first set has potentially unnormalized prices, the second
2067 // set simply has the normalized versions of those prices this set of checks should pass
2068 // because normalization is automatically performed on the prices before they are
2069 // multiplied this set of checks passing indicates that it doesn't matter whether the
2070 // prices passed in are normalized
2071 #[quickcheck]
2072 fn quickcheck_affine_combination_normalize_prices(
2073 x1_inp: i32,
2074 p1: i32,
2075 x2_inp: i32,
2076 p2: i32,
2077 x_query_inp: i32,
2078 ) -> TestResult {
2079 // generating xs and prices from i32 to limit the range to reasonable values and guard
2080 // against overflow/bespoke constraint setting for quickcheck
2081 let y1 = construct_quickcheck_affine_combination_price(i64::try_from(p1).ok().unwrap());
2082 let y2 = construct_quickcheck_affine_combination_price(i64::try_from(p2).ok().unwrap());
2083
2084 let x1 = i64::try_from(x1_inp).ok().unwrap();
2085 let x2 = i64::try_from(x2_inp).ok().unwrap();
2086 let x_query = i64::try_from(x_query_inp).ok().unwrap();
2087
2088 // stick with single expo for ease of testing and generation
2089 let pre_add_expo = -9;
2090
2091 // require x2 > x1, as needed for affine_combination
2092 if x1 >= x2 {
2093 return TestResult::discard();
2094 }
2095
2096 // original result
2097 let result_orig = Price::affine_combination(x1, y1, x2, y2, x_query, pre_add_expo).unwrap();
2098
2099 let y1_norm = y1.normalize().unwrap();
2100 let y2_norm = y2.normalize().unwrap();
2101
2102 // result with normalized price inputs
2103 let result_norm =
2104 Price::affine_combination(x1, y1_norm, x2, y2_norm, x_query, pre_add_expo).unwrap();
2105
2106 // results should match exactly
2107 TestResult::from_bool(result_norm == result_orig)
2108 }
2109
2110 // quickcheck to confirm affine_combination introduces bounded error if close fraction x/y
2111 // passed in first this quickcheck calls affine_combination with two sets of similar inputs:
2112 // the first set has xs generated by the quickcheck generation process, leading to
2113 // potentially inexact fractions that don't fit within 8 digits of precision the second
2114 // set "normalizes" down to 8 digits of precision by setting x1 to 0, x2 to 100_000_000,
2115 // and xquery proportionally based on the bounds described in the docstring of
2116 // affine_combination, we expect error due to this to be leq 4*10^-7
2117 #[quickcheck]
2118 fn quickcheck_affine_combination_normalize_fractions(
2119 x1_inp: i32,
2120 p1: i32,
2121 x2_inp: i32,
2122 p2: i32,
2123 x_query_inp: i32,
2124 ) -> TestResult {
2125 // generating xs and prices from i32 to limit the range to reasonable values and guard
2126 // against overflow/bespoke constraint setting for quickcheck
2127 let y1 = construct_quickcheck_affine_combination_price(i64::try_from(p1).ok().unwrap());
2128 let y2 = construct_quickcheck_affine_combination_price(i64::try_from(p2).ok().unwrap());
2129
2130 let x1 = i64::try_from(x1_inp).ok().unwrap();
2131 let x2 = i64::try_from(x2_inp).ok().unwrap();
2132 let x_query = i64::try_from(x_query_inp).ok().unwrap();
2133
2134 // stick with single expo for ease of testing and generation
2135 let pre_add_expo = -9;
2136
2137 // require x2 > x1, as needed for affine_combination
2138 if x1 >= x2 {
2139 return TestResult::discard();
2140 }
2141
2142 // constrain x_query to be within 5 interval lengths of x1 or x2
2143 if (x_query > x2 + 5 * (x2 - x1)) || (x_query < x1 - 5 * (x2 - x1)) {
2144 return TestResult::discard();
2145 }
2146
2147 // generate new xs based on scaling x_1 --> 0, x_2 --> 10^8
2148 let x1_new: i64;
2149 let xq_new: i64;
2150 let x2_new: i64;
2151
2152 if x2 == 0 {
2153 x1_new = x1;
2154 xq_new = x_query;
2155 x2_new = x2;
2156 } else {
2157 let mut frac_q2 = Price::fraction(x_query - x1, x2 - x1).unwrap();
2158 frac_q2 = frac_q2.scale_to_exponent(-8).unwrap();
2159
2160 x1_new = 0;
2161 xq_new = frac_q2.price;
2162 x2_new = 100_000_000 as i64;
2163 }
2164
2165 // original result
2166 let result_orig = Price::affine_combination(x1, y1, x2, y2, x_query, pre_add_expo)
2167 .unwrap()
2168 .scale_to_exponent(-7)
2169 .unwrap();
2170
2171 // xs "normalized" result
2172 let result_norm = Price::affine_combination(x1_new, y1, x2_new, y2, xq_new, pre_add_expo)
2173 .unwrap()
2174 .scale_to_exponent(-7)
2175 .unwrap();
2176
2177 // compute difference in prices
2178 let price_diff = result_norm.add(&result_orig.cmul(-1, 0).unwrap()).unwrap();
2179
2180 // results should differ by less than 4*10^-7
2181 TestResult::from_bool((price_diff.price < 4) && (price_diff.price > -4))
2182 }
2183
2184 #[test]
2185 fn test_fraction() {
2186 fn succeeds(x: i64, y: i64, expected: Price) {
2187 let frac = Price::fraction(x, y).unwrap();
2188
2189 assert_eq!(frac, expected);
2190 }
2191
2192 fn fails(x: i64, y: i64) {
2193 let result = Price::fraction(x, y);
2194
2195 assert_eq!(result, None);
2196 }
2197
2198 // check basic tests of fraction division
2199 succeeds(100, 1000, pc(100_000_000, 0, -9));
2200 succeeds(1, 1_000_000_000, pc(10, 0, -10));
2201 // when x and y and x/y can be represented in 8 digits, no loss
2202 succeeds(10_000_001, 20_000_002, pc(500_000_000, 0, -9));
2203 succeeds(102, 3, pc(34_000_000_000, 0, -9));
2204 succeeds(11_111_111, 10_000_000, pc(1_111_111_100, 0, -9));
2205
2206 // test loss due to big numer (x cannot be represented in 8 digits)--only preserves 8 digits
2207 // of precision
2208 succeeds(3_000_000_021_123, 1, pc(30_000_000_000_000_000, 0, -4));
2209
2210 // test loss due to big denom (y cannot be represented in 8 digits)
2211 succeeds(1, 10_000_000_011, pc(10, 0, -11));
2212
2213 // x and y representable within 8 digits, but x/y is not
2214 succeeds(1, 7, pc(142_857_142, 0, -9));
2215
2216 // Test with big inputs where the output will lose precision.
2217 // x not representable within 8 digits
2218 succeeds(i64::MAX, 100, pc(922337200000000, 0, 2));
2219 succeeds(i64::MAX, 1, pc(92233720000000000, 0, 2));
2220 // Neither x nor y representable within 8 digits
2221 succeeds(
2222 i64::MAX - 10,
2223 i64::MAX - 10_000_000_000,
2224 pc(1000000000, 0, -9),
2225 );
2226 // Neither x nor y representable within 8 digits, but this subtraction actually influences
2227 // relevant digit for precision
2228 succeeds(
2229 i64::MAX - 10,
2230 i64::MAX - 100_000_000_000,
2231 pc(1_000_000_010, 0, -9),
2232 );
2233
2234 // Test with end range of possible inputs where the output should not lose precision.
2235 succeeds(MAX_PD_V_I64, MAX_PD_V_I64, pc(1_000_000_000, 0, -9));
2236 succeeds(MAX_PD_V_I64, 1, pc(MAX_PD_V_I64 * 1_000_000_000, 0, -9));
2237 succeeds(MAX_PD_V_I64, MIN_PD_V_I64, pc(-1_000_000_000, 0, -9));
2238 succeeds(MIN_PD_V_I64, 1, pc(MIN_PD_V_I64 * 1_000_000_000, 0, -9));
2239 // test cases near the boundary where output should lose precision
2240 succeeds(
2241 MAX_PD_V_I64 + 1,
2242 1,
2243 pc(MAX_PD_V_I64 / 10 * 1_000_000_000, 0, -8),
2244 );
2245 succeeds(
2246 MAX_PD_V_I64 + 10,
2247 1,
2248 pc((MAX_PD_V_I64 / 10 + 1) * 1_000_000_000, 0, -8),
2249 );
2250
2251 // fails due to div by 0
2252 fails(100, 0);
2253 }
2254}