Skip to content

Commit b34ebdb

Browse files
committed
fix: debt limit changes no longer block partial liquidations (#228)
In this PR: * debt principal is allowed to stay above max debt limit after repayment, which increases chances of successful partial liquidations after max debt decrease * debt principal is no longer allowed to be zero after borrowing, which prevents a no-op * a few other tiny fixes
1 parent ffc853f commit b34ebdb

File tree

5 files changed

+45
-24
lines changed

5 files changed

+45
-24
lines changed

contracts/credit/CreditFacadeV3.sol

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
9898
/// @notice Maximum amount that can be borrowed by a credit manager in a single block, as a multiple of `maxDebt`
9999
uint8 public override maxDebtPerBlockMultiplier = DEFAULT_LIMIT_PER_BLOCK_MULTIPLIER;
100100

101-
/// @notice Last block when underlying was borrowed by a credit manager
101+
/// @dev Last block when underlying was borrowed by a credit manager
102102
uint64 internal lastBlockBorrowed;
103103

104-
/// @notice The total amount borrowed by a credit manager in `lastBlockBorrowed`
104+
/// @dev The total amount borrowed by a credit manager in `lastBlockBorrowed`
105105
uint128 internal totalBorrowedInBlock;
106106

107107
/// @notice Bot list address
@@ -694,7 +694,7 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
694694
(uint256 newDebt,,) =
695695
ICreditManagerV3(creditManager).manageDebt(creditAccount, amount, enabledTokensMask, action); // U:[FA-27,31]
696696

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

700700
/// @dev `ICreditFacadeV3Multicall.updateQuota` implementation
@@ -788,7 +788,7 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
788788
override
789789
creditConfiguratorOnly // U:[FA-6]
790790
{
791-
if ((uint256(newMaxDebtPerBlockMultiplier) * newMaxDebt) >= type(uint128).max) {
791+
if ((uint256(newMaxDebtPerBlockMultiplier) * newMaxDebt) > type(uint128).max) {
792792
revert IncorrectParameterException(); // U:[FA-49]
793793
}
794794

@@ -881,9 +881,11 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
881881
totalBorrowedInBlock = uint128(newDebtInCurrentBlock); // U:[FA-43]
882882
}
883883

884-
/// @dev Ensures that account's debt principal is within allowed range or is zero
885-
function _revertIfOutOfDebtLimits(uint256 debt) internal view {
886-
if (debt == 0) return;
884+
/// @dev Ensures that account's debt principal takes allowed values:
885+
/// - for borrowing, new debt must be within allowed bounds
886+
/// - for repayment, new debt must be above allowed lower bound or zero
887+
function _revertIfOutOfDebtLimits(uint256 debt, ManageDebtAction action) internal view {
888+
if (debt == 0 && action == ManageDebtAction.DECREASE_DEBT) return;
887889
uint256 minDebt;
888890
uint256 maxDebt;
889891

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

898-
if (debt < minDebt || debt > maxDebt) {
900+
if (debt < minDebt || debt > maxDebt && action == ManageDebtAction.INCREASE_DEBT) {
899901
revert BorrowAmountOutOfLimitsException(); // U:[FA-44]
900902
}
901903
}

contracts/interfaces/ICreditFacadeV3Multicall.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ interface ICreditFacadeV3Multicall {
102102
/// @param amount Underlying amount to borrow
103103
/// @dev Increasing debt is prohibited when closing an account
104104
/// @dev Increasing debt is prohibited if it was previously updated in the same block
105-
/// @dev The resulting debt amount must be within allowed range
105+
/// @dev The resulting debt amount must be within allowed bounds
106106
/// @dev Increasing debt is prohibited if there are forbidden tokens enabled as collateral on the account
107107
/// @dev After debt increase, total amount borrowed by the credit manager in the current block must not exceed
108108
/// the limit defined in the facade
@@ -112,7 +112,8 @@ interface ICreditFacadeV3Multicall {
112112
/// @param amount Underlying amount to repay, value above account's total debt indicates full repayment
113113
/// @dev Decreasing debt is prohibited when opening an account
114114
/// @dev Decreasing debt is prohibited if it was previously updated in the same block
115-
/// @dev The resulting debt amount must be within allowed range or zero
115+
/// @dev The resulting debt amount must be above allowed lower bound or zero (upper bound is not checked here
116+
/// to allow small repayments and partial liquidations in case configurator lowers it)
116117
/// @dev Full repayment brings account into a special mode that skips collateral checks and thus requires
117118
/// an account to have no potential debt sources, e.g., all quotas must be disabled
118119
function decreaseDebt(uint256 amount) external;

contracts/test/helpers/IntegrationTestHelper.sol

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -495,16 +495,23 @@ contract IntegrationTestHelper is TestHelper, BalanceHelper, ConfigManager {
495495

496496
return creditFacade.openCreditAccount(
497497
onBehalfOf,
498-
MultiCallBuilder.build(
499-
MultiCall({
500-
target: address(creditFacade),
501-
callData: abi.encodeCall(ICreditFacadeV3Multicall.increaseDebt, (debt))
502-
}),
503-
MultiCall({
504-
target: address(creditFacade),
505-
callData: abi.encodeCall(ICreditFacadeV3Multicall.addCollateral, (underlying, amount))
506-
})
507-
),
498+
debt == 0
499+
? MultiCallBuilder.build(
500+
MultiCall({
501+
target: address(creditFacade),
502+
callData: abi.encodeCall(ICreditFacadeV3Multicall.addCollateral, (underlying, amount))
503+
})
504+
)
505+
: MultiCallBuilder.build(
506+
MultiCall({
507+
target: address(creditFacade),
508+
callData: abi.encodeCall(ICreditFacadeV3Multicall.increaseDebt, (debt))
509+
}),
510+
MultiCall({
511+
target: address(creditFacade),
512+
callData: abi.encodeCall(ICreditFacadeV3Multicall.addCollateral, (underlying, amount))
513+
})
514+
),
508515
referralCode
509516
);
510517
}

contracts/test/unit/credit/CreditFacadeV3.unit.t.sol

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ contract CreditFacadeV3UnitTest is TestHelper, BalanceHelper, ICreditFacadeV3Eve
306306
/// @dev U:[FA-7]: payable functions wraps eth to msg.sender
307307
function test_U_FA_07_payable_functions_wraps_eth_to_msg_sender() public notExpirableCase {
308308
vm.deal(USER, 3 ether);
309+
creditManagerMock.setManageDebt(1 ether);
309310

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

15821583
vm.expectRevert(BorrowAmountOutOfLimitsException.selector);
1583-
creditFacade.revertIfOutOfDebtLimits(minDebt - 1);
1584+
creditFacade.revertIfOutOfDebtLimits(0, ManageDebtAction.INCREASE_DEBT);
1585+
1586+
creditFacade.revertIfOutOfDebtLimits(0, ManageDebtAction.DECREASE_DEBT);
1587+
1588+
vm.expectRevert(BorrowAmountOutOfLimitsException.selector);
1589+
creditFacade.revertIfOutOfDebtLimits(minDebt - 1, ManageDebtAction.INCREASE_DEBT);
15841590

15851591
vm.expectRevert(BorrowAmountOutOfLimitsException.selector);
1586-
creditFacade.revertIfOutOfDebtLimits(maxDebt + 1);
1592+
creditFacade.revertIfOutOfDebtLimits(minDebt - 1, ManageDebtAction.DECREASE_DEBT);
1593+
1594+
vm.expectRevert(BorrowAmountOutOfLimitsException.selector);
1595+
creditFacade.revertIfOutOfDebtLimits(maxDebt + 1, ManageDebtAction.INCREASE_DEBT);
1596+
1597+
creditFacade.revertIfOutOfDebtLimits(maxDebt + 1, ManageDebtAction.DECREASE_DEBT);
15871598
}
15881599

15891600
/// @dev U:[FA-45]: multicall handles forbidden tokens properly

contracts/test/unit/credit/CreditFacadeV3Harness.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ contract CreditFacadeV3Harness is CreditFacadeV3 {
5656
return totalBorrowedInBlock;
5757
}
5858

59-
function revertIfOutOfDebtLimits(uint256 debt) external view {
60-
_revertIfOutOfDebtLimits(debt);
59+
function revertIfOutOfDebtLimits(uint256 debt, ManageDebtAction action) external view {
60+
_revertIfOutOfDebtLimits(debt, action);
6161
}
6262

6363
function isExpired() external view returns (bool) {

0 commit comments

Comments
 (0)