function batchRemove(bytes32[] memory _ids) external override {
_requireCallerIsCdpManager();
uint256 _len = _ids.length;
require(_len > 1, "SortedCdps: batchRemove() only apply to multiple cdpIds!");
bytes32 _firstPrev = data.nodes[_ids[0]].prevId;
bytes32 _lastNext = data.nodes[_ids[_len - 1]].nextId;
require(
_firstPrev != dummyId || _lastNext != dummyId,
"SortedCdps: batchRemove() leave ZERO node left!"
);
for (uint256 i = 0; i < _len; ++i) {
require(contains(_ids[i]), "SortedCdps: List does not contain the id");
}
// orphan nodes in between to save gas
if (_firstPrev != dummyId) {
data.nodes[_firstPrev].nextId = _lastNext;
} else {
data.head = _lastNext;
}
if (_lastNext != dummyId) {
data.nodes[_lastNext].prevId = _firstPrev;
} else {
data.tail = _firstPrev;
}
// delete node & owner storages to get gas refund
for (uint i = 0; i < _len; ++i) {
delete data.nodes[_ids[i]];
emit NodeRemoved(_ids[i]);
}
size = size - _len;
}
Function batchRemove(bytes32[] memory _ids) is used by CDP Manager to remove CDP IDs in batch. However, its assuming that _ids provided to remove are sorted in the same order as in the input array. If required ordering is not followed, it will also remove CDP's of the users which its not supposed to remove i.e., whose ID is not specified in _ids.
If this happened, it will remove CDP of some users, which then wont be able to claim their reward / collateral as their position is removed.
Impact
Since there is no validation on _ids to be in sorted order in the same way as in the input array, if its provided in out of order, it will remove some additional CDP's that its not intending to delete. Which will make those additional CDP position non existent from the system and the user belonging to those positions wont be able to claim their rewards / collateral back.
Recommendation
There should be a validation on _ids to be in a sorted order in the same way as in the input array. It can be done as below
function batchRemove(bytes32[] memory _ids) external override {
_requireCallerIsCdpManager();
uint256 _len = _ids.length;
require(_len > 1, "SortedCdps: batchRemove() only apply to multiple cdpIds!");
+ uint256 nicr_prev = cdpManager.getCachedNominalICR(cdps[0]);
+ uint256 nicr_next;
+ for (uint i = 1; i < cdps.length; i++) {
+ nicr_next = cdpManager.getCachedNominalICR(cdps[i]);
+ require(nicr_prev <= nicr_next, "SortedCdps: List should be sorted");
+ nicr_prev = nicr_next;
+ }
bytes32 _firstPrev = data.nodes[_ids[0]].prevId;
bytes32 _lastNext = data.nodes[_ids[_len - 1]].nextId;
require(
_firstPrev != dummyId || _lastNext != dummyId,
"SortedCdps: batchRemove() leave ZERO node left!"
);
for (uint256 i = 0; i < _len; ++i) {
require(contains(_ids[i]), "SortedCdps: List does not contain the id");
}
// orphan nodes in between to save gas
if (_firstPrev != dummyId) {
data.nodes[_firstPrev].nextId = _lastNext;
} else {
data.head = _lastNext;
}
if (_lastNext != dummyId) {
data.nodes[_lastNext].prevId = _firstPrev;
} else {
data.tail = _firstPrev;
}
// delete node & owner storages to get gas refund
for (uint i = 0; i < _len; ++i) {
delete data.nodes[_ids[i]];
emit NodeRemoved(_ids[i]);
}
size = size - _len;
}