Table of contents
- Summary :
- Key Innovations in Compound V3
- 1. Base Asset Model
- 2. Interest Rate Model
- 3. Principal and Present Value Accounting
- 4. Rebasing Token Functionality (cUSDC V3)
- 5. Liquidations and Collateral Management
- 6. COMP Rewards Mechanism
- 7. Bulkers and Transaction Efficiency
- Technical Architecture
- Mandatory knowledge to have before diving into CompoundV3 :
- CompoundV3 Book
- Compound V3 Architecture :
- Key Components of the Architecture:
- How It Works:
- Compound V3 Interest per Second
- cUSDC V3 as a non standard Rebasing Token
- Understanding Collateral,Liquidations and Reserves in Compound V3
- Checking if a borrower can be liquidated :
- Reserves
- Liquidation :
- How compound V3 Allocates COMP Rewards
- Reward Mechanism Overview:
- Key Differences from MasterChef:
- Reward Claiming:
- Why Minimum Thresholds Exist:
- The Comet Rewards Contract Requires Occasional Topping Up
- trackingSupplyIndex and trackingBorrowIndex behave like rewardPerTokenAcc
- accrueInternal() revisited
- Tracking user-level rewards: baseTrackingAccrued and baseTrackingIndex
- Claiming rewards
- Bulkers in CompoundV3
- Conclusion
This article has been written during Rareskills reading, it’s a summary of it, providing me a way to better learn CompoundV3
, I encourage the reader to take notes, draw graphs to better understand this long article, don’t hesitate to read it in multiple steps.
Summary :
Compound V3 Overview
Compound V3 introduces significant architectural and functional changes compared to its predecessor, focusing on simplicity, efficiency, and scalability. This version operates with a single borrowable asset per market, referred to as the base asset. For example, in a USDC market, users can only borrow USDC, simplifying risk management and protocol design.
Key Innovations in Compound V3
1. Base Asset Model
Each Compound V3 market supports only one borrowable asset (e.g., USDC, ETH).
Collateral assets are used to secure the borrowed base asset.
Governance sets the collateralization ratios and interest rate models.
2. Interest Rate Model
Interest rates are determined based on utilization rates.
Borrow interest rates follow a predefined curve, and supply interest rates are derived directly from utilization.
The "kink" defines an optimal utilization point, beyond which interest rates increase sharply.
Interest rates are calculated per second for precision and scalability.
3. Principal and Present Value Accounting
Instead of tracking deposit amounts directly, Compound V3 uses a system inspired by the SushiSwap Masterchef algorithm.
BaseSupplyIndex tracks the hypothetical gain of one dollar lent since the protocol's launch.
Principal Value: Deposited amount adjusted by the current BaseSupplyIndex.
Present Value: Principal Value multiplied by the current BaseSupplyIndex.
4. Rebasing Token Functionality (cUSDC V3)
cUSDC V3 behaves as a rebasing ERC-20 token.
The present value of a user's principal balance can be transferred as if it were an ERC-20 token.
Unlike standard vault-based ERC-20 tokens (e.g., ERC-4626), Compound V3 uses dynamic principal accounting.
5. Liquidations and Collateral Management
Collateral is tracked using immutable structs in the smart contract storage.
A borrower's position becomes liquidatable if the collateral's value (adjusted by the liquidation factor) falls below the debt value.
Liquidators absorb under-collateralized loans and repay debts on behalf of the borrower.
6. COMP Rewards Mechanism
Borrowers and lenders earn COMP tokens based on their participation.
Rewards are calculated using trackingSupplyIndex and trackingBorrowIndex.
Minimum thresholds for borrowing and lending must be met to accumulate COMP rewards.
Rewards must be manually claimed.
7. Bulkers and Transaction Efficiency
Bulker Contracts: Enable batching multiple actions (e.g., supplying assets, borrowing, repaying) into a single transaction.
These contracts are gas-optimized and non-custodial, ensuring user assets remain secure.
Technical Architecture
Comet Contract: Core lending and borrowing logic.
CometExt Contract: Supports extended ERC-20 functionalities.
Governance Control: Immutable parameters ensure predictable protocol behavior.
Mandatory knowledge to have before diving into CompoundV3 :
MasterChef summary :
Every time we have a state changing transaction, we look back at how many blocks passed, multiply that by the reward per block, then divide that by the total supply staked. This is how much reward a token accrued over that interval. We then add this value to a global accumulator that started at zero at the beginning of time. If we keep repeating this process every time a transaction comes in, we know how much a single token has accumulated in rewards since the beginning of time.
There is a variable called the “reward debt.” The moment a user deposits, the reward debt equal the deposited balance times the reward per token accumulator. That will prevent a user from claiming a reward right away since at that moment, the rewards due to him would be zero (current rewards minus reward debt).
We have a separate variable for Bob call “reward debt” or “rewards already issued” and assign it to that hypothetical reward amount. At block 10, the accumulated reward per token was 100, and Bob’s deposit was 100, so his reward debt is 10,000.
If Bob claims rewards at block 20, we subtract the 15,000 reward by the reward debt of 10,000. Bob will only be able to claim 5,000 reward at block 20.
Interest Rate Model
Lending protocols often set aside a “reserve factor” — a percentage of capital supplied by lenders that they do not receive interest on. The interest from this capital instead goes to the protocol, and is sometimes called the “spread.”
The degree of borrowing influences interest rates — we call this “borrow demand.” The greater the percentage of deposits loaned out, the more the interest rates rise. We refer to the degree of borrowing relative to the supply of borrowable funds as “utilization.” If there are no borrowers, utilization is 0% and if the deposits are fully loaned out, utilization is 100%. The value of utilization is used as the sole driver of interest rates.
Note that borrow demand is relative to total deposits. If the total deposits increases but borrow demand stays the same, utilization goes down.
Fallback Extension Pattern :
The fallback-extension pattern is a simple way to circumvent the 24kb smart contract size limit.
Suppose we have functions foo()
and bar()
in our primary contract and wish to add baz()
but cannot due to a lack of space.
We add a fallback function to our primary smart contract that delegates unknown function calls to an extension contract similar to how a proxy works.
We put baz()
in the extension contract. When we call baz()
on the main contract, it will not match any of the function selectors in the primary contract, and the thus trigger the fallback function. Then, baz()
will be delegatecalled in the extension contract.
CompoundV3 Book
Compound V3 Architecture :
Compound V3 introduces a streamlined and efficient architecture, optimizing both protocol performance and user experience. At the core of this system lies the Comet contract, which handles the primary lending and borrowing logic.
Key Components of the Architecture:
Base Asset Model: Each market in Compound V3 is built around a single base asset, which serves as the primary currency for borrowing and lending. This design simplifies risk management and reduces complexity.
Collateral Management: Users provide various collateral assets to secure their borrowing positions. These assets are tracked and evaluated through immutable storage structures, ensuring transparency and efficiency.
Interest Rate Mechanism: Interest rates are dynamically calculated based on the utilization rate, which measures the proportion of borrowed funds relative to available supply. This approach ensures that interest rates respond predictably to changing market conditions.
Immutable Configuration: Critical protocol parameters, such as interest rate curves and collateral factors, are set as immutable variables. Any updates require deploying a new implementation and carefully appending changes to prevent breaking existing configurations.
Reward Distribution: Participants in Compound V3 are incentivized through COMP token rewards, distributed based on their lending and borrowing activities.
Technical Footprint: Compound V3 comprises 4,304 lines of Solidity code, meticulously optimized for gas efficiency and security.
How It Works:
Lenders deposit their assets into the protocol, earning interest based on market utilization.
Borrowers secure loans against collateral, adhering to pre-defined collateralization ratios.
Interest accrues in real-time, calculated per second for precision.
Rewards are distributed based on protocol participation and can be claimed manually by users.
The architectural choices in Compound V3 reflect a focus on scalability, efficiency, and robust risk management, positioning it as a cornerstone in decentralized finance (DeFi).
- Lenders and borrowers are rewarded with COMP tokens for their participation in the ecosystem
Compound V3 Interest per Second
In Compound V2 (and AAVE V3), the supply interest rates are the borrow interest rates multiplied by the utilization.
In Compound V3, interest rates are calculated with precision and efficiency, leveraging a per-second interest accrual mechanism. This approach ensures accuracy in reward and debt tracking, regardless of transaction timing.
In the article on Interest Rates in DeFi, it is noted that popular interest rate models are piecewise functions with a notion of an “optimal” utilization. Compound V3 refers to this as the “kink” — when the interest rates start rising more sharply. These two different terms refer to the exact same concept.
Compound V3 uses a 1e18 scale to measure interest rates.
Key Features of Interest Accrual:
Dynamic Interest Rates: Interest rates are derived directly from the utilization rate, which measures the proportion of borrowed funds relative to the total supply.
Separation of Borrow and Supply Rates: Unlike previous versions, supply and borrow interest rates follow distinct curves. Supply interest rates are directly tied to utilization, while borrow interest rates follow a pre-defined curve.
Utilization "Kink": A critical utilization point, referred to as the kink, determines when interest rates increase more sharply. This ensures predictable market behavior as utilization approaches full capacity.
Per-Second Accuracy: Compound V3 calculates interest on a per-second basis, using a fixed annual constant of 31,536,000 seconds per year to ensure consistency and precision.
How It Works:
Interest accrues per second based on the utilization rate and the defined interest rate curves.
Borrowers pay interest proportional to their borrowed amount, and lenders earn interest based on their share of the total supply.
Interest rates dynamically adjust as market utilization fluctuates.
The
getSupplyRate()
andgetBorrowRate()
functions provide real-time access to current interest rates.
Mathematical Example:
If the annual borrow interest rate is set at 1%, the per-second rate can be calculated as:
borrowInterestRatePerSecond = annualBorrowRate / SECONDS_PER_YEAR
This granular approach eliminates inconsistencies caused by varying transaction frequencies and ensures fairness across all participants.
Compound V3's per-second interest rate model enhances transparency, accuracy, and adaptability, setting a new standard for decentralized lending protocols.
How to get hypothetical interest rate :
To calculate interest rates as a function of utilization, users can get the current utilization using getUtilization()
and plug that into getSupplyRate()
and getBorrowRate()
.
contract GetCurrentRatesComet {
function getRates(IComet comet) external returns (uint64, uint64) {
uint64 private constant SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
uint256 utilization = comet.getUtilization();
// these are 18 decimal fixed point numbers
// measuring interest per second
uint64 supplyRate = comet.getSupplyRate();
uint64 borrowRate = comet.getBorrowRate();
// return them as APR
return (supplyRate * SECONDS_PER_YEAR,
borrowRate * SECONDS_PER_YEAR);
}
}
The intuitive way to track lender deposits is to record the amount of USDC they deposited and the time they deposited. Compound V3 does not do this.
Instead, similar to SushiSwap Masterchef Staking Algorithm, Compound V3 tracks the hypothetical gain of one dollar lent “since the beginning of time.” (Readers not already familiar with this algorithm should read the linked resource).
The hypothetical gain of one dollar lent since the beginning of time is tracked in the baseSupplyIndex
(in CometStorage.sol). It behaves very similar to to the “rewardPerTokenAccumulator” from Sushiswap. It starts at 1.0 and each time a state-changing operation occurs (deposit, withdraw, borrow, etc), it is increased proportional to the time passed and the interest rate over that period. For example, if 100 seconds passed, and the interest rate was 0.001 per second (unrealistically high, but easy to reason about), then baseSupplyIndex
will be updated to 1.1. Specifically, the following formula is used:
baseSupplyIndex += supplyInterestRatePerSecond(utilization) × secondsElapsed
Example :
Alice deposits $1,000 at a time when the baseSupplyIndex is 2.5. She is not credited with depositing $1000, instead she is credited with depositing $400, which is her deposit divided by the current baseSupplyIndex ($1,000 ÷ 2.5). Alice has a “principal value” of $400 in her account (yellow box). This is the value Compound stores for users (CometStorage.sol).
***The variable baseTrackingAccrued
is used by CometRewards to determine how much COMP to award the account for participation in the protocol. The variable baseTrackingIndex
is related to that but currently unused. The variable assetsIn
is only used for borrowers as an indicator for whether certain collateral assets are used. The variable _reserved
is unused.Therefore, principal
is the only essential variable for lender accounting
If she were to withdraw right away, Compound would calculate her balance as $400 times the current baseSupplyIndex
, which is 2.5, so she would withdraw $1,000. Compound calls the principal value times the baseSupplyIndex
the “present value”.
Compound V3 does not “remember” that her original real deposit was $1,000. This is implied because the baseSupplyIndex
is currently at 2.5 and her deposit is recorded as being $400.
The “downscaled” or “scaled backward” dollar value that Compound V3 stores is called the “principal value.” When we multiply the principal value by the current baseSupplyIndex
, or “scale forward” we get the “present value.”
In CometCore.sol, we see that:
the “principal value” is computed by dividing the “present value” by the baseSupplyIndex
the “present value” is computed by multiplying by the “principal value” by the baseSupplyIndex
.
the “principal value” is computed by dividing the “present value” by the
baseSupplyIndex
the “present value” is computed by multiplying by the “principal value” by the
baseSupplyIndex
.
cUSDC V3 as a non standard Rebasing Token
The Compound V3 contract behaves like a rebasing ERC 20 token. A rebasing token is a token which has an algorithmically adjusted supply rather than a fixed one. The “token” here represents the present value of positive USDC balances. That is, lenders can transfer the present value of their principal to other addresses as if it were an ERC 20 token. Since the principal value is generally increasing due to interest accrual, this ERC 20 token is rebasing upwards over time.
Compound V3 does not use a token vault standard (e.g. ERC-4626) to track “shares” of the lending pool.
As we noted in our discussion of principal and present value, a user may have deposited 100 USDC but have a credit of 110 USDC due to interest accrued — the 110 is the present value. It is this unit of account that the ERC20 functionality of Compound V3 manages.
In the vault :
totalSupply() is not the amount of USDC lenders deposited into Compound — it is the present value of the total deposits.
totalBorrow
, as the name implies, is the total amount of USDC borrowed. That is, it is the present value of the debt. This is not just the amount of USDC borrowers withdrew from the platform — it includes the interest accrued on the borrowed USDC.
Understanding Collateral,Liquidations and Reserves in Compound V3
Remind UserBasic Storage Struct :
If the principal
(blue box) is negative, it means the user is a borrower, and the negative value will be the principal value of their debt.
assetsIn
(red box) is a bitmap to indicate whether they’ve deposited an certain collateral asset or not. At this time of writing, the bitmap is laid out as follows:
Note that this struct does not tell us how much collateral the user is holding. That is held in balance
variable the UserCollateral struct which is stored in the userCollateral
nested mapping. The _reserved
variable is unused.
To list out the collateral a user has supplied, we loop through 0…numAssets
Compound stores and check if that bit is set to one for that user. If it is, we obtain the token address associated with that bit and check the user balance in userCollateral[user][collateralAsset]
to see how much of that collateral the user holds.
By multiplying the balance
with the oracle price, we know the dollar value of the user’s collateral. The following table gives an example of summing up the total value of a user’s collateral.
The address of the oracle where Compound obtains the collateral price from is stored in the AssetInfo struct (blue box).
When we query the current values for the token UNI (assetId 3) , we can compare the values to the ones displayed on the marketplace. The relationship should be clear
the liquidation penalty is
1 - liquidation factor
.The
liquidateCollateralFactor
is the LTV at which the loan gets liquidated.The
liquidationFactor
encodes the liquidation penalty.The fact that the
liquidationFactor
in the struct does not mean the same thing as theliquidationFactor
in the UI is confusing.
Each asset’s info is packed into immutable variables — it is not kept in storage for gas efficiency purposes. Since it takes two 32 byte words to store the AssetInfo
struct, Comet numbers off the uint256 words with assetXX_a, assetXX_b
. The XX
here indicates the asset index. So asset00_a
and asset00_b
collectively hold the AssetInfo
struct for asset 0. Remember, it takes two 256 bit variables to store AssetInfo, which is 432 bits large.
We can now show the implementation of getAssetInfo()
from Comet.sol:280-356. It simply unpacks the immutable variable into the AccountInfo
struct and returns it. The bitshifting and packing it uses is straightforward, so we will not explain it here.
Because these variables are immutable, governance must deploy a new implementation and update the proxy if it wishes to add another collateral asset or change the parameters of one of the assets. Care must be taken to only append assets and not interfere with previous definitions.
Checking if a borrower can be liquidated :
The Comet.sol isLiquidatable() function sums up the collateral assets held by a user multiplied by their liquidationFactor
. If this sum is less than the present value of their debt (which is a negative number), then the user is liquidatable.
This means a borrower might have one asset below the liquidation threshold, but if other collateral assets balance it out the deficit, then the user is not liquidateable.
The full value of the collateral does not “count” towards the user’s collateral balance — it is reduced by the liquidation factor.
Note that when a borrower gets liquidated, they will become a lender if the collateral is sufficient to cover the debt.
If the collateral is not sufficient to cover the debt, then the protocol implicitly takes a loss from its reserves.
Reserves
Interest paid by borrowers that is in excess of what lenders earned are called “reserves” in Compound V3.
The function getReserves()
returns this excess value. The USDC the protcol “owns” is the sum of
the balance of USDC held by Compound i.e.
ERC20(baseToken).balanceOf(address(this))
andthe present value of the
totalBorrow
,minus the amount the protocol owes to lenders, i.e. the present value of the
totalSupply
.
In other words, it is usdc_balance + totalBorrow - totalSupply
.
As you can see in the function below, totalSupply
is assigned a negative sign because that is what Compound owes to the lenders. The positive factors — the amount of held USDC and the net amount of USDC owed to Compound from borrowers — are the amount of USDC “owned” by the Compound.
Liquidation :
To liquidate a borrower, the liquidator calls absorb()
with the borrower’s account as arguments, then calls buyCollateral()
in the same transaction. The liquidator should check that reserves are not more than the target reserves, and that the account is liquidate able via isLiquidateable()
.
Compound V3 provides a reference liquidator bot. Keep in mind this is a reference implementation — trying to get liquidations before others is highly competitive, so your code will need to be extremely gas optimized to be able to profitably liquidate a position before others do.
buyCollateral()
Collateral is still inside the protocol after an absorb. All that happened was that the user’s collateral balance was set to zero — however the collateral was not transferred anywhere. This collateral is still “inside” Compound V3.
To incentivize liquidators, collateral held by Compound is sold at a discount via the buyCollateral() function.
There are two crucial pieces of business logic in this function:
If the reserves amount is larger than the target reserves ($5 million) this function will revert, not allowing liquidators to purchase collateral. (yellow box in the code below). As mentioned above, Compound wishes to speculate on the collateral. Since it is already in a cash-heavy position, they don’t wish to accumulate more cash.
The exchange rate the protocol sells the collateral at is determined by the
quoteCollateral()
function (red box in the code below).
If the reserves amount is larger than the target reserves ($5 million) this function will revert, not allowing liquidators to purchase collateral. (yellow box in the code below). As mentioned above, Compound wishes to speculate on the collateral. Since it is already in a cash-heavy position, they don’t wish to accumulate more cash.
The exchange rate the protocol sells the collateral at is determined by the quoteCollateral()
function (red box in the code below).
The rest of the code is be self-explanatory.
How compound V3 Allocates COMP Rewards
Compound V3 rewards participants with COMP tokens, distributed proportionally to their share of the market’s lending and borrowing activities.
Reward Mechanism Overview:
Rewards are tracked using
trackingSupplyIndex
(for lenders) andtrackingBorrowIndex
(for borrowers).These indices measure how much one hypothetical "staked" USDC has earned in rewards since the protocol's inception.
Rewards are diluted when more USDC is supplied or borrowed, ensuring fair distribution.
Immutable variables like
baseSupplyTrackingSupplySpeed
andbaseTrackingBorrowSpeed
determine the reward rate per unit of time.
Key Differences from MasterChef:
While inspired by the MasterChef Staking Algorithm, Compound V3 separates supply and borrow reward rates.
Governance can rescale rewards, allowing dynamic adjustments without requiring contract redeployment.
Reward Claiming:
Rewards are not automatically distributed but must be manually claimed via the Comet Rewards Contract.
If the total supply or borrowed amount falls below certain minimum thresholds, rewards stop accumulating to prevent potential exploits or inefficiencies.
Why Minimum Thresholds Exist:
Prevent accumulator overflow in edge cases with extremely low supply or borrow amounts.
Ensure meaningful reward distribution, avoiding wasted emissions of COMP tokens.
Compound V3’s reward system balances precision, flexibility, and scalability, ensuring fair incentives for all participants in the lending and borrowing ecosystem.
The Comet Rewards Contract Requires Occasional Topping Up
The Comet Rewards contract does not mint COMP tokens, it relies on Governance transferring tokens to it. There are a total supply of 10 million COMP tokens in circulation, and all of them have already been minted. A significant amount of the supply is held by governance. Periodically, COMP tokens are transferred from the governance treasury to the reward contract. You can see the following governance transactions that “top up” the reward contract.
Because there is a fixed supply, the COMP rewards for ecosystem participation cannot continue indefinitely unless governance buys COMP tokens on the open market.
trackingSupplyIndex
and trackingBorrowIndex
behave like rewardPerTokenAcc
The plot below should be familiar from MasterChef. The more USDC that is “staked” the less reward each token receives because only a fixed amount is issued each period (determined by trackingSupplyIndex
and trackingBorrowIndex
).
One noteworthy variation is that if the amount of USDC supplied or borrowed (pink line) is below the baseMinForRewards
(red text and dashed line) threshold, then USDC does not accumulate rewards, and the trackingSupplyIndex (or trackingBorrowIndex) does not increase for that state update.
These variables are not public, but can be retrieved via the totalsBasic()
public function in CometExt. Since CometExt is a separate contract that Comet issues delegatecalls to, we cannot retrieve the values via Etherscan. Instead we use cast from Foundry to retrieve them, as the screenshot below shows.
** Compound doesn’t issue rewards to lenders or borrowers if there are less than 1 million dollars (1e12 USDC as USDC has 6 decimals) lent out. Similarly, a borrowed USDC will not accumulate COMP rewards if there is less than 1 million dollars borrowed.
accrueInternal() revisited
The trackingSupplyIndex
and trackingBorrowIndex
are updated whenever accrueInternal()
is called.
The code below implements the logic described in the above sections. The if
conditions in red boxes prevent trackingSupplyIndex
or trackingBorrowIndex
from accumulating more rewards if the supply or borrow amount is below baseMinForRewards
. The baseTrackingSupplySpeed
and baseTrackingBorowSpeed
(blue boxes) are immutable variables, so the amount the indexes are incremented by only depends on timeElapsed
and (inversely) to totalSupplyBase
(or totalBorrowBase
).
You can think of the baseTrackingSupplySpeed
and baseTrackingBorrowSpeed
as the “reward per unit time (there are analogous to the rewardBlock
of masterchef).” When multiplied by timeElapsed
, that computes the amount of rewards accumulated for a single participating USDC. Finally, dividing that result by totalSupplyBase
or totalBorrowBase
dilutes that USDC based on the total amount. They are 15 decimal fixed point numbers.
Tracking user-level rewards: baseTrackingAccrued and baseTrackingIndex
Like MasterChef, Compound Rewards accumulates rewards to an account when that account does a state-changing transaction. And also like MasterChef, the rewards the user accumulates are proportional to their balance and how much the “index” or “accumulator” changed since the last time the user did a state changing operation.
Let’s look at the user struct again
baseTrackingIndex
is the value of trackingSupplyIndex
or trackingBorrowIndex
at the time the UserBasic storage struct was last updated, depending on if the account is a lender or borrower respectively. The delta between the current trackingSupplyIndex
(or trackingBorrowIndex
) and the user’s stored value of baseTrackingIndex
determines how many rewards they will accumulate for that transaction. Consider the plot below :
Whenever a user does something that will change their principal, a call to the internal function updateBasePrincipal()
is made. The function will determine how much the trackingSupplyIndex
or trackingBorrowIndex
has changed since the last update and accumulate rewards to the user’s baseTrackingAccrued
accordingly. The function is shown below
In summary baseTrackingIndex
is the value of the index when the user last updated. baseTrackingAccrued
is the total rewards owed to the user since they participated in the protocol regardless of past claims which are negated with reward debt tracked in the reward contract.
** In the code above, we see the user’s accumulated rewards are divided by accrualDescaleFactor
. This allows both ETH and USDC to be tracked on the same scale. Since ETH has 18 decimals and USDC has 6 decimals, ETH baseTrackingAccrued is divided by 1e12 so that it effectively has the same number of “decimals” as USDC. This allows baseTrackingAccrued
to track both assets on the same scale.
Claiming rewards
To claim rewards, a user simply calls the claim()
function in CometReward.sol. The rewardsClaimed
mapping (red box) behaves like the rewardDebt from MasterChef.
Bulkers in CompoundV3
The bulker contracts in Compound V3 are multicall-like contracts for batching several transactions.
For example, if we wanted to supply Ether, LINK, and wBTC as collateral and borrow USDC against it in one transaction, we can do that.
We can also reduce the collateral holdings and withdraw a loan in one transaction as the screenshot below demonstrates. This of course assumes that we stay within the collateral factor limits.
The bulker does not behave like a traditional multicall where it accepts a list of arbitrary calldata. Instead, it takes two arguments: a list of actions (of which there are 6 choices) and the arguments to supply to them. The function is shown below. Specifically, we can
supply an ERC 20 (ACTION_SUPPLY_ASSET
)supply ETH (ACTION_SUPPLY_NATIVE_TOKEN
)transfer an asset (ACTION_TRANSFER_ASSET
), see our article on how Compound V3 behaves like a rebasing ERC 20 token to see how this workswithdraw an ERC 20 (ACTION_WITHDRAW_ASSET
)withdraw ETH (ACTION_WITHDRAW_NATIVE_TOKEN
)claim accumulated COMP rewards. The underlying function claimReward will interact with the reward contract instead of the main lending contract (Comet.sol).
supply an ERC 20 (
ACTION_SUPPLY_ASSET
)supply ETH (
ACTION_SUPPLY_NATIVE_TOKEN
)transfer an asset (
ACTION_TRANSFER_ASSET
), see our article on how Compound V3 behaves like a rebasing ERC 20 token to see how this workswithdraw an ERC 20 (
ACTION_WITHDRAW_ASSET
)withdraw ETH (
ACTION_WITHDRAW_NATIVE_TOKEN
)claim accumulated COMP rewards. The underlying function claimReward will interact with the reward contract instead of the main lending contract (Comet.sol).
The bulker never holds the tokens of the user. Instead, the user gives approval to the bulker and the bulker supplies assets to Compound on behalf of the user. Compound does not assume that msg.sender
is the depositor; doing so is generally not a good design pattern because it breaks composability — it prevents other contracts from acting on behalf of the user.
Conclusion
Compound V3 represents a significant evolution in decentralized finance (DeFi) lending protocols, emphasizing scalability, efficiency, and transparency. Through its innovative base asset model, precise per-second interest accrual, and rebasing cUSDC token, Compound V3 offers a streamlined approach to lending and borrowing.
The introduction of immutable parameters, COMP rewards distribution mechanisms, and gas-optimized bulker contracts showcases Compound's commitment to protocol sustainability and user experience. The protocol’s architectural design ensures accurate accounting, flexible governance, and predictable market behavior.
As DeFi continues to evolve, Compound V3 sets a benchmark for future lending protocols, combining technical sophistication with practical usability. Whether you are a borrower, lender, or protocol contributor, Compound V3 offers a robust framework for participating in the decentralized financial ecosystem.
By simplifying complexities and optimizing critical operations, Compound V3 remains a cornerstone in the DeFi landscape, providing a reliable foundation for the next generation of decentralized applications.