protocol-manager-contract.redeem_collateral is supposed to work on troves with ICR >= MCR, but in current implementation, troves(whose ICR < MCR) might be redeemed.
Vulnerability Details
Quting from and the comment . We can see that in liquity's implementation, the redeem starts first trove with ICR >= MCR, which is also the lowest collateral ratio. This applies to the our project's code in [https://github.com/Hydrogen-Labs/fluid-protocol/blob/78ab7bdd243b414b424fca6e1eb144218f36a18a/contracts/protocol-manager-contract/src/main.sw#L326-L329]:
294 // Get information about all assets in the system
295 #[storage(read)]
296 fn get_all_assets_info() -> AssetInfo {
...
313 while (i < length) {
314 let oracle = abi(Oracle, asset_contracts.get(i).unwrap().oracle.into());
315 let trove_manager = abi(TroveManager, asset_contracts.get(i).unwrap().trove_manager.into());
316 let asset = assets.get(i).unwrap();
317 let price = oracle.get_price();
318 let mut current_borrower = sorted_troves.get_last(asset);
319 let mut current_cr = u64::max();
320 if (current_borrower != null_identity_address()) {
321 current_cr = trove_manager.get_current_icr(current_borrower, price);
322 }
323 prices.push(price);
324 system_debt.push(trove_manager.get_entire_system_debt());
325 redemption_totals.push(RedemptionTotals::default());
--->>> this code is used to find the first trove with ICR >= MCR
326 while (current_borrower != null_identity_address() && current_cr < MCR) {
327 current_borrower = sorted_troves.get_prev(current_borrower, asset);
328 current_cr = trove_manager.get_current_icr(current_borrower, price);
329 }
330 current_borrowers.push(current_borrower);
331 current_crs.push(current_cr);
332 i += 1;
333 }
...
343 }
Impact Details
Please consider the following case:
a new asset(TokenA) is added to the protocol by calling protocol-manager-contract.register_asset
A few users start to borrow USDF using TokenA as collateral with icr a little higher than MCR
TokenA's price has fluctuated, all the troves become liquidatable(icr < MCR).
in such case, when redeem_collateral is called, the liquidatable troves will be redeemed.
As the above output shows, when the user open a trove, the collateral's price is 1e9, and the icr is 1368159203. And then the price is changed to 0.9e9, the icr is 1231343283, but the trove can still be redeem.
we define a new price function to support 0.9e9 as price
However, in above code, there is a case that after the while loop in , the last trove's icr might be less than MCR,in such case, the trove(icr <MCR) still will be added to current_borrowers, and later might be redeemed.