Skip to content

Commit

Permalink
fix: debt limit changes no longer break partial liquidations
Browse files Browse the repository at this point in the history
  • Loading branch information
lekhovitsky committed Sep 1, 2024
1 parent 026e7ed commit 693de97
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 21 deletions.
12 changes: 7 additions & 5 deletions contracts/credit/CreditFacadeV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
(uint256 newDebt,,) =
ICreditManagerV3(creditManager).manageDebt(creditAccount, amount, enabledTokensMask, action); // U:[FA-27,31]

_revertIfOutOfDebtLimits(newDebt); // U:[FA-28,32,33]
_revertIfOutOfDebtLimits(newDebt, action); // U:[FA-28,32,33]
}

/// @dev `ICreditFacadeV3Multicall.updateQuota` implementation
Expand Down Expand Up @@ -881,9 +881,11 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
totalBorrowedInBlock = uint128(newDebtInCurrentBlock); // U:[FA-43]
}

/// @dev Ensures that account's debt principal is within allowed range or is zero
function _revertIfOutOfDebtLimits(uint256 debt) internal view {
if (debt == 0) return;
/// @dev Ensures that account's debt principal takes allowed values:
/// - for borrowing, new debt must be within allowed limits
/// - for repayment, new debt must be above allowed minimum or zero
function _revertIfOutOfDebtLimits(uint256 debt, ManageDebtAction action) internal view {
if (debt == 0 && action == ManageDebtAction.DECREASE_DEBT) return;
uint256 minDebt;
uint256 maxDebt;

Expand All @@ -895,7 +897,7 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
minDebt := and(data, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}

if (debt < minDebt || debt > maxDebt) {
if (debt < minDebt || debt > maxDebt && action == ManageDebtAction.INCREASE_DEBT) {
revert BorrowAmountOutOfLimitsException(); // U:[FA-44]
}
}
Expand Down
5 changes: 3 additions & 2 deletions contracts/interfaces/ICreditFacadeV3Multicall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ interface ICreditFacadeV3Multicall {
/// @param amount Underlying amount to borrow
/// @dev Increasing debt is prohibited when closing an account
/// @dev Increasing debt is prohibited if it was previously updated in the same block
/// @dev The resulting debt amount must be within allowed range
/// @dev The resulting debt amount must be within allowed limits
/// @dev Increasing debt is prohibited if there are forbidden tokens enabled as collateral on the account
/// @dev After debt increase, total amount borrowed by the credit manager in the current block must not exceed
/// the limit defined in the facade
Expand All @@ -112,7 +112,8 @@ interface ICreditFacadeV3Multicall {
/// @param amount Underlying amount to repay, value above account's total debt indicates full repayment
/// @dev Decreasing debt is prohibited when opening an account
/// @dev Decreasing debt is prohibited if it was previously updated in the same block
/// @dev The resulting debt amount must be within allowed range or zero
/// @dev The resulting debt amount must be above allowed minimum or zero (maximum is not checked here
/// to allow small repayments and partial liquidations in case configurator lowers it)
/// @dev Full repayment brings account into a special mode that skips collateral checks and thus requires
/// an account to have no potential debt sources, e.g., all quotas must be disabled
function decreaseDebt(uint256 amount) external;
Expand Down
27 changes: 17 additions & 10 deletions contracts/test/helpers/IntegrationTestHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -495,16 +495,23 @@ contract IntegrationTestHelper is TestHelper, BalanceHelper, ConfigManager {

return creditFacade.openCreditAccount(
onBehalfOf,
MultiCallBuilder.build(
MultiCall({
target: address(creditFacade),
callData: abi.encodeCall(ICreditFacadeV3Multicall.increaseDebt, (debt))
}),
MultiCall({
target: address(creditFacade),
callData: abi.encodeCall(ICreditFacadeV3Multicall.addCollateral, (underlying, amount))
})
),
debt == 0
? MultiCallBuilder.build(
MultiCall({
target: address(creditFacade),
callData: abi.encodeCall(ICreditFacadeV3Multicall.addCollateral, (underlying, amount))
})
)
: MultiCallBuilder.build(
MultiCall({
target: address(creditFacade),
callData: abi.encodeCall(ICreditFacadeV3Multicall.increaseDebt, (debt))
}),
MultiCall({
target: address(creditFacade),
callData: abi.encodeCall(ICreditFacadeV3Multicall.addCollateral, (underlying, amount))
})
),
referralCode
);
}
Expand Down
15 changes: 13 additions & 2 deletions contracts/test/unit/credit/CreditFacadeV3.unit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ contract CreditFacadeV3UnitTest is TestHelper, BalanceHelper, ICreditFacadeV3Eve
/// @dev U:[FA-7]: payable functions wraps eth to msg.sender
function test_U_FA_07_payable_functions_wraps_eth_to_msg_sender() public notExpirableCase {
vm.deal(USER, 3 ether);
creditManagerMock.setManageDebt(1 ether);

vm.prank(CONFIGURATOR);
creditFacade.setDebtLimits(1 ether, 9 ether, 9);
Expand Down Expand Up @@ -1580,10 +1581,20 @@ contract CreditFacadeV3UnitTest is TestHelper, BalanceHelper, ICreditFacadeV3Eve
creditFacade.setDebtLimits(minDebt, maxDebt, 1);

vm.expectRevert(BorrowAmountOutOfLimitsException.selector);
creditFacade.revertIfOutOfDebtLimits(minDebt - 1);
creditFacade.revertIfOutOfDebtLimits(0, ManageDebtAction.INCREASE_DEBT);

creditFacade.revertIfOutOfDebtLimits(0, ManageDebtAction.DECREASE_DEBT);

vm.expectRevert(BorrowAmountOutOfLimitsException.selector);
creditFacade.revertIfOutOfDebtLimits(minDebt - 1, ManageDebtAction.INCREASE_DEBT);

vm.expectRevert(BorrowAmountOutOfLimitsException.selector);
creditFacade.revertIfOutOfDebtLimits(maxDebt + 1);
creditFacade.revertIfOutOfDebtLimits(minDebt - 1, ManageDebtAction.DECREASE_DEBT);

vm.expectRevert(BorrowAmountOutOfLimitsException.selector);
creditFacade.revertIfOutOfDebtLimits(maxDebt + 1, ManageDebtAction.INCREASE_DEBT);

creditFacade.revertIfOutOfDebtLimits(maxDebt + 1, ManageDebtAction.DECREASE_DEBT);
}

/// @dev U:[FA-45]: multicall handles forbidden tokens properly
Expand Down
4 changes: 2 additions & 2 deletions contracts/test/unit/credit/CreditFacadeV3Harness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ contract CreditFacadeV3Harness is CreditFacadeV3 {
return totalBorrowedInBlock;
}

function revertIfOutOfDebtLimits(uint256 debt) external view {
_revertIfOutOfDebtLimits(debt);
function revertIfOutOfDebtLimits(uint256 debt, ManageDebtAction action) external view {
_revertIfOutOfDebtLimits(debt, action);
}

function isExpired() external view returns (bool) {
Expand Down

0 comments on commit 693de97

Please sign in to comment.