@@ -99,49 +99,83 @@ impl Price {
99
99
/// scaled by the proportion of max depositable amount that has been deposited.
100
100
/// This essentially assumes a linear liquidity cumulative density function,
101
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 {
102
+ ///
103
+ /// Args
104
+ /// deposits: u64, quantity of token deposited in the protocol
105
+ /// max_deposits: u64, max quantity of token that can be deposited in the protocol
106
+ /// discount_initial: u64, initial discount rate at 0 deposits (units given by discount_precision)
107
+ /// discount_final: u64, final discount rate at max_deposits deposits (units given by discount_precision)
108
+ /// discount_precision: u64, the precision used for discounts
109
+ ///
110
+ /// Logic
111
+ /// collateral_valuation_price = (deposits * price * (discount_precision-discount_final) + (max_deposits - deposits) * price * (discount_precision - discount_initial)) / (max_deposits * discount_precision)
112
+ pub fn get_collateral_valuation_price ( & self , deposits : u64 , max_deposits : u64 , discount_initial : u64 , discount_final : u64 , discount_precision : u64 ) -> Result < Price , LiquidityOracleError > {
103
113
if max_deposits < deposits {
104
- return err ! ( LiquidityOracleError :: ExceedsMaxDeposits ) ;
114
+ return Err ( LiquidityOracleError :: ExceedsMaxDeposits . into ( ) ) ;
105
115
}
106
116
107
117
if discount_initial > discount_final {
108
- return err ! ( LiquidityOracleError :: InitialDiscountExceedsFinalDiscount ) ;
118
+ return Err ( LiquidityOracleError :: InitialDiscountExceedsFinalDiscount . into ( ) ) ;
109
119
}
110
120
111
121
if discount_final > discount_precision {
112
- return err ! ( LiquidityOracleError :: FinalDiscountExceedsPrecision ) ;
122
+ return Err ( LiquidityOracleError :: FinalDiscountExceedsPrecision . into ( ) ) ;
113
123
}
114
-
115
- // get initial expo; use later to convert conf to the right expo
116
- let expo_init = self . expo ;
117
124
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 ) ?;
125
+ let remaining_depositable = ( max_deposits-deposits) as i64 ;
126
+ let diff_discount_precision_initial = ( discount_precision-discount_initial) as i64 ;
127
+ let diff_discount_precision_final = ( discount_precision-discount_final) as i64 ;
128
+
129
+ let mut left = self . cmul ( deposits as i64 , 0 ) .
130
+ ok_or ( LiquidityOracleError :: NoneEncountered ) ?.
131
+ cmul ( diff_discount_precision_final, 0 ) .
132
+ ok_or ( LiquidityOracleError :: NoneEncountered ) ?
133
+ ;
134
+ let mut right = self . cmul ( remaining_depositable, 0 ) .
135
+ ok_or ( LiquidityOracleError :: NoneEncountered ) ?.
136
+ cmul ( diff_discount_precision_initial, 0 ) .
137
+ ok_or ( LiquidityOracleError :: NoneEncountered ) ?
138
+ ;
139
+ // scale left and right to match expo
140
+ if left. expo > right. expo {
141
+ left = left.
142
+ scale_to_exponent ( right. expo ) .
143
+ ok_or ( LiquidityOracleError :: NoneEncountered ) ?
144
+ ;
145
+ }
146
+ else if left. expo < right. expo {
147
+ right = right.
148
+ scale_to_exponent ( left. expo ) .
149
+ ok_or ( LiquidityOracleError :: NoneEncountered ) ?
150
+ ;
151
+ }
120
152
121
- let numer = left. add ( & right) ?;
153
+ let numer = left. add ( & right) . ok_or ( LiquidityOracleError :: NoneEncountered ) ?;
122
154
let denom = max_deposits * discount_precision;
123
155
124
156
// TODO: divide price by a constant (denom)
125
157
// can denote denom as a Price with tiny confidence,
126
158
// perform the div, and throw away the resulting confidence,
127
159
// since all we care about from the div is price and expo
128
160
let denom_as_price = Price {
129
- price : denom,
161
+ price : denom as i64 ,
130
162
conf : 1 ,
131
163
expo : 0 ,
132
164
publish_time : self . publish_time ,
133
165
} ;
134
- let price_discounted = numer. div ( denom_as_price) ?;
166
+ let price_discounted = numer. div ( & denom_as_price) . ok_or ( LiquidityOracleError :: NoneEncountered ) ?;
135
167
136
168
// we want to scale the original confidence to the new expo
137
169
let conf_scaled = self . scale_confidence_to_exponent ( price_discounted. expo ) ;
138
170
139
- Price {
140
- price : price_discounted. price ,
141
- conf : conf_scaled,
142
- expo : price_discounted. expo ,
143
- publish_time : self . publish_time ,
144
- }
171
+ Ok (
172
+ Price {
173
+ price : price_discounted. price ,
174
+ conf : conf_scaled. ok_or ( LiquidityOracleError :: NoneEncountered ) ?,
175
+ expo : price_discounted. expo ,
176
+ publish_time : self . publish_time ,
177
+ }
178
+ )
145
179
}
146
180
147
181
/// Get the price of a basket of currencies.
@@ -437,12 +471,12 @@ impl Price {
437
471
438
472
#[ cfg( test) ]
439
473
mod test {
440
- use crate :: price:: {
474
+ use crate :: { price:: {
441
475
Price ,
442
476
MAX_PD_V_U64 ,
443
477
PD_EXPO ,
444
478
PD_SCALE ,
445
- } ;
479
+ } , error :: LiquidityOracleError } ;
446
480
447
481
const MAX_PD_V_I64 : i64 = MAX_PD_V_U64 as i64 ;
448
482
const MIN_PD_V_I64 : i64 = -MAX_PD_V_I64 ;
@@ -987,22 +1021,137 @@ mod test {
987
1021
988
1022
#[ test]
989
1023
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) ;
1024
+ fn succeeds ( price : Price , deposits : u64 , max_deposits : u64 , discount_initial : u64 , discount_final : u64 , discount_precision : u64 , mut expected : Price ) {
1025
+ let mut price_collat = price. get_collateral_valuation_price ( deposits, max_deposits, discount_initial, discount_final, discount_precision) . unwrap ( ) ;
1026
+ print ! ( "Output: price is {}, conf is {}, expo is {}, ts is {}" , price_collat. price, price_collat. conf, price_collat. expo, price_collat. publish_time) ;
1027
+ print ! ( "Exepcted: price is {}, conf is {}, expo is {}, ts is {}" , expected. price, expected. conf, expected. expo, expected. publish_time) ;
1028
+
1029
+ // scale price_collat and expected to match in expo
1030
+ if price_collat. expo > expected. expo {
1031
+ price_collat = price_collat. scale_to_exponent ( expected. expo ) . unwrap ( ) ;
1032
+ }
1033
+ else if price_collat. expo < expected. expo {
1034
+ expected = expected. scale_to_exponent ( price_collat. expo ) . unwrap ( ) ;
1035
+ }
1036
+
1037
+ assert_eq ! ( price_collat, expected) ;
992
1038
}
993
1039
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 ) ;
1040
+ fn fails_exceeds_max_deposits ( price : Price , deposits : u64 , max_deposits : u64 , discount_initial : u64 , discount_final : u64 , discount_precision : u64 ) {
1041
+ let result = price. get_collateral_valuation_price ( deposits, max_deposits, discount_initial, discount_final, discount_precision) ;
1042
+ assert_eq ! ( result. unwrap_err( ) , LiquidityOracleError :: ExceedsMaxDeposits ) ;
996
1043
}
997
1044
1045
+ fn fails_initial_discount_exceeds_final_discount ( price : Price , deposits : u64 , max_deposits : u64 , discount_initial : u64 , discount_final : u64 , discount_precision : u64 ) {
1046
+ let result = price. get_collateral_valuation_price ( deposits, max_deposits, discount_initial, discount_final, discount_precision) ;
1047
+ assert_eq ! ( result. unwrap_err( ) , LiquidityOracleError :: InitialDiscountExceedsFinalDiscount ) ;
1048
+ }
1049
+
1050
+ fn fails_final_discount_exceeds_precision ( price : Price , deposits : u64 , max_deposits : u64 , discount_initial : u64 , discount_final : u64 , discount_precision : u64 ) {
1051
+ let result = price. get_collateral_valuation_price ( deposits, max_deposits, discount_initial, discount_final, discount_precision) ;
1052
+ assert_eq ! ( result. unwrap_err( ) , LiquidityOracleError :: FinalDiscountExceedsPrecision ) ;
1053
+ }
1054
+
1055
+ fn fails_none_encountered ( price : Price , deposits : u64 , max_deposits : u64 , discount_initial : u64 , discount_final : u64 , discount_precision : u64 ) {
1056
+ let result = price. get_collateral_valuation_price ( deposits, max_deposits, discount_initial, discount_final, discount_precision) ;
1057
+ assert_eq ! ( result. unwrap_err( ) , LiquidityOracleError :: NoneEncountered ) ;
1058
+ }
1059
+
1060
+ // 0 deposits
1061
+ succeeds (
1062
+ pc ( 100 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 ) ,
1063
+ 0 ,
1064
+ 100 ,
1065
+ 0 ,
1066
+ 10 ,
1067
+ 100 ,
1068
+ pc ( 100 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 )
1069
+ ) ;
1070
+
1071
+ // half deposits
1072
+ succeeds (
1073
+ pc ( 100 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 ) ,
1074
+ 50 ,
1075
+ 100 ,
1076
+ 0 ,
1077
+ 10 ,
1078
+ 100 ,
1079
+ pc ( 95 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 )
1080
+ ) ;
1081
+
1082
+ // full deposits
1083
+ succeeds (
1084
+ pc ( 100 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 ) ,
1085
+ 100 ,
1086
+ 100 ,
1087
+ 0 ,
1088
+ 10 ,
1089
+ 100 ,
1090
+ pc ( 90 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 )
1091
+ ) ;
1092
+
1093
+ // 0 deposits, staggered initial discount
998
1094
succeeds (
999
1095
pc ( 100 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 ) ,
1000
1096
0 ,
1001
1097
100 ,
1098
+ 2 ,
1099
+ 10 ,
1100
+ 100 ,
1101
+ pc ( 98 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 )
1102
+ ) ;
1103
+
1104
+ // half deposits, staggered initial discount
1105
+ succeeds (
1106
+ pc ( 100 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 ) ,
1107
+ 50 ,
1108
+ 100 ,
1109
+ 2 ,
1110
+ 10 ,
1111
+ 100 ,
1112
+ pc ( 94 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 )
1113
+ ) ;
1114
+
1115
+ // full deposits, staggered initial discount
1116
+ succeeds (
1117
+ pc ( 100 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 ) ,
1118
+ 100 ,
1119
+ 100 ,
1120
+ 2 ,
1121
+ 10 ,
1122
+ 100 ,
1123
+ pc ( 90 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 )
1124
+ ) ;
1125
+
1126
+ // fails bc over max deposit limit
1127
+ fails_exceeds_max_deposits (
1128
+ pc ( 100 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 ) ,
1129
+ 101 ,
1130
+ 100 ,
1002
1131
0 ,
1003
- 5 ,
1132
+ 10 ,
1133
+ 100
1134
+ ) ;
1135
+
1136
+ // fails bc initial discount exceeds final discount
1137
+ fails_initial_discount_exceeds_final_discount (
1138
+ pc ( 100 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 ) ,
1139
+ 50 ,
1140
+ 100 ,
1141
+ 11 ,
1142
+ 10 ,
1143
+ 100
1144
+ ) ;
1145
+
1146
+ // fails bc final discount exceeds precision
1147
+ fails_final_discount_exceeds_precision (
1148
+ pc ( 100 * ( PD_SCALE as i64 ) , 2 * PD_SCALE , 0 ) ,
1149
+ 50 ,
1150
+ 100 ,
1151
+ 0 ,
1152
+ 101 ,
1004
1153
100 ,
1005
- pc ( 100 * ( PD_SCALE ) , 2 * PD_SCALE , 0 )
1006
1154
) ;
1155
+
1007
1156
}
1008
1157
}
0 commit comments