Overview
PoolQuotaKeeperV3 manages the quota system for risky assets in Gearbox Protocol V3. Quotas limit system exposure to volatile tokens by requiring credit accounts to “purchase” quotas with two types of fees:
- Quota interest: Accrues over time based on rates from the gauge (suited for leveraged farming)
- Quota increase fee: One-time fee when purchasing additional quota (suited for leveraged trading)
Contract Location: contracts/pool/PoolQuotaKeeperV3.sol
Key Features
- Per-Account Quota Tracking: Tracks quotas for each credit account and token
- Interest Accrual: Accumulates quota interest using cumulative indexes
- Total Quota Limits: Enforces system-wide limits per token
- Multi-Credit Manager: Supports multiple credit managers from the same pool
- Quota Revenue Updates: Keeps pool’s quota revenue synchronized
- Gauge Integration: Gets quota rates from the gauge contract
Architecture
contract PoolQuotaKeeperV3 is
IPoolQuotaKeeperV3,
ACLTrait,
ContractsRegisterTrait,
SanityCheckTrait
State Variables
Immutable
Contract type identifier: "POOL_QUOTA_KEEPER"
Address of the pool’s underlying token
Address of the pool this quota keeper is connected to
Configuration
Address of the gauge contract that determines quota rates
Timestamp of the last quota rate update
Data Structures
TokenQuotaParams
struct TokenQuotaParams {
uint16 rate; // Annual quota interest rate in bps
uint192 cumulativeIndexLU; // Cumulative index at last update
uint16 quotaIncreaseFee; // One-time increase fee in bps
uint96 totalQuoted; // Total quota amount across all accounts
uint96 limit; // Maximum total quota for this token
}
AccountQuota
struct AccountQuota {
uint96 quota; // Account's quota amount
uint192 cumulativeIndexLU; // Cumulative index at last account update
}
Core Functions
Quota Management
updateQuota
function updateQuota(
address creditAccount,
address token,
int96 requestedChange,
uint96 minQuota,
uint96 maxQuota
) external returns (
uint128 caQuotaInterestChange,
uint128 fees,
bool enableToken,
bool disableToken
)
Updates a credit account’s quota for a token. Only callable by authorized credit managers.
Credit account to update quota for
Token to update quota for
Requested change in quota (negative to decrease, type(int96).min to remove all)
Minimum acceptable quota after update
Maximum acceptable quota after update
Returns:
caQuotaInterestChange: Accrued quota interest since last update
fees: Quota increase fees charged (if increasing)
enableToken: Whether token should be enabled as collateral
disableToken: Whether token should be disabled as collateral
Logic:
- Updates account’s cumulative index
- Calculates accrued interest
- Adjusts quota by requested amount (subject to total limit)
- Charges increase fee if quota increased
- Checks min/max bounds
- Updates pool’s quota revenue
// Example: Increase quota by 5000 USDC, require at least 4000
(uint128 interest, uint128 fees, bool enable, bool disable) =
quotaKeeper.updateQuota(
creditAccount,
token,
5000e6, // Request +5000
4000e6, // Min 4000 (allows less if limit hit)
10000e6 // Max 10000
);
removeQuotas
function removeQuotas(
address creditAccount,
address[] calldata tokens,
bool setLimitsToZero
) external
Removes all quotas for a credit account. Only callable by credit managers.
Credit account to remove quotas from
Array of tokens to remove quotas for
Whether to also set token limits to zero (used in extreme cases like liquidations with loss)
// Example: Remove all quotas on liquidation
address[] memory tokens = new address[](2);
tokens[0] = WETH;
tokens[1] = WBTC;
quotaKeeper.removeQuotas(creditAccount, tokens, false);
accrueQuotaInterest
function accrueQuotaInterest(
address creditAccount,
address[] calldata tokens
) external
Updates cumulative indexes for an account’s quoted tokens without changing quotas.
Array of tokens to accrue interest for
View Functions
getQuotaAndOutstandingInterest
function getQuotaAndOutstandingInterest(
address creditAccount,
address token
) external view returns (
uint96 quoted,
uint128 outstandingInterest
)
Returns account’s quota and accrued interest for a token.
// Check quota and interest owed
(uint96 quota, uint128 interest) =
quotaKeeper.getQuotaAndOutstandingInterest(creditAccount, WETH);
cumulativeIndex
function cumulativeIndex(address token) external view returns (uint192)
Returns the current cumulative quota interest index for a token (in ray).
getQuotaRate
function getQuotaRate(address token) external view returns (uint16)
Returns the current quota interest rate for a token in basis points.
getTokenQuotaParams
function getTokenQuotaParams(address token) external view returns (
uint16 rate,
uint192 cumulativeIndexLU,
uint16 quotaIncreaseFee,
uint96 totalQuoted,
uint96 limit,
bool isActive
)
Returns all quota parameters for a token.
// Check WETH quota params
(
uint16 rate, // e.g., 200 = 2% annual
uint192 index,
uint16 increaseFee, // e.g., 100 = 1% one-time
uint96 totalQuoted, // Total across all accounts
uint96 limit, // System-wide limit
bool isActive
) = quotaKeeper.getTokenQuotaParams(WETH);
poolQuotaRevenue
function poolQuotaRevenue() external view returns (uint256)
Returns the pool’s total annual quota revenue in underlying tokens.
quotedTokens
function quotedTokens() external view returns (address[] memory)
Returns array of all tokens with quotas enabled.
isQuotedToken
function isQuotedToken(address token) external view returns (bool)
Checks if a token has quotas enabled.
Configuration
setGauge
function setGauge(address _gauge) external
Sets the gauge contract used to determine quota rates. Only callable by configurator.
Requirements:
- Gauge must be compatible with the pool
- All currently quoted tokens must be added in the new gauge
addQuotaToken
function addQuotaToken(address token) external
Adds a new token to the quota system. Only callable by gauge.
The gauge is responsible for ensuring the token is not the pool’s underlying.
updateRates
function updateRates() external
Updates quota rates for all tokens from the gauge. Only callable by gauge.
Process:
- Updates cumulative indexes for all tokens using old rates
- Queries new rates from gauge
- Sets new rates
- Updates pool’s total quota revenue
- Updates timestamp
addCreditManager
function addCreditManager(address _creditManager) external
Authorizes a new credit manager to call quota functions. Only callable by configurator.
setTokenLimit
function setTokenLimit(address token, uint96 limit) external
Sets the total quota limit for a token. Only callable by configurator.
New limit (must be ≤ type(int96).max)
setTokenQuotaIncreaseFee
function setTokenQuotaIncreaseFee(address token, uint16 fee) external
Sets the one-time increase fee for a token in basis points.
Events
event UpdateQuota(address indexed creditAccount, address indexed token, int96 quotaChange)
event UpdateTokenQuotaRate(address indexed token, uint16 rate)
event SetGauge(address indexed newGauge)
event AddCreditManager(address indexed creditManager)
event AddQuotaToken(address indexed token)
event SetTokenLimit(address indexed token, uint96 limit)
event SetQuotaIncreaseFee(address indexed token, uint16 fee)
Example Usage
Updating Account Quota
// Credit manager requests quota increase for WETH
(uint128 interest, uint128 fees, bool enable, bool disable) =
quotaKeeper.updateQuota(
creditAccount,
WETH,
int96(5 ether), // Add 5 WETH worth of quota
uint96(4 ether), // Min 4 WETH (allows less if limit hit)
uint96(20 ether) // Max 20 WETH
);
if (enable) {
// Enable WETH as collateral in credit manager
}
if (interest > 0 || fees > 0) {
// Charge account for interest and fees
totalDebt += interest + fees;
}
Checking Quota Status
// Get account's current quota and interest
(uint96 quota, uint128 interest) =
quotaKeeper.getQuotaAndOutstandingInterest(creditAccount, WETH);
console.log("Current quota:", quota);
console.log("Interest owed:", interest);
// Check token parameters
(
uint16 rate,
,
uint16 increaseFee,
uint96 totalQuoted,
uint96 limit,
bool isActive
) = quotaKeeper.getTokenQuotaParams(WETH);
console.log("Rate:", rate, "bps");
console.log("Increase fee:", increaseFee, "bps");
console.log("Total quoted:", totalQuoted, "/", limit);
Removing Quotas on Liquidation
// Get all quoted tokens for the account
address[] memory tokens = getAccountQuotedTokens(creditAccount);
// Remove all quotas
quotaKeeper.removeQuotas(
creditAccount,
tokens,
false // Don't set limits to zero (normal liquidation)
);
// For liquidations with loss, optionally set limits to zero
// to prevent further exposure
if (hasLoss) {
quotaKeeper.removeQuotas(creditAccount, tokens, true);
}
Quota Interest Calculation
Quota interest uses a cumulative index system similar to lending protocols:
// Interest accrued since last update:
interest = quota * (cumulativeIndexNow - cumulativeIndexLU) / RAY
// Cumulative index grows linearly:
cumulativeIndexNow = cumulativeIndexLU * (1 + rate * timeElapsed / YEAR)
Example:
- Account has 10,000 USDC quota
- Rate is 5% (500 bps)
- 30 days passed since last update
- Interest ≈ 10,000 * 0.05 * (30/365) ≈ 41 USDC
Security Considerations
Access Control: Only authorized credit managers can update quotas for their credit accounts.
Quota Limits: Total quoted amount across all accounts is capped per token to limit system exposure.
Interest Accrual: Indexes are updated before any quota changes to ensure accurate interest calculations.
Revenue Synchronization: Pool’s quota revenue is kept in sync whenever quotas or rates change.