Skip to main content

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 operation
  • tokensOut: Net collateral amount deposited (excluding interest)
  • tokensTransferred: Amount of cTokens received

Process:

  1. Validates collateral balance
  2. Calculates accrued interest
  3. Approves and mints cTokens
  4. Transfers cTokens to pool
  5. 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 operation
  • tokensOut: Net collateral amount withdrawn
  • tokensTransferred: Actual collateral tokens transferred

Process:

  1. Calculates accrued interest
  2. Redeems cTokens for underlying collateral
  3. Transfers collateral to recipient
  4. 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:

  1. Gets current underlying balance from Compound
  2. Subtracts deposited amounts and unclaimed fees
  3. 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:

  1. Gets account snapshot from Compound
  2. Calculates underlying value using exchange rate
  3. 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:

  1. Decodes Comptroller address from extra args
  2. Iterates through all markets
  3. Matches underlying token to find cToken
  4. 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 value
  • actualTotalCollateral: 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 cToken ratio
  • Calculation: underlyingAmount = cTokenAmount * exchangeRate
  • Growth: Exchange rate increases as interest accrues

Error Codes

Compound operations return error codes:

  • 0: Success
  • Non-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 deposits
  • unclaimedDaoCommission: DAO fees not yet claimed
  • unclaimedDaoJRT: JRT buyback funds not yet used
  • totalInterest: 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

  1. Regular Monitoring: Track Compound protocol health
  2. Upgrade Preparedness: Plan for Compound protocol upgrades
  3. Risk Limits: Implement limits on Compound exposure
  4. 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.