Credit Configurator
The CreditConfiguratorV3 is the administrative gateway for the Credit Suite. It validates parameter changes and propagates them to the Credit Manager and Credit Facade. Importantly, it does not store configuration state itself - it acts as a validation layer.
Architecture
User/DAO -> CreditConfiguratorV3 (validation) -> CreditManagerV3/CreditFacadeV3 (state)
The Configurator ensures all changes are valid before forwarding them to the appropriate contract. This separation allows for:
- Centralized validation logic
- Consistent access control
- Audit trail through events
Token & Risk Management
Adding Collateral Tokens
function addCollateralToken(address token, uint16 liquidationThreshold);
Validation:
- Token must be valid ERC-20
- Must have price feed in PriceOracle
- Must be quoted in PoolQuotaKeeper
- LT cannot exceed underlying's LT
For Phantom Tokens:
- The deposited (underlying) token must already exist as collateral
- Phantom token represents staked/wrapped position
Adjusting Liquidation Thresholds
// Immediate change function setLiquidationThreshold(address token, uint16 liquidationThreshold); // Gradual change (ramping) function rampLiquidationThreshold( address token, uint16 ltFinal, uint40 rampStart, uint24 rampDuration );
Ramping allows gradual LT changes over time, preventing sudden liquidation cascades when risk parameters are adjusted.
Forbidding/Allowing Tokens
| Function | Access | Use Case |
|---|---|---|
forbidToken(address token) | Pausable Admins | Emergency: mark token as risky |
allowToken(address token) | Configurator | Restore normal token status |
Forbidden tokens still count toward collateral (with safe pricing) but have restrictions on quota increases and balance changes.
// TypeScript: Reading token configuration const creditManager = getContract({ address: cmAddress, abi: creditManagerV3Abi, client: publicClient, }); // Get collateral token data const tokenMask = await creditManager.read.getTokenMaskOrRevert([tokenAddress]); const tokenData = await creditManager.read.collateralTokenByMask([tokenMask]); // Returns: { token, ltInitial, ltFinal, timestampRampStart, rampDuration } // Check if token is forbidden const forbiddenMask = await creditManager.read.forbiddenTokenMask(); const isForbidden = (tokenMask & forbiddenMask) !== 0n;
Fee Management
Configurable Fees
function setFees( uint16 feeLiquidation, uint16 liquidationPremium, uint16 feeLiquidationExpired, uint16 liquidationPremiumExpired );
| Parameter | Description |
|---|---|
feeLiquidation | DAO fee on standard liquidations |
liquidationPremium | Reward for liquidators |
feeLiquidationExpired | Higher DAO fee for expired accounts |
liquidationPremiumExpired | Higher reward for expired liquidations |
Constraints:
feeLiquidation <= liquidationPremiumfeeLiquidationExpired <= feeLiquidationliquidationPremium + feeLiquidation < 100%- Fee sum must remain constant (prevents sudden changes)
The relationship ensures liquidators are always incentivized and the protocol takes a smaller cut than the liquidator reward.
Borrowing Limits
Debt Bounds
function setDebtLimits(uint128 newMinDebt, uint128 newMaxDebt);
Validation:
minDebt <= maxDebtmaxDebt * maxEnabledTokens <= minDebt * 100(safety ratio)- USD value of minDebt must be non-zero
The safety ratio ensures accounts aren't opened with tiny debt that would be uneconomical to liquidate.
Per-Block Multiplier
function setMaxDebtPerBlockMultiplier(uint8 multiplier); function forbidBorrowing(); // Sets multiplier to 0
forbidBorrowing() is an emergency action available to Pausable Admins. It immediately halts all new borrowing without requiring a DAO vote.
Adapter Management
Allowing Adapters
function allowAdapter(address adapter); function forbidAdapter(address adapter);
Validation:
- Adapter must implement
creditManager()returning this Credit Manager - Adapter must implement
targetContract()returning the DeFi protocol - Cannot target the Facade or Manager itself
Registration:
- Creates bidirectional mapping:
adapter <-> targetContract - Credit Account can only call whitelisted adapters
- Each target protocol has exactly one adapter
// TypeScript: Checking adapter status const creditManager = getContract({ address: cmAddress, abi: creditManagerV3Abi, client: publicClient, }); // Get adapter for a target protocol const adapterAddress = await creditManager.read.contractToAdapter([uniswapRouterAddress]); if (adapterAddress === '0x0000000000000000000000000000000000000000') { console.log('No adapter registered for this protocol'); } else { console.log(`Adapter: ${adapterAddress}`); } // Get all adapters const adaptersData = await creditManager.read.adapters();
System Upgrades
Oracle Updates
function setPriceOracle(address newPriceOracle);
Allows switching to a new price oracle implementation. The new oracle must support all currently configured collateral tokens.
Facade Migration
function setCreditFacade(address newCreditFacade, bool migrateParams);
When migrateParams is true, debt limits and other Facade parameters are copied to the new contract. This enables upgrading the user-facing interface while preserving configuration.
Configurator Upgrade
function upgradeCreditConfigurator(address newCreditConfigurator);
Transfers configurator role to a new contract. Used when the validation logic itself needs updating.
Access Control Model
Role Hierarchy
| Role | Capabilities |
|---|---|
| Configurator | All structural changes: tokens, fees, adapters, debt limits, upgrades |
| Pausable Admin | Emergency actions: forbidToken, forbidBorrowing (no DAO vote required) |
Cross-Contract Verification
The Credit Manager and Facade verify that configuration calls come from the registered Configurator:
modifier creditConfiguratorOnly() { require(msg.sender == creditConfigurator); _; }
This prevents unauthorized parameter changes even if an attacker gains access to admin keys for other contracts.
// TypeScript: Reading configurator address const creditManager = getContract({ address: cmAddress, abi: creditManagerV3Abi, client: publicClient, }); const configuratorAddress = await creditManager.read.creditConfigurator(); console.log(`Configurator: ${configuratorAddress}`); // Check access control roles (from ACL contract) const acl = getContract({ address: aclAddress, abi: aclAbi, client: publicClient, }); const isPausableAdmin = await acl.read.isPausableAdmin([someAddress]); const isConfigurator = await acl.read.isConfigurator([someAddress]);