24
24
} ,
25
25
spl_token_lending:: {
26
26
self ,
27
- instruction:: { init_lending_market, init_reserve} ,
27
+ instruction:: { init_lending_market, init_reserve, update_reserve_config } ,
28
28
math:: WAD ,
29
29
state:: { LendingMarket , Reserve , ReserveConfig , ReserveFees } ,
30
30
} ,
@@ -40,6 +40,39 @@ struct Config {
40
40
dry_run : bool ,
41
41
}
42
42
43
+ /// Reserve config with optional fields
44
+ struct PartialReserveConfig {
45
+ /// Optimal utilization rate, as a percentage
46
+ pub optimal_utilization_rate : Option < u8 > ,
47
+ /// Target ratio of the value of borrows to deposits, as a percentage
48
+ /// 0 if use as collateral is disabled
49
+ pub loan_to_value_ratio : Option < u8 > ,
50
+ /// Bonus a liquidator gets when repaying part of an unhealthy obligation, as a percentage
51
+ pub liquidation_bonus : Option < u8 > ,
52
+ /// Loan to value ratio at which an obligation can be liquidated, as a percentage
53
+ pub liquidation_threshold : Option < u8 > ,
54
+ /// Min borrow APY
55
+ pub min_borrow_rate : Option < u8 > ,
56
+ /// Optimal (utilization) borrow APY
57
+ pub optimal_borrow_rate : Option < u8 > ,
58
+ /// Max borrow APY
59
+ pub max_borrow_rate : Option < u8 > ,
60
+ /// Program owner fees assessed, separate from gains due to interest accrual
61
+ pub fees : PartialReserveFees ,
62
+ /// Deposit limit
63
+ pub deposit_limit : Option < u64 > ,
64
+ }
65
+
66
+ /// Reserve Fees with optional fields
67
+ struct PartialReserveFees {
68
+ pub borrow_fee_wad : Option < u64 > ,
69
+ /// Fee for flash loan, expressed as a Wad.
70
+ /// 0.3% (Aave flash loan fee) = 3_000_000_000_000_000
71
+ pub flash_loan_fee_wad : Option < u64 > ,
72
+ /// Amount of fee going to host account, if provided in liquidate and repay
73
+ pub host_fee_percentage : Option < u8 > ,
74
+ }
75
+
43
76
type Error = Box < dyn std:: error:: Error > ;
44
77
type CommandResult = Result < ( ) , Error > ;
45
78
@@ -328,6 +361,137 @@ fn main() {
328
361
. help ( "Deposit limit" ) ,
329
362
)
330
363
)
364
+ . subcommand (
365
+ SubCommand :: with_name ( "update-reserve" )
366
+ . about ( "Update a reserve config" )
367
+ . arg (
368
+ Arg :: with_name ( "reserve" )
369
+ . long ( "reserve" )
370
+ . validator ( is_pubkey)
371
+ . value_name ( "PUBKEY" )
372
+ . takes_value ( true )
373
+ . required ( true )
374
+ . help ( "Reserve address" ) ,
375
+ )
376
+ . arg (
377
+ Arg :: with_name ( "lending_market_owner" )
378
+ . long ( "market-owner" )
379
+ . validator ( is_keypair)
380
+ . value_name ( "KEYPAIR" )
381
+ . takes_value ( true )
382
+ . required ( true )
383
+ . help ( "Owner of the lending market" ) ,
384
+ )
385
+ // @TODO: use is_valid_signer
386
+ . arg (
387
+ Arg :: with_name ( "lending_market" )
388
+ . long ( "market" )
389
+ . validator ( is_pubkey)
390
+ . value_name ( "PUBKEY" )
391
+ . takes_value ( true )
392
+ . required ( true )
393
+ . help ( "Lending market address" ) ,
394
+ )
395
+ . arg (
396
+ Arg :: with_name ( "optimal_utilization_rate" )
397
+ . long ( "optimal-utilization-rate" )
398
+ . validator ( is_parsable :: < u8 > )
399
+ . value_name ( "INTEGER_PERCENT" )
400
+ . takes_value ( true )
401
+ . required ( false )
402
+ . help ( "Optimal utilization rate: [0, 100]" ) ,
403
+ )
404
+ . arg (
405
+ Arg :: with_name ( "loan_to_value_ratio" )
406
+ . long ( "loan-to-value-ratio" )
407
+ . validator ( is_parsable :: < u8 > )
408
+ . value_name ( "INTEGER_PERCENT" )
409
+ . takes_value ( true )
410
+ . required ( false )
411
+ . help ( "Target ratio of the value of borrows to deposits: [0, 100)" ) ,
412
+ )
413
+ . arg (
414
+ Arg :: with_name ( "liquidation_bonus" )
415
+ . long ( "liquidation-bonus" )
416
+ . validator ( is_parsable :: < u8 > )
417
+ . value_name ( "INTEGER_PERCENT" )
418
+ . takes_value ( true )
419
+ . required ( false )
420
+ . help ( "Bonus a liquidator gets when repaying part of an unhealthy obligation: [0, 100]" ) ,
421
+ )
422
+ . arg (
423
+ Arg :: with_name ( "liquidation_threshold" )
424
+ . long ( "liquidation-threshold" )
425
+ . validator ( is_parsable :: < u8 > )
426
+ . value_name ( "INTEGER_PERCENT" )
427
+ . takes_value ( true )
428
+ . required ( false )
429
+ . help ( "Loan to value ratio at which an obligation can be liquidated: (LTV, 100]" ) ,
430
+ )
431
+ . arg (
432
+ Arg :: with_name ( "min_borrow_rate" )
433
+ . long ( "min-borrow-rate" )
434
+ . validator ( is_parsable :: < u8 > )
435
+ . value_name ( "INTEGER_PERCENT" )
436
+ . takes_value ( true )
437
+ . required ( false )
438
+ . help ( "Min borrow APY: min <= optimal <= max" ) ,
439
+ )
440
+ . arg (
441
+ Arg :: with_name ( "optimal_borrow_rate" )
442
+ . long ( "optimal-borrow-rate" )
443
+ . validator ( is_parsable :: < u8 > )
444
+ . value_name ( "INTEGER_PERCENT" )
445
+ . takes_value ( true )
446
+ . required ( false )
447
+ . help ( "Optimal (utilization) borrow APY: min <= optimal <= max" ) ,
448
+ )
449
+ . arg (
450
+ Arg :: with_name ( "max_borrow_rate" )
451
+ . long ( "max-borrow-rate" )
452
+ . validator ( is_parsable :: < u8 > )
453
+ . value_name ( "INTEGER_PERCENT" )
454
+ . takes_value ( true )
455
+ . required ( false )
456
+ . help ( "Max borrow APY: min <= optimal <= max" ) ,
457
+ )
458
+ . arg (
459
+ Arg :: with_name ( "borrow_fee" )
460
+ . long ( "borrow-fee" )
461
+ . validator ( is_parsable :: < f64 > )
462
+ . value_name ( "DECIMAL_PERCENT" )
463
+ . takes_value ( true )
464
+ . required ( false )
465
+ . help ( "Fee assessed on borrow, expressed as a percentage: [0, 1)" ) ,
466
+ )
467
+ . arg (
468
+ Arg :: with_name ( "flash_loan_fee" )
469
+ . long ( "flash-loan-fee" )
470
+ . validator ( is_parsable :: < f64 > )
471
+ . value_name ( "DECIMAL_PERCENT" )
472
+ . takes_value ( true )
473
+ . required ( false )
474
+ . help ( "Fee assessed for flash loans, expressed as a percentage: [0, 1)" ) ,
475
+ )
476
+ . arg (
477
+ Arg :: with_name ( "host_fee_percentage" )
478
+ . long ( "host-fee-percentage" )
479
+ . validator ( is_parsable :: < u8 > )
480
+ . value_name ( "INTEGER_PERCENT" )
481
+ . takes_value ( true )
482
+ . required ( false )
483
+ . help ( "Amount of fee going to host account: [0, 100]" ) ,
484
+ )
485
+ . arg (
486
+ Arg :: with_name ( "deposit_limit" )
487
+ . long ( "deposit-limit" )
488
+ . validator ( is_parsable :: < u64 > )
489
+ . value_name ( "INTEGER" )
490
+ . takes_value ( true )
491
+ . required ( false )
492
+ . help ( "Deposit Limit" ) ,
493
+ )
494
+ )
331
495
. get_matches ( ) ;
332
496
333
497
let mut wallet_manager = None ;
@@ -432,6 +596,48 @@ fn main() {
432
596
switchboard_feed_pubkey,
433
597
)
434
598
}
599
+ ( "update-reserve" , Some ( arg_matches) ) => {
600
+ let reserve_pubkey = pubkey_of ( arg_matches, "reserve" ) . unwrap ( ) ;
601
+ let lending_market_owner_keypair =
602
+ keypair_of ( arg_matches, "lending_market_owner" ) . unwrap ( ) ;
603
+ let lending_market_pubkey = pubkey_of ( arg_matches, "lending_market" ) . unwrap ( ) ;
604
+ let optimal_utilization_rate = value_of ( arg_matches, "optimal_utilization_rate" ) ;
605
+ let loan_to_value_ratio = value_of ( arg_matches, "loan_to_value_ratio" ) ;
606
+ let liquidation_bonus = value_of ( arg_matches, "liquidation_bonus" ) ;
607
+ let liquidation_threshold = value_of ( arg_matches, "liquidation_threshold" ) ;
608
+ let min_borrow_rate = value_of ( arg_matches, "min_borrow_rate" ) ;
609
+ let optimal_borrow_rate = value_of ( arg_matches, "optimal_borrow_rate" ) ;
610
+ let max_borrow_rate = value_of ( arg_matches, "max_borrow_rate" ) ;
611
+ let borrow_fee = value_of :: < f64 > ( arg_matches, "borrow_fee" ) ;
612
+ let flash_loan_fee = value_of :: < f64 > ( arg_matches, "flash_loan_fee" ) ;
613
+ let host_fee_percentage = value_of ( arg_matches, "host_fee_percentage" ) ;
614
+ let deposit_limit = value_of ( arg_matches, "deposit_limit" ) ;
615
+
616
+ let borrow_fee_wad = borrow_fee. map ( |fee| ( fee * WAD as f64 ) as u64 ) ;
617
+ let flash_loan_fee_wad = flash_loan_fee. map ( |fee| ( fee * WAD as f64 ) as u64 ) ;
618
+
619
+ command_update_reserve (
620
+ & mut config,
621
+ PartialReserveConfig {
622
+ optimal_utilization_rate,
623
+ loan_to_value_ratio,
624
+ liquidation_bonus,
625
+ liquidation_threshold,
626
+ min_borrow_rate,
627
+ optimal_borrow_rate,
628
+ max_borrow_rate,
629
+ fees : PartialReserveFees {
630
+ borrow_fee_wad,
631
+ flash_loan_fee_wad,
632
+ host_fee_percentage,
633
+ } ,
634
+ deposit_limit,
635
+ } ,
636
+ reserve_pubkey,
637
+ lending_market_pubkey,
638
+ lending_market_owner_keypair,
639
+ )
640
+ }
435
641
_ => unreachable ! ( ) ,
436
642
}
437
643
. map_err ( |err| {
@@ -707,6 +913,137 @@ fn command_add_reserve(
707
913
Ok ( ( ) )
708
914
}
709
915
916
+ #[ allow( clippy:: too_many_arguments) ]
917
+ fn command_update_reserve (
918
+ config : & mut Config ,
919
+ reserve_config : PartialReserveConfig ,
920
+ reserve_pubkey : Pubkey ,
921
+ lending_market_pubkey : Pubkey ,
922
+ lending_market_owner_keypair : Keypair ,
923
+ ) -> CommandResult {
924
+ let reserve_info = config. rpc_client . get_account ( & reserve_pubkey) ?;
925
+ let mut reserve = Reserve :: unpack_from_slice ( reserve_info. data . borrow ( ) ) ?;
926
+ if reserve_config. optimal_utilization_rate . is_some ( ) {
927
+ println ! (
928
+ "Updating optimal_utilization_rate from {} to {}" ,
929
+ reserve_config. optimal_utilization_rate. unwrap( ) ,
930
+ reserve. config. optimal_utilization_rate,
931
+ ) ;
932
+ reserve. config . optimal_utilization_rate = reserve_config. optimal_utilization_rate . unwrap ( ) ;
933
+ }
934
+
935
+ if reserve_config. loan_to_value_ratio . is_some ( ) {
936
+ println ! (
937
+ "Updating loan_to_value_ratio from {} to {}" ,
938
+ reserve_config. loan_to_value_ratio. unwrap( ) ,
939
+ reserve. config. loan_to_value_ratio,
940
+ ) ;
941
+ reserve. config . loan_to_value_ratio = reserve_config. loan_to_value_ratio . unwrap ( ) ;
942
+ }
943
+
944
+ if reserve_config. liquidation_bonus . is_some ( ) {
945
+ println ! (
946
+ "Updating liquidation_bonus from {} to {}" ,
947
+ reserve_config. liquidation_bonus. unwrap( ) ,
948
+ reserve. config. liquidation_bonus,
949
+ ) ;
950
+ reserve. config . liquidation_bonus = reserve_config. liquidation_bonus . unwrap ( ) ;
951
+ }
952
+
953
+ if reserve_config. liquidation_threshold . is_some ( ) {
954
+ println ! (
955
+ "Updating liquidation_threshold from {} to {}" ,
956
+ reserve_config. liquidation_threshold. unwrap( ) ,
957
+ reserve. config. liquidation_threshold,
958
+ ) ;
959
+ reserve. config . liquidation_threshold = reserve_config. liquidation_threshold . unwrap ( ) ;
960
+ }
961
+
962
+ if reserve_config. min_borrow_rate . is_some ( ) {
963
+ println ! (
964
+ "Updating min_borrow_rate from {} to {}" ,
965
+ reserve_config. min_borrow_rate. unwrap( ) ,
966
+ reserve. config. min_borrow_rate,
967
+ ) ;
968
+ reserve. config . min_borrow_rate = reserve_config. min_borrow_rate . unwrap ( ) ;
969
+ }
970
+
971
+ if reserve_config. optimal_borrow_rate . is_some ( ) {
972
+ println ! (
973
+ "Updating optimal_borrow_rate from {} to {}" ,
974
+ reserve_config. optimal_borrow_rate. unwrap( ) ,
975
+ reserve. config. optimal_borrow_rate,
976
+ ) ;
977
+ reserve. config . optimal_borrow_rate = reserve_config. optimal_borrow_rate . unwrap ( ) ;
978
+ }
979
+
980
+ if reserve_config. max_borrow_rate . is_some ( ) {
981
+ println ! (
982
+ "Updating max_borrow_rate from {} to {}" ,
983
+ reserve_config. max_borrow_rate. unwrap( ) ,
984
+ reserve. config. max_borrow_rate,
985
+ ) ;
986
+ reserve. config . max_borrow_rate = reserve_config. max_borrow_rate . unwrap ( ) ;
987
+ }
988
+
989
+ if reserve_config. fees . borrow_fee_wad . is_some ( ) {
990
+ println ! (
991
+ "Updating borrow_fee_wad from {} to {}" ,
992
+ reserve_config. fees. borrow_fee_wad. unwrap( ) ,
993
+ reserve. config. fees. borrow_fee_wad,
994
+ ) ;
995
+ reserve. config . fees . borrow_fee_wad = reserve_config. fees . borrow_fee_wad . unwrap ( ) ;
996
+ }
997
+
998
+ if reserve_config. fees . flash_loan_fee_wad . is_some ( ) {
999
+ println ! (
1000
+ "Updating flash_loan_fee_wad from {} to {}" ,
1001
+ reserve_config. fees. flash_loan_fee_wad. unwrap( ) ,
1002
+ reserve. config. fees. flash_loan_fee_wad,
1003
+ ) ;
1004
+ reserve. config . fees . flash_loan_fee_wad = reserve_config. fees . flash_loan_fee_wad . unwrap ( ) ;
1005
+ }
1006
+
1007
+ if reserve_config. fees . host_fee_percentage . is_some ( ) {
1008
+ println ! (
1009
+ "Updating host_fee_percentage from {} to {}" ,
1010
+ reserve_config. fees. host_fee_percentage. unwrap( ) ,
1011
+ reserve. config. fees. host_fee_percentage,
1012
+ ) ;
1013
+ reserve. config . fees . host_fee_percentage = reserve_config. fees . host_fee_percentage . unwrap ( ) ;
1014
+ }
1015
+
1016
+ if reserve_config. deposit_limit . is_some ( ) {
1017
+ println ! (
1018
+ "Updating deposit_limit from {} to {}" ,
1019
+ reserve_config. deposit_limit. unwrap( ) ,
1020
+ reserve. config. deposit_limit,
1021
+ ) ;
1022
+ reserve. config . deposit_limit = reserve_config. deposit_limit . unwrap ( ) ;
1023
+ }
1024
+
1025
+ let mut transaction = Transaction :: new_with_payer (
1026
+ & [ update_reserve_config (
1027
+ config. lending_program_id ,
1028
+ reserve. config ,
1029
+ reserve_pubkey,
1030
+ lending_market_pubkey,
1031
+ lending_market_owner_keypair. pubkey ( ) ,
1032
+ ) ] ,
1033
+ Some ( & config. fee_payer . pubkey ( ) ) ,
1034
+ ) ;
1035
+
1036
+ let ( recent_blockhash, fee_calculator) = config. rpc_client . get_recent_blockhash ( ) ?;
1037
+ check_fee_payer_balance ( config, fee_calculator. calculate_fee ( transaction. message ( ) ) ) ?;
1038
+
1039
+ transaction. sign (
1040
+ & vec ! [ config. fee_payer. as_ref( ) , & lending_market_owner_keypair] ,
1041
+ recent_blockhash,
1042
+ ) ;
1043
+ send_transaction ( config, transaction) ?;
1044
+ Ok ( ( ) )
1045
+ }
1046
+
710
1047
// HELPERS
711
1048
712
1049
fn check_fee_payer_balance ( config : & Config , required_balance : u64 ) -> Result < ( ) , Error > {
0 commit comments