Skip to content

Commit

Permalink
feat: additional checks in CreditConfiguratorV3
Browse files Browse the repository at this point in the history
- `CreditManagerV3`'s constructor now accepts liquidation fees as parameters
- `ltUnderlying` is now `immutable`
- `setFees` performs more checks on passed values
- `rampDuration` should be at least 2 days
- `newExpirationDate` is updated to be at least 2 weeks from the current timestamp
  • Loading branch information
lekhovitsky committed Dec 21, 2024
1 parent 60af9e7 commit b8a2c07
Show file tree
Hide file tree
Showing 15 changed files with 564 additions and 363 deletions.
112 changes: 42 additions & 70 deletions contracts/credit/CreditConfiguratorV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol";

// LIBRARIES & CONSTANTS
import {BitMask} from "../libraries/BitMask.sol";
import {CreditLogic} from "../libraries/CreditLogic.sol";
import {PERCENTAGE_FACTOR, UNDERLYING_TOKEN_MASK, WAD} from "../libraries/Constants.sol";

// CONTRACTS
Expand Down Expand Up @@ -180,6 +179,7 @@ contract CreditConfiguratorV3 is ICreditConfiguratorV3, ACLTrait, SanityCheckTra
/// @dev Reverts if `token` is underlying
/// @dev Reverts if `token` is not recognized as collateral in the credit manager
/// @dev Reverts if `liquidationThresholdFinal` is greater than underlying's LT
/// @dev Reverts if `rampDuration` is less than 2 days
function rampLiquidationThreshold(
address token,
uint16 liquidationThresholdFinal,
Expand All @@ -198,6 +198,7 @@ contract CreditConfiguratorV3 is ICreditConfiguratorV3, ACLTrait, SanityCheckTra
revert IncorrectLiquidationThresholdException(); // I:[CC-30]
}

if (rampDuration < 2 days) revert RampDurationTooShortException(); // I:[CC-30]
rampStart = block.timestamp > rampStart ? uint40(block.timestamp) : rampStart; // I:[CC-30]
if (uint256(rampStart) + rampDuration > type(uint40).max) {
revert IncorrectParameterException(); // I:[CC-30]
Expand Down Expand Up @@ -351,14 +352,15 @@ contract CreditConfiguratorV3 is ICreditConfiguratorV3, ACLTrait, SanityCheckTra
// -------------- //

/// @notice Sets new fees params in the credit manager (all fields in bps)
/// @notice Sets underlying token's liquidation threshold to 1 - liquidation fee - liquidation premium
/// @param feeLiquidation Percentage of liquidated account value taken by the protocol as profit
/// @param liquidationPremium Percentage of liquidated account value that can be taken by liquidator
/// @param feeLiquidationExpired Percentage of liquidated expired account value taken by the protocol as profit
/// @param liquidationPremiumExpired Percentage of liquidated expired account value that can be taken by liquidator
/// @dev Reverts if `liquidationPremium + feeLiquidation` is above 100%
/// @dev Reverts if `liquidationPremiumExpired + feeLiquidationExpired` is above 100%
/// @dev Reverts if new underlying's LT is below some collateral token's LT, accounting for ramps
/// @dev Performs parameters validation:
/// - liquidation fee must not be greater than premium (same for expired liquidations)
/// - expired liquidation premium or fee must not be greater than that of non-expired
/// - the sum of liquidation premium and fee must not change compared to its previous value
/// (for expired liquidations, it must not change less than 2 weeks before the expiration)
function setFees(
uint16 feeLiquidation,
uint16 liquidationPremium,
Expand All @@ -370,34 +372,11 @@ contract CreditConfiguratorV3 is ICreditConfiguratorV3, ACLTrait, SanityCheckTra
configuratorOnly // I:[CC-2]
{
if (
(liquidationPremium + feeLiquidation) >= PERCENTAGE_FACTOR
|| (liquidationPremiumExpired + feeLiquidationExpired) >= PERCENTAGE_FACTOR
feeLiquidation > liquidationPremium || feeLiquidationExpired > liquidationPremiumExpired
|| feeLiquidationExpired > feeLiquidation || liquidationPremiumExpired > liquidationPremium
|| liquidationPremium + feeLiquidation >= PERCENTAGE_FACTOR
) revert IncorrectParameterException(); // I:[CC-17]

_setFees({
feeLiquidation: feeLiquidation,
liquidationDiscount: PERCENTAGE_FACTOR - liquidationPremium,
feeLiquidationExpired: feeLiquidationExpired,
liquidationDiscountExpired: PERCENTAGE_FACTOR - liquidationPremiumExpired
});
}

/// @dev `setFees` implementation
function _setFees(
uint16 feeLiquidation,
uint16 liquidationDiscount,
uint16 feeLiquidationExpired,
uint16 liquidationDiscountExpired
) internal {
uint16 newLTUnderlying = liquidationDiscount - feeLiquidation; // I:[CC-18]
(, uint16 ltUnderlying) =
CreditManagerV3(creditManager).collateralTokenByMask({tokenMask: UNDERLYING_TOKEN_MASK});

if (newLTUnderlying != ltUnderlying) {
_updateUnderlyingLT(newLTUnderlying); // I:[CC-18]
emit SetTokenLiquidationThreshold({token: underlying, liquidationThreshold: newLTUnderlying}); // I:[CC-1A,18]
}

(
uint16 _feeInterestCurrent,
uint16 _feeLiquidationCurrent,
Expand All @@ -406,49 +385,39 @@ contract CreditConfiguratorV3 is ICreditConfiguratorV3, ACLTrait, SanityCheckTra
uint16 _liquidationDiscountExpiredCurrent
) = CreditManagerV3(creditManager).fees();

uint16 liquidationDiscount = PERCENTAGE_FACTOR - liquidationPremium;
uint16 liquidationDiscountExpired = PERCENTAGE_FACTOR - liquidationPremiumExpired;

if (
(feeLiquidation == _feeLiquidationCurrent) && (liquidationDiscount == _liquidationDiscountCurrent)
&& (feeLiquidationExpired == _feeLiquidationExpiredCurrent)
&& (liquidationDiscountExpired == _liquidationDiscountExpiredCurrent)
) return;

if (liquidationDiscount - feeLiquidation != _liquidationDiscountCurrent - _feeLiquidationCurrent) {
revert InconsistentLiquidationFeesException(); // I:[CC-17]
}

if (
liquidationDiscountExpired - feeLiquidationExpired
!= _liquidationDiscountExpiredCurrent - _feeLiquidationExpiredCurrent
) {
uint256 expirationDate = CreditFacadeV3(creditFacade()).expirationDate();
if (expirationDate != 0 && block.timestamp + 2 weeks > expirationDate) {
revert InconsistentExpiredLiquidationFeesException(); // I:[CC-17]
}
}

CreditManagerV3(creditManager).setFees(
_feeInterestCurrent, feeLiquidation, liquidationDiscount, feeLiquidationExpired, liquidationDiscountExpired
); // I:[CC-19]

emit UpdateFees({
feeLiquidation: feeLiquidation,
liquidationPremium: PERCENTAGE_FACTOR - liquidationDiscount,
liquidationPremium: liquidationPremium,
feeLiquidationExpired: feeLiquidationExpired,
liquidationPremiumExpired: PERCENTAGE_FACTOR - liquidationDiscountExpired
}); // I:[CC-1A,19]
}

/// @dev Updates underlying token's liquidation threshold
function _updateUnderlyingLT(uint16 ltUnderlying) internal {
CreditManagerV3(creditManager).setCollateralTokenData({
token: underlying,
ltInitial: ltUnderlying,
ltFinal: ltUnderlying,
timestampRampStart: type(uint40).max,
rampDuration: 0
}); // I:[CC-18]

uint256 len = CreditManagerV3(creditManager).collateralTokensCount();
unchecked {
for (uint256 i = 1; i < len; ++i) {
address token = CreditManagerV3(creditManager).getTokenByMask(1 << i);
(uint16 ltInitial, uint16 ltFinal, uint40 timestampRampStart, uint24 rampDuration) =
CreditManagerV3(creditManager).ltParams(token);
uint16 lt = CreditLogic.getLiquidationThreshold({
ltInitial: ltInitial,
ltFinal: ltFinal,
timestampRampStart: timestampRampStart,
rampDuration: rampDuration
});
if (lt > ltUnderlying || ltFinal > ltUnderlying) revert IncorrectLiquidationThresholdException(); // I:[CC-18]
}
}
liquidationPremiumExpired: liquidationPremiumExpired
}); // I:[CC-19]
}

// -------- //
Expand Down Expand Up @@ -598,7 +567,7 @@ contract CreditConfiguratorV3 is ICreditConfiguratorV3, ACLTrait, SanityCheckTra
if (currentMinDebt == minDebt && currentMaxDebt == maxDebt) return;

cf.setDebtLimits(minDebt, maxDebt, cf.maxDebtPerBlockMultiplier()); // I:[CC-16]
emit SetBorrowingLimits(minDebt, maxDebt); // I:[CC-1A,19]
emit SetBorrowingLimits(minDebt, maxDebt); // I:[CC-19]
}

/// @notice Sets the new max debt per block multiplier in the credit facade
Expand Down Expand Up @@ -628,7 +597,7 @@ contract CreditConfiguratorV3 is ICreditConfiguratorV3, ACLTrait, SanityCheckTra

(uint128 minDebt, uint128 maxDebt) = cf.debtLimits();
cf.setDebtLimits(minDebt, maxDebt, newMaxDebtLimitPerBlockMultiplier); // I:[CC-24]
emit SetMaxDebtPerBlockMultiplier(newMaxDebtLimitPerBlockMultiplier); // I:[CC-1A,24]
emit SetMaxDebtPerBlockMultiplier(newMaxDebtLimitPerBlockMultiplier); // I:[CC-24]
}

/// @notice Sets the new loss liquidator which can enforce policies on how liquidations with loss are performed
Expand All @@ -652,26 +621,29 @@ contract CreditConfiguratorV3 is ICreditConfiguratorV3, ACLTrait, SanityCheckTra
emit SetLossLiquidator(newLossLiquidator); // I:[CC-26]
}

/// @notice Sets a new credit facade expiration timestamp
/// @param newExpirationDate New expiration timestamp
/// @dev Reverts if `newExpirationDate` is in the past
/// @dev Reverts if `newExpirationDate` is older than the current expiration date
/// @notice Sets a new credit facade expiration date
/// @param newExpirationDate New expiration date
/// @dev Reverts if `newExpirationDate` is in the past and updates it to be at least 2 weeks
/// from the current timestamp otherwise
/// @dev Reverts if updated `newExpirationDate` is before the current expiration date
/// @dev Reverts if credit facade is not expirable
function setExpirationDate(uint40 newExpirationDate)
external
override
configuratorOnly // I:[CC-2]
{
if (newExpirationDate < block.timestamp) revert IncorrectExpirationDateException(); // I:[CC-25]
if (newExpirationDate < block.timestamp + 2 weeks) newExpirationDate = uint40(block.timestamp + 2 weeks); // I:[CC-25]
_setExpirationDate(newExpirationDate); // I:[CC-25]
}

/// @dev `setExpirationDate` implementation
function _setExpirationDate(uint40 newExpirationDate) internal {
CreditFacadeV3 cf = CreditFacadeV3(creditFacade());

if (block.timestamp > newExpirationDate || cf.expirationDate() >= newExpirationDate) {
revert IncorrectExpirationDateException(); // I:[CC-25]
}
uint256 expirationDate = cf.expirationDate();
if (newExpirationDate == expirationDate) return;
else if (newExpirationDate < expirationDate) revert IncorrectExpirationDateException(); // I:[CC-25]

cf.setExpirationDate(newExpirationDate); // I:[CC-25]
emit SetExpirationDate(newExpirationDate); // I:[CC-25]
Expand Down
59 changes: 33 additions & 26 deletions contracts/credit/CreditManagerV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,7 @@ import {IPriceOracleV3} from "../interfaces/IPriceOracleV3.sol";
import {IPoolQuotaKeeperV3} from "../interfaces/IPoolQuotaKeeperV3.sol";

// LIBRARIES
import {
INACTIVE_CREDIT_ACCOUNT_ADDRESS,
PERCENTAGE_FACTOR,
UNDERLYING_TOKEN_MASK,
DEFAULT_FEE_LIQUIDATION,
DEFAULT_LIQUIDATION_PREMIUM,
DEFAULT_FEE_LIQUIDATION_EXPIRED,
DEFAULT_LIQUIDATION_PREMIUM_EXPIRED
} from "../libraries/Constants.sol";
import {INACTIVE_CREDIT_ACCOUNT_ADDRESS, PERCENTAGE_FACTOR, UNDERLYING_TOKEN_MASK} from "../libraries/Constants.sol";

// EXCEPTIONS
import "../interfaces/IExceptions.sol";
Expand Down Expand Up @@ -87,22 +79,22 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
uint8 public override collateralTokensCount;

/// @dev Liquidation threshold for the underlying token in bps
uint16 internal ltUnderlying = PERCENTAGE_FACTOR - DEFAULT_LIQUIDATION_PREMIUM - DEFAULT_FEE_LIQUIDATION_EXPIRED;
uint16 internal immutable ltUnderlying;

/// @dev Percentage of accrued interest in bps taken by the protocol as profit
uint16 internal immutable feeInterest;

/// @dev Percentage of liquidated account value in bps taken by the protocol as profit
uint16 internal feeLiquidation = DEFAULT_FEE_LIQUIDATION;
uint16 internal feeLiquidation;

/// @dev Percentage of liquidated account value in bps that is used to repay debt
uint16 internal liquidationDiscount = PERCENTAGE_FACTOR - DEFAULT_LIQUIDATION_PREMIUM;
uint16 internal liquidationDiscount;

/// @dev Percentage of liquidated expired account value in bps taken by the protocol as profit
uint16 internal feeLiquidationExpired = DEFAULT_FEE_LIQUIDATION_EXPIRED;
uint16 internal feeLiquidationExpired;

/// @dev Percentage of liquidated expired account value in bps that is used to repay debt
uint16 internal liquidationDiscountExpired = PERCENTAGE_FACTOR - DEFAULT_LIQUIDATION_PREMIUM_EXPIRED;
uint16 internal liquidationDiscountExpired;

/// @dev Active credit account which is an account adapters can interfact with
address internal _activeCreditAccount = INACTIVE_CREDIT_ACCOUNT_ADDRESS;
Expand Down Expand Up @@ -149,6 +141,10 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
/// @param _priceOracle Price oracle address
/// @param _maxEnabledTokens Maximum number of tokens that a credit account can have enabled as collateral
/// @param _feeInterest Percentage of accrued interest in bps to take by the protocol as profit
/// @param _feeLiquidation Percentage of liquidated account value taken by the protocol as profit
/// @param _liquidationPremium Percentage of liquidated account value that can be taken by liquidator
/// @param _feeLiquidationExpired Percentage of liquidated expired account value taken by the protocol as profit
/// @param _liquidationPremiumExpired Percentage of liquidated expired account value that can be taken by liquidator
/// @param _name Credit manager name
/// @dev Adds pool's underlying as collateral token with LT = 0
/// @dev Checks that `_priceOracle` has a price for underlying
Expand All @@ -161,15 +157,29 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
address _priceOracle,
uint8 _maxEnabledTokens,
uint16 _feeInterest,
uint16 _feeLiquidation,
uint16 _liquidationPremium,
uint16 _feeLiquidationExpired,
uint16 _liquidationPremiumExpired,
string memory _name
) {
if (bytes(_name).length == 0 || _maxEnabledTokens == 0) revert IncorrectParameterException(); // U:[CM-1]
if (
_feeLiquidation > _liquidationPremium || _feeLiquidationExpired > _liquidationPremiumExpired
|| _feeLiquidationExpired > _feeLiquidation || _liquidationPremiumExpired > _liquidationPremium
|| _liquidationPremium + _feeLiquidation >= PERCENTAGE_FACTOR
) revert IncorrectParameterException(); // U:[CM-1]

pool = _pool; // U:[CM-1]
accountFactory = _accountFactory; // U:[CM-1]
priceOracle = _priceOracle; // U:[CM-1]
maxEnabledTokens = _maxEnabledTokens; // U:[CM-1]
ltUnderlying = PERCENTAGE_FACTOR - _liquidationPremium - _feeLiquidation; // U:[CM-1]
feeInterest = _feeInterest; // U:[CM-1]
feeLiquidation = _feeLiquidation; // U:[CM-1]
liquidationDiscount = PERCENTAGE_FACTOR - _liquidationPremium; // U:[CM-1]
feeLiquidationExpired = _feeLiquidationExpired; // U:[CM-1]
liquidationDiscountExpired = PERCENTAGE_FACTOR - _liquidationPremiumExpired; // U:[CM-1]
name = _name; // U:[CM-1]

underlying = IPoolV3(_pool).underlyingToken(); // U:[CM-1]
Expand Down Expand Up @@ -1206,8 +1216,7 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
/// @param ltFinal LT at the end of the ramp in bps
/// @param timestampRampStart Timestamp of the beginning of the ramp
/// @param rampDuration Ramp duration in seconds
/// @dev If `token` is `underlying`, sets LT to `ltInitial` and ignores other parameters
/// @dev Reverts if `token` is not recognized as collateral in the credit manager
/// @dev Reverts if `token` is `underlying` or is not recognized as collateral in the credit manager
function setCollateralTokenData(
address token,
uint16 ltInitial,
Expand All @@ -1219,17 +1228,15 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
override
creditConfiguratorOnly // U:[CM-4]
{
if (token == underlying) {
ltUnderlying = ltInitial; // U:[CM-42]
} else {
uint256 tokenMask = getTokenMaskOrRevert({token: token}); // U:[CM-41]
CollateralTokenData storage tokenData = collateralTokensData[tokenMask];
if (token == underlying) revert TokenNotAllowedException(); // U:[CM-42]

tokenData.ltInitial = ltInitial; // U:[CM-42]
tokenData.ltFinal = ltFinal; // U:[CM-42]
tokenData.timestampRampStart = timestampRampStart; // U:[CM-42]
tokenData.rampDuration = rampDuration; // U:[CM-42]
}
uint256 tokenMask = getTokenMaskOrRevert({token: token}); // U:[CM-41]
CollateralTokenData storage tokenData = collateralTokensData[tokenMask];

tokenData.ltInitial = ltInitial; // U:[CM-42]
tokenData.ltFinal = ltFinal; // U:[CM-42]
tokenData.timestampRampStart = timestampRampStart; // U:[CM-42]
tokenData.rampDuration = rampDuration; // U:[CM-42]
}

/// @notice Sets the link between the adapter and the target contract
Expand Down
17 changes: 16 additions & 1 deletion contracts/credit/CreditManagerV3_USDT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,24 @@ contract CreditManagerV3_USDT is CreditManagerV3, USDT_Transfer {
address _priceOracle,
uint8 _maxEnabledTokens,
uint16 _feeInterest,
uint16 _feeLiquidation,
uint16 _liquidationPremium,
uint16 _feeLiquidationExpired,
uint16 _liquidationPremiumExpired,
string memory _name
)
CreditManagerV3(_pool, _accountFactory, _priceOracle, _maxEnabledTokens, _feeInterest, _name)
CreditManagerV3(
_pool,
_accountFactory,
_priceOracle,
_maxEnabledTokens,
_feeInterest,
_feeLiquidation,
_liquidationPremium,
_feeLiquidationExpired,
_liquidationPremiumExpired,
_name
)
USDT_Transfer(IPoolV3(_pool).asset())
{}

Expand Down
9 changes: 9 additions & 0 deletions contracts/interfaces/IExceptions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ error AdapterIsNotRegisteredException();
/// @notice Thrown if new credit configurator's set of allowed adapters differs from the current one
error IncorrectAdaptersSetException();

/// @notice Thrown if attempting to schedule a token's LT ramping that is too short in duration
error RampDurationTooShortException();

/// @notice Thrown if attempting to set liquidation fees such that the sum of premium and fee changes
error InconsistentLiquidationFeesException();

/// @notice Thrown if attempting to set expired liquidation fees such that the sum of premium and fee changes
error InconsistentExpiredLiquidationFeesException();

// ------------- //
// CREDIT FACADE //
// ------------- //
Expand Down
4 changes: 0 additions & 4 deletions contracts/libraries/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ uint256 constant EPOCHS_TO_WITHDRAW = 4;

uint8 constant MAX_WITHDRAW_FEE = 100;

uint16 constant DEFAULT_FEE_LIQUIDATION = 1_50;
uint16 constant DEFAULT_LIQUIDATION_PREMIUM = 4_00;
uint16 constant DEFAULT_FEE_LIQUIDATION_EXPIRED = 1_00;
uint16 constant DEFAULT_LIQUIDATION_PREMIUM_EXPIRED = 2_00;
uint8 constant DEFAULT_LIMIT_PER_BLOCK_MULTIPLIER = 2;

uint8 constant BOT_PERMISSIONS_SET_FLAG = 1;
Expand Down
Loading

0 comments on commit b8a2c07

Please sign in to comment.