2424 } ,
2525 spl_token_lending:: {
2626 self ,
27- instruction:: { init_lending_market, init_reserve} ,
27+ instruction:: { init_lending_market, init_reserve, update_reserve_config } ,
2828 math:: WAD ,
2929 state:: { LendingMarket , Reserve , ReserveConfig , ReserveFees } ,
3030 } ,
@@ -40,6 +40,39 @@ struct Config {
4040 dry_run : bool ,
4141}
4242
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+
4376type Error = Box < dyn std:: error:: Error > ;
4477type CommandResult = Result < ( ) , Error > ;
4578
@@ -328,6 +361,137 @@ fn main() {
328361 . help ( "Deposit limit" ) ,
329362 )
330363 )
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+ )
331495 . get_matches ( ) ;
332496
333497 let mut wallet_manager = None ;
@@ -432,6 +596,48 @@ fn main() {
432596 switchboard_feed_pubkey,
433597 )
434598 }
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+ }
435641 _ => unreachable ! ( ) ,
436642 }
437643 . map_err ( |err| {
@@ -707,6 +913,137 @@ fn command_add_reserve(
707913 Ok ( ( ) )
708914}
709915
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+
7101047// HELPERS
7111048
7121049fn check_fee_payer_balance ( config : & Config , required_balance : u64 ) -> Result < ( ) , Error > {
0 commit comments