Skip to content

Commit 5122002

Browse files
authored
Solend v2.0.1 (#131)
* 0xripleys outflow limits (#125) Use a sliding window rate limiter to limit borrows and withdraws at the lending pool owner's discretion. * 0xripleys borrow coefficient (#127) Add a borrow weight to the Reserve * Two Prices PR (#129) - Add a smoothed_market_price to Reserve that is used to limit borrows and withdraws in cases where smoothed price and spot price diverge. - allowed_borrow_value now uses the min(smoothed_market_price, current spot price) - new field on obligation called borrowed_value_upper_bound that uses max(smoothed_market_price, current spot price) * audit nits * audit fixes pt 2 * disable rate limiter if window duration == 0 * cli changes for v2.0.1 (#133)
1 parent 5a53f75 commit 5122002

31 files changed

+3132
-271
lines changed

token-lending/cli/src/main.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use lending_state::SolendState;
22
use solana_client::rpc_config::RpcSendTransactionConfig;
33
use solana_sdk::{commitment_config::CommitmentLevel, compute_budget::ComputeBudgetInstruction};
4+
use solend_program::{instruction::set_lending_market_owner_and_config, state::RateLimiterConfig};
45
use solend_sdk::{
56
instruction::{
67
liquidate_obligation_and_redeem_reserve_collateral, redeem_reserve_collateral,
@@ -88,6 +89,12 @@ struct PartialReserveConfig {
8889
pub protocol_liquidation_fee: Option<u8>,
8990
/// Protocol take rate is the amount borrowed interest protocol recieves, as a percentage
9091
pub protocol_take_rate: Option<u8>,
92+
/// Rate Limiter's max window size
93+
pub rate_limiter_window_duration: Option<u64>,
94+
/// Rate Limiter's max outflow per window
95+
pub rate_limiter_max_outflow: Option<u64>,
96+
/// Added borrow weight in basis points
97+
pub added_borrow_weight_bps: Option<u64>,
9198
}
9299

93100
/// Reserve Fees with optional fields
@@ -523,6 +530,55 @@ fn main() {
523530
.help("Borrow limit"),
524531
)
525532
)
533+
.subcommand(
534+
SubCommand::with_name("set-lending-market-owner-and-config")
535+
.about("Set lending market owner and config")
536+
.arg(
537+
Arg::with_name("lending_market_owner")
538+
.long("market-owner")
539+
.validator(is_keypair)
540+
.value_name("KEYPAIR")
541+
.takes_value(true)
542+
.required(true)
543+
.help("Owner of the lending market"),
544+
)
545+
.arg(
546+
Arg::with_name("lending_market")
547+
.long("market")
548+
.validator(is_pubkey)
549+
.value_name("PUBKEY")
550+
.takes_value(true)
551+
.required(true)
552+
.help("Lending market address"),
553+
)
554+
.arg(
555+
Arg::with_name("new_lending_market_owner")
556+
.long("new-lending-market-owner")
557+
.validator(is_keypair)
558+
.value_name("KEYPAIR")
559+
.takes_value(true)
560+
.required(false)
561+
.help("Owner of the lending market"),
562+
)
563+
.arg(
564+
Arg::with_name("rate_limiter_window_duration")
565+
.long("rate-limiter-window-duration")
566+
.validator(is_parsable::<u64>)
567+
.value_name("INTEGER")
568+
.takes_value(true)
569+
.required(false)
570+
.help("Rate Limiter Window Duration in Slots"),
571+
)
572+
.arg(
573+
Arg::with_name("rate_limiter_max_outflow")
574+
.long("rate-limiter-max-outflow")
575+
.validator(is_parsable::<u64>)
576+
.value_name("INTEGER")
577+
.takes_value(true)
578+
.required(false)
579+
.help("Rate Limiter max outflow denominated in dollars within 1 window"),
580+
)
581+
)
526582
.subcommand(
527583
SubCommand::with_name("update-reserve")
528584
.about("Update a reserve config")
@@ -716,6 +772,33 @@ fn main() {
716772
.required(false)
717773
.help("Switchboard price feed account: https://switchboard.xyz/#/explorer"),
718774
)
775+
.arg(
776+
Arg::with_name("rate_limiter_window_duration")
777+
.long("rate-limiter-window-duration")
778+
.validator(is_parsable::<u64>)
779+
.value_name("INTEGER")
780+
.takes_value(true)
781+
.required(false)
782+
.help("Rate Limiter Window Duration in Slots"),
783+
)
784+
.arg(
785+
Arg::with_name("rate_limiter_max_outflow")
786+
.long("rate-limiter-max-outflow")
787+
.validator(is_parsable::<u64>)
788+
.value_name("INTEGER")
789+
.takes_value(true)
790+
.required(false)
791+
.help("Rate Limiter max outflow of token amounts within 1 window"),
792+
)
793+
.arg(
794+
Arg::with_name("added_borrow_weight_bps")
795+
.long("added-borrow-weight-bps")
796+
.validator(is_parsable::<u64>)
797+
.value_name("INTEGER")
798+
.takes_value(true)
799+
.required(false)
800+
.help("Added borrow weight in basis points"),
801+
)
719802
)
720803
.get_matches();
721804

@@ -871,6 +954,7 @@ fn main() {
871954
fee_receiver: liquidity_fee_receiver_keypair.pubkey(),
872955
protocol_liquidation_fee,
873956
protocol_take_rate,
957+
added_borrow_weight_bps: 10000,
874958
},
875959
source_liquidity_pubkey,
876960
source_liquidity_owner_keypair,
@@ -883,6 +967,24 @@ fn main() {
883967
source_liquidity,
884968
)
885969
}
970+
("set-lending-market-owner-and-config", Some(arg_matches)) => {
971+
let lending_market_owner_keypair =
972+
keypair_of(arg_matches, "lending_market_owner").unwrap();
973+
let lending_market_pubkey = pubkey_of(arg_matches, "lending_market").unwrap();
974+
let new_lending_market_owner_keypair =
975+
keypair_of(arg_matches, "new_lending_market_owner");
976+
let rate_limiter_window_duration =
977+
value_of(arg_matches, "rate_limiter_window_duration");
978+
let rate_limiter_max_outflow = value_of(arg_matches, "rate_limiter_max_outflow");
979+
command_set_lending_market_owner_and_config(
980+
&mut config,
981+
lending_market_pubkey,
982+
lending_market_owner_keypair,
983+
new_lending_market_owner_keypair,
984+
rate_limiter_window_duration,
985+
rate_limiter_max_outflow,
986+
)
987+
}
886988
("update-reserve", Some(arg_matches)) => {
887989
let reserve_pubkey = pubkey_of(arg_matches, "reserve").unwrap();
888990
let lending_market_owner_keypair =
@@ -906,6 +1008,10 @@ fn main() {
9061008
let pyth_product_pubkey = pubkey_of(arg_matches, "pyth_product");
9071009
let pyth_price_pubkey = pubkey_of(arg_matches, "pyth_price");
9081010
let switchboard_feed_pubkey = pubkey_of(arg_matches, "switchboard_feed");
1011+
let rate_limiter_window_duration =
1012+
value_of(arg_matches, "rate_limiter_window_duration");
1013+
let rate_limiter_max_outflow = value_of(arg_matches, "rate_limiter_max_outflow");
1014+
let added_borrow_weight_bps = value_of(arg_matches, "added_borrow_weight_bps");
9091015

9101016
let borrow_fee_wad = borrow_fee.map(|fee| (fee * WAD as f64) as u64);
9111017
let flash_loan_fee_wad = flash_loan_fee.map(|fee| (fee * WAD as f64) as u64);
@@ -930,6 +1036,9 @@ fn main() {
9301036
fee_receiver,
9311037
protocol_liquidation_fee,
9321038
protocol_take_rate,
1039+
rate_limiter_window_duration,
1040+
rate_limiter_max_outflow,
1041+
added_borrow_weight_bps,
9331042
},
9341043
pyth_product_pubkey,
9351044
pyth_price_pubkey,
@@ -1437,6 +1546,50 @@ fn command_add_reserve(
14371546
Ok(())
14381547
}
14391548

1549+
fn command_set_lending_market_owner_and_config(
1550+
config: &mut Config,
1551+
lending_market_pubkey: Pubkey,
1552+
lending_market_owner_keypair: Keypair,
1553+
new_lending_market_owner_keypair: Option<Keypair>,
1554+
rate_limiter_window_duration: Option<u64>,
1555+
rate_limiter_max_outflow: Option<u64>,
1556+
) -> CommandResult {
1557+
let lending_market_info = config.rpc_client.get_account(&lending_market_pubkey)?;
1558+
let lending_market = LendingMarket::unpack_from_slice(lending_market_info.data.borrow())?;
1559+
println!("{:#?}", lending_market);
1560+
1561+
let recent_blockhash = config.rpc_client.get_latest_blockhash()?;
1562+
let message = Message::new_with_blockhash(
1563+
&[set_lending_market_owner_and_config(
1564+
config.lending_program_id,
1565+
lending_market_pubkey,
1566+
lending_market_owner_keypair.pubkey(),
1567+
if let Some(owner) = new_lending_market_owner_keypair {
1568+
owner.pubkey()
1569+
} else {
1570+
lending_market.owner
1571+
},
1572+
RateLimiterConfig {
1573+
window_duration: rate_limiter_window_duration
1574+
.unwrap_or(lending_market.rate_limiter.config.window_duration),
1575+
max_outflow: rate_limiter_max_outflow
1576+
.unwrap_or(lending_market.rate_limiter.config.max_outflow),
1577+
},
1578+
)],
1579+
Some(&config.fee_payer.pubkey()),
1580+
&recent_blockhash,
1581+
);
1582+
1583+
let transaction = Transaction::new(
1584+
&vec![config.fee_payer.as_ref(), &lending_market_owner_keypair],
1585+
message,
1586+
recent_blockhash,
1587+
);
1588+
1589+
send_transaction(config, transaction)?;
1590+
Ok(())
1591+
}
1592+
14401593
#[allow(clippy::too_many_arguments, clippy::unnecessary_unwrap)]
14411594
fn command_update_reserve(
14421595
config: &mut Config,
@@ -1450,6 +1603,7 @@ fn command_update_reserve(
14501603
) -> CommandResult {
14511604
let reserve_info = config.rpc_client.get_account(&reserve_pubkey)?;
14521605
let mut reserve = Reserve::unpack_from_slice(reserve_info.data.borrow())?;
1606+
println!("Reserve: {:#?}", reserve);
14531607
let mut no_change = true;
14541608
if reserve_config.optimal_utilization_rate.is_some()
14551609
&& reserve.config.optimal_utilization_rate
@@ -1664,6 +1818,46 @@ fn command_update_reserve(
16641818
);
16651819
reserve.liquidity.switchboard_oracle_pubkey = switchboard_feed_pubkey.unwrap();
16661820
}
1821+
1822+
if reserve_config.rate_limiter_window_duration.is_some()
1823+
&& reserve.rate_limiter.config.window_duration
1824+
!= reserve_config.rate_limiter_window_duration.unwrap()
1825+
{
1826+
no_change = false;
1827+
println!(
1828+
"Updating rate_limiter_window_duration from {} to {}",
1829+
reserve.rate_limiter.config.window_duration,
1830+
reserve_config.rate_limiter_window_duration.unwrap(),
1831+
);
1832+
reserve.rate_limiter.config.window_duration =
1833+
reserve_config.rate_limiter_window_duration.unwrap();
1834+
}
1835+
1836+
if reserve_config.rate_limiter_max_outflow.is_some()
1837+
&& reserve.rate_limiter.config.max_outflow
1838+
!= reserve_config.rate_limiter_max_outflow.unwrap()
1839+
{
1840+
no_change = false;
1841+
println!(
1842+
"Updating rate_limiter_max_outflow from {} to {}",
1843+
reserve.rate_limiter.config.max_outflow,
1844+
reserve_config.rate_limiter_max_outflow.unwrap(),
1845+
);
1846+
reserve.rate_limiter.config.max_outflow = reserve_config.rate_limiter_max_outflow.unwrap();
1847+
}
1848+
1849+
if reserve_config.added_borrow_weight_bps.is_some()
1850+
&& reserve.config.added_borrow_weight_bps != reserve_config.added_borrow_weight_bps.unwrap()
1851+
{
1852+
no_change = false;
1853+
println!(
1854+
"Updating added_borrow_weight_bps from {} to {}",
1855+
reserve.config.added_borrow_weight_bps,
1856+
reserve_config.added_borrow_weight_bps.unwrap(),
1857+
);
1858+
reserve.config.added_borrow_weight_bps = reserve_config.added_borrow_weight_bps.unwrap();
1859+
}
1860+
16671861
if no_change {
16681862
println!("No changes made for reserve {}", reserve_pubkey);
16691863
return Ok(());
@@ -1675,6 +1869,10 @@ fn command_update_reserve(
16751869
&[update_reserve_config(
16761870
config.lending_program_id,
16771871
reserve.config,
1872+
RateLimiterConfig {
1873+
window_duration: reserve.rate_limiter.config.window_duration,
1874+
max_outflow: reserve.rate_limiter.config.max_outflow,
1875+
},
16781876
reserve_pubkey,
16791877
lending_market_pubkey,
16801878
lending_market_owner_keypair.pubkey(),

0 commit comments

Comments
 (0)