Skip to content

Commit 1a0f12a

Browse files
authored
fix: update isolated debt (#179)
Currently repay & liquidationCall use the same codePath for updating isolated debt. This is problematic, as: 1. is simply wrong, given that on liquidationCall the function is called **after** removing the collateral flag 2. is inefficient. While on repay we don't know the collateral backing the debt - a lookup makes sense. On liquidationCall on the other hand, we already know the collateral of the user. Probably on repay the codepath could also be optimized, but as this is a 3.2/3.3 patch let's keep the code changes minimal.
1 parent 3bdd8c7 commit 1a0f12a

4 files changed

Lines changed: 62 additions & 37 deletions

File tree

snapshots/Pool.Operations.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"borrow: first borrow->borrowingEnabled": "256480",
33
"borrow: recurrent borrow": "248834",
4-
"liquidationCall: deficit on liquidated asset": "392439",
5-
"liquidationCall: deficit on liquidated asset + other asset": "491810",
6-
"liquidationCall: full liquidation": "392439",
7-
"liquidationCall: full liquidation and receive ATokens": "368797",
8-
"liquidationCall: partial liquidation": "383954",
9-
"liquidationCall: partial liquidation and receive ATokens": "360308",
4+
"liquidationCall: deficit on liquidated asset": "392180",
5+
"liquidationCall: deficit on liquidated asset + other asset": "491819",
6+
"liquidationCall: full liquidation": "392180",
7+
"liquidationCall: full liquidation and receive ATokens": "368538",
8+
"liquidationCall: partial liquidation": "382981",
9+
"liquidationCall: partial liquidation and receive ATokens": "359335",
1010
"repay: full repay": "176521",
1111
"repay: full repay with ATokens": "173922",
1212
"repay: partial repay": "189949",

src/contracts/protocol/libraries/logic/IsolationModeLogic.sol

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ library IsolationModeLogic {
2020
event IsolationModeTotalDebtUpdated(address indexed asset, uint256 totalDebt);
2121

2222
/**
23-
* @notice updated the isolated debt whenever a position collateralized by an isolated asset is repaid or liquidated
23+
* @notice updated the isolated debt whenever a position collateralized by an isolated asset is repaid
2424
* @param reservesData The state of all the reserves
2525
* @param reservesList The addresses of all the active reserves
2626
* @param userConfig The user configuration mapping
@@ -38,27 +38,43 @@ library IsolationModeLogic {
3838
.getIsolationModeState(reservesData, reservesList);
3939

4040
if (isolationModeActive) {
41-
uint128 isolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
42-
.isolationModeTotalDebt;
41+
updateIsolatedDebt(reservesData, reserveCache, repayAmount, isolationModeCollateralAddress);
42+
}
43+
}
44+
45+
/**
46+
* @notice updated the isolated debt whenever a position collateralized by an isolated asset is liquidated
47+
* @param reservesData The state of all the reserves
48+
* @param reserveCache The cached data of the reserve
49+
* @param repayAmount The amount being repaid
50+
* @param isolationModeCollateralAddress The address of the isolated collateral
51+
*/
52+
function updateIsolatedDebt(
53+
mapping(address => DataTypes.ReserveData) storage reservesData,
54+
DataTypes.ReserveCache memory reserveCache,
55+
uint256 repayAmount,
56+
address isolationModeCollateralAddress
57+
) internal {
58+
uint128 isolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
59+
.isolationModeTotalDebt;
4360

44-
uint128 isolatedDebtRepaid = (repayAmount /
45-
10 **
46-
(reserveCache.reserveConfiguration.getDecimals() -
47-
ReserveConfiguration.DEBT_CEILING_DECIMALS)).toUint128();
61+
uint128 isolatedDebtRepaid = (repayAmount /
62+
10 **
63+
(reserveCache.reserveConfiguration.getDecimals() -
64+
ReserveConfiguration.DEBT_CEILING_DECIMALS)).toUint128();
4865

49-
// since the debt ceiling does not take into account the interest accrued, it might happen that amount
50-
// repaid > debt in isolation mode
51-
if (isolationModeTotalDebt <= isolatedDebtRepaid) {
52-
reservesData[isolationModeCollateralAddress].isolationModeTotalDebt = 0;
53-
emit IsolationModeTotalDebtUpdated(isolationModeCollateralAddress, 0);
54-
} else {
55-
uint256 nextIsolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
56-
.isolationModeTotalDebt = isolationModeTotalDebt - isolatedDebtRepaid;
57-
emit IsolationModeTotalDebtUpdated(
58-
isolationModeCollateralAddress,
59-
nextIsolationModeTotalDebt
60-
);
61-
}
66+
// since the debt ceiling does not take into account the interest accrued, it might happen that amount
67+
// repaid > debt in isolation mode
68+
if (isolationModeTotalDebt <= isolatedDebtRepaid) {
69+
reservesData[isolationModeCollateralAddress].isolationModeTotalDebt = 0;
70+
emit IsolationModeTotalDebtUpdated(isolationModeCollateralAddress, 0);
71+
} else {
72+
uint256 nextIsolationModeTotalDebt = reservesData[isolationModeCollateralAddress]
73+
.isolationModeTotalDebt = isolationModeTotalDebt - isolatedDebtRepaid;
74+
emit IsolationModeTotalDebtUpdated(
75+
isolationModeCollateralAddress,
76+
nextIsolationModeTotalDebt
77+
);
6278
}
6379
}
6480
}

src/contracts/protocol/libraries/logic/LiquidationLogic.sol

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -367,16 +367,17 @@ library LiquidationLogic {
367367
hasNoCollateralLeft
368368
);
369369

370-
// IsolationModeTotalDebt only discounts `actualDebtToLiquidate`, not the fully burned amount in case of deficit creation.
371-
// This is by design as otherwise debt debt ceiling would render ineffective if a collateral asset faces bad debt events.
372-
// The governance can decide the raise the ceiling to discount manifested deficit.
373-
IsolationModeLogic.updateIsolatedDebtIfIsolated(
374-
reservesData,
375-
reservesList,
376-
userConfig,
377-
vars.debtReserveCache,
378-
vars.actualDebtToLiquidate
379-
);
370+
if (collateralReserve.configuration.getDebtCeiling() != 0) {
371+
// IsolationModeTotalDebt only discounts `actualDebtToLiquidate`, not the fully burned amount in case of deficit creation.
372+
// This is by design as otherwise debt debt ceiling would render ineffective if a collateral asset faces bad debt events.
373+
// The governance can decide the raise the ceiling to discount manifested deficit.
374+
IsolationModeLogic.updateIsolatedDebt(
375+
reservesData,
376+
vars.debtReserveCache,
377+
vars.actualDebtToLiquidate,
378+
params.collateralAsset
379+
);
380+
}
380381

381382
if (params.receiveAToken) {
382383
_liquidateATokens(reservesData, reservesList, usersConfig, collateralReserve, params, vars);

tests/protocol/pool/Pool.Liquidations.t.sol

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,9 @@ contract PoolLiquidationTests is TestnetProcedures {
598598
liquidatorBalanceBefore = IERC20(params.collateralAsset).balanceOf(bob);
599599
}
600600

601+
vm.expectEmit();
602+
emit IsolationModeTotalDebtUpdated(params.collateralAsset, 0);
603+
601604
vm.expectEmit(address(contracts.poolProxy));
602605
emit LiquidationLogic.LiquidationCall(
603606
params.collateralAsset,
@@ -608,7 +611,6 @@ contract PoolLiquidationTests is TestnetProcedures {
608611
bob,
609612
params.receiveAToken
610613
);
611-
612614
// Liquidate
613615
vm.prank(bob);
614616
contracts.poolProxy.liquidationCall(
@@ -677,6 +679,12 @@ contract PoolLiquidationTests is TestnetProcedures {
677679
);
678680
params.receiveAToken = true;
679681

682+
vm.expectEmit(true, true, false, false);
683+
emit IsolationModeTotalDebtUpdated(
684+
params.collateralAsset,
685+
((borrowAmount - params.actualDebtToLiquidate) / 1e4)
686+
);
687+
680688
// Liquidate
681689
vm.prank(alice);
682690
contracts.poolProxy.liquidationCall(

0 commit comments

Comments
 (0)