@@ -8,6 +8,7 @@ use schemars::JsonSchema;
8
8
use crate :: {
9
9
utils,
10
10
UnixTimestamp ,
11
+ error:: LiquidityOracleError ,
11
12
} ;
12
13
13
14
// Constants for working with pyth's number representation
@@ -89,6 +90,60 @@ impl Price {
89
90
self . div ( quote) ?. scale_to_exponent ( result_expo)
90
91
}
91
92
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
+
92
147
/// Get the price of a basket of currencies.
93
148
///
94
149
/// Each entry in `amounts` is of the form `(price, qty, qty_expo)`, and the result is the sum
@@ -334,6 +389,38 @@ impl Price {
334
389
}
335
390
}
336
391
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
+
337
424
/// Helper function to convert signed integers to unsigned and a sign bit, which simplifies
338
425
/// some of the computations above.
339
426
fn to_unsigned ( x : i64 ) -> ( u64 , i64 ) {
@@ -897,4 +984,25 @@ mod test {
897
984
assert_eq ! ( p1. mul( & p2) . unwrap( ) . publish_time, 100 ) ;
898
985
assert_eq ! ( p2. mul( & p1) . unwrap( ) . publish_time, 100 ) ;
899
986
}
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
+ }
900
1008
}
0 commit comments