The Vault is the core abstract contract inherited by every cUSD deployment. It holds the backing assets deposited by cUSD minters and lent out to whitelisted Operators, tracking total supplies and borrows per asset. Mint/burn/redeem operations flow through the Minter for fee calculation and through the FractionalReserve layer for liquidity management — the Vault itself only tracks principal amounts, leaving interest calculations to the Lender.
Depositing a backing asset in exchange for cUSD:
- Deposit cap check:
_amountInis silently capped to the remaining deposit capacity for the asset. - Fee calculation: The Minter computes
(amountOut, fee)based on the asset's current allocation ratio vs. its optimal ratio. - Slippage / deadline check: Reverts if
amountOut < _minAmountOutor the deadline has passed. - Transfer in: The asset is pulled from
msg.senderandtotalSupplies[asset]is incremented. - Mint:
amountOutcUSD is minted to_receiver; if a fee applies, an additionalfeecUSD is minted to the insurance fund.
Exchanging cUSD for a single backing asset:
- Fee calculation: The Minter computes the post-fee asset output and fee amount.
- cUSD burn:
_amountIncUSD is burned frommsg.sender. - Divest: The FractionalReserve layer divests
amountOut + feeof the asset from the active yield strategy if needed to cover the withdrawal. - Transfer out:
amountOutis sent to_receiver;feeis sent to the insurance fund.totalSupplies[asset]is decremented.
Proportional withdrawal across every backing asset:
- Amount calculation: The Minter computes proportional
amountsOutandfeesfor each asset in the vault. - cUSD burn:
_amountIncUSD is burned frommsg.sender. - Divest many: The FractionalReserve layer divests the required amounts across all assets.
- Transfer out: For each asset,
amountsOut[i]is sent to_receiverandfees[i]to the insurance fund.
These paths are access-controlled and called only by the Lender contract:
- Borrow: Divests the asset from the yield strategy if needed, decrements available balance, transfers to
_receiver, incrementstotalBorrows[asset]. - Repay: Accepts the asset back from the Lender via
transferFrom, decrementstotalBorrows[asset].
The vault tracks a cumulative utilization index per asset — a time-weighted integral of the utilization ratio. This is consumed by the RateOracle to compute borrow interest rates.
utilizationIndex += utilization * (block.timestamp - lastUpdate)
utilization = totalBorrows / totalSupplies (ray format, 1e27 = 100%)
The index is updated on every mint, burn, borrow, and repay via the updateIndex modifier.
mint(address _asset, uint256 _amountIn, uint256 _minAmountOut, address _receiver, uint256 _deadline)
function mint(
address _asset,
uint256 _amountIn,
uint256 _minAmountOut,
address _receiver,
uint256 _deadline
) external returns (uint256 amountOut);Deposit _asset and receive cUSD. Requires ERC20 approval from msg.sender to the vault. _amountIn is silently reduced to the remaining deposit cap if exceeded.
| Parameter | Type | Description |
|---|---|---|
_asset |
address |
Whitelisted backing asset to deposit |
_amountIn |
uint256 |
Amount of asset to deposit |
_minAmountOut |
uint256 |
Minimum cUSD to receive (slippage protection) |
_receiver |
address |
Recipient of the minted cUSD |
_deadline |
uint256 |
Unix timestamp after which the tx reverts |
Returns: amountOut — cUSD minted to _receiver (excludes fee).
burn(address _asset, uint256 _amountIn, uint256 _minAmountOut, address _receiver, uint256 _deadline)
function burn(
address _asset,
uint256 _amountIn,
uint256 _minAmountOut,
address _receiver,
uint256 _deadline
) external returns (uint256 amountOut);Burn _amountIn cUSD and receive _asset. The caller must hold the cUSD.
| Parameter | Type | Description |
|---|---|---|
_asset |
address |
Backing asset to withdraw |
_amountIn |
uint256 |
Amount of cUSD to burn |
_minAmountOut |
uint256 |
Minimum asset to receive (slippage protection) |
_receiver |
address |
Recipient of the asset |
_deadline |
uint256 |
Unix timestamp after which the tx reverts |
Returns: amountOut — asset sent to _receiver (excludes fee).
function redeem(
uint256 _amountIn,
uint256[] calldata _minAmountsOut,
address _receiver,
uint256 _deadline
) external returns (uint256[] memory amountsOut);Burn _amountIn cUSD and receive a proportional share of every backing asset. _minAmountsOut must have one entry per vault asset in the same order as assets().
Returns: amountsOut — asset amounts sent to _receiver (one per vault asset).
function borrow(address _asset, uint256 _amount, address _receiver) external;Access-controlled. Called by the Lender to transfer assets to an Operator. Reverts with InsufficientReserves if available balance is too low.
function repay(address _asset, uint256 _amount) external;Access-controlled. Called by the Lender when an Operator repays. Pulls _amount from msg.sender via transferFrom.
function availableBalance(address _asset) external view returns (uint256 amount);Returns totalSupplies[_asset] - totalBorrows[_asset] — the amount currently available to borrow or withdraw.
function utilization(address _asset) external view returns (uint256 ratio);Returns the current utilization ratio in ray format (1e27 = 100%). For example, 5e26 = 50% utilization.
function currentUtilizationIndex(address _asset) external view returns (uint256 index);Returns the up-to-date cumulative utilization index including the current unsettled period. Used by the RateOracle for interest rate calculations.
function getRemainingMintCapacity(address _asset) external view returns (uint256 remainingMintCapacity);Returns how much more of _asset can be deposited before hitting the deposit cap. Returns 0 if the cap is already reached.
| Function | Description |
|---|---|
addAsset(address _asset) |
Add a new backing asset to the vault |
removeAsset(address _asset) |
Remove an asset (only if totalSupplies == 0) |
pauseAsset(address _asset) |
Pause minting, burning, and borrowing for a specific asset |
unpauseAsset(address _asset) |
Resume operations for a paused asset |
pauseProtocol() |
Pause all vault operations globally |
unpauseProtocol() |
Resume all vault operations |
setInsuranceFund(address) |
Update the insurance fund address |
rescueERC20(address _asset, address _receiver) |
Recover a non-vault, non-strategy token sent to the contract by mistake |
ERC-7201 namespaced storage for the Vault module.
struct VaultStorage {
EnumerableSet.AddressSet assets;
mapping(address => uint256) totalSupplies;
mapping(address => uint256) totalBorrows;
mapping(address => uint256) utilizationIndex;
mapping(address => uint256) lastUpdate;
mapping(address => bool) paused;
address insuranceFund;
}| Field | Type | Description |
|---|---|---|
assets |
EnumerableSet.AddressSet |
Set of whitelisted backing assets |
totalSupplies |
mapping(address => uint256) |
Total asset deposited (principal, not accounting for yield) |
totalBorrows |
mapping(address => uint256) |
Total asset currently borrowed by Operators |
utilizationIndex |
mapping(address => uint256) |
Cumulative time-weighted utilization integral per asset |
lastUpdate |
mapping(address => uint256) |
Timestamp of the last index update per asset |
paused |
mapping(address => bool) |
Per-asset pause state |
insuranceFund |
address |
Address that receives minting/burning fees in cUSD |
struct MintBurnParams {
address asset;
uint256 amountIn;
uint256 amountOut;
uint256 minAmountOut;
address receiver;
uint256 deadline;
uint256 fee;
}struct RedeemParams {
uint256 amountIn;
uint256[] amountsOut;
uint256[] minAmountsOut;
address receiver;
uint256 deadline;
uint256[] fees;
}| Event | Key Parameters | Emitted when |
|---|---|---|
Mint |
minter, receiver, asset, amountIn, amountOut, fee |
cUSD minted |
Burn |
burner, receiver, asset, amountIn, amountOut, fee |
cUSD burned for a single asset |
Redeem |
redeemer, receiver, amountIn, amountsOut, fees |
cUSD redeemed across all assets |
Borrow |
borrower, asset, amount |
Operator borrow executed |
Repay |
repayer, asset, amount |
Operator repayment received |
AddAsset |
asset |
New backing asset added |
RemoveAsset |
asset |
Backing asset removed |
PauseAsset |
asset |
Asset paused |
UnpauseAsset |
asset |
Asset unpaused |
| Error | Condition |
|---|---|
PastDeadline |
block.timestamp > deadline |
Slippage |
amountOut < minAmountOut |
InvalidAmount |
amountOut == 0 |
AssetPaused |
Mint/burn/borrow attempted on a paused asset |
AssetNotSupported |
Asset is not in the vault's asset list |
AssetAlreadySupported |
addAsset called for an already-listed asset |
AssetHasSupplies |
removeAsset called while totalSupplies > 0 |
AssetNotRescuable |
rescueERC20 called for a listed or strategy-held asset |
InvalidMinAmountsOut |
_minAmountsOut.length doesn't match assets().length |
InsufficientReserves |
Available balance is less than the amount requested |
interface IVault {
function mint(address _asset, uint256 _amountIn, uint256 _minAmountOut, address _receiver, uint256 _deadline)
external returns (uint256 amountOut);
function getMintAmount(address _user, address _asset, uint256 _amountIn)
external view returns (uint256 amountOut, uint256 fee);
}
address vault = 0x...; // cUSD vault proxy
address usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
uint256 amountIn = 10_000e6; // 10,000 USDC
// 1. Preview the mint
(uint256 expectedOut, ) = IVault(vault).getMintAmount(msg.sender, usdc, amountIn);
// 2. Approve and mint
IERC20(usdc).approve(vault, amountIn);
uint256 cusdReceived = IVault(vault).mint(
usdc,
amountIn,
expectedOut * 99 / 100, // 1% slippage tolerance
msg.sender,
block.timestamp + 5 minutes
);interface IVault {
function availableBalance(address _asset) external view returns (uint256);
function utilization(address _asset) external view returns (uint256 ratio);
function totalSupplies(address _asset) external view returns (uint256);
function totalBorrows(address _asset) external view returns (uint256);
}
function vaultStatus(address vault, address asset)
external
view
returns (uint256 available, uint256 utilizationPct)
{
available = IVault(vault).availableBalance(asset);
utilizationPct = IVault(vault).utilization(asset) / 1e25; // convert ray to bps (1e27 → 100%)
}interface IVault {
function assets() external view returns (address[] memory);
function getRedeemAmount(address _user, uint256 _amountIn)
external view returns (uint256[] memory amountsOut, uint256[] memory fees);
function redeem(uint256 _amountIn, uint256[] calldata _minAmountsOut, address _receiver, uint256 _deadline)
external returns (uint256[] memory amountsOut);
}
address vault = 0x...;
uint256 cusdAmount = 5_000e18;
// Preview
(uint256[] memory expected, ) = IVault(vault).getRedeemAmount(msg.sender, cusdAmount);
// Apply 1% slippage tolerance to each asset
uint256[] memory minOut = new uint256[](expected.length);
for (uint256 i; i < expected.length; i++) {
minOut[i] = expected[i] * 99 / 100;
}
IVault(vault).redeem(cusdAmount, minOut, msg.sender, block.timestamp + 5 minutes);