l have previously tested this issue with a POC and it isn't valid. The system may keep empty nodes when partially redeeming and reInserting, but at the end of the function when this empty nodes are removed from the sorted list, the partially redeemed Cdp is on the correct place in the list.
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import "forge-std/Test.sol";
import {eBTCBaseInvariants} from "./BaseInvariants.sol";
contract checkSorting is eBTCBaseInvariants {
address wallet = address(0xbad455);
function setUp() public override {
super.setUp();
connectCoreContracts();
connectLQTYContractsToCore();
}
function testSorting() public {
uint256 grossColl = 11e18 + cdpManager.LIQUIDATOR_REWARD();
uint256 price = priceFeedMock.fetchPrice();
uint256 debtMCR = _utils.calculateBorrowAmount(
11e18,
price,
MINIMAL_COLLATERAL_RATIO
);
uint256 debtColl = _utils.calculateBorrowAmount(
11e18,
price,
COLLATERAL_RATIO
);
// Cdp with the biggest NICR and the head of the sorted list
bytes32 first = _openTestCDP(wallet, grossColl, debtColl - 10000);
// Cdp with second best NICR in the sorted list
bytes32 second = _openTestCDP(wallet, grossColl, debtColl - 7500);
// Cdp with second lowest NICR in the sorted list
bytes32 third = _openTestCDP(wallet, grossColl, debtColl - 5000);
// Cdp with the lowest NICR and the tail of the sorted list
bytes32 fourth = _openTestCDP(wallet, grossColl, debtMCR);
vm.startPrank(wallet);
// Check the TCR is above CCR
assert(cdpManager.getCachedTCR(price) > CCR);
// Lower the price so fourth node can go underwater
// and the third node is the first hint for redeeming
priceFeedMock.setPrice(price - 1e1);
// Extra check to ensure:
// Fourth node is underwater ICR < MCR
// Third node has ICR > MCR
assert(_getCachedICR(fourth) < MINIMAL_COLLATERAL_RATIO);
assert(_getCachedICR(third) > MINIMAL_COLLATERAL_RATIO);
(bytes32 hint, uint256 partialNICR, , ) = hintHelpers.getRedemptionHints((debtColl), priceFeedMock.fetchPrice(), 0);
// Check the hint is the third node in the sorted list
// Which is the node above the underwater Cdp
assert(hint == third);
// Approve cdp manager to use ebtc tokens
eBTCToken.approve(address(cdpManager), eBTCToken.balanceOf(wallet));
// Fully redeem the third node and partially redeem from the second node.
cdpManager.redeemCollateral(debtColl, hint, first, fourth, partialNICR, 0, 1e18);
// Check the third node is fully redeemed and no longer exists in the list
assert(!sortedCdps.contains(third));
// Check the lowest NICR node is still the fourth node
assert(fourth == sortedCdps.getLast());
// Check the biggest NICR node is still the first node
assert(first == sortedCdps.getFirst());
// Check the second node is correctly place before the first and fourth node
assert(second == sortedCdps.getNext(first));
assert(second == sortedCdps.getPrev(fourth));
// Giving wrong hints and keeping the empty nodes doesn't lead to anything
// In the end when both reInsert happens and the empty nodes are removed from the sorted list
// The partially redeemed nodes will still be placed correctly in the node list
vm.stopPrank();
}
}
Yeah, mathematically after partial redemption, the partially redeemed CDP might have a higher NICR.
In the above POC example, the output looks likes the following: