Skip to content

Commit

Permalink
Merge pull request #54 from Gearbox-protocol/poolQuotaKeeper-update
Browse files Browse the repository at this point in the history
fix: Pool quota keeper update
  • Loading branch information
0xmikko authored May 31, 2023
2 parents 1835b2a + 0e6e44c commit 037655f
Show file tree
Hide file tree
Showing 15 changed files with 377 additions and 261 deletions.
19 changes: 14 additions & 5 deletions contracts/credit/CreditFacadeV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {BalancesLogic, Balance, BalanceWithMask} from "../libraries/BalancesLogic.sol";
import {ACLNonReentrantTrait} from "../traits/ACLNonReentrantTrait.sol";
import {BitMask, UNDERLYING_TOKEN_MASK} from "../libraries/BitMask.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

// DATA
import {MultiCall} from "@gearbox-protocol/core-v2/contracts/libraries/MultiCall.sol";
Expand Down Expand Up @@ -59,6 +60,12 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
using BitMask for uint256;
using SafeCast for uint256;

/// @notice Contract version
uint256 public constant override version = 3_00;

/// @notice maxDebt to maxQuota multiplier
uint256 public constant maxQuotaMultiplier = 8;

/// @notice Credit Manager connected to this Credit Facade
address public immutable creditManager;

Expand Down Expand Up @@ -116,9 +123,6 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
/// And are compensated by the Gearbox DAO separately.
mapping(address => bool) public override canLiquidateWhilePaused;

/// @notice Contract version
uint256 public constant override version = 3_00;

/// @notice Restricts functions to the connected Credit Configurator only
modifier creditConfiguratorOnly() {
_checkCreditConfigurator();
Expand Down Expand Up @@ -948,8 +952,13 @@ contract CreditFacadeV3 is ICreditFacadeV3, ACLNonReentrantTrait {
{
int96 realQuotaChange;
(address token, int96 quotaChange, uint96 minQuota) = abi.decode(callData, (address, int96, uint96)); // U:[FA-34]
(realQuotaChange, tokensToEnable, tokensToDisable) =
ICreditManagerV3(creditManager).updateQuota(creditAccount, token, quotaChange, minQuota); // U:[FA-34]
(realQuotaChange, tokensToEnable, tokensToDisable) = ICreditManagerV3(creditManager).updateQuota({
creditAccount: creditAccount,
token: token,
quotaChange: quotaChange,
minQuota: minQuota,
maxQuota: uint96(Math.min(type(uint96).max, maxQuotaMultiplier * debtLimits.maxDebt))
}); // U:[FA-34]

emit UpdateQuota({creditAccount: creditAccount, token: token, quotaChange: realQuotaChange}); // U:[FA-34]
}
Expand Down
5 changes: 3 additions & 2 deletions contracts/credit/CreditManagerV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -951,7 +951,7 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
/// @param creditAccount Address of credit account
/// @param token Address of quoted token
/// @param quotaChange Change in quota in SIGNED format
function updateQuota(address creditAccount, address token, int96 quotaChange, uint96 minQuota)
function updateQuota(address creditAccount, address token, int96 quotaChange, uint96 minQuota, uint96 maxQuota)
external
override
nonReentrant // U:[CM-5]
Expand All @@ -970,7 +970,8 @@ contract CreditManagerV3 is ICreditManagerV3, SanityCheckTrait, ReentrancyGuardT
creditAccount: creditAccount,
token: token,
quotaChange: quotaChange,
minQuota: minQuota
minQuota: minQuota,
maxQuota: maxQuota
}); // U:[CM-25] // I: [CMQ-3]

if (enable) {
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/ICreditManagerV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ interface ICreditManagerV3 is ICreditManagerV3Events, IVersion {

/// @dev Updates credit account's quotas
/// @param creditAccount Address of credit account
function updateQuota(address creditAccount, address token, int96 quotaChange, uint96 minQuota)
function updateQuota(address creditAccount, address token, int96 quotaChange, uint96 minQuota, uint96 maxQuota)
external
returns (int96 realQuotaChange, uint256 tokensToEnable, uint256 tokensToDisable);

Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IExceptions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,4 @@ error InsufficientBalanceException();

error OpenCloseAccountInOneBlockException();

error QuotaLessThanMinialException();
error QuotaIsOutOfBoundsException();
2 changes: 1 addition & 1 deletion contracts/interfaces/IPoolQuotaKeeperV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ interface IPoolQuotaKeeperV3 is IPoolQuotaKeeperV3Events, IVersion {
/// @param creditAccount Address of credit account
/// @param token Address of the token to change the quota for
/// @param quotaChange Requested quota change in pool's underlying asset units
function updateQuota(address creditAccount, address token, int96 quotaChange, uint96 minQuota)
function updateQuota(address creditAccount, address token, int96 quotaChange, uint96 minQuota, uint96 maxQuota)
external
returns (uint256 caQuotaInterestChange, int96 change, bool enableToken, bool disableToken);

Expand Down
1 change: 1 addition & 0 deletions contracts/libraries/QuotasLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {CreditLogic} from "./CreditLogic.sol";
import {RAY, SECONDS_PER_YEAR, PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";

import "../interfaces/IExceptions.sol";
import "forge-std/console.sol";

uint192 constant RAY_DIVIDED_BY_PERCENTAGE = uint192(RAY / PERCENTAGE_FACTOR);

Expand Down
77 changes: 41 additions & 36 deletions contracts/pool/PoolQuotaKeeperV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ contract PoolQuotaKeeperV3 is IPoolQuotaKeeperV3, ACLNonReentrantTrait, Contract
ACLNonReentrantTrait(IPoolV3(_pool).addressProvider())
ContractsRegisterTrait(IPoolV3(_pool).addressProvider())
{
pool = _pool; // F:[PQK-1]
underlying = IPoolV3(_pool).asset(); // F:[PQK-1]
pool = _pool; // U:[PQK-1]
underlying = IPoolV3(_pool).asset(); // U:[PQK-1]
}

/// @notice Updates a Credit Account's quota amount for a token
Expand All @@ -100,10 +100,10 @@ contract PoolQuotaKeeperV3 is IPoolQuotaKeeperV3, ACLNonReentrantTrait, Contract
/// at capacity.
/// @return enableToken Whether the token needs to be enabled
/// @return disableToken Whether the token needs to be disabled
function updateQuota(address creditAccount, address token, int96 quotaChange, uint96 minQuota)
function updateQuota(address creditAccount, address token, int96 quotaChange, uint96 minQuota, uint96 maxQuota)
external
override
creditManagerOnly // F:[PQK-4]
creditManagerOnly // U:[PQK-4]
returns (uint256 caQuotaInterestChange, int96 realQuotaChange, bool enableToken, bool disableToken)
{
int256 quotaRevenueChange;
Expand All @@ -130,9 +130,9 @@ contract PoolQuotaKeeperV3 is IPoolQuotaKeeperV3, ACLNonReentrantTrait, Contract
accountQuota: accountQuota,
lastQuotaRateUpdate: lastQuotaRateUpdate,
quotaChange: quotaChange
});
}); // U:[PQK-14]

if (accountQuota.quota < minQuota) revert QuotaLessThanMinialException();
if (accountQuota.quota < minQuota || accountQuota.quota > maxQuota) revert QuotaIsOutOfBoundsException();

/// Quota revenue must be changed on each quota updated, so that the
/// pool can correctly compute its liquidity metrics in the future
Expand All @@ -148,7 +148,7 @@ contract PoolQuotaKeeperV3 is IPoolQuotaKeeperV3, ACLNonReentrantTrait, Contract
function removeQuotas(address creditAccount, address[] memory tokens, bool setLimitsToZero)
external
override
creditManagerOnly // F:[PQK-4]
creditManagerOnly // U:[PQK-4]
{
int256 quotaRevenueChange;

Expand Down Expand Up @@ -195,7 +195,7 @@ contract PoolQuotaKeeperV3 is IPoolQuotaKeeperV3, ACLNonReentrantTrait, Contract
function accrueQuotaInterest(address creditAccount, address[] memory tokens)
external
override
creditManagerOnly // F:[PQK-4]
creditManagerOnly // U:[PQK-4]
{
uint256 len = tokens.length;
uint40 _lastQuotaRateUpdate = lastQuotaRateUpdate;
Expand Down Expand Up @@ -281,28 +281,28 @@ contract PoolQuotaKeeperV3 is IPoolQuotaKeeperV3, ACLNonReentrantTrait, Contract
/// @param token Address of the token
function addQuotaToken(address token)
external
gaugeOnly // F:[PQK-3]
gaugeOnly // U:[PQK-3]
{
if (quotaTokensSet.contains(token)) {
revert TokenAlreadyAddedException(); // F:[PQK-6]
revert TokenAlreadyAddedException(); // U:[PQK-6]
}

/// The interest rate is not set immediately on adding a quoted token,
/// since all rates are updated during a general epoch update in the Gauge
quotaTokensSet.add(token); // F:[PQK-5]
totalQuotaParams[token].initialise(); // F:[PQK-5]
quotaTokensSet.add(token); // U:[PQK-5]
totalQuotaParams[token].initialise(); // U:[PQK-5]

emit NewQuotaTokenAdded(token); // F:[PQK-5]
emit NewQuotaTokenAdded(token); // U:[PQK-5]
}

/// @notice Batch updates the quota rates and changes the combined quota revenue
function updateRates()
external
override
gaugeOnly // F:[PQK-3]
gaugeOnly // U:[PQK-3]
{
address[] memory tokens = quotaTokensSet.values();
uint16[] memory rates = IGaugeV3(gauge).getRates(tokens); // F:[PQK-7]
uint16[] memory rates = IGaugeV3(gauge).getRates(tokens); // U:[PQK-7]

uint256 quotaRevenue;
uint256 timestampLU = lastQuotaRateUpdate;
Expand All @@ -314,18 +314,18 @@ contract PoolQuotaKeeperV3 is IPoolQuotaKeeperV3, ACLNonReentrantTrait, Contract

/// Before writing a new rate, the token's interest index current value is also
/// saved, to ensure that further calculations with the new rates are correct
TokenQuotaParams storage tokenQuotaParams = totalQuotaParams[token];
quotaRevenue += tokenQuotaParams.updateRate(timestampLU, rate);
TokenQuotaParams storage tokenQuotaParams = totalQuotaParams[token]; // U:[PQK-7]
quotaRevenue += tokenQuotaParams.updateRate(timestampLU, rate); // U:[PQK-7]

emit UpdateTokenQuotaRate(token, rate); // F:[PQK-7]
emit UpdateTokenQuotaRate(token, rate); // U:[PQK-7]

unchecked {
++i;
}
}

IPoolV3(pool).setQuotaRevenue(quotaRevenue); // F:[PQK-7]
lastQuotaRateUpdate = uint40(block.timestamp); // F:[PQK-7]
IPoolV3(pool).setQuotaRevenue(quotaRevenue); // U:[PQK-7]
lastQuotaRateUpdate = uint40(block.timestamp); // U:[PQK-7]
}

//
Expand All @@ -336,31 +336,31 @@ contract PoolQuotaKeeperV3 is IPoolQuotaKeeperV3, ACLNonReentrantTrait, Contract
/// @param _gauge The new contract's address
function setGauge(address _gauge)
external
configuratorOnly // F:[PQK-2]
configuratorOnly // U:[PQK-2]
{
if (gauge != _gauge) {
gauge = _gauge; // F:[PQK-8]
lastQuotaRateUpdate = uint40(block.timestamp); // F:[PQK-8]
emit SetGauge(_gauge); // F:[PQK-8]
gauge = _gauge; // U:[PQK-8]
lastQuotaRateUpdate = uint40(block.timestamp); // U:[PQK-8]
emit SetGauge(_gauge); // U:[PQK-8]
}
}

/// @notice Adds a new Credit Manager to the set of allowed CM's
/// @param _creditManager Address of the new Credit Manager
function addCreditManager(address _creditManager)
external
configuratorOnly // F:[PQK-2]
configuratorOnly // U:[PQK-2]
nonZeroAddress(_creditManager)
registeredCreditManagerOnly(_creditManager) // F:[PQK-9]
registeredCreditManagerOnly(_creditManager) // U:[PQK-9]
{
if (ICreditManagerV3(_creditManager).pool() != address(pool)) {
revert IncompatibleCreditManagerException(); // F:[PQK-9]
if (ICreditManagerV3(_creditManager).pool() != pool) {
revert IncompatibleCreditManagerException(); // U:[PQK-9]
}

/// Checks if creditManager is already in list
if (!creditManagerSet.contains(_creditManager)) {
creditManagerSet.add(_creditManager); // F:[PQK-10]
emit AddCreditManager(_creditManager); // F:[PQK-10]
creditManagerSet.add(_creditManager); // U:[PQK-10]
emit AddCreditManager(_creditManager); // U:[PQK-10]
}
}

Expand All @@ -369,26 +369,31 @@ contract PoolQuotaKeeperV3 is IPoolQuotaKeeperV3, ACLNonReentrantTrait, Contract
/// @param limit The limit to set
function setTokenLimit(address token, uint96 limit)
external
controllerOnly // F:[PQK-2]
controllerOnly // U:[PQK-2]
{
TokenQuotaParams storage tokenQuotaParams = totalQuotaParams[token];
_setTokenLimit(tokenQuotaParams, token, limit);
}

/// @dev IMPLEMENTATION: setTokenLimit
function _setTokenLimit(TokenQuotaParams storage tokenQuotaParams, address token, uint96 limit) internal {
// setLimit checks that token is initialize, otherwise it reverts
// F:[PQK-11]
if (tokenQuotaParams.setLimit(limit)) {
emit SetTokenLimit(token, limit);
} // F:[PQK-12]
emit SetTokenLimit(token, limit); // U:[PQK-12]
}
}

/// @notice Sets the one-time fee paid on each quota increase
/// @param token Token to set the fee for
/// @param fee The new fee value in PERCENTAGE_FACTOR format
function setTokenQuotaIncreaseFee(address token, uint16 fee) external controllerOnly {
TokenQuotaParams storage tokenQuotaParams = totalQuotaParams[token];
function setTokenQuotaIncreaseFee(address token, uint16 fee)
external
controllerOnly // U:[PQK-2]
{
TokenQuotaParams storage tokenQuotaParams = totalQuotaParams[token]; // U:[PQK-13]
if (tokenQuotaParams.setQuotaIncreaseFee(fee)) {
emit SetQuotaIncreaseFee(token, fee);
emit SetQuotaIncreaseFee(token, fee); // U:[PQK-13]
}
}
}
Loading

0 comments on commit 037655f

Please sign in to comment.