DocumentationOpen App

Core Extension

Advanced patterns for extending Gearbox protocol contracts beyond standard adapter integration.

This is advanced material. For standard integrations, see Adapter Development or Protocol Integration.

When to Extend vs Integrate

Different levels of integration require different approaches:

ApproachComplexityWhen to Use
AdapterLowWrap existing DeFi protocols for Credit Account access
Protocol IntegrationMediumBuild contracts that compose with Credit Accounts externally
Core ExtensionHighModify Credit Manager/Pool behavior, create protocol-specific spokes

Examples by approach:

Adapter: Wrapping Uniswap, Curve, Yearn for Credit Account users

Protocol Integration: Strategy vaults that open/manage Credit Accounts

Core Extension: Custom Credit Manager for protocol-specific accounting, custom interest rate models, pool hooks for fee distribution

Build a core extension when you need to:

  • Modify how debt/collateral calculations work
  • Change liquidity flow patterns between pools and Credit Managers
  • Implement protocol-specific Credit Manager variants ("spokes")
  • Create custom interest rate logic beyond linear models
  • Add lifecycle hooks to pool operations

Understanding Money Flows

Core extensions require deep understanding of how liquidity moves through the protocol.

The Fundamental Flow

Lenders → Pool → Credit Manager → Credit Account → DeFi Protocols
         (ERC-4626)      |              |
                         |              +-> Holds collateral
                         +-> Tracks debt

Key invariants:

  1. Pool lends only to whitelisted Credit Managers
  2. Credit Managers borrow on behalf of Credit Accounts
  3. Credit Accounts hold all collateral and debt
  4. All value flows are tracked via indexed interest and quota systems

Detailed Liquidity Flow

When borrowing:

Solidity
// 1. User opens Credit Account via CreditFacade CreditFacade.openCreditAccount(owner, calls, referralCode) | v // 2. Facade instructs Manager to borrow CreditManager.openCreditAccount(debt, onBehalfOf) | v // 3. Manager requests liquidity from Pool Pool.lendCreditAccount(borrowedAmount, creditAccount) | v // 4. Pool transfers underlying directly to Credit Account IERC20(underlying).transfer(creditAccount, borrowedAmount)

When repaying:

Solidity
// 1. User closes account via CreditFacade CreditFacade.closeCreditAccount(creditAccount, calls) | v // 2. Manager calculates total debt totalDebt = principal + accruedInterest + quotaInterest + fees | v // 3. Underlying transfers from Credit Account to Pool IERC20(underlying).transferFrom(creditAccount, pool, totalDebt) | v // 4. Manager reports repayment to Pool Pool.repayCreditAccount(repaidAmount, profit, loss)

Critical detail: The Credit Manager never holds funds. It orchestrates transfers between Pool and Credit Account while maintaining accounting state.

Pool Extension Patterns

Custom Interest Rate Models

The pool queries an external IRM contract for interest rates. You can implement custom logic by deploying a new IRM.

IInterestRateModel interface:

Solidity
interface ILinearInterestRateModelV3 { function calcBorrowRate( uint256 expectedLiquidity, uint256 availableLiquidity, bool checkOptimalBorrowing ) external view returns (uint256 borrowRate); function getModelParameters() external view returns ( uint16 U_1, uint16 U_2, uint16 R_base, uint16 R_slope1, uint16 R_slope2, uint16 R_slope3 ); }

Example: Time-weighted interest model

Solidity
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {ILinearInterestRateModelV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ILinearInterestRateModelV3.sol"; contract TimeWeightedIRM is ILinearInterestRateModelV3 { uint16 public immutable U_1 = 7000; // 70% uint16 public immutable U_2 = 9000; // 90% uint16 public immutable R_base = 100; // 1% uint16 public immutable R_slope1 = 200; // 2% uint16 public immutable R_slope2 = 500; // 5% // Time-based slope adjustment uint16 public immutable peakHourMultiplier = 150; // 1.5x during peak hours function calcBorrowRate( uint256 expectedLiquidity, uint256 availableLiquidity, bool checkOptimalBorrowing ) external view returns (uint256 borrowRate) { uint256 utilizationBps = (expectedLiquidity - availableLiquidity) * 10_000 / expectedLiquidity; // Apply time-based multiplier during peak hours (12-18 UTC) uint256 hour = (block.timestamp / 3600) % 24; uint16 multiplier = (hour >= 12 && hour < 18) ? peakHourMultiplier : 100; // Standard linear calculation with time adjustment uint256 baseRate = R_base; if (utilizationBps <= U_1) { borrowRate = baseRate + (R_slope1 * utilizationBps * multiplier) / 10_000 / 100; } else if (utilizationBps <= U_2) { borrowRate = baseRate + (R_slope2 * utilizationBps * multiplier) / 10_000 / 100; } else { borrowRate = baseRate + (R_slope3 * utilizationBps * multiplier) / 10_000 / 100; } // Convert to RAY (27 decimals) borrowRate = borrowRate * 10**27 / 10_000; } function getModelParameters() external view returns ( uint16, uint16, uint16, uint16, uint16, uint16 ) { // Return adjusted slope based on current time uint256 hour = (block.timestamp / 3600) % 24; uint16 adjustedSlope1 = (hour >= 12 && hour < 18) ? R_slope1 * peakHourMultiplier / 100 : R_slope1; return (U_1, U_2, R_base, adjustedSlope1, R_slope2, R_slope3); } }

Deploying custom IRM:

Custom IRMs can be set via governance:

Solidity
// Only CONFIGURATOR can update IRM IPoolV3(pool).setInterestRateModel(newIRMAddress);

Pool Withdrawal Hooks

Pools in V3 support withdrawal fees. These are not "hooks" in the callback sense, but configurable parameters:

Solidity
// Read current withdrawal fee uint16 withdrawFee = IPoolV3(pool).withdrawFee(); // basis points // Fee is applied during withdraw/redeem uint256 assets = pool.withdraw(amount, receiver, owner); // Actual received = amount - (amount * withdrawFee / 10000)

For custom fee distribution logic, you would typically:

  1. Deploy a fee collector contract
  2. Configure the pool's treasury address to your collector
  3. Implement distribution logic in your collector

Credit Manager Extension Patterns

Spoke Development Concept

A "spoke" is a specialized Credit Manager variant designed for protocol-specific use cases. Unlike standard Credit Managers, spokes extend core functionality for unique accounting or liquidity patterns.

When to build a spoke:

  • Your protocol needs custom collateral valuation logic
  • You're integrating Credit Accounts as a core protocol primitive
  • You need specialized debt/liquidation mechanics
  • You want to modify how positions are opened/closed

Example use case: A derivatives protocol where Credit Accounts are used as margin accounts with custom position tracking.

Spoke Architecture Pattern

Solidity
import {CreditManagerV3} from "@gearbox-protocol/core-v3/contracts/credit/CreditManagerV3.sol"; contract DerivativesSpoke is CreditManagerV3 { // Custom state for protocol-specific tracking mapping(address => PositionData) public positions; struct PositionData { uint256 longExposure; uint256 shortExposure; int256 unrealizedPnL; } constructor( address _pool, address _addressProvider ) CreditManagerV3(_pool, _addressProvider) {} // Override collateral calculation to include unrealized PnL function calcDebtAndCollateral( address creditAccount, CollateralCalcTask task ) public view override returns ( CollateralDebtData memory cdd ) { // Call parent implementation cdd = super.calcDebtAndCollateral(creditAccount, task); // Adjust TWV based on unrealized PnL PositionData memory pos = positions[creditAccount]; if (pos.unrealizedPnL > 0) { // Add unrealized profit to collateral cdd.twvUSD += uint256(pos.unrealizedPnL); } else { // Subtract unrealized loss from collateral cdd.twvUSD -= uint256(-pos.unrealizedPnL); } } // Protocol-specific function to update position state function updatePosition( address creditAccount, uint256 longExposure, uint256 shortExposure, int256 pnl ) external { // Only allow calls from authorized adapters require( adapterToContract[msg.sender] != address(0), "Not authorized adapter" ); positions[creditAccount] = PositionData({ longExposure: longExposure, shortExposure: shortExposure, unrealizedPnL: pnl }); } }

Custom Collateral Valuation

Extend collateral calculations for protocol-specific assets:

Solidity
// Override token price resolution function priceOracle() public view override returns (IPriceOracleV3) { return IPriceOracleV3(customOracleAddress); } // Implement custom oracle with protocol-specific pricing contract CustomOracle is IPriceOracleV3 { function convertToUSD( uint256 amount, address token ) external view override returns (uint256) { if (token == protocolSpecificToken) { // Custom valuation logic return amount * getCustomPrice() / 10**tokenDecimals; } return defaultOracle.convertToUSD(amount, token); } }

Extending Liquidation Logic

Custom liquidation premiums based on collateral type:

Solidity
contract CustomLiquidationCM is CreditManagerV3 { mapping(address => uint16) public tokenLiquidationPremiums; function liquidateCreditAccount( address creditAccount, address to, MultiCall[] calldata calls ) external override { // Calculate custom premium based on collateral composition uint256 enabledMask = enabledTokensMaskOf(creditAccount); uint16 premium = _calculatePremium(enabledMask); // Set premium temporarily uint16 oldPremium = liquidationPremium; liquidationPremium = premium; // Execute standard liquidation super.liquidateCreditAccount(creditAccount, to, calls); // Restore liquidationPremium = oldPremium; } function _calculatePremium(uint256 mask) internal view returns (uint16) { uint16 maxPremium = 0; for (uint256 i = 0; i < 256; i++) { if (mask & (1 << i) != 0) { address token = getTokenByMask(1 << i); if (tokenLiquidationPremiums[token] > maxPremium) { maxPremium = tokenLiquidationPremiums[token]; } } } return maxPremium; } }

Working with Configurators

The CreditConfigurator provides governance interface for Credit Manager parameters. When building spokes, you typically extend the configurator as well.

Standard Configurator Usage

Solidity
import {ICreditConfiguratorV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditConfiguratorV3.sol"; ICreditConfiguratorV3 configurator = ICreditConfiguratorV3(configuratorAddress); // Add new collateral token configurator.addCollateralToken( tokenAddress, 8500 // liquidationThreshold (85% = 8500 basis points) ); // Add adapter configurator.allowAdapter(adapterAddress); // Set fees configurator.setFees( 500, // feeInterest (5%) 150, // feeLiquidation (1.5%) 500, // liquidationPremium (5%) 100, // feeLiquidationExpired (1%) 500 // liquidationPremiumExpired (5%) );

Custom Configurator for Spokes

Solidity
import {CreditConfiguratorV3} from "@gearbox-protocol/core-v3/contracts/credit/CreditConfiguratorV3.sol"; contract DerivativesSpokeConfigurator is CreditConfiguratorV3 { DerivativesSpoke public immutable spoke; constructor( address _creditManager, address _creditFacade ) CreditConfiguratorV3(_creditManager, _creditFacade) { spoke = DerivativesSpoke(_creditManager); } // Protocol-specific configuration function setPositionLimits( uint256 maxLongExposure, uint256 maxShortExposure ) external configuratorOnly { spoke.setPositionLimits(maxLongExposure, maxShortExposure); } function setCustomLiquidationPremium( address token, uint16 premium ) external configuratorOnly { spoke.setTokenLiquidationPremium(token, premium); } }

Governance Integration

For production deployments, configurator changes should go through governance:

Solidity
// Propose change via governance forum // After approval, execute via timelock // Example: Adding new collateral token bytes memory data = abi.encodeCall( ICreditConfiguratorV3.addCollateralToken, (newTokenAddress, 8000) ); // Submit to governance contract governance.propose( configuratorAddress, 0, // value data, "Add NEWTOKEN as collateral with 80% LT" );

Key patterns demonstrated:

  1. State extension: Added Position tracking on top of base Credit Manager
  2. Collateral override: Modified TWV calculation to include unrealized PnL
  3. Protocol-specific logic: Market management and position tracking
  4. Governance integration: configuratorOnly modifier for admin functions

For architectural background, see Credit Suite Architecture and Pool Architecture.

Security Considerations

When extending core contracts:

  1. Preserve invariants - Never break core protocol assumptions (debt tracking, collateral checks)
  2. Test extensively - Fork mainnet and test against real pools/Credit Managers
  3. Audit thoroughly - Core extensions require professional audits
  4. Consider upgrade paths - Plan for parameter changes and emergency controls
  5. Monitor gas costs - Overrides affect every user operation
  6. Validate inputs - Custom logic must not allow manipulation of debt/collateral calculations

Deployment Checklist

  • Core contracts audited by reputable firm
  • Fork tests against production Gearbox contracts
  • Governance proposal prepared with detailed specification
  • Documentation for users and integrators
  • Emergency pause mechanisms tested
  • Oracle manipulation scenarios analyzed
  • Gas profiling completed for all overridden functions
  • Upgradeability plan documented