Skip to content

Commit aed6f3a

Browse files
author
Anirudh Suresh
committed
add logic and init test for getting collateral valuation price
1 parent d5144f6 commit aed6f3a

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed

pyth-sdk/src/price.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use schemars::JsonSchema;
88
use crate::{
99
utils,
1010
UnixTimestamp,
11+
error::LiquidityOracleError,
1112
};
1213

1314
// Constants for working with pyth's number representation
@@ -89,6 +90,60 @@ impl Price {
8990
self.div(quote)?.scale_to_exponent(result_expo)
9091
}
9192

93+
/// Get the valuation of a collateral position according to:
94+
/// 1. the net amount deposited (across the protocol)
95+
/// 2. the max amount depositable (across the protocol)
96+
/// 3. the initial and final valuation discount rates
97+
///
98+
/// We use a linear interpolation between the the initial and final discount rates,
99+
/// scaled by the proportion of max depositable amount that has been deposited.
100+
/// This essentially assumes a linear liquidity cumulative density function,
101+
/// which has been shown to be a reasonable assumption for many crypto tokens in literature.
102+
pub fn get_collateral_valuation_price(&self, deposits: u64, max_deposits: u64, discount_initial: u64, discount_final: u64, discount_precision: u64) -> Price {
103+
if max_deposits < deposits {
104+
return err!(LiquidityOracleError::ExceedsMaxDeposits);
105+
}
106+
107+
if discount_initial > discount_final {
108+
return err!(LiquidityOracleError::InitialDiscountExceedsFinalDiscount);
109+
}
110+
111+
if discount_final > discount_precision {
112+
return err!(LiquidityOracleError::FinalDiscountExceedsPrecision);
113+
}
114+
115+
// get initial expo; use later to convert conf to the right expo
116+
let expo_init = self.expo;
117+
118+
let left = self.cmul(deposits, 0)?.cmul(discount_precision-discount_final, 0)?;
119+
let right = self.cmul(max_deposits-deposits, 0)?.cmul(discount_precision-discount_initial, 0)?;
120+
121+
let numer = left.add(&right)?;
122+
let denom = max_deposits * discount_precision;
123+
124+
// TODO: divide price by a constant (denom)
125+
// can denote denom as a Price with tiny confidence,
126+
// perform the div, and throw away the resulting confidence,
127+
// since all we care about from the div is price and expo
128+
let denom_as_price = Price {
129+
price: denom,
130+
conf: 1,
131+
expo: 0,
132+
publish_time: self.publish_time,
133+
};
134+
let price_discounted = numer.div(denom_as_price)?;
135+
136+
// we want to scale the original confidence to the new expo
137+
let conf_scaled = self.scale_confidence_to_exponent(price_discounted.expo);
138+
139+
Price {
140+
price: price_discounted.price,
141+
conf: conf_scaled,
142+
expo: price_discounted.expo,
143+
publish_time: self.publish_time,
144+
}
145+
}
146+
92147
/// Get the price of a basket of currencies.
93148
///
94149
/// Each entry in `amounts` is of the form `(price, qty, qty_expo)`, and the result is the sum
@@ -334,6 +389,38 @@ impl Price {
334389
}
335390
}
336391

392+
/// Scale confidence so that its exponent is `target_expo`.
393+
///
394+
/// Logic of this function similar to that of scale_to_exponent;
395+
/// only difference is that this is scaling the confidence alone,
396+
/// and it returns a u64 option.
397+
///
398+
/// Useful in the case of get_collateral_valuation_price function,
399+
/// since separate explicit confidence scaling required there.
400+
pub fn scale_confidence_to_exponent(&self, target_expo: i32) -> Option<u64> {
401+
let mut delta = target_expo.checked_sub(self.expo)?;
402+
if delta >= 0 {
403+
let mut c = self.conf;
404+
// 2nd term is a short-circuit to bound op consumption
405+
while delta > 0 && (c != 0) {
406+
c = c.checked_div(10)?;
407+
delta = delta.checked_sub(1)?;
408+
}
409+
410+
Some(c)
411+
} else {
412+
let mut c = self.conf;
413+
414+
// c == None will short-circuit to bound op consumption
415+
while delta < 0 {
416+
c = c.checked_mul(10)?;
417+
delta = delta.checked_add(1)?;
418+
}
419+
420+
Some(c)
421+
}
422+
}
423+
337424
/// Helper function to convert signed integers to unsigned and a sign bit, which simplifies
338425
/// some of the computations above.
339426
fn to_unsigned(x: i64) -> (u64, i64) {
@@ -897,4 +984,25 @@ mod test {
897984
assert_eq!(p1.mul(&p2).unwrap().publish_time, 100);
898985
assert_eq!(p2.mul(&p1).unwrap().publish_time, 100);
899986
}
987+
988+
#[test]
989+
fn test_get_collateral_valuation_price() {
990+
fn succeeds(price1: Price, deposits: u64, max_deposits: u64, discount_initial: u64, discount_final: u64, discount_precision: u64, expected: Price) {
991+
assert_eq!(price1.get_collateral_valuation_price(deposits, max_deposits, discount_initial, discount_final, discount_precision).unwrap(), expected);
992+
}
993+
994+
fn fails(price1: Price, deposits: u64, max_deposits: u64, discount_initial: u64, discount_final: u64, discount_precision: u64) {
995+
assert_eq!(price1.get_collateral_valuation_price(deposits, max_deposits, discount_initial, discount_final, discount_precision), None);
996+
}
997+
998+
succeeds(
999+
pc(100 * (PD_SCALE as i64), 2 * PD_SCALE, 0),
1000+
0,
1001+
100,
1002+
0,
1003+
5,
1004+
100,
1005+
pc(100 * (PD_SCALE), 2 * PD_SCALE, 0)
1006+
);
1007+
}
9001008
}

0 commit comments

Comments
 (0)