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(&eth_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}