CompoundModule
Overview
The CompoundModule contract (src/lending-module/lending-modules/Compound.sol) integrates the Citadel Finance protocol with Compound Protocol, enabling liquidity pools to earn yield on idle collateral by supplying it to Compound's lending markets.
Purpose
- Yield Generation: Earn interest on unused collateral in liquidity pools
- Capital Efficiency: Maximize returns on deposited assets
- Risk Management: Automated interest tracking and withdrawal
- Protocol Integration: Seamless interaction with Compound Protocol
Contract Details
- File:
src/lending-module/lending-modules/Compound.sol - Inheritance:
ILendingModule,ExponentialNoError - License: AGPL-3.0-only
Key Functions
Core Operations
deposit
function deposit(
ILendingStorageManager.PoolStorage calldata _poolData,
bytes calldata,
uint256 _amount
) external override returns (
uint256 totalInterest,
uint256 tokensOut,
uint256 tokensTransferred
)
Purpose: Deposits collateral into Compound Protocol to earn interest.
Parameters:
_poolData: Pool storage data including collateral and cToken addresses_amount: Amount of collateral to deposit
Returns:
totalInterest: Interest accrued since last operationtokensOut: Net collateral amount deposited (excluding interest)tokensTransferred: Amount of cTokens received
Process:
- Validates collateral balance
- Calculates accrued interest
- Approves and mints cTokens
- Transfers cTokens to pool
- Updates return values
withdraw
function withdraw(
ILendingStorageManager.PoolStorage calldata _poolData,
address _pool,
bytes calldata,
uint256 _cTokenAmount,
address _recipient
) external override returns (
uint256 totalInterest,
uint256 tokensOut,
uint256 tokensTransferred
)
Purpose: Withdraws collateral from Compound Protocol.
Parameters:
_poolData: Pool storage data_pool: Pool address for interest calculation_cTokenAmount: Amount of cTokens to redeem_recipient: Address to receive withdrawn collateral
Returns:
totalInterest: Interest accrued since last operationtokensOut: Net collateral amount withdrawntokensTransferred: Actual collateral tokens transferred
Process:
- Calculates accrued interest
- Redeems cTokens for underlying collateral
- Transfers collateral to recipient
- Updates return values
Interest Calculation
getUpdatedInterest
function getUpdatedInterest(
address _poolAddress,
ILendingStorageManager.PoolStorage calldata _poolData,
bytes calldata
) external override returns (uint256 totalInterest)
Purpose: Calculates current total interest with state updates.
Parameters:
_poolAddress: Address of the pool_poolData: Current pool storage data
Returns: Total interest earned since pool inception
Process:
- Gets current underlying balance from Compound
- Subtracts deposited amounts and unclaimed fees
- Returns net interest earned
getAccumulatedInterest
function getAccumulatedInterest(
address _poolAddress,
ILendingStorageManager.PoolStorage calldata _poolData,
bytes calldata
) external view override returns (uint256 totalInterest)
Purpose: Calculates accumulated interest without state changes (view function).
Parameters: Same as getUpdatedInterest
Returns: Total interest earned (view-only calculation)
Process:
- Gets account snapshot from Compound
- Calculates underlying value using exchange rate
- Subtracts deposited amounts and fees
Utility Functions
getInterestBearingToken
function getInterestBearingToken(
address _collateral,
bytes calldata _extraArgs
) external view override returns (address token)
Purpose: Finds the corresponding cToken for a given collateral.
Parameters:
_collateral: Underlying token address_extraArgs: Encoded Comptroller address
Returns: Address of the corresponding cToken
Process:
- Decodes Comptroller address from extra args
- Iterates through all markets
- Matches underlying token to find cToken
- Returns matching cToken address
collateralToInterestToken
function collateralToInterestToken(
uint256 _collateralAmount,
address,
address _interestToken,
bytes calldata
) external view override returns (uint256 interestTokenAmount)
Purpose: Converts collateral amount to equivalent cToken amount.
Parameters:
_collateralAmount: Amount of underlying collateral_interestToken: cToken address
Returns: Equivalent cToken amount using current exchange rate
interestTokenToCollateral
function interestTokenToCollateral(
uint256 _interestTokenAmount,
address,
address _interestToken,
bytes calldata
) external view override returns (uint256 collateralAmount)
Purpose: Converts cToken amount to equivalent collateral amount.
Parameters:
_interestTokenAmount: Amount of cTokens_interestToken: cToken address
Returns: Equivalent underlying collateral amount
Migration Support
totalTransfer
function totalTransfer(
address _oldPool,
address _newPool,
address,
address _interestToken,
bytes calldata
) external override returns (
uint256 prevTotalCollateral,
uint256 actualTotalCollateral
)
Purpose: Supports pool migration by transferring all cTokens to new pool.
Parameters:
_oldPool: Source pool address_newPool: Destination pool address_interestToken: cToken being transferred
Returns:
prevTotalCollateral: Previous total collateral valueactualTotalCollateral: Current total collateral value
Integration with Compound
Compound Interfaces
interface ICompoundToken {
function mint(uint mintAmount) external returns (uint);
function redeem(uint redeemTokens) external returns (uint);
function balanceOf(address owner) external view returns (uint256);
function balanceOfUnderlying(address owner) external returns (uint);
function exchangeRateCurrent() external returns (uint);
function exchangeRateStored() external view returns (uint);
function underlying() external view returns (address);
}
interface IComptroller {
function getAllMarkets() external view returns (address[] memory);
}
Exchange Rate Mathematics
Compound uses an exchange rate mechanism:
- Exchange Rate:
underlying per cTokenratio - Calculation:
underlyingAmount = cTokenAmount * exchangeRate - Growth: Exchange rate increases as interest accrues
Error Codes
Compound operations return error codes:
0: SuccessNon-zero: Various error conditions
The module validates all operations return 0 or reverts.
Interest Distribution
Interest Splitting
struct InterestSplit {
uint256 poolInterest; // Interest for pool operations
uint256 jrtInterest; // Interest for JRT buyback
uint256 daoInterest; // Interest for DAO treasury
}
Interest is split according to pool configuration:
- Pool Share: Used for pool operations and LP rewards
- DAO Share: Sent to protocol treasury
- JRT Share: Used for token buybacks
Tracking Components
The module tracks several collateral components:
collateralDeposited: Original LP depositsunclaimedDaoCommission: DAO fees not yet claimedunclaimedDaoJRT: JRT buyback funds not yet usedtotalInterest: Net interest earned from lending
Usage Examples
Initial Setup
// Compound USDC market on Ethereum
address cUSDC = 0x39AA39c021dfbaE8faC545936693aC917d5E7563;
address comptroller = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B;
// Configure pool with Compound integration
bytes memory extraArgs = abi.encode(comptroller);
Deposit Flow
// Pool deposits 1000 USDC to Compound
uint256 depositAmount = 1000e6; // 1000 USDC
(uint256 interest, uint256 tokensOut, uint256 cTokensReceived) =
compoundModule.deposit(poolData, "", depositAmount);
// Pool now holds cUSDC tokens earning interest
Interest Calculation
// Check accumulated interest (view function)
uint256 totalInterest = compoundModule.getAccumulatedInterest(
poolAddress,
poolData,
""
);
// Get updated interest (with state changes)
uint256 currentInterest = compoundModule.getUpdatedInterest(
poolAddress,
poolData,
""
);
Withdrawal Process
// Withdraw 500 cUSDC tokens
uint256 cTokenAmount = 500e8; // 500 cUSDC
(uint256 interest, uint256 tokensOut, uint256 collateralReceived) =
compoundModule.withdraw(
poolData,
poolAddress,
"",
cTokenAmount,
recipient
);
Security Considerations
Compound Protocol Risks
- Smart Contract Risk: Compound protocol vulnerabilities
- Governance Risk: Compound governance parameter changes
- Oracle Risk: Compound's price oracle manipulation
- Liquidity Risk: Inability to withdraw during market stress
Module-Specific Security
- Interest Calculation: Precise arithmetic to prevent rounding errors
- Exchange Rate Manipulation: Protection against exchange rate attacks
- Approval Management: Careful token approval handling
- Error Handling: Proper validation of Compound operation results
Best Practices
- Regular Monitoring: Track Compound protocol health
- Upgrade Preparedness: Plan for Compound protocol upgrades
- Risk Limits: Implement limits on Compound exposure
- Emergency Procedures: Prepare for rapid withdrawal if needed
Gas Optimization
Efficient Operations
- Batch multiple operations when possible
- Cache exchange rates for multiple calculations
- Use view functions for simulations
- Minimize storage reads/writes
Exchange Rate Caching
// Cache exchange rate for multiple conversions
uint256 exchangeRate = cToken.exchangeRateStored();
Exp memory rate = Exp({mantissa: exchangeRate});
// Use cached rate for conversions
uint256 underlying1 = mul_ScalarTruncate(rate, cTokenAmount1);
uint256 underlying2 = mul_ScalarTruncate(rate, cTokenAmount2);
The CompoundModule enables Citadel Finance pools to earn yield on idle collateral through integration with the battle-tested Compound Protocol, enhancing capital efficiency while maintaining security.