-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Solend v2.0.1 #131
Solend v2.0.1 #131
Conversation
Codecov Report
📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more @@ Coverage Diff @@
## upcoming #131 +/- ##
============================================
+ Coverage 79.96% 82.90% +2.94%
============================================
Files 40 44 +4
Lines 12715 14814 +2099
============================================
+ Hits 10167 12281 +2114
+ Misses 2548 2533 -15
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
Use a sliding window rate limiter to limit borrows and withdraws at the lending pool owner's discretion.
Add a borrow weight to the Reserve
- 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)
@@ -2024,6 +2071,11 @@ fn process_update_reserve_config( | |||
return Err(LendingError::InvalidMarketAuthority.into()); | |||
} | |||
|
|||
// if window duration and max outflow are different, then create a new rate limiter instance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "and" should be "or"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are there rate limits on collateral redemptions (but not for liquidations?).
It seems like both cases should be similar -- my argument for collateral redemption safety is that the collateral must have been introduced into the system at some point by the same person. Absent some ctoken interest manipulation, it seems unlikely to incur that much additional risk. It's also not great UX if you deposit a lot and are unable to immediately withdraw I think.
On the other hand, maybe worth keeping as defense in depth.
pub allowed_borrow_value: Decimal, | ||
/// The dangerous borrow value at the weighted average liquidation threshold | ||
/// The dangerous borrow value at the weighted average liquidation threshold. | ||
/// ie sum(d.deposited_amount * d.liquidation_threshold * min(d.current_spot_price, d.smoothed_price) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment seems inaccurate, it doesn't consider the smoothed price for liquidation threshold right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😅
let market_price = pyth_price_to_decimal(&pyth_price); | ||
let ema_price = { | ||
let price_feed = price_account.to_price_feed(pyth_price_info.key); | ||
// this can be unchecked bc the ema price is only used to _limit_ borrows and withdraws. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems true but also might be bad UX if you have a really outdated ema price that's never updated and users can never initiate a borrow/withdraw.
That being said, from my light skimming of pyth code it seems like EMA should always be updated the same slot as the latest valid price update, so not sure if it practically matters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That being said, from my light skimming of pyth code it seems like EMA should always be updated the same slot as the latest valid price update, so not sure if it practically matters
err yeah this was also partially my reasoning, ill update the comment
@@ -62,6 +65,8 @@ pub struct InitLendingMarketParams { | |||
pub oracle_program_id: Pubkey, | |||
/// Oracle (Switchboard) program id | |||
pub switchboard_oracle_program_id: Pubkey, | |||
/// Current slot | |||
pub current_slot: u64, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this added?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, i used to call RateLimiter::new() in init_lending_market, which needed the current slot. but then later i added a Default implementation. will remove
} | ||
withdraw_amount | ||
}; | ||
if withdraw_amount == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this removed? I don't think it has an impact but just curious
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all this logic got moved into max_withdraw_amount in reserve.rs for easier testing, and also i think it was hard to modify this logic as is after adding the two prices stuff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
max_withdraw_amount
and withdraw_amount
are separate and I don't think this check gets added to the other call right
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh sorry were you asking specifically about if withdraw_amount == 0
? Yeah I guess it doesn't matter, although I think i just I just forgot to re-add it
edit: i did add this check further below
if max_withdraw_amount == 0 {
msg!("Maximum withdraw value is zero");
return Err(LendingError::WithdrawTooLarge.into());
}
which yeah i guess isn't the same thing. i could change it back i guess, don't think it matters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah don't feel too strongly (but why risk it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will change tmrw
@@ -55,6 +55,11 @@ impl Decimal { | |||
Self(U192::from(percent as u64 * PERCENT_SCALER)) | |||
} | |||
|
|||
/// Create scaled decimal from bps value | |||
pub fn from_bps(bps: u64) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (and the other from_percent
) breaks at around 1800% since you multiply before calling ::from
. Just a heads up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah good catch. yeah i think we want to set the borrow weight as high as possible. will fix
.rate_limiter | ||
.update( | ||
clock.slot, | ||
reserve.market_value(Decimal::from(liquidity_amount))?, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this should be based on worst-case market value right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
heh, yeah. i did this PR before adding the 2 prices stuff. will change
usually exploits occur able to overborrow or overwithdraw (or both) so having the limit on both makes sense. for liquidations, without the ability to reliably internalize liquidations it's not great to implicitly block liquidations from happening when needed with a rate limiter as liquidators will just run out of money pretty quickly on a large price movement. |
This PR is 3 different features combined into one:
The first 3 commits link to descriptions of each feature.
tbh idk how PR diffs work but i wouldn't trust the individual feature PR diffs. I would only trust the commit diffs in this PR. In all likelihood, everything should be identical (i did check). But yeah I'm paranoid