Solend V2 Litepaper
Solend has been battle-tested countless times since launch. In the past year, Solend survived the depegging of UST, the collapse of 3AC (plus contagion), the USDH oracle manipulation attack, the collapse of FTX, and the unbacking of soBTC. Also during that time, SOL fell from over $250 to under $10 with big candles along the way. These events have led to countless learnings which have culminated in this Solend V2 litepaper. Solend V2 aims to reduce risk and improve decentralization, and will be released over the coming months.
Note that this paper is a work in progress, and features may be added or modified as designs are finalized.
Protected Collateral
One issue faced by depositors is that withdrawals aren’t always available (when utilization is at 100%). This is an expected consequence of a variable-term loan model. However, some borrowers prefer access to their collateral at any time in exchange for not earning yield. The Protected Collateral feature provides this functionality.
In other words, users can borrow against USDC, not cUSDC. The benefit is that the USDC can be withdrawn at any time (but does not earn yield), whereas the cUSDC withdrawals can be unavailable if reserve utilization is at 100%.
TWAP Oracle
Solend V1 uses spot prices to determine if an operation is allowed. Solend V2 uses TWAP for operations where it’s more conservative to, but continues to use spot price for liquidations.
In Solend V1, when a user wants to borrow an asset, the program checks that the user’s new proposed borrow value is less than their allowed borrow value. using the spot prices. In code it checks to make sure the following holds true:
borrowed_value_usd = sum(b.borrow_amount * b.token_price for b in obligation.borrows)
allowed_borrow_value_usd = sum(d.deposit_amount * d.token_price * d.ltv for d in obligation.deposits)
assert new_borrow_amount * token_price + borrow_value_usd <= allowed_borrow_value_usd
However this can lead to some issues when dealing with low liquidity tokens, as demonstrated in the Mango exploit and the Solend USDH exploit in late 2022. Prices are manipulatable enough to make for a profitable attack.
To prevent similar attacks in the future, v2 implements the following modification to the checks in which the less favorable of spot or TWAP price is considered. This makes it more expensive to manipulate prices. In code it looks like:
borrowed_value_usd = sum(b.borrow_amount * max(b.token_price, b.token_price_twap) for b in obligation.borrows)
allowed_borrow_value_usd = sum(d.deposit_amount * min(d.token_price, d.token_price_twap) * d.ltv for d in obligation.deposits)
assert new_borrow_amount * max(token_price, token_price_smoothed) + borrow_value_usd <= allowed_borrow_value_usd
This calculation essentially amplifies borrow values and minimizes deposit values, which makes it strictly safer than Solend V1, at the expense of temporary capital inefficiency when there are price movements.
Similar logic is done on withdraws as well.
Borrow Weights
Assets have varying levels of risk, which Solend V1 accounts for using LTVs. However, this only captures the risk of using an asset as collateral, not the risk of borrowing an asset.
For example, consider three assets:
- USDC (a safe asset) with an LTV of 80%
- USDT (another safe asset) with an LTV of 80%
- BONK (a volatile asset) with an LTV of 40%
With 100 USDC as collateral, the same amount of USDT or BONK can be borrowed despite their stark difference in risk.
Solend V2 introduces a borrow weight coefficient per asset which effectively amplifies that asset’s value when being borrowed.
borrowed_value_usd = sum(b.borrow_weight * b.borrow_amount * b.token_price for b in obligation.borrows)
allowed_borrow_value_usd = sum(d.loan_to_value_ratio * d.deposit_amount * d.token_price for d in obligation.deposits)
unhealthy_borrow_value = sum(d.liquidation_threshold * d.deposit_amount * d.token_price for d in obligation.deposits)
# for opening borrows or withdrawing
assert new_borrow_amount * max(token_price, token_price_smoothed) + borrow_value_usd <= allowed_borrow_value_usd
# for determining liquidation criteria
assert borrowed_value_usd <= unhealthy_borrow_value
Expanding on the example above, consider borrow weights of 1, 1, 2 for USDC, USDT, and BONK respectively. Now when using 100 USDC as collateral, only $40 of BONK can be borrowed compared to $80 of USDT, properly reflecting the asset’s risk.
Note that the coefficient does not effect liquidation price but does affect liquidation eligibility.
The borrow weights feature adds a degree of freedom enabling Solend V2 to manage risk with more granularity.
Outflow Rate Limits
Solend V2 introduces a rolling limit on the amount of borrows and withdrawals allowed, configurable per pool and per asset. For example, a maximum of $10M can be borrowed or withdrawn from the Main pool in a 24h period.
This limits the damage possible due to an exploit.
The following graph shows Solend outflows, which gives a sense for what is normal:
Collateralization Limits
Solend V2 introduces collateralization limits, which are global limits on how much can be borrowed against an asset. For example, a collateralization limit of $20M for SOL means only $20M of borrows can be secured by SOL collateral. This feature is an additional tool for managing risk, since in V1 there was no way to deal situations where changing market conditions cause in which there are too many borrows against a collateral type
borrowed_value_usd = sum(b.borrow_amount * b.token_price for b in obligation.borrows)
total_deposit_value_usd = sum(d.deposit_amount * d.token_price for d in obligation.deposits)
# this is calculated for each asset
collateralizing_borrows_usd = (deposit_amount * token_price) / total_deposit_value_usd * borrowed_value_usd
If the collateralization limit for an asset is hit, increasing borrows against that asset (via borrows or withdraws) is not allowed. Additionally, the top riskiest obligations of any reserve are liquidatable even if the obligation is otherwise healthy with respect to LTVs. The reserve will constantly keep track of the 10 obligations with the biggest collateralizing_borrows_usd value and these will be the obligations allowed to be liquidated when collateralization limits are hit.
Isolated Tier Assets
In Solend V2, some assets can be designated to only be borrowable if they are the sole asset being borrowed.
The benefit of this is that a lot more assets can be listed in the main pool, which translates to a better user experience.
Why is isolated tier safer? Suppose assets ORCA and JET weren’t isolated tier assets (ie could be borrowed in the same obligation), and consider the following scenario:
- t=0: User O supplies 50 ORCA. 1 ORCA = $1.
- t=0: User J supplies 100 JET. 1 JET = $1.
- t=1: User U supplies 200 USDC and borrows 50 ORCA and 100 JET.
- Borrow value: $150
- t=2: the price of ORCA goes to $100.
- U’s borrow value is now 50 * $100 + 100 * $1 = $5100
- t=3: liquidator liquidates U by repaying 2 ORCA and taking 200 USDC
- User U still owes 48 ORCA ($4800), 100 JET ($100) with no deposits
- End result:
- Protocol has taken on bad debt
- User O is unable to withdraw 48 ORCA
- User J is unable to withdraw 100 JET
- Protocol has taken on bad debt
User J has lost 100 JET even though the price of JET didn’t change. More importantly, the ORCA price change caused bad debt in other assets, which is very undesirable.
Now, consider the case where ORCA and JET are isolated tier assets.
- t=0: user O supplies 50 ORCA. 1 ORCA = $1.
- t=0: user J supplies 100 JET. 1 JET = $1.
- t=1: user U supplies 75 USDC and borrows 50 ORCA in obligation 1
- t=1: user U supplies 125 USDC and borrows 100 JET in obligation 2
- t=2: the price of ORCA goes to $100.
- Borrow value of obligation 1 is $5000
- t=3: liquidator liquidates obligation 1 by supplying 0.75 ORCA and taking 75 USDC
- End result:
- Protocol has taken on bad debt
- User O has lost 49.25 ORCA
- User J has lost nothing!
- Protocol has taken on bad debt
Using isolated tiers lets the protocol contain bad debt. Only ORCA lenders were affected in this scenario, and nobody else. If ORCA was not an isolated tier asset, then the bad debt would spread across many other tokens in the pool.
Dynamic Liquidation Bonus
In Solend V1, liquidators are rewarded with a bonus calculated as a fixed percentage of the assets liquidated. Because of this, liquidations have become a winner-take-all race, causing liquidators to optimize for speed. This isn’t aligned with the goals of the protocol, which would prefer to optimize for execution.
Liquidations in Solend V2 have a dynamic bonus, depending on how unhealthy the obligation is. For example, an obligation that is barely eligible for liquidation will have a liquidation bonus of 0%, while a very unhealthy obligation will have a liquidation bonus of 10%.
As a result, slow liquidators will also be profitable, provided they’re efficient. For example, Liquidator A might be profitable with a bonus of 3% by exchanging on-chain, but liquidator B might be profitable with a bonus of 1% by using a combination of on-chain and off-chain liquidity. Liquidator B would win the most liquidations since they can make a profit at a bonus that liquidator A would make a loss.
Another benefit of dynamic liquidation bonuses is that liquidated users will be penalized less for getting liquidated.
Trilinear Interest Rate Model
In Solend V2, the interest rate function is extended to be trilinear. This is in contrast to the implementation in V1, which is bilinear.
A gentle curve is better for offering less volatile rates while a steep curve is better for capturing the full range of market rates. A trilinear function allows for a better tradeoff between predictable rates and dealing with extreme market conditions.
Risk Authority
Solend V2 introduces an additional risk authority that has the ability to adjust risk parameters, only if the change results in the protocol being safer (e.g. lowering borrow limits, lowering LTVs). This allows different authorities to be custodied according to their sensitivity, rather than having just one authority responsible for everything. In particular, this risk authority has low enough sensitivity that it can be stored on a server to allow for automated configuration, for example automatically pausing borrows when anomalies are detected.
On-Chain Metadata
Solend V1 serves metadata from a backend, creating a centralized dependency to fetch things like pool names, descriptions, and lookup tables. Solend V2 stores metadata on-chain, reducing Solend’s dependency on a backend and improving decentralization.
Deprecated Asset Handling
Solend V2 introduces a permissionless crank that can only be called when a reserve is deprecated, which pushes deposits back to their depositor’s wallet. When a pool has been fully deprecated, there’s no reason for deposits to linger. This feature allows pools to be neatly sunset when deprecation is finished.
On-chain and Permissionless Liquidity Mining
Liquidity mining for Solend V2 is on-chain, eliminating the need for a backend. Since setup no longer requires custom work, liquidity mining can be opened up to permissionless pools as well. Permissionless pool owners will be able to set up liquidity mining incentives by interacting with the protocol directly. Since there’s no dependency on a backend, this makes Solend one step closer to decentralization.
Account Delegation
Solend V2 introduces the ability to assign a delegate to take actions on behalf of an account. One powerful use case is delegating a mobile wallet to be able to withdraw or borrow from an obligation, while the account’s private key remains securely stored (e.g. in cold storage).
Loss Socializing
For uninsured pools currently when underwater debt occurs it becomes a “race to exit” where the last person in the pool loses. loss socializing will allow pool operators to choose to socialize the losses so everyone loses a little bit instead of some people losing nothing and others losing all.
currently the way the ctoken redemption rate is calculated is taking the total available liquidity plus the total debt and dividing by the total ctoken minted quantity (see code below). but if some debt is underwater it will never get repaid. So during loss socialization debt can be forgiven and then subtracted from this equation.
# total supply for a reserve
total_supply = reserve.availible_amount + reserve.borrowed_amount - reserve.accumulated_protocol_fees
ctoken_exchange_rate = total_supply / reserve.mint_total_supply
# when loss socialization occurs on borrowed asset index i
reserve.borrowed_amount -= obligation.borrows[i].borrowed_amount
obligation.borrows[i].borrowed_amount = 0
as you can see in the above scenario reserve.borrowed_amount is decreased and thus ctoken_exchange_rate is decreased.
Conclusion
The features described in this litepaper are the result of trial by fire through a long year of Solana DeFi being pushed to its limits. With these features, similar tail events will be handled more gracefully in the future. The Solana DeFi ecosystem has been through a lot in the past year, but we’re confident it’ll bounce back stronger and more robust than before. The Solend team has been hard at work to make this a reality. We hope you enjoyed this reading, and are as excited as we are about Solend V2.