-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: Use last zombie trove first in a redemption sequence #426
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -111,6 +111,8 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { | |
// Array of all batch managers - used to fetch them off-chain | ||
address[] public batchIds; | ||
|
||
uint256 public lastZombieTroveId; | ||
|
||
// Error trackers for the trove redistribution calculation | ||
uint256 internal lastCollError_Redistribution; | ||
uint256 internal lastBoldDebtError_Redistribution; | ||
|
@@ -149,6 +151,7 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { | |
uint256 oldWeightedRecordedDebt; | ||
uint256 newWeightedRecordedDebt; | ||
uint256 newStake; | ||
bool isZombieTrove; | ||
LatestTroveData trove; | ||
LatestBatchData batch; | ||
} | ||
|
@@ -452,7 +455,7 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { | |
} | ||
|
||
function _isLiquidatableStatus(Status _status) internal pure returns (bool) { | ||
return _status == Status.active || _status == Status.unredeemable; | ||
return _status == Status.active || _status == Status.zombie; | ||
} | ||
|
||
function _batchLiquidateTroves( | ||
|
@@ -663,14 +666,26 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { | |
bool isTroveInBatch = _singleRedemption.batchAddress != address(0); | ||
uint256 newDebt = _applySingleRedemption(_defaultPool, _singleRedemption, isTroveInBatch); | ||
|
||
// Make Trove unredeemable if it's tiny, in order to prevent griefing future (normal, sequential) redemptions | ||
// Make Trove zombie if it's tiny (and it wasn’t already), in order to prevent griefing future (normal, sequential) redemptions | ||
if (newDebt < MIN_DEBT) { | ||
Troves[_singleRedemption.troveId].status = Status.unredeemable; | ||
if (isTroveInBatch) { | ||
sortedTroves.removeFromBatch(_singleRedemption.troveId); | ||
} else { | ||
sortedTroves.remove(_singleRedemption.troveId); | ||
if (!_singleRedemption.isZombieTrove) { | ||
Troves[_singleRedemption.troveId].status = Status.zombie; | ||
if (isTroveInBatch) { | ||
sortedTroves.removeFromBatch(_singleRedemption.troveId); | ||
} else { | ||
sortedTroves.remove(_singleRedemption.troveId); | ||
} | ||
// If it’s a partial redemption, let’s store a pointer to it so it’s used first in the next one | ||
if (newDebt > 0) { | ||
lastZombieTroveId = _singleRedemption.troveId; | ||
} | ||
} else if (newDebt == 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn’t say “unzombified”. It’s still zombie, but it doesn’t hold the pointer to be the first one in the next redemption sequence anymore. |
||
// Reset last zombie trove pointer if the previous one was fully redeemed now | ||
lastZombieTroveId = 0; | ||
} | ||
} else { | ||
// Reset last zombie trove pointer if the previous one ended up above min debt | ||
lastZombieTroveId = 0; | ||
} | ||
} | ||
|
||
|
@@ -730,15 +745,27 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { | |
uint256 remainingBold = _boldamount; | ||
|
||
SingleRedemptionValues memory singleRedemption; | ||
singleRedemption.troveId = sortedTrovesCached.getLast(); | ||
// Let’s check if there’s a pending zombie trove from previous redemption | ||
if (lastZombieTroveId != 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can never have a valid troveId == 0, right? |
||
singleRedemption.troveId = lastZombieTroveId; | ||
singleRedemption.isZombieTrove = true; | ||
} else { | ||
singleRedemption.troveId = sortedTrovesCached.getLast(); | ||
} | ||
address lastBatchUpdatedInterest = address(0); | ||
|
||
// Loop through the Troves starting from the one with lowest collateral ratio until _amount of Bold is exchanged for collateral | ||
if (_maxIterations == 0) _maxIterations = type(uint256).max; | ||
while (singleRedemption.troveId != 0 && remainingBold > 0 && _maxIterations > 0) { | ||
_maxIterations--; | ||
// Save the uint256 of the Trove preceding the current one | ||
uint256 nextUserToCheck = sortedTrovesCached.getPrev(singleRedemption.troveId); | ||
uint256 nextUserToCheck; | ||
if (singleRedemption.isZombieTrove) { | ||
nextUserToCheck = sortedTrovesCached.getLast(); | ||
} else { | ||
nextUserToCheck = sortedTrovesCached.getPrev(singleRedemption.troveId); | ||
} | ||
|
||
// Skip if ICR < 100%, to make sure that redemptions always improve the CR of hit Troves | ||
if (getCurrentICR(singleRedemption.troveId, _price) < _100pct) { | ||
singleRedemption.troveId = nextUserToCheck; | ||
|
@@ -769,6 +796,7 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { | |
|
||
remainingBold -= singleRedemption.boldLot; | ||
singleRedemption.troveId = nextUserToCheck; | ||
singleRedemption.isZombieTrove = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We only need to do this if it was a |
||
} | ||
|
||
// We are removing this condition to prevent blocking redemptions | ||
|
@@ -811,7 +839,7 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { | |
bool isTroveInBatch = _singleRedemption.batchAddress != address(0); | ||
_applySingleRedemption(_defaultPool, _singleRedemption, isTroveInBatch); | ||
|
||
// No need to make this Trove unredeemable if it has tiny debt, since: | ||
// No need to make this Trove zombie if it has tiny debt, since: | ||
// - This collateral branch has shut down and urgent redemptions are enabled | ||
// - Urgent redemptions aren't sequential, so they can't be griefed by tiny Troves. | ||
} | ||
|
@@ -1284,6 +1312,9 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { | |
function setTroveStatusToActive(uint256 _troveId) external { | ||
_requireCallerIsBorrowerOperations(); | ||
Troves[_troveId].status = Status.active; | ||
if (lastZombieTroveId == _troveId) { | ||
lastZombieTroveId = 0; | ||
} | ||
} | ||
|
||
function onAdjustTroveInterestRate( | ||
|
@@ -1436,6 +1467,8 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { | |
if (_batchAddress != address(0)) { | ||
if (trove.status == Status.active) { | ||
sortedTroves.removeFromBatch(_troveId); | ||
} else if (trove.status == Status.zombie && lastZombieTroveId == _troveId) { | ||
lastZombieTroveId = 0; | ||
} | ||
|
||
_removeTroveSharesFromBatch( | ||
|
@@ -1450,6 +1483,8 @@ contract TroveManager is LiquityBase, ITroveManager, ITroveEvents { | |
} else { | ||
if (trove.status == Status.active) { | ||
sortedTroves.remove(_troveId); | ||
} else if (trove.status == Status.zombie && lastZombieTroveId == _troveId) { | ||
lastZombieTroveId = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this logic twice? Could we do it once at the end? |
||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"last" here refers to the last Trove that was redeemed to below MIN_DEBT right?