@@ -329,7 +329,8 @@ fn process_init_reserve(
329
329
validate_pyth_keys ( & lending_market, pyth_product_info, pyth_price_info) ?;
330
330
validate_switchboard_keys ( & lending_market, switchboard_feed_info) ?;
331
331
332
- let market_price = get_price ( Some ( switchboard_feed_info) , pyth_price_info, clock) ?;
332
+ let ( market_price, smoothed_market_price) =
333
+ get_price ( Some ( switchboard_feed_info) , pyth_price_info, clock) ?;
333
334
334
335
let authority_signer_seeds = & [
335
336
lending_market_info. key . as_ref ( ) ,
@@ -360,6 +361,7 @@ fn process_init_reserve(
360
361
pyth_oracle_pubkey : * pyth_price_info. key ,
361
362
switchboard_oracle_pubkey : * switchboard_feed_info. key ,
362
363
market_price,
364
+ smoothed_market_price : smoothed_market_price. unwrap_or ( market_price) ,
363
365
} ) ,
364
366
collateral : ReserveCollateral :: new ( NewReserveCollateralParams {
365
367
mint_pubkey : * reserve_collateral_mint_info. key ,
@@ -482,7 +484,21 @@ fn _refresh_reserve<'a>(
482
484
return Err ( LendingError :: InvalidOracleConfig . into ( ) ) ;
483
485
}
484
486
485
- reserve. liquidity . market_price = get_price ( switchboard_feed_info, pyth_price_info, clock) ?;
487
+ let ( market_price, smoothed_market_price) =
488
+ get_price ( switchboard_feed_info, pyth_price_info, clock) ?;
489
+
490
+ reserve. liquidity . market_price = market_price;
491
+
492
+ if let Some ( smoothed_market_price) = smoothed_market_price {
493
+ reserve. liquidity . smoothed_market_price = smoothed_market_price;
494
+ }
495
+
496
+ // currently there's no way to support two prices without a pyth oracle. So if a reserve
497
+ // only supports switchboard, reserve.smoothed_market_price == reserve.market_price
498
+ if reserve. liquidity . pyth_oracle_pubkey == solend_program:: NULL_PUBKEY {
499
+ reserve. liquidity . smoothed_market_price = market_price;
500
+ }
501
+
486
502
Reserve :: pack ( reserve, & mut reserve_info. data . borrow_mut ( ) ) ?;
487
503
488
504
_refresh_reserve_interest ( program_id, reserve_info, clock)
@@ -881,6 +897,7 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) ->
881
897
882
898
let mut deposited_value = Decimal :: zero ( ) ;
883
899
let mut borrowed_value = Decimal :: zero ( ) ;
900
+ let mut borrowed_value_upper_bound = Decimal :: zero ( ) ;
884
901
let mut allowed_borrow_value = Decimal :: zero ( ) ;
885
902
let mut unhealthy_borrow_value = Decimal :: zero ( ) ;
886
903
@@ -910,25 +927,22 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) ->
910
927
return Err ( LendingError :: ReserveStale . into ( ) ) ;
911
928
}
912
929
913
- // @TODO: add lookup table https://git.io/JOCYq
914
- let decimals = 10u64
915
- . checked_pow ( deposit_reserve. liquidity . mint_decimals as u32 )
916
- . ok_or ( LendingError :: MathOverflow ) ?;
917
-
918
- let market_value = deposit_reserve
930
+ let liquidity_amount = deposit_reserve
919
931
. collateral_exchange_rate ( ) ?
920
- . decimal_collateral_to_liquidity ( collateral. deposited_amount . into ( ) ) ?
921
- . try_mul ( deposit_reserve. liquidity . market_price ) ?
922
- . try_div ( decimals) ?;
923
- collateral. market_value = market_value;
932
+ . decimal_collateral_to_liquidity ( collateral. deposited_amount . into ( ) ) ?;
933
+
934
+ let market_value = deposit_reserve. market_value ( liquidity_amount) ?;
935
+ let market_value_lower_bound =
936
+ deposit_reserve. market_value_lower_bound ( liquidity_amount) ?;
924
937
925
938
let loan_to_value_rate = Rate :: from_percent ( deposit_reserve. config . loan_to_value_ratio ) ;
926
939
let liquidation_threshold_rate =
927
940
Rate :: from_percent ( deposit_reserve. config . liquidation_threshold ) ;
928
941
942
+ collateral. market_value = market_value;
929
943
deposited_value = deposited_value. try_add ( market_value) ?;
930
944
allowed_borrow_value =
931
- allowed_borrow_value. try_add ( market_value . try_mul ( loan_to_value_rate) ?) ?;
945
+ allowed_borrow_value. try_add ( market_value_lower_bound . try_mul ( loan_to_value_rate) ?) ?;
932
946
unhealthy_borrow_value =
933
947
unhealthy_borrow_value. try_add ( market_value. try_mul ( liquidation_threshold_rate) ?) ?;
934
948
}
@@ -962,10 +976,14 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) ->
962
976
liquidity. accrue_interest ( borrow_reserve. liquidity . cumulative_borrow_rate_wads ) ?;
963
977
964
978
let market_value = borrow_reserve. market_value ( liquidity. borrowed_amount_wads ) ?;
979
+ let market_value_upper_bound =
980
+ borrow_reserve. market_value_upper_bound ( liquidity. borrowed_amount_wads ) ?;
965
981
liquidity. market_value = market_value;
966
982
967
983
borrowed_value =
968
984
borrowed_value. try_add ( market_value. try_mul ( borrow_reserve. borrow_weight ( ) ) ?) ?;
985
+ borrowed_value_upper_bound = borrowed_value_upper_bound
986
+ . try_add ( market_value_upper_bound. try_mul ( borrow_reserve. borrow_weight ( ) ) ?) ?;
969
987
}
970
988
971
989
if account_info_iter. peek ( ) . is_some ( ) {
@@ -975,6 +993,7 @@ fn process_refresh_obligation(program_id: &Pubkey, accounts: &[AccountInfo]) ->
975
993
976
994
obligation. deposited_value = deposited_value;
977
995
obligation. borrowed_value = borrowed_value;
996
+ obligation. borrowed_value_upper_bound = borrowed_value_upper_bound;
978
997
979
998
let global_unhealthy_borrow_value = Decimal :: from ( 70000000u64 ) ;
980
999
let global_allowed_borrow_value = Decimal :: from ( 65000000u64 ) ;
@@ -1310,49 +1329,14 @@ fn _withdraw_obligation_collateral<'a>(
1310
1329
return Err ( LendingError :: InvalidMarketAuthority . into ( ) ) ;
1311
1330
}
1312
1331
1313
- let withdraw_amount = if obligation. borrows . is_empty ( ) {
1314
- if collateral_amount == u64:: MAX {
1315
- collateral. deposited_amount
1316
- } else {
1317
- collateral. deposited_amount . min ( collateral_amount)
1318
- }
1319
- } else if obligation. deposited_value == Decimal :: zero ( ) {
1320
- msg ! ( "Obligation deposited value is zero" ) ;
1321
- return Err ( LendingError :: ObligationDepositsZero . into ( ) ) ;
1322
- } else {
1323
- let max_withdraw_value = obligation. max_withdraw_value ( Rate :: from_percent (
1324
- withdraw_reserve. config . loan_to_value_ratio ,
1325
- ) ) ?;
1332
+ let max_withdraw_amount = obligation. max_withdraw_amount ( collateral, & withdraw_reserve) ?;
1326
1333
1327
- if max_withdraw_value == Decimal :: zero ( ) {
1328
- msg ! ( "Maximum withdraw value is zero" ) ;
1329
- return Err ( LendingError :: WithdrawTooLarge . into ( ) ) ;
1330
- }
1334
+ if max_withdraw_amount == 0 {
1335
+ msg ! ( "Maximum withdraw value is zero" ) ;
1336
+ return Err ( LendingError :: WithdrawTooLarge . into ( ) ) ;
1337
+ }
1331
1338
1332
- let withdraw_amount = if collateral_amount == u64:: MAX {
1333
- let withdraw_value = max_withdraw_value. min ( collateral. market_value ) ;
1334
- let withdraw_pct = withdraw_value. try_div ( collateral. market_value ) ?;
1335
- withdraw_pct
1336
- . try_mul ( collateral. deposited_amount ) ?
1337
- . try_floor_u64 ( ) ?
1338
- . min ( collateral. deposited_amount )
1339
- } else {
1340
- let withdraw_amount = collateral_amount. min ( collateral. deposited_amount ) ;
1341
- let withdraw_pct =
1342
- Decimal :: from ( withdraw_amount) . try_div ( collateral. deposited_amount ) ?;
1343
- let withdraw_value = collateral. market_value . try_mul ( withdraw_pct) ?;
1344
- if withdraw_value > max_withdraw_value {
1345
- msg ! ( "Withdraw value cannot exceed maximum withdraw value" ) ;
1346
- return Err ( LendingError :: WithdrawTooLarge . into ( ) ) ;
1347
- }
1348
- withdraw_amount
1349
- } ;
1350
- if withdraw_amount == 0 {
1351
- msg ! ( "Withdraw amount is too small to transfer collateral" ) ;
1352
- return Err ( LendingError :: WithdrawTooSmall . into ( ) ) ;
1353
- }
1354
- withdraw_amount
1355
- } ;
1339
+ let withdraw_amount = std:: cmp:: min ( collateral_amount, max_withdraw_amount) ;
1356
1340
1357
1341
obligation. withdraw ( withdraw_amount, collateral_index) ?;
1358
1342
obligation. last_update . mark_stale ( ) ;
@@ -1486,7 +1470,9 @@ fn process_borrow_obligation_liquidity(
1486
1470
return Err ( LendingError :: InvalidMarketAuthority . into ( ) ) ;
1487
1471
}
1488
1472
1489
- let remaining_borrow_value = obligation. remaining_borrow_value ( ) ?;
1473
+ let remaining_borrow_value = obligation
1474
+ . remaining_borrow_value ( )
1475
+ . unwrap_or_else ( |_| Decimal :: zero ( ) ) ;
1490
1476
if remaining_borrow_value == Decimal :: zero ( ) {
1491
1477
msg ! ( "Remaining borrow value is zero" ) ;
1492
1478
return Err ( LendingError :: BorrowTooLarge . into ( ) ) ;
@@ -2606,19 +2592,26 @@ fn get_pyth_product_quote_currency(
2606
2592
} )
2607
2593
}
2608
2594
2595
+ /// get_price tries to load the oracle price from pyth, and if it fails, uses switchboard.
2596
+ /// The first element in the returned tuple is the market price, and the second is the optional
2597
+ /// smoothed price (eg ema, twap).
2609
2598
fn get_price (
2610
2599
switchboard_feed_info : Option < & AccountInfo > ,
2611
2600
pyth_price_account_info : & AccountInfo ,
2612
2601
clock : & Clock ,
2613
- ) -> Result < Decimal , ProgramError > {
2614
- let pyth_price = get_pyth_price ( pyth_price_account_info, clock) . unwrap_or_default ( ) ;
2615
- if pyth_price != Decimal :: zero ( ) {
2616
- return Ok ( pyth_price) ;
2602
+ ) -> Result < ( Decimal , Option < Decimal > ) , ProgramError > {
2603
+ if let Ok ( prices) = get_pyth_price ( pyth_price_account_info, clock) {
2604
+ return Ok ( ( prices. 0 , Some ( prices. 1 ) ) ) ;
2617
2605
}
2618
2606
2619
2607
// if switchboard was not passed in don't try to grab the price
2620
2608
if let Some ( switchboard_feed_info_unwrapped) = switchboard_feed_info {
2621
- return get_switchboard_price ( switchboard_feed_info_unwrapped, clock) ;
2609
+ // TODO: add support for switchboard smoothed prices. Probably need to add a new
2610
+ // switchboard account per reserve.
2611
+ return match get_switchboard_price ( switchboard_feed_info_unwrapped, clock) {
2612
+ Ok ( price) => Ok ( ( price, None ) ) ,
2613
+ Err ( e) => Err ( e) ,
2614
+ } ;
2622
2615
}
2623
2616
2624
2617
Err ( LendingError :: InvalidOracleConfig . into ( ) )
0 commit comments