Skip to content

Commit 3774ca9

Browse files
committed
fix: forbid unhealthy self repay
This(or a variant of this) was recommended by stermi for v3.4. While we did not include it back then, we decided to include it now.
1 parent 547589e commit 3774ca9

8 files changed

Lines changed: 60 additions & 13 deletions

File tree

docs/3.5/Aave-v3.5-features.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,8 @@ This is done to ensure correct behavior in regards to user operations, so that e
4646

4747
On transfers, the scaled amount is rounded `up`.
4848
While for other methods the rational is obvious(and well specified on ERC4626), on transfers the "correct" path is more debatable. We think that currently the more "problematic" code-path is, if you pull x funds, but you get x-1. Therefore by rounding up the scaled amount, we ensure that the contract pulling receives at least the amount it requested.
49+
50+
### Misc changes
51+
52+
- smaller refactoring in `LiquidationLogic` making the code more consistent
53+
- `repayWithAToken` is only allowed when the user is still healthy **after** the repayment. This is done in order to prevent some edge cases where the user would have debt, but no collateral or now with the adjustments on rounding, bringing himself into liquidation area through selfRepayment.

snapshots/Pool.Operations.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
"liquidationCall: full liquidation and receive ATokens": "349352",
1313
"liquidationCall: partial liquidation": "343069",
1414
"liquidationCall: partial liquidation and receive ATokens": "340268",
15-
"repay: full repay": "153786",
16-
"repay: full repay with ATokens": "156959",
17-
"repay: partial repay": "150138",
18-
"repay: partial repay with ATokens": "152261",
15+
"repay: full repay": "161792",
16+
"repay: full repay with ATokens": "181180",
17+
"repay: partial repay": "158144",
18+
"repay: partial repay with ATokens": "179304",
1919
"supply: collateralDisabled": "139047",
2020
"supply: collateralEnabled": "139047",
2121
"supply: first supply->collateralEnabled": "168559",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"batchLiquidate: liquidate 2 users": "485787",
3-
"repayAndWithdraw: borrow disabled, collateral disabled": "289610",
3+
"repayAndWithdraw: borrow disabled, collateral disabled": "291116",
44
"supplyAndBorrow: first supply->collateralEnabled, first borrow": "371572"
55
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"borrowETH": "235541",
33
"depositETH": "194960",
4-
"repayETH": "171130",
4+
"repayETH": "179136",
55
"withdrawETH": "245748"
66
}

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ library BorrowLogic {
123123
function executeRepay(
124124
mapping(address => DataTypes.ReserveData) storage reservesData,
125125
mapping(uint256 => address) storage reservesList,
126+
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
126127
DataTypes.UserConfigurationMap storage onBehalfOfConfig,
127128
DataTypes.ExecuteRepayParams memory params
128129
) external returns (uint256) {
@@ -149,9 +150,9 @@ library BorrowLogic {
149150
// Allows a user to repay with aTokens without leaving dust from interest.
150151
if (params.useATokens && paybackAmount == type(uint256).max) {
151152
// Replicate aToken.balanceOf (round down), to always underestimate the collateral.
152-
paybackAmount = IAToken(reserveCache.aTokenAddress)
153-
.scaledBalanceOf(params.onBehalfOf)
154-
.rayMulFloor(reserveCache.nextLiquidityIndex);
153+
paybackAmount = IAToken(reserveCache.aTokenAddress).scaledBalanceOf(params.user).rayMulFloor(
154+
reserveCache.nextLiquidityIndex
155+
);
155156
}
156157

157158
if (paybackAmount > userDebt) {
@@ -196,6 +197,15 @@ library BorrowLogic {
196197
if (isCollateral && IAToken(reserveCache.aTokenAddress).scaledBalanceOf(params.user) == 0) {
197198
onBehalfOfConfig.setUsingAsCollateral(reserve.id, params.asset, params.user, false);
198199
}
200+
ValidationLogic.validateHealthFactor(
201+
reservesData,
202+
reservesList,
203+
eModeCategories,
204+
onBehalfOfConfig,
205+
params.user,
206+
params.userEModeCategory,
207+
params.oracle
208+
);
199209
} else {
200210
IERC20(params.asset).safeTransferFrom(params.user, reserveCache.aTokenAddress, paybackAmount);
201211
}

src/contracts/protocol/libraries/types/DataTypes.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ library DataTypes {
213213
InterestRateMode interestRateMode;
214214
address onBehalfOf;
215215
bool useATokens;
216+
address oracle;
217+
uint8 userEModeCategory;
216218
}
217219

218220
struct ExecuteWithdrawParams {

src/contracts/protocol/pool/Pool.sol

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ abstract contract Pool is VersionedInitializable, PoolStorage, IPool, Multicall
238238
BorrowLogic.executeRepay(
239239
_reserves,
240240
_reservesList,
241+
_eModeCategories,
241242
_usersConfig[onBehalfOf],
242243
DataTypes.ExecuteRepayParams({
243244
asset: asset,
@@ -246,7 +247,9 @@ abstract contract Pool is VersionedInitializable, PoolStorage, IPool, Multicall
246247
amount: amount,
247248
interestRateMode: DataTypes.InterestRateMode(interestRateMode),
248249
onBehalfOf: onBehalfOf,
249-
useATokens: false
250+
useATokens: false,
251+
oracle: ADDRESSES_PROVIDER.getPriceOracle(),
252+
userEModeCategory: _usersEModeCategory[onBehalfOf]
250253
})
251254
);
252255
}
@@ -282,9 +285,18 @@ abstract contract Pool is VersionedInitializable, PoolStorage, IPool, Multicall
282285
amount: amount,
283286
interestRateMode: DataTypes.InterestRateMode(interestRateMode),
284287
onBehalfOf: onBehalfOf,
285-
useATokens: false
288+
useATokens: false,
289+
oracle: ADDRESSES_PROVIDER.getPriceOracle(),
290+
userEModeCategory: _usersEModeCategory[onBehalfOf]
286291
});
287-
return BorrowLogic.executeRepay(_reserves, _reservesList, _usersConfig[onBehalfOf], params);
292+
return
293+
BorrowLogic.executeRepay(
294+
_reserves,
295+
_reservesList,
296+
_eModeCategories,
297+
_usersConfig[onBehalfOf],
298+
params
299+
);
288300
}
289301
}
290302

@@ -298,6 +310,7 @@ abstract contract Pool is VersionedInitializable, PoolStorage, IPool, Multicall
298310
BorrowLogic.executeRepay(
299311
_reserves,
300312
_reservesList,
313+
_eModeCategories,
301314
_usersConfig[_msgSender()],
302315
DataTypes.ExecuteRepayParams({
303316
asset: asset,
@@ -306,7 +319,9 @@ abstract contract Pool is VersionedInitializable, PoolStorage, IPool, Multicall
306319
amount: amount,
307320
interestRateMode: DataTypes.InterestRateMode(interestRateMode),
308321
onBehalfOf: _msgSender(),
309-
useATokens: true
322+
useATokens: true,
323+
oracle: ADDRESSES_PROVIDER.getPriceOracle(),
324+
userEModeCategory: _usersEModeCategory[_msgSender()]
310325
})
311326
);
312327
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,21 @@ contract PoolRepayTests is TestnetProcedures {
176176
);
177177
}
178178

179+
function test_repayWithATokens_shouldRevertIfUnhealthyAfterRepayment() external {
180+
uint256 amount = 2000e6;
181+
uint256 borrowAmount = 1600e6;
182+
vm.startPrank(alice);
183+
// supply enough collateral to borrow out everything
184+
contracts.poolProxy.supply(tokenList.usdx, amount, alice, 0);
185+
contracts.poolProxy.borrow(tokenList.usdx, borrowAmount, 2, 0, alice);
186+
vm.warp(vm.getBlockTimestamp() + 365 days * 100);
187+
188+
vm.expectRevert(
189+
abi.encodeWithSelector(Errors.HealthFactorLowerThanLiquidationThreshold.selector)
190+
);
191+
contracts.poolProxy.repayWithATokens(tokenList.usdx, 2, 2);
192+
}
193+
179194
function test_repayWithATokens_fuzz_collateral_variable_borrow(
180195
uint256 repayAmount,
181196
uint32 timeDelta

0 commit comments

Comments
 (0)