Skip to content

Commit 520c307

Browse files
authored
feat: emit all user state changes on LiquidationCall event (#992)
1 parent db53919 commit 520c307

File tree

10 files changed

+396
-219
lines changed

10 files changed

+396
-219
lines changed

snapshots/Spoke.Operations.ZeroRiskPremium.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"borrow: first": "187966",
33
"borrow: second action, same reserve": "167938",
4-
"liquidationCall (receiveShares): full": "294802",
5-
"liquidationCall (receiveShares): partial": "293124",
6-
"liquidationCall: full": "302109",
7-
"liquidationCall: partial": "300431",
4+
"liquidationCall (receiveShares): full": "296660",
5+
"liquidationCall (receiveShares): partial": "294982",
6+
"liquidationCall: full": "303967",
7+
"liquidationCall: partial": "302289",
88
"permitReserve + repay (multicall)": "196047",
99
"permitReserve + supply (multicall)": "173494",
1010
"permitReserve + supply + enable collateral (multicall)": "216808",

snapshots/Spoke.Operations.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"borrow: first": "257929",
33
"borrow: second action, same reserve": "200901",
4-
"liquidationCall (receiveShares): full": "327985",
5-
"liquidationCall (receiveShares): partial": "314827",
6-
"liquidationCall: full": "335292",
7-
"liquidationCall: partial": "322134",
4+
"liquidationCall (receiveShares): full": "329843",
5+
"liquidationCall (receiveShares): partial": "316685",
6+
"liquidationCall: full": "337150",
7+
"liquidationCall: partial": "323992",
88
"permitReserve + repay (multicall)": "195488",
99
"permitReserve + supply (multicall)": "173494",
1010
"permitReserve + supply + enable collateral (multicall)": "216808",

src/spoke/interfaces/ISpokeBase.sol

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,23 @@ interface ISpokeBase {
7171
/// @param debtReserveId The identifier of the reserve to be repaid with the liquidation.
7272
/// @param user The address of the borrower getting liquidated.
7373
/// @param debtToLiquidate The debt amount of borrowed reserve to be liquidated.
74+
/// @param drawnSharesToLiquidate The amount of drawn shares to be liquidated.
75+
/// @param premiumDelta A struct representing the changes to premium debt after liquidation.
7476
/// @param collateralToLiquidate The total amount of collateral asset to be liquidated, inclusive of liquidation fee.
77+
/// @param collateralSharesToLiquidate The total amount of collateral shares to liquidate.
78+
/// @param collateralSharesToLiquidator The amount of collateral shares that the liquidator received.
7579
/// @param liquidator The address of the liquidator.
76-
/// @param receiveShares True if the liquidator receives collateral in supplied shares rather than underlying assets.
80+
/// @param receiveShares True if the liquidator received collateral in supplied shares rather than underlying assets.
7781
event LiquidationCall(
7882
uint256 indexed collateralReserveId,
7983
uint256 indexed debtReserveId,
8084
address indexed user,
8185
uint256 debtToLiquidate,
86+
uint256 drawnSharesToLiquidate,
87+
IHubBase.PremiumDelta premiumDelta,
8288
uint256 collateralToLiquidate,
89+
uint256 collateralSharesToLiquidate,
90+
uint256 collateralSharesToLiquidator,
8391
address liquidator,
8492
bool receiveShares
8593
);

src/spoke/libraries/LiquidationLogic.sol

Lines changed: 91 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,19 @@ library LiquidationLogic {
116116
bool receiveShares;
117117
}
118118

119+
struct LiquidateUserVars {
120+
uint256 collateralReserveBalance;
121+
uint256 collateralToLiquidate;
122+
uint256 collateralToLiquidator;
123+
uint256 debtToLiquidate;
124+
bool isCollateralPositionEmpty;
125+
uint256 collateralSharesToLiquidate;
126+
uint256 collateralSharesToLiquidator;
127+
bool isDebtPositionEmpty;
128+
uint256 drawnSharesToLiquidate;
129+
IHubBase.PremiumDelta premiumDelta;
130+
}
131+
119132
// see ISpoke.HEALTH_FACTOR_LIQUIDATION_THRESHOLD docs
120133
uint64 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18;
121134

@@ -140,7 +153,9 @@ library LiquidationLogic {
140153
ISpoke.DynamicReserveConfig storage collateralDynConfig,
141154
LiquidateUserParams memory params
142155
) external returns (bool) {
143-
uint256 collateralReserveBalance = collateralReserve.hub.previewRemoveByShares(
156+
LiquidateUserVars memory vars;
157+
158+
vars.collateralReserveBalance = collateralReserve.hub.previewRemoveByShares(
144159
collateralReserve.assetId,
145160
positions[params.user][params.collateralReserveId].suppliedShares
146161
);
@@ -158,58 +173,62 @@ library LiquidationLogic {
158173
healthFactor: params.healthFactor,
159174
collateralReserveId: params.collateralReserveId,
160175
collateralFactor: collateralDynConfig.collateralFactor,
161-
collateralReserveBalance: collateralReserveBalance,
176+
collateralReserveBalance: vars.collateralReserveBalance,
162177
debtReserveBalance: params.drawnDebt + params.premiumDebt,
163178
receiveShares: params.receiveShares
164179
})
165180
);
166181

167182
(
168-
uint256 collateralToLiquidate,
169-
uint256 collateralToLiquidator,
170-
uint256 debtToLiquidate
183+
vars.collateralToLiquidate,
184+
vars.collateralToLiquidator,
185+
vars.debtToLiquidate
171186
) = _calculateLiquidationAmounts(
172-
CalculateLiquidationAmountsParams({
173-
healthFactorForMaxBonus: liquidationConfig.healthFactorForMaxBonus,
174-
liquidationBonusFactor: liquidationConfig.liquidationBonusFactor,
175-
targetHealthFactor: liquidationConfig.targetHealthFactor,
176-
debtReserveBalance: params.drawnDebt + params.premiumDebt,
177-
collateralReserveBalance: collateralReserveBalance,
178-
debtToCover: params.debtToCover,
179-
totalDebtValue: params.totalDebtValue,
180-
healthFactor: params.healthFactor,
181-
maxLiquidationBonus: collateralDynConfig.maxLiquidationBonus,
182-
collateralFactor: collateralDynConfig.collateralFactor,
183-
liquidationFee: collateralDynConfig.liquidationFee,
184-
debtAssetPrice: IAaveOracle(params.oracle).getReservePrice(params.debtReserveId),
185-
debtAssetDecimals: debtReserve.decimals,
186-
collateralAssetPrice: IAaveOracle(params.oracle).getReservePrice(
187-
params.collateralReserveId
188-
),
189-
collateralAssetDecimals: collateralReserve.decimals
190-
})
191-
);
187+
CalculateLiquidationAmountsParams({
188+
healthFactorForMaxBonus: liquidationConfig.healthFactorForMaxBonus,
189+
liquidationBonusFactor: liquidationConfig.liquidationBonusFactor,
190+
targetHealthFactor: liquidationConfig.targetHealthFactor,
191+
debtReserveBalance: params.drawnDebt + params.premiumDebt,
192+
collateralReserveBalance: vars.collateralReserveBalance,
193+
debtToCover: params.debtToCover,
194+
totalDebtValue: params.totalDebtValue,
195+
healthFactor: params.healthFactor,
196+
maxLiquidationBonus: collateralDynConfig.maxLiquidationBonus,
197+
collateralFactor: collateralDynConfig.collateralFactor,
198+
liquidationFee: collateralDynConfig.liquidationFee,
199+
debtAssetPrice: IAaveOracle(params.oracle).getReservePrice(params.debtReserveId),
200+
debtAssetDecimals: debtReserve.decimals,
201+
collateralAssetPrice: IAaveOracle(params.oracle).getReservePrice(
202+
params.collateralReserveId
203+
),
204+
collateralAssetDecimals: collateralReserve.decimals
205+
})
206+
);
192207

193-
bool isCollateralPositionEmpty = _liquidateCollateral(
208+
(
209+
vars.collateralSharesToLiquidate,
210+
vars.collateralSharesToLiquidator,
211+
vars.isCollateralPositionEmpty
212+
) = _liquidateCollateral(
194213
collateralReserve,
195214
positions,
196215
LiquidateCollateralParams({
197216
collateralReserveId: params.collateralReserveId,
198-
collateralToLiquidate: collateralToLiquidate,
199-
collateralToLiquidator: collateralToLiquidator,
217+
collateralToLiquidate: vars.collateralToLiquidate,
218+
collateralToLiquidator: vars.collateralToLiquidator,
200219
liquidator: params.liquidator,
201220
user: params.user,
202221
receiveShares: params.receiveShares
203222
})
204223
);
205224

206-
bool isDebtPositionEmpty = _liquidateDebt(
225+
(vars.drawnSharesToLiquidate, vars.premiumDelta, vars.isDebtPositionEmpty) = _liquidateDebt(
207226
debtReserve,
208227
positions[params.user][params.debtReserveId],
209228
positionStatus[params.user],
210229
LiquidateDebtParams({
211230
debtReserveId: params.debtReserveId,
212-
debtToLiquidate: debtToLiquidate,
231+
debtToLiquidate: vars.debtToLiquidate,
213232
premiumDebt: params.premiumDebt,
214233
accruedPremium: params.accruedPremium,
215234
liquidator: params.liquidator,
@@ -221,16 +240,20 @@ library LiquidationLogic {
221240
params.collateralReserveId,
222241
params.debtReserveId,
223242
params.user,
224-
debtToLiquidate,
225-
collateralToLiquidate,
243+
vars.debtToLiquidate,
244+
vars.drawnSharesToLiquidate,
245+
vars.premiumDelta,
246+
vars.collateralToLiquidate,
247+
vars.collateralSharesToLiquidate,
248+
vars.collateralSharesToLiquidator,
226249
params.liquidator,
227250
params.receiveShares
228251
);
229252

230253
return
231254
_evaluateDeficit({
232-
isCollateralPositionEmpty: isCollateralPositionEmpty,
233-
isDebtPositionEmpty: isDebtPositionEmpty,
255+
isCollateralPositionEmpty: vars.isCollateralPositionEmpty,
256+
isDebtPositionEmpty: vars.isDebtPositionEmpty,
234257
activeCollateralCount: params.activeCollateralCount,
235258
borrowedCount: params.borrowedCount
236259
});
@@ -277,21 +300,22 @@ library LiquidationLogic {
277300
}
278301

279302
/// @dev Invoked by `liquidateUser` method.
303+
/// @return The total amount of collateral shares to be liquidated.
304+
/// @return The amount of collateral shares that the liquidator receives.
280305
/// @return True if the user collateral position becomes empty after removing.
281306
function _liquidateCollateral(
282307
ISpoke.Reserve storage collateralReserve,
283308
mapping(address user => mapping(uint256 reserveId => ISpoke.UserPosition)) storage positions,
284309
LiquidateCollateralParams memory params
285-
) internal returns (bool) {
310+
) internal returns (uint256, uint256, bool) {
286311
ISpoke.UserPosition storage collateralPosition = positions[params.user][
287312
params.collateralReserveId
288313
];
289314
IHubBase hub = collateralReserve.hub;
290315
uint256 assetId = collateralReserve.assetId;
291316

292317
uint256 sharesToLiquidate = hub.previewRemoveByAssets(assetId, params.collateralToLiquidate);
293-
uint120 suppliedShares = collateralPosition.suppliedShares - sharesToLiquidate.toUint120();
294-
collateralPosition.suppliedShares = suppliedShares;
318+
uint120 userSuppliedShares = collateralPosition.suppliedShares - sharesToLiquidate.toUint120();
295319

296320
uint256 sharesToLiquidator;
297321
if (params.collateralToLiquidator > 0) {
@@ -306,52 +330,55 @@ library LiquidationLogic {
306330
}
307331
}
308332

333+
collateralPosition.suppliedShares = userSuppliedShares;
334+
309335
if (sharesToLiquidate > sharesToLiquidator) {
310336
hub.payFeeShares(assetId, sharesToLiquidate.uncheckedSub(sharesToLiquidator));
311337
}
312338

313-
return suppliedShares == 0;
339+
return (sharesToLiquidate, sharesToLiquidator, userSuppliedShares == 0);
314340
}
315341

316342
/// @dev Invoked by `liquidateUser` method.
343+
/// @return The amount of drawn shares to be liquidated.
344+
/// @return A struct representing the changes to premium debt after liquidation.
317345
/// @return True if the debt position becomes zero after restoring.
318346
function _liquidateDebt(
319347
ISpoke.Reserve storage debtReserve,
320348
ISpoke.UserPosition storage debtPosition,
321349
ISpoke.PositionStatus storage positionStatus,
322350
LiquidateDebtParams memory params
323-
) internal returns (bool) {
324-
{
325-
uint256 premiumDebtToLiquidate = params.premiumDebt.min(params.debtToLiquidate);
326-
uint256 drawnDebtToLiquidate = params.debtToLiquidate - premiumDebtToLiquidate;
327-
328-
IHubBase.PremiumDelta memory premiumDelta = IHubBase.PremiumDelta({
329-
sharesDelta: -debtPosition.premiumShares.toInt256(),
330-
offsetDelta: -debtPosition.premiumOffset.toInt256(),
331-
realizedDelta: params.accruedPremium.toInt256() - premiumDebtToLiquidate.toInt256()
332-
});
351+
) internal returns (uint256, IHubBase.PremiumDelta memory, bool) {
352+
uint256 premiumDebtToLiquidate = params.premiumDebt.min(params.debtToLiquidate);
353+
uint256 drawnDebtToLiquidate = params.debtToLiquidate - premiumDebtToLiquidate;
354+
355+
IHubBase.PremiumDelta memory premiumDelta = IHubBase.PremiumDelta({
356+
sharesDelta: -debtPosition.premiumShares.toInt256(),
357+
offsetDelta: -debtPosition.premiumOffset.toInt256(),
358+
realizedDelta: params.accruedPremium.toInt256() - premiumDebtToLiquidate.toInt256()
359+
});
333360

334-
debtReserve.underlying.safeTransferFrom(
335-
params.liquidator,
336-
address(debtReserve.hub),
337-
drawnDebtToLiquidate + premiumDebtToLiquidate
338-
);
339-
uint256 drawnSharesLiquidated = debtReserve.hub.restore(
340-
debtReserve.assetId,
341-
drawnDebtToLiquidate,
342-
premiumDebtToLiquidate,
343-
premiumDelta
344-
);
345-
debtPosition.settlePremiumDebt(premiumDelta.realizedDelta);
346-
debtPosition.drawnShares -= drawnSharesLiquidated.toUint120();
347-
}
361+
debtReserve.underlying.safeTransferFrom(
362+
params.liquidator,
363+
address(debtReserve.hub),
364+
drawnDebtToLiquidate + premiumDebtToLiquidate
365+
);
366+
uint256 drawnSharesLiquidated = debtReserve.hub.restore(
367+
debtReserve.assetId,
368+
drawnDebtToLiquidate,
369+
premiumDebtToLiquidate,
370+
premiumDelta
371+
);
372+
debtPosition.settlePremiumDebt(premiumDelta.realizedDelta);
373+
debtPosition.drawnShares -= drawnSharesLiquidated.toUint120();
348374

375+
bool isDebtPositionEmpty = false;
349376
if (debtPosition.drawnShares == 0) {
350377
positionStatus.setBorrowing(params.debtReserveId, false);
351-
return true;
378+
isDebtPositionEmpty = true;
352379
}
353380

354-
return false;
381+
return (drawnSharesLiquidated, premiumDelta, isDebtPositionEmpty);
355382
}
356383

357384
/// @notice Validates the liquidation call.

tests/mocks/LiquidationLogicWrapper.sol

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pragma solidity ^0.8.0;
55
import {SafeCast} from 'src/dependencies/openzeppelin/SafeCast.sol';
66
import {SafeERC20} from 'src/dependencies/openzeppelin/SafeERC20.sol';
77
import {IERC20} from 'src/dependencies/openzeppelin/IERC20.sol';
8-
import {IHub} from 'src/hub/interfaces/IHub.sol';
8+
import {IHub, IHubBase} from 'src/hub/interfaces/IHub.sol';
99
import {ISpoke} from 'src/spoke/interfaces/ISpoke.sol';
1010
import {PositionStatusMap} from 'src/spoke/libraries/PositionStatusMap.sol';
1111
import {LiquidationLogic} from 'src/spoke/libraries/LiquidationLogic.sol';
@@ -214,7 +214,7 @@ contract LiquidationLogicWrapper {
214214

215215
function liquidateCollateral(
216216
LiquidationLogic.LiquidateCollateralParams memory params
217-
) public returns (bool) {
217+
) public returns (uint256, uint256, bool) {
218218
return
219219
LiquidationLogic._liquidateCollateral(
220220
_reserves[_collateralReserveId],
@@ -223,7 +223,9 @@ contract LiquidationLogicWrapper {
223223
);
224224
}
225225

226-
function liquidateDebt(LiquidationLogic.LiquidateDebtParams memory params) public returns (bool) {
226+
function liquidateDebt(
227+
LiquidationLogic.LiquidateDebtParams memory params
228+
) public returns (uint256, IHubBase.PremiumDelta memory, bool) {
227229
return
228230
LiquidationLogic._liquidateDebt(
229231
_reserves[_debtReserveId],

tests/unit/Hub/Hub.Rescue.t.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ contract HubRescueTest is HubBase {
198198

199199
vm.prank(address(rescueSpoke));
200200
hub1.add(daiAssetId, liquidityFee);
201+
202+
assertEq(hub1.getAssetAccruedFees(daiAssetId), liquidityFee, 'accrued liquidity fee');
201203
}
202204

203205
function _rescue(

0 commit comments

Comments
 (0)