Skip to content

Commit 7fea84a

Browse files
committed
fix: Prevent underflow in liquidations due to smalll offset
1 parent a16a42b commit 7fea84a

File tree

3 files changed

+36
-14
lines changed

3 files changed

+36
-14
lines changed

packages/contracts/contracts/Interfaces/IStabilityPool.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ interface IStabilityPool {
158158
*/
159159
function getTotalLUSDDeposits() external view returns (uint);
160160

161+
/*
162+
* Returns the max amount of LUSD held in the pool that can be used for liquidations.
163+
* It makes sure that at least 1 LUSD remains.
164+
* If the max amount is used, it makes sure it won’t revert by underflow due to the accumulated offset error.
165+
*/
166+
function getMaxAmountToOffset() external view returns (uint);
167+
161168
/*
162169
* Calculates the ETH gain earned by the deposit since its last snapshots were taken.
163170
*/

packages/contracts/contracts/StabilityPool.sol

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,20 @@ contract StabilityPool is LiquityBase, Ownable, CheckContract, IStabilityPool {
492492

493493
// --- Liquidation functions ---
494494

495+
function getMaxAmountToOffset() external view override returns (uint) {
496+
uint totalLUSD = totalLUSDDeposits; // cache
497+
// - If the SP has total deposits >= 1e18, we leave 1e18 in it untouched.
498+
// - If it has 0 < x < 1e18 total deposits, we leave x in it.
499+
uint256 lusdToLeaveInSP = LiquityMath._min(MIN_LUSD_IN_SP, totalLUSD);
500+
uint LUSDInSPForOffsets = totalLUSD - lusdToLeaveInSP; // safe, for the line above
501+
// Let’s avoid underflow in case of a tiny offset
502+
if (LUSDInSPForOffsets.mul(DECIMAL_PRECISION) <= lastLUSDLossError_Offset) {
503+
LUSDInSPForOffsets = 0;
504+
}
505+
506+
return LUSDInSPForOffsets;
507+
}
508+
495509
/*
496510
* Cancels out the specified debt against the LUSD contained in the Stability Pool (as far as possible)
497511
* and transfers the Trove's ETH collateral from ActivePool to StabilityPool.
@@ -536,7 +550,19 @@ contract StabilityPool is LiquityBase, Ownable, CheckContract, IStabilityPool {
536550
uint ETHNumerator = _collToAdd.mul(DECIMAL_PRECISION).add(lastETHError_Offset);
537551

538552
assert(_debtToOffset < _totalLUSDDeposits);
539-
uint LUSDLossNumerator = _debtToOffset.mul(DECIMAL_PRECISION).sub(lastLUSDLossError_Offset);
553+
uint LUSDLossNumerator;
554+
/* Let’s avoid underflow in case of a small offset
555+
* Per getMaxAmountToOffset, if the max used, this will never happen.
556+
* If the max is not used, then offset value is at least MN_NET_DEBT,
557+
* which means that total LUSD deposits when error was produced was around 2e21 LUSD.
558+
* See: https://github.com/liquity/dev/pull/417#issuecomment-805721292
559+
* As we are doing floor + 1 in the division, it will still offset something
560+
*/
561+
if (_debtToOffset.mul(DECIMAL_PRECISION) <= lastLUSDLossError_Offset) {
562+
LUSDLossNumerator = 0;
563+
} else {
564+
LUSDLossNumerator = _debtToOffset.mul(DECIMAL_PRECISION).sub(lastLUSDLossError_Offset);
565+
}
540566
/*
541567
* Add 1 to make error in quotient positive. We want "slightly too much" LUSD loss,
542568
* which ensures the error in any given compoundedLUSDDeposit favors the Stability Pool.

packages/contracts/contracts/TroveManager.sol

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@ contract TroveManager is LiquityBase, Ownable, CheckContract, ITroveManager {
3838

3939
// --- Data structures ---
4040

41-
// From StabilityPool
42-
uint constant internal MIN_LUSD_IN_SP = 1e18;
43-
4441
uint constant public SECONDS_IN_ONE_MINUTE = 60;
4542
/*
4643
* Half-life of 12h. 12h = 720 min
@@ -512,11 +509,7 @@ contract TroveManager is LiquityBase, Ownable, CheckContract, ITroveManager {
512509
LiquidationTotals memory totals;
513510

514511
vars.price = priceFeed.fetchPrice();
515-
// - If the SP has total deposits >= 1e18, we leave 1e18 in it untouched.
516-
// - If it has 0 < x < 1e18 total deposits, we leave x in it.
517-
uint256 totalLUSDDeposits = stabilityPoolCached.getTotalLUSDDeposits();
518-
uint256 lusdToLeaveInSP = LiquityMath._min(MIN_LUSD_IN_SP, totalLUSDDeposits);
519-
vars.LUSDInSPForOffsets = totalLUSDDeposits - lusdToLeaveInSP; // safe, for the line above
512+
vars.LUSDInSPForOffsets = stabilityPoolCached.getMaxAmountToOffset();
520513
vars.recoveryModeAtStart = _checkRecoveryMode(vars.price);
521514

522515
// Perform the appropriate liquidation sequence - tally the values, and obtain their totals
@@ -658,11 +651,7 @@ contract TroveManager is LiquityBase, Ownable, CheckContract, ITroveManager {
658651
LiquidationTotals memory totals;
659652

660653
vars.price = priceFeed.fetchPrice();
661-
// - If the SP has total deposits >= 1e18, we leave 1e18 in it untouched.
662-
// - If it has 0 < x < 1e18 total deposits, we leave x in it.
663-
uint256 totalLUSDDeposits = stabilityPoolCached.getTotalLUSDDeposits();
664-
uint256 lusdToLeaveInSP = LiquityMath._min(MIN_LUSD_IN_SP, totalLUSDDeposits);
665-
vars.LUSDInSPForOffsets = totalLUSDDeposits - lusdToLeaveInSP; // safe, for the line above
654+
vars.LUSDInSPForOffsets = stabilityPoolCached.getMaxAmountToOffset();
666655
vars.recoveryModeAtStart = _checkRecoveryMode(vars.price);
667656

668657
// Perform the appropriate liquidation sequence - tally values and obtain their totals.

0 commit comments

Comments
 (0)