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; ```
} ``` We can see that the test fails at: ``` failures: local_tests::scenarios::severity_squad_pocs::poc_not_liquidatable ``` because the position is not liquidatable.