Skip to content

Commit

Permalink
Merge pull request #70 from Gearbox-protocol/remove-exceptions-from-b…
Browse files Browse the repository at this point in the history
…alances-logic
  • Loading branch information
lekhovitsky authored Jun 2, 2023
2 parents 442bda7 + 3f0d3f4 commit 61079d9
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 169 deletions.
6 changes: 4 additions & 2 deletions contracts/credit/CreditFacadeV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -824,7 +824,8 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
// If expectedBalances was set by calling revertIfGetLessThan,
// checks that actual token balances are not less than expected balances
if (expectedBalances.length != 0) {
BalancesLogic.compareBalances(creditAccount, expectedBalances); // U:[FA-23]
bool success = BalancesLogic.compareBalances(creditAccount, expectedBalances);
if (!success) revert BalanceLessThanMinimumDesiredException(); // U:[FA-23]
}

/// If increaseDebt was called during the multicall, all forbidden tokens must be disabled at the end
Expand Down Expand Up @@ -1129,13 +1130,14 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
fullCheckParams.minHealthFactor
);

BalancesLogic.checkForbiddenBalances({
bool success = BalancesLogic.checkForbiddenBalances({
creditAccount: creditAccount,
enabledTokensMaskBefore: enabledTokensMaskBefore,
enabledTokensMaskAfter: enabledTokensMaskUpdated,
forbiddenBalances: forbiddenBalances,
forbiddenTokenMask: _forbiddenTokenMask
});
if (!success) revert ForbiddenTokensException(); // U:[FA-30]

emit SetEnabledTokensMask(creditAccount, enabledTokensMaskUpdated);
}
Expand Down
6 changes: 3 additions & 3 deletions contracts/credit/CreditManagerV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -857,18 +857,18 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
/// The logic for computing collateral is isolated into the `CreditLogic` library. See `CreditLogic.calcCollateral` for details.
uint256 tokensToDisable;

/// The limit is a TWV threshold at which lazy computation stops. Normally, it happens when TWV
/// Target is a TWV threshold at which lazy computation stops. Normally, it happens when TWV
/// exceeds the total debt, but the user can also configure a custom HF threshold (above 1),
/// in order to maintain a desired level of position health
uint256 limit = (task == CollateralCalcTask.FULL_COLLATERAL_CHECK_LAZY)
uint256 target = (task == CollateralCalcTask.FULL_COLLATERAL_CHECK_LAZY)
? collateralDebtData.totalDebtUSD * minHealthFactor / PERCENTAGE_FACTOR
: type(uint256).max;

(collateralDebtData.totalValueUSD, collateralDebtData.twvUSD, tokensToDisable) = collateralDebtData
.calcCollateral({
creditAccount: creditAccount,
underlying: underlying,
limit: limit,
twvUSDTarget: target,
collateralHints: collateralHints,
quotasPacked: quotasPacked,
priceOracle: _priceOracle,
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IExceptions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ error BorrowAmountOutOfLimitsException();

/// @dev Thrown if one of the balances on a Credit Account is less than expected
/// at the end of a multicall, if revertIfReceivedLessThan was called
error BalanceLessThanMinimumDesiredException(address);
error BalanceLessThanMinimumDesiredException();

/// @dev Thrown if a user attempts to open an account on a Credit Facade that has expired
error NotAllowedAfterExpirationException();
Expand Down
47 changes: 23 additions & 24 deletions contracts/libraries/BalancesLogic.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
// (c) Gearbox Holdings, 2023
pragma solidity ^0.8.17;

import {IERC20Helper} from "./IERC20Helper.sol";
import "../interfaces/IExceptions.sol";
import {BitMask} from "./BitMask.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";
Expand All @@ -18,7 +17,6 @@ struct BalanceWithMask {

/// @title Balances logic library
/// @notice Implements functions that used for before-and-after balance comparisons

library BalancesLogic {
using BitMask for uint256;

Expand All @@ -30,30 +28,30 @@ library BalancesLogic {
view
returns (Balance[] memory expected)
{
expected = deltas;
expected = deltas; // U:[BLL-1]
uint256 len = deltas.length;

for (uint256 i = 0; i < len;) {
expected[i].balance += IERC20Helper.balanceOf(expected[i].token, creditAccount);
expected[i].balance += IERC20Helper.balanceOf(expected[i].token, creditAccount); // U:[BLL-1]
unchecked {
++i;
}
}
}

/// @dev Compares current balances to previously saved expected balances.
/// Reverts if at least one balance is lower than expected
/// @param creditAccount Credit Account to check
/// @param expected Expected balances after all operations
function compareBalances(address creditAccount, Balance[] memory expected) internal view {
uint256 len = expected.length; // F:[FA-45]
/// @return success False if at least one balance is lower than expected, true otherwise
function compareBalances(address creditAccount, Balance[] memory expected) internal view returns (bool success) {
uint256 len = expected.length;
unchecked {
for (uint256 i = 0; i < len; ++i) {
if (IERC20Helper.balanceOf(expected[i].token, creditAccount) < expected[i].balance) {
revert BalanceLessThanMinimumDesiredException(expected[i].token);
} // F:[FA-45]
return false; // U:[BLL-2]
}
}
}
return true; // U:[BLL-2]
}

/// @dev Computes balances of forbidden tokens and returns them for later checks
Expand All @@ -67,7 +65,7 @@ library BalancesLogic {
uint256 forbiddenTokenMask,
function (uint256) view returns (address) getTokenByMaskFn
) internal view returns (BalanceWithMask[] memory forbiddenBalances) {
uint256 forbiddenTokensOnAccount = enabledTokensMask & forbiddenTokenMask;
uint256 forbiddenTokensOnAccount = enabledTokensMask & forbiddenTokenMask; // U:[BLL-3]

if (forbiddenTokensOnAccount != 0) {
forbiddenBalances = new BalanceWithMask[](forbiddenTokensOnAccount.calcEnabledTokens());
Expand All @@ -76,49 +74,50 @@ library BalancesLogic {
for (uint256 tokenMask = 1; tokenMask <= forbiddenTokensOnAccount; tokenMask <<= 1) {
if (forbiddenTokensOnAccount & tokenMask != 0) {
address token = getTokenByMaskFn(tokenMask);
forbiddenBalances[i].token = token;
forbiddenBalances[i].tokenMask = tokenMask;
forbiddenBalances[i].balance = IERC20Helper.balanceOf(token, creditAccount);
forbiddenBalances[i].token = token; // U:[BLL-3]
forbiddenBalances[i].tokenMask = tokenMask; // U:[BLL-3]
forbiddenBalances[i].balance = IERC20Helper.balanceOf(token, creditAccount); // U:[BLL-3]
++i;
}
}
}
}
}

/// @dev Checks that no new forbidden tokens were enabled and that balances of existing forbidden tokens
/// were not increased
/// @dev Checks that no new forbidden tokens were enabled and that balances of existing forbidden tokens didn't increase
/// @param creditAccount Credit Account to check
/// @param enabledTokensMaskBefore Mask of enabled tokens on the account before operations
/// @param enabledTokensMaskAfter Mask of enabled tokens on the account after operations
/// @param forbiddenBalances Array of balances of forbidden tokens (received from `storeForbiddenBalances`)
/// @param forbiddenTokenMask Mask of forbidden tokens
/// @return success False if new forbidden tokens were enabled or balance of at least one forbidden token has increased, true otherwise
function checkForbiddenBalances(
address creditAccount,
uint256 enabledTokensMaskBefore,
uint256 enabledTokensMaskAfter,
BalanceWithMask[] memory forbiddenBalances,
uint256 forbiddenTokenMask
) internal view {
) internal view returns (bool success) {
uint256 forbiddenTokensOnAccount = enabledTokensMaskAfter & forbiddenTokenMask;
if (forbiddenTokensOnAccount == 0) return;
if (forbiddenTokensOnAccount == 0) return true; // U:[BLL-4]

/// A diff between the forbidden tokens before and after is computed
/// If there are forbidden tokens enabled during operations, the function would revert
// A diff between the forbidden tokens before and after is computed
// If there are forbidden tokens enabled during operations, the function would return false
uint256 forbiddenTokensOnAccountBefore = enabledTokensMaskBefore & forbiddenTokenMask;
if (forbiddenTokensOnAccount & ~forbiddenTokensOnAccountBefore != 0) revert ForbiddenTokensException();
if (forbiddenTokensOnAccount & ~forbiddenTokensOnAccountBefore != 0) return false; // U:[BLL-4]

/// Then, the function checks that any remaining forbidden tokens didn't have their balances increased
// Then, the function checks that any remaining forbidden tokens didn't have their balances increased
unchecked {
uint256 len = forbiddenBalances.length;
for (uint256 i = 0; i < len; ++i) {
if (forbiddenTokensOnAccount & forbiddenBalances[i].tokenMask != 0) {
uint256 currentBalance = IERC20Helper.balanceOf(forbiddenBalances[i].token, creditAccount);
if (currentBalance > forbiddenBalances[i].balance) {
revert ForbiddenTokensException();
return false; // U:[BLL-4]
}
}
}
}
return true; // U:[BLL-4]
}
}
23 changes: 12 additions & 11 deletions contracts/libraries/CollateralLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ library CollateralLogic {
/// DEBT_ONLY task or higher
/// @param creditAccount Credit Account to compute collateral for
/// @param underlying The underlying token of the corresponding Credit Manager
/// @param twvUSDTarget Target twvUSD value to stop calculation after (`type(uint256).max` to perform full calculation)
/// @param collateralHints Array of token masks denoting the order to check collateral in.
/// Note that this is only used for non-quoted tokens
/// @param collateralTokenByMaskFn A function to return collateral token data by its mask. Must accept inputs:
Expand All @@ -39,7 +40,7 @@ library CollateralLogic {
CollateralDebtData memory collateralDebtData,
address creditAccount,
address underlying,
uint256 limit,
uint256 twvUSDTarget,
uint256[] memory collateralHints,
uint256[] memory quotasPacked,
function (uint256, bool) view returns (address, uint16) collateralTokenByMaskFn,
Expand All @@ -58,16 +59,16 @@ library CollateralLogic {
quotasPacked: quotasPacked,
creditAccount: creditAccount,
underlyingPriceRAY: underlyingPriceRAY,
limit: limit,
twvUSDTarget: twvUSDTarget,
convertToUSDFn: convertToUSDFn,
priceOracle: priceOracle
}); // U:[CLL-5]

if (twvUSD >= limit) {
if (twvUSD >= twvUSDTarget) {
return (totalValueUSD, twvUSD, 0); // U:[CLL-5]
} else {
unchecked {
limit -= twvUSD; // U:[CLL-5]
twvUSDTarget -= twvUSD; // U:[CLL-5]
}
}
}
Expand All @@ -86,7 +87,7 @@ library CollateralLogic {
tokensToCheckMask: tokensToCheckMask,
priceOracle: priceOracle,
creditAccount: creditAccount,
limit: limit,
twvUSDTarget: twvUSDTarget,
collateralHints: collateralHints,
collateralTokenByMaskFn: collateralTokenByMaskFn,
convertToUSDFn: convertToUSDFn
Expand All @@ -102,7 +103,7 @@ library CollateralLogic {
/// Quota-related data must be filled in for this function to work
/// @param creditAccount A Credit Account to compute value for
/// @param underlyingPriceRAY Price of the underlying token, in RAY format
/// @param limit TWV threshold to stop computing at
/// @param twvUSDTarget Target twvUSD value to stop calculation after (`type(uint256).max` to perform full calculation)
/// @param convertToUSDFn A function to return collateral value in USD. Must accept inputs:
/// * address priceOracle - price oracle to convert assets in
/// * uint256 amount - amount of token to convert
Expand All @@ -115,7 +116,7 @@ library CollateralLogic {
uint256[] memory quotasPacked,
address creditAccount,
uint256 underlyingPriceRAY,
uint256 limit,
uint256 twvUSDTarget,
function (address, uint256, address) view returns(uint256) convertToUSDFn,
address priceOracle
) internal view returns (uint256 totalValueUSD, uint256 twvUSD) {
Expand Down Expand Up @@ -145,7 +146,7 @@ library CollateralLogic {
totalValueUSD += valueUSD; // U:[CLL-4]
twvUSD += weightedValueUSD; // U:[CLL-4]
}
if (twvUSD >= limit) {
if (twvUSD >= twvUSDTarget) {
return (totalValueUSD, twvUSD); // U:[CLL-4]
}

Expand All @@ -157,7 +158,7 @@ library CollateralLogic {

/// @dev Computes USD value of non-quoted tokens an a Credit Account
/// @param creditAccount A Credit Account to compute value for
/// @param limit TWV threshold to stop computing at
/// @param twvUSDTarget Target twvUSD value to stop calculation after (`type(uint256).max` to perform full calculation)
/// @param collateralHints Array of token masks denoting the order to check collateral in.
/// @param convertToUSDFn A function to return collateral value in USD. Must accept inputs:
/// * address priceOracle - price oracle to convert assets in
Expand All @@ -174,7 +175,7 @@ library CollateralLogic {
/// @return tokensToDisable Mask of tokens that have zero balances and need to be disabled
function calcNonQuotedTokensCollateral(
address creditAccount,
uint256 limit,
uint256 twvUSDTarget,
uint256[] memory collateralHints,
function (address, uint256, address) view returns(uint256) convertToUSDFn,
function (uint256, bool) view returns (address, uint16) collateralTokenByMaskFn,
Expand Down Expand Up @@ -210,7 +211,7 @@ library CollateralLogic {
twvUSD += weightedValueUSD; // U:[CLL-3]
}
if (nonZero) {
if (twvUSD >= limit) {
if (twvUSD >= twvUSDTarget) {
break; // U:[CLL-3]
}
} else {
Expand Down
8 changes: 2 additions & 6 deletions contracts/test/integration/credit/CreditFacade.int.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1514,7 +1514,7 @@ contract CreditFacadeIntegrationTest is

/// [TODO]: add new test

/// @dev I:[FA-45]: rrevertIfGetLessThan during multicalls works correctly
/// @dev I:[FA-45]: revertIfGetLessThan during multicalls works correctly
function test_I_FA_45_revertIfGetLessThan_works_correctly() public {
(address creditAccount,) = _openTestCreditAccount();

Expand Down Expand Up @@ -1556,11 +1556,7 @@ contract CreditFacadeIntegrationTest is

for (uint256 i = 0; i < 2; i++) {
vm.prank(USER);
vm.expectRevert(
abi.encodeWithSelector(
BalanceLessThanMinimumDesiredException.selector, ((i == 0) ? underlying : tokenLINK)
)
);
vm.expectRevert(BalanceLessThanMinimumDesiredException.selector);

creditFacade.multicall(
creditAccount,
Expand Down
2 changes: 1 addition & 1 deletion contracts/test/unit/credit/CreditFacadeV3.unit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,7 @@ contract CreditFacadeV3UnitTest is TestHelper, BalanceHelper, ICreditFacadeV3Eve
);

if (testCase == 1) {
vm.expectRevert(abi.encodeWithSelector(BalanceLessThanMinimumDesiredException.selector, (link)));
vm.expectRevert(BalanceLessThanMinimumDesiredException.selector);
}

if (testCase == 2) {
Expand Down
Loading

0 comments on commit 61079d9

Please sign in to comment.