#35758 [SC-Critical] Loss of yield to the protocol due to incorrect interest rate applied

Submitted on Oct 6th 2024 at 21:02:19 UTC by @SeveritySquad for IOP | Swaylend

  • Report ID: #35758

  • Report Type: Smart Contract

  • Report severity: Critical

  • Target: https://github.com/Swaylend/swaylend-monorepo/blob/develop/contracts/market/src/main.sw

  • Impacts:

    • Theft of unclaimed yield

    • Permanent freezing of unclaimed yield

    • Loss of yield

Description

Brief/Intro

When a liquidator wants to liquidate a position he calls the `absorb()` method. In that method, the position of a given user is checked if it is liquidatable. This check is done however erroneously with Supply Rate rather than Borrow Rate. This prevents the liquidation to happen until the collateral value drops in value more, hence when it can finally be liquidated, the yield that the protocol earns from buying the collateral is less than it should.

Vulnerability Details

The problem lies in the `is_liquidatable_internal()` function, in how the present value is calculated here: ``` let present: u256 = present_value(principal.wrapping_neg()).try_into().unwrap(); ``` The supplied `principal` value to the `present_value()` is turned from negative to positive. If the value is positive then inside `present_value` the Supply Rate is applied instead of Borrow Rate: ``` fn present_value(principal: I256) -> I256 { let market_basic = storage.market_basic.read(); if principal >= I256::zero() { let present_value = present_value_supply( market_basic .base_supply_index, principal .try_into() .unwrap(), ); I256::try_from(present_value).unwrap() } else { let present_value = present_value_borrow( market_basic .base_borrow_index, principal .wrapping_neg() .try_into() .unwrap(), ); I256::neg_try_from(present_value).unwrap() } } ``` And Supply Rate is always smaller than Borrow Rate. This means that the liquidatee `present` value is lower when compared against the collateral value, hence the collateral must drop in value even more to reach the liquidation threshold.

Impact Details

Due to lower rate (Supply Rate instead of Borrow Rate) applied to calculate the `present` value of a position the liquidation can happen only when the collateral drops more in price. The result is that the amount that the protocol receives from liquidation is lower than it should according to the Borrow Rate. The difference is lost to the protocol and the lenders in terms of yield that is not obtained, hence we chose the impact to be High according the Impacts in Scope.

Solution Proposal

The Present value should be calculated as it is done in `is_borrow_collateralized()`. The following line: ``` let present: u256 = present_value(principal.wrapping_neg()).try_into().unwrap(); ``` should be changed to: ``` let present = present_value(principal); ``` and the following line: ``` let borrow_amount = present * base_token_price / base_token_price_scale; ``` to: ``` let borrow_amount = u256::try_from(present.wrapping_neg()).unwrap() * base_token_price / base_token_price_scale; ```

References

Problematic line: https://github.com/Swaylend/swaylend-monorepo/blob/d7fec5cd27bafa4b0d04a0690e71a2751fb66979/contracts/market/src/main.sw#L1379

Proof of Concept

Proof of Concept

The PoC: ``` #[tokio::test] async fn poc_not_liquidatable() { let TestData { wallets, alice, alice_account, bob, bob_account, chad, market, assets, usdc, eth, oracle, price_feed_ids, publish_time, prices, usdc_contract, .. } = setup().await;

} ``` We can see that the test fails at: ``` failures: local_tests::scenarios::severity_squad_pocs::poc_not_liquidatable ``` because the position is not liquidatable.

Last updated

Was this helpful?