# API
Source: https://metavaults.mellow.finance/api
`GET /v1/vaults`
`GET /v1/defi/protocols`
`GET /v1/users/{user_address}`
`GET /v1/defi/users/{user_address}`
# Get DeFi protocol integrations
Source: https://metavaults.mellow.finance/api-reference/get-defi-protocols
GET /v1/defi/protocols
Retrieves information about all DeFi protocol integrations and their points distribution across different chains.
# Get user's vaults DeFi positions
Source: https://metavaults.mellow.finance/api-reference/get-user-defi-positions
GET /v1/defi/users/{user_address}
Retrieves detailed information about a user's positions across all integrated DeFi protocols,
including points earned, balances, and protocol-specific details.
# Get user's vaults positions
Source: https://metavaults.mellow.finance/api-reference/get-user-positions
GET /v1/users/{user_address}
Retrieves detailed information about a specific user's positions across all Mellow Protocol vaults.
This includes their liquidity provisions, earned profits, and current holdings as a Liquidity Provider.
# Get all vaults
Source: https://metavaults.mellow.finance/api-reference/get-vaults
GET /v1/vaults
Retrieves comprehensive information about all available vaults in the Mellow Protocol ecosystem.
Each vault represents a smart contract that manages liquidity across different DeFi protocols.
The response includes vault configurations, associated tokens, current performance metrics, and operational parameters.
# APY
Source: https://metavaults.mellow.finance/apy
When you deposit tokens into the vault, you receive **receipt tokens** representing your proportional ownership of the vault’s assets.
Your receipt token balance remains constant. However, the **value per token increases over time** to reflect performance. In simple terms, your number of tokens does not increase - their value does.
#### How Yield Is Generated
APY is driven by continuous rewards generated across the underlying applications and protocols used in the strategy (such as lending, staking, or liquidity provision).
All rewards are automatically compounded back into the vault, increasing the share value over time.
#### How APY Is Calculated
APY is calculated based on **Oracle Reports**, which update the vault’s share price to reflect changes in asset value.
The calculation compares the share price from a past Oracle Report with the most recent report and annualizes the return over that period.
Formula:
```
currentReport = oracle.getReport(ETH) # current block
initialReport = oracle.getReport(ETH) # 1 day old block
apy := (
(currentReport.priceD18 / initialReport.priceD18)
** (365 * 24 * 3600 / (currentReport.timestamp - initialReport.timestamp))
- 1
) * 100
```
#### Claiming Receipt Tokens
Once your funds enter the vault, receipt tokens are generated and can be claimed via the Lido UI or Mellow UI.
Not claiming your receipt tokens does **not** affect reward accrual - yield continues to accumulate based on your proportional ownership of the vault.
#### Important Disclaimer
APY figures are estimates and may change at any time. Past performance does not guarantee future results.
Rewards are influenced by factors outside the platform’s control, including changes to blockchain protocols, validator performance, and market conditions.
# Factory
Source: https://metavaults.mellow.finance/architecture/factory
#### Overview
The `Factory` contract is a generalized deployment mechanism for creating upgradeable proxy instances of pre-approved implementations. It tracks multiple implementation versions, proposal workflows, blacklisting for security, and ownership-based access control.
It conforms to the `IFactory` interface and supports deploying any `IFactoryEntity`-compliant contracts via `TransparentUpgradeableProxy`, using `initialize()` for configuration.
#### Key Capabilities
* **Versioned Deployment**: Track multiple logic contract versions, each deployable by index.
* **Proposal System**: Allow anyone to propose implementations, with owner approval required.
* **Blacklist Mechanism**: Prevent deployment of insecure or deprecated versions.
* **Deterministic Deployments**: Uses `create2` salt to ensure predictable addresses.
* **Entity Tracking**: Keeps a registry of all deployed entities.
#### Storage Structure
The contract uses a deterministic storage layout via:
```solidity theme={null}
bytes32 _factoryStorageSlot = SlotLibrary.getSlot("Factory", name_, version_);
```
Storage fields (in `FactoryStorage`) include:
| Field | Description |
| ----------------- | ------------------------------------- |
| `entities` | Set of all deployed proxy instances |
| `implementations` | Approved logic contract addresses |
| `proposals` | Pending implementation proposals |
| `isBlacklisted` | Mapping from version → is blacklisted |
#### Initialization
```solidity theme={null}
function initialize(bytes calldata data) external initializer
```
* Accepts encoded owner address
* Sets initial admin and emits `Initialized`
#### Entity Deployment
```solidity theme={null}
function create(uint256 version, address owner, bytes calldata initParams) external returns (address instance)
```
Deploys a new `TransparentUpgradeableProxy`:
* Uses implementation from `implementations.at(version)`
* Rejects if version is out-of-bounds or blacklisted
* Uses `salt = keccak256(version, owner, initParams, currentEntityCount)` for deterministic deployment
* Calls `initialize(initParams)` on the new proxy
Emits:
```solidity theme={null}
event Created(address instance, uint256 version, address owner, bytes initParams);
```
#### Implementation Management
#### Propose New Implementation
```solidity theme={null}
function proposeImplementation(address implementation) external
```
* Fails if already in `implementations` or `proposals`
* Adds to `proposals`
* Emits: `ProposeImplementation(implementation)`
* Permissionless function
#### Accept Proposed Implementation
```solidity theme={null}
function acceptProposedImplementation(address implementation) external onlyOwner
```
* Only callable by owner
* Fails if not proposed
* Moves from `proposals` → `implementations`
* Emits: `AcceptProposedImplementation(implementation)`
#### Blacklisting
```solidity theme={null}
function setBlacklistStatus(uint256 version, bool flag) external onlyOwner
```
* Blocks deployments using specific version
* Enforces that version index exists
* Emits: `SetBlacklistStatus(version, flag)`
#### View Functions
| Function | Returns |
| ------------------------- | -------------------------------------------- |
| `entities()` | Total number of deployed entities |
| `entityAt(index)` | Deployed entity at index |
| `isEntity(address)` | Checks if address is a deployed entity |
| `implementations()` | Total implementation count |
| `implementationAt(index)` | Implementation at index |
| `proposals()` | Total proposals pending approval |
| `proposalAt(index)` | Proposal at index |
| `isBlacklisted(version)` | Whether a version is blocked from deployment |
#### Access Control
* Uses `OwnableUpgradeable`
* Only owner can accept implementations and blacklist versions
#### Security Considerations
* **Immutable logic whitelist**: Only approved contracts can be deployed
* **Blacklisting**: Emergency response for vulnerabilities
* **Replay protection**: Deployment salt ensures unique addresses
* **Decentralized proposals**: Anyone can propose implementations, but only owner can accept
# BasicRedeemHook
Source: https://metavaults.mellow.finance/architecture/hooks/basicredeemhook
### Overview
`BasicRedeemHook` is a minimal hook implementation for `IHook`, designed to dynamically fetch liquidity from subvaults during redemption processing in a `VaultModule`. It ensures that enough assets are available for user redemptions by pulling liquidity from a set of registered subvaults.
This hook is typically invoked by a vault’s redemption queue or during `redeem()` operations when assets must be made liquid.
### Purpose
* Ensures **sufficient liquidity** in the vault to fulfill asset redemptions.
* Minimizes idle capital by **pulling only when needed**.
* Supports **liquidity routing** across subvaults.
### Key Functions
### `callHook(address asset, uint256 assets)`
Attempts to make `assets` of `asset` liquid within the main vault by pulling from subvaults if needed.
**Execution Flow:**
1. Checks how much of the `asset` the vault already holds.
2. If balance is sufficient → no-op.
3. Otherwise:
* Iterates over subvaults (via `subvaultAt(i)`)
* For each subvault:
* Pulls **only the required portion** via `hookPullAssets()`
* Stops when total required assets have been pulled
**Guarantees:**
* Only pulls the **exact missing amount**, no over-pulling
* Efficient: stops once liquidity need is satisfied
* Skips subvaults with `0` balance
### `getLiquidAssets(address asset) → uint256`
Returns the **total liquid amount of a given asset** available across the vault and all its subvaults.
* Reads balances:
* `vault.balanceOf(asset)`
* `subvault[i].balanceOf(asset)` for each subvault
* Aggregates and returns sum
### Contract Assumptions
* The `vault` invoking this hook implements `IVaultModule` and supports:
* `subvaults()` → total number of subvaults
* `subvaultAt(index)` → address of a given subvault
* `hookPullAssets(subvault, asset, amount)` → callable method to move funds
### Security Considerations
* Hook only pulls assets using vault-controlled `hookPullAssets()`, ensuring controlled asset flow.
* Assumes vault validates which hook is active — no permissioning within the hook itself.
# Hooks
Source: https://metavaults.mellow.finance/architecture/hooks/index
In this directory, you will find a detailed per-contract overview of the "Hooks" contract category, including the following hooks:
[BasicRedeemHook](/architecture/hooks/basicredeemhook)
[LidoDepositHook](/architecture/hooks/lidodeposithook)
[RedirectingDepositHook](/architecture/hooks/redirectingdeposithook)
# LidoDepositHook
Source: https://metavaults.mellow.finance/architecture/hooks/lidodeposithook
### Overview
`LidoDepositHook` is an implementation of the `IHook` interface that acts as a **conversion adapter** for incoming deposits. It standardizes various ETH-like assets into **`wstETH`** for use in downstream vault logic. The hook supports **ETH**, **WETH**, and **stETH** as input formats and ensures conversion to `wstETH` before optionally forwarding execution to a downstream `nextHook`.
### Primary Purpose
* Converts **ETH**, **WETH**, or **stETH** into **`wstETH`** on deposit.
* Ensures compatibility with protocols that expect `wstETH`.
* Provides **composable hooks** by chaining into a downstream `IHook` (`nextHook`).
### Constructor Parameters
| Parameter | Type | Description |
| ----------- | ------- | -------------------------------------------------------------------- |
| `wsteth_` | address | Address of the `wstETH` token contract |
| `weth_` | address | Address of the `WETH` token contract |
| `nextHook_` | address | Address of the optional downstream hook to forward to after wrapping |
### Key Function
### `callHook(address asset, uint256 assets)`
Handles conversion of an input asset into `wstETH` and optionally delegates the call to a downstream hook.
**Supported Input Types:**
1. `wstETH` — forwarded directly
2. `stETH` — wrapped into `wstETH` via `IWSTETH(wsteth).wrap()`
3. `WETH` — unwrapped into ETH via `IWETH(weth).withdraw()`, then deposited into `wstETH`
4. `ETH` (i.e. `address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)`) — directly deposited into `wstETH`
**Execution Steps:**
* If `asset == wstETH`: do nothing
* If `asset == stETH`:
* Approves `wstETH` to pull `stETH`
* Calls `wrap()` on `wstETH` to convert to `wstETH`
* If `asset == WETH`:
* Unwraps to ETH using `withdraw()`
* Sends ETH to `wstETH` contract to mint `wstETH`
* If `asset == ETH`:
* Sends ETH directly to `wstETH`
* After conversion:
* Calculates how much new `wstETH` was received
* If `nextHook` is configured, delegates `callHook(wstETH, amount)` to it
### Errors
* `UnsupportedAsset(address asset)` — Thrown if the provided asset is neither `wstETH`, `stETH`, `WETH`, nor `ETH`
### Assumptions:
It is assumed this hook will not trigger `STAKE_LIMIT` or other limit-related errors in the Lido contracts. If such errors do occur, the vault admin can reconfigure the system to bypass the automated staking hook. In this case, `RedirectingDepositHook` can be assigned to the relevant queues, delegating staking responsibilities to the vault curator via manual liquidity management.
# RedirectingDepositHook
Source: https://metavaults.mellow.finance/architecture/hooks/redirectingdeposithook
### Overview
`RedirectingDepositHook` is a deposit-time liquidity allocation hook implementing the `IHook` interface. It is designed to **redirect newly deposited assets** from a vault into its underlying **subvaults**, based on per-subvault risk and capacity constraints defined by a `RiskManager`.
This hook helps distribute liquidity optimally during deposit flows.
### Purpose
* Automatically **forwards newly deposited assets** from the main vault into eligible subvaults.
* Delegates decision-making to the vault’s configured `RiskManager`, which determines per-subvault deposit limits.
* Ensures that **subvaults do not exceed their capacity constraints**.
### Key Function
### `callHook(address asset, uint256 assets)`
Distributes a given amount of `asset` across available subvaults based on their individual deposit capacity.
**Execution Logic:**
1. Retrieves:
* Active `vault` context via `IVaultModule(address(this))`
* Configured `RiskManager` via `vault.riskManager()`
* Number of subvaults via `vault.subvaults()`
2. Iterates over each subvault:
* Fetches max allowed deposit via `riskManager.maxDeposit(subvault, asset)`
* If allowed amount is zero → skip
* Otherwise:
* Pushes `min(assets, allowed)` via `vault.hookPushAssets()`
* Decrements `assets` accordingly
* Stops early if full amount has been distributed
### Components and Assumptions
* **Vault:** Must implement `IVaultModule`:
* `subvaults()` → number of subvaults
* `subvaultAt(index)` → returns subvault address
* `hookPushAssets(subvault, asset, amount)` → transfers tokens to subvault
* `riskManager()` → returns associated `IRiskManager`
* **Risk Manager:** Must implement:
* `maxDeposit(subvault, asset)` → returns max allowed deposit into that subvault
# Architecture
Source: https://metavaults.mellow.finance/architecture/index
## Mellow Core Vaults
### Abstract
MetaVaults are built **on top of the Core Vaults architecture** and inherit its fundamental design principles, execution model, and security guarantees. Rather than introducing a separate vault system, MetaVaults utilizes the existing Core Vault framework to enable aggregation and orchestration of multiple strategies under a single vault abstraction.
This means that all core mechanics, including liquidity handling, accounting, permissioning, and execution constraints, follow the same proven architectural patterns, while MetaVault-specific logic focuses on coordinating allocations across multiple onchain destinations.
### Vaults Architecture
Core Vaults are built around a modular architecture that orchestrates interactions across both DeFi protocols and centralized exchanges. It enables trustless execution of complex, institutional-grade strategies while maintaining full composability and transparency.
No matter how diverse or sophisticated the underlying strategies become, the vault framework remains stable and predictable – providing a unified, programmable setup for managing assets, risks, and logic.
The result is infrastructure that supports scalable, curated access to onchain yield – secure, standardized, and ready for real users.
#### Core Vaults Features & Functionality
* Deposits & Withdrawals for the Liquidity Providers
* Valuation Oracle
* Strategy performance analytics for Liquidity Providers
* Fee management
* Vault management tools & Curation UI
* Reward distribution engine
* Receipt token distribution infrastructure
* Smart contract safeguards
* Granular role-based control
* Access to external apps and protocols
#### Vault system architecture overview
####
#### 📥 Deposit Queue
Manages user deposits through a time-buffered queuing system. This delay helps prevent front-running and price manipulation by ensuring deposits are not executed based on stale or externally influenced oracle price data.
The deposit queue is also responsible for issuing receipt tokens.
* **Technical Details\`**
**Deposit flow:**
* Step 1: **User Deposits**
* A user submits a deposit request via `deposit(assets, referral, merkleProof)`.
* Deposits are validated via optional Merkle whitelist logic (using `merkleProof`) or onchain mapping (if `hasWhitelist` flag is set).
* If a previous request exists, it must be claimed or canceled before creating a new one.
* The deposited amount and timestamp is stored in the `DepositQueue` contract
* Step 2: **Oracle Report**
* Oracle report is propagated via the `handleReport(priceD18, timestamp)` method.
* The queue validates the report:
* It must be called by the `Vault`.
* Provided `timestamp` must be in the past (usually `request.timestamp - depositInterval`).
* `priceD18` must be non-zero.
* The queue handles deposit requests that are pending for at least `depositInterval` seconds (the interval is specified in the oracle’s security parameters).
* The contract stores a `(timestamp, reducedByDepositFeePriceD18)` pair in the `prices` array. This value is used to convert accumulated assets into shares based on the user’s request timestamp. The `reducedByDepositFeePriceD18` is derived by applying the deposit fee to the actual reported `priceD18`.
* The corresponding shares are allocated but not yet minted.
* Emits the `ReportHandled` event.
* Step 3: **User Claims**
* A user calls `claim(account)` (or `claimShares(account)` in the `Vault`) to mint and receive previously allocated shares.
* The number of shares is computed as:
```solidity theme={null}
uint256 shares = (request.assets * reducedByDepositFeePriceD18) / 1e18;
```
* Shares are minted to the user via `mintAllocatedShares`.
#### Assumptions & Properties
1. Single Active Request
Each user may have at most one unprocessed deposit request. New deposits are blocked until the previous request is claimed. If the pending request is already claimable, the claim will be automatically processed during the next deposit.
2. Delayed Execution
Deposit processing requires an Oracle report submitted after a configured `depositInterval`.
3. Lazy Claiming Deposits are converted into shares during oracle processing, but users must call the claim function (in `ShareModule`, `ShareManager` or in each `DepositQueue` separately) to receive them. However, even without explicitly claiming, the user's full share balance — including all claimable shares across all deposit queues — is accurately reflected in `shareManager.sharesOf(user)`.
4. Whitelist Enforcement
Deposits may require Merkle proof for depositor whitelisting.\
\
#### 📤 Redeem Queue
Serving as the counterpart to the Deposit Queue, it manages withdrawal requests.
Redemptions are processed in two phases:
1. Oracle pricing – reports are processed in batches, with each batch assigned a specific conversion price derived from its corresponding Oracle report.
2. Liquidity settlement — Vault liquidity is pulled asynchronously, allowing the curator to finalize withdrawals and perform asset swaps before processing user redemption requests.
This separation allows asynchronous liquidity management, gas efficiency, and protection against griefing. Unlike deposit requests, withdrawal requests in the Redeem Queue cannot be canceled to prevent yield-griefing.
**Technical Details**
**Redeem flow**:
* **Step 1: User redeems**
* User submits `redeem` request, vault shares are immediately burned.
* The vault curator monitors and manages liquidity across connected Subvaults, pulling funds and swapping assets as needed to fulfill redemption requests.
* **Step 2: Oracle report**
* Valid and non-suspicious `Oracle` `report` arrives.
* The vault curator invokes `handleBatches(n)` on the `RedeemQueue`.
* This triggers the movement of required assets from the vault (and associated subvaults) to process redemption requests.
* **Step 3: User claims**
* Users call `claim(receiver, timestamps)` to withdraw assets.
**Check the receiver address carefully**
Before signing, verify that `receiver` is the exact destination for the tokens.
Verify that you are using the official Mellow app URL: `https://app.mellow.finance/`
This matters most for large withdrawals. A compromised device, browser, wallet extension, clipboard, or signing environment can alter the address or transaction details before signing.
**Always verify the full receiver address and the app URL before confirming the transaction.**
#### Assumptions & Properties
1. Non-Cancellable Requests Prevents griefing where a user requests redemption, causing curator to pull liquidity, then cancels.
2. Time-sensitive request handling Oracle reports can only process a redemption request if at least `redeemInterval` seconds have passed since the request was submitted — i.e., `report.timestamp` must be greater than or equal to `request.timestamp + redeemInterval`.
3. Asynchronous Fulfillment Liquidity can be managed independently of oracle report submission.
#### 🔏 Signature Deposit and Redeem Queues
Signature Queues allow deposits and redemptions to be processed instantly, bypassing the standard time-buffered flow. That happens when a trusted consensus group issues offchain-signed approvals.
Key Features:
* **Instant execution** without waiting for Oracle price reports
* **Nonce-based signature protection** to prevent replay attacks
* **EIP-712/EIP-1271 compatible** signed orders
* **Oracle price validation** enforced onchain
* **Stateless and removable** (does not accumulate shares or process claims)
* **Fee-bypass**: No Deposit Fee or Redeem Fee is charged for actions via this queue
* **Technical Details**
**Signature Queue flow:**
* **Step 1: Offchain signing**
* Offchain consensus actors (operators, curators, admins) generate signed `Order` messages.
* **Step 2: Onchain execution**
* A user submits this order to the `SignatureQueue` contract for execution.
* The queue:
* Verifies the order signature
* Validates nonce, queue address, asset match, deadline, and caller
* Computes the implied asset/share price and checks it against the vault's oracle
* If all checks pass, the order is executed atomically.
#### Assumptions & Properties
* Only **trusted offchain actors** (consensus group) are authorized to sign orders.
* Price quotes must be valid and non-suspicious.
* Users cannot reuse old signatures due to nonce tracking.
* Orders must be executed before `deadline`.\
\
#### 💼 Vault
The Vault contract serves as the central entry point into the Core Vault system. It is configured by four internal modules:
* **BaseModule**: Implements different auxiliary interfaces such as `onERC721Received`, `getStorageAt` and `receive` callback
* **ACLModule:** Role-based access control for system components
* **ShareModule:** Management of shares, fees, deposit and redeem queues lifecycle, `Oracle` report handling
* **VaultModule:** Subvaults management, pushing and pulling of assets in subvaults\
\
#### 🗃️ Subvault
The Subvault contract is a vault component designed to manage delegated asset strategies, acting as a controlled execution unit within a system. Like the Vault contract, it is configured by modules:
* **BaseModule**: Implements different auxiliary interfaces such as `onERC721Received`, `getStorageAt` and `receive` callback.
* **SubvaultModule:** Represents an isolated child vault within a modular vault system, responsible for securely holding and releasing assets upon authenticated requests.
* **VerifierModule:** is an abstract extension of the Base Module designed to provide standardized access to a Verifier contract.
* **CallModule:** Enables arbitrary low-level calls to external contracts (used by curator of the vault), and verification through a verifier module.\
\
#### 👁️ Verifier
Each Subvault is paired with a Verifier contract, which validates function calls and ensures only pre-approved actions from valid actors are permitted across vault-connected modules.
While each Subvault is expected to have its own dedicated Verifier contract, it is still possible for the same Verifier to be shared across multiple Subvaults.
* **Technical Details**
Verifier allows multiple types of verification:
* **ONCHAIN\_COMPACT**: Checks `CompactCall` (who | where | selector) hash against internal admin-controlled set
* **MERKLE\_COMPACT**: Verifies Merkle proof of `CompactCall` (who | where | selector) hash
* **MERKLE\_EXTENDED**: Verifies Merkle proof of `ExtendedCall` (who | where | value | callData) hash
* **CUSTOM\_VERIFIER:** Delegates full verification to an external verifier
Two admin-owned parameters are saved in the state of the Verifier contract: `compactCallHashes` that defines all allowed calls for **ONCHAIN\_COMPACT** verification type
`compactCalls` is an optional mapping for reverse lookup of call metadata by hash
`merkleRoot` that defines all allowed calls for **MERKLE\_COMPACT, MERKLE\_EXTENDED** and **CUSTOM\_VERIFIER**\
\
#### 🔮 Oracle
The Oracle contract handles secure and configurable price reporting for supported assets. Closely integrated with the `ShareModule`, it provides price validation, deviation monitoring, and time-restricted report submissions.
It enforces strict guarantees around **report timing** and **trust minimization** using role permissions and deviation thresholds. This oracle ensures consistent pricing across all queue, share, and limit-related calculations.
Key considerations:
* Only authorized roles can submit price updates
* Suspicious reports require explicit approval before acceptance
* Price validation is performed locally without relying on external oracle feeds
* Manipulation is prevented through absolute and relative deviation limits
* **Technical Details**
Oracle Configurable Security Parameters:
* **Absolute Deviation**: Hard limits on price delta in price units
* **Relative Deviation**: Tolerance as a percentage (e.g., 5% = 0.05e18)
* **Timeout**: Minimum time between valid reports (ignored if the previous report is suspicious)
* **depositInterval**: Minimum age required for a deposit to be processed
* **redeemInterval**: Same, but for redemptions
**Reporting flow**:
* **Step 1: Report is submitted:**
* Each asset is checked for support
* The previous report state is evaluated:
* If `timeout` has not passed, and the report is not suspicious → revert `TooEarly`
* Price is compared against previous:
* `maxAbsolute` → revert `InvalidPrice`
* `maxRelative` → flagged `isSuspicious`
* If the report is valid and non-suspicious (deviation \< suspicious && deviation \< max), it is immediately accepted
* If the report is suspicious it will be accepted only after validation from the Admin (ACCEPT\_REPORT\_ROLE holder)
* **Step 2: Accepted report propagation:**
* Triggers `vault.handleReport(...)`, processing deposit requests and pending redeem requests
* Emits `ReportsSubmitted`
**Validation Logic:**
Reports are validated by:
* Calculating **absolute deviation (**`maxAbsolute` )
* Calculating **relative deviation (**`maxRelative` )
* Comparing against `max` and `suspicious` thresholds
A report is:
* **Rejected** if either deviation exceeds max
* **Accepted but marked as suspicious** if above the suspicious threshold, flagging the report
* **Accepted as normal** if within all limits
Used by:
* `SignatureDepositQueue`, `SignatureRedeemQueue`, `DepositQueue` and `RedeemQueue` contracts
* Vault's limit accounting (`RiskManager`)\
\
#### 📊 Share Manager
The Share Manager is an upgradeable contract responsible for managing vault share issuance, allocation, whitelisting, permissions, and lockups within a modular vault system.
Key responsibilities include:
* Tracking total and active share supply
* Managing global and targeted account lockups
* Verifying whitelist status and transfer permissions
* Enforcing mint, burn, and transfer pauses
* Handling share allocation and claims through queues
* Whitelisting for implementing KYC & compliance features
**Technical Details**
Share Manager relies on a compact bitmask (`flags`) for enabling/disabling features and supports configurable per-account permissions. Controlled via `ShareManagerFlagLibrary`:
* `hasMintPause`
* `hasBurnPause`
* `hasTransferPause`
* `hasWhitelist`
* `hasTransferWhitelist`
* `globalLockup`
* `targetedLockup`
Lockups are enforced in `updateChecks`.
Share Manager operates under the control of defined roles:
* `SET_FLAGS_ROLE`: Allows changing global flags (e.g., mint pause, whitelist enforcement).
* `SET_ACCOUNT_INFO_ROLE`: Grants permission to set per-account configuration.
* `SET_WHITELIST_MERKLE_ROOT_ROLE`: Grants permission to set new whitelist merkle root.\
#### 💰 Fee Manager
The Fee Manager oversees the calculation and management of multiple fee types within the vault system. Currently, it supports four fee-acquiring methods:
* **Deposit Fee:** Charged on asset deposits
* **Redeem Fee:** Applied on share redemptions
* **Performance Fee:** Based on decreases in share price (assets × price = shares)
* **Protocol Fee:** Time-based fee accrued per vault
All fees are paid in vault shares rather than the underlying assets.
Admin can update recipient and fee parameters.
**Technical Details**
Fee Manager will require identifying the base asset of the vault. `setBaseAsset`: Called once per vault to register its performance reference token.
`updateState(asset, price)`: Vaults call this to refresh timestamp and minimum price.
Performance and Protocol fees are activated by a fresh Oracle report on the base asset.
#### Fee Calculation Logic:
* **Deposit fee** – applied linearly and calculated as `shares * depositFeeD6 / 1e6`, deducted during the oracle report handling process.
* **Redeem fee** – applied linearly and calculated as `shares * redeemFeeD6 / 1e6`, deducted when shares are requested for redemption.
* **Performance fee** – due to the use of a non-standard pricing mechanism (`price = shares / assets`), delegation yield will result in a lower reported price by the oracle; if `priceD18` falls below `minPriceD18`, the fee is charged as `(minPriceD18 - priceD18) * performanceFeeD6 * totalShares / 1e24` to capture the implied yield.
* **Protocol fee** – a continuously accruing time-based fee calculated as `totalShares * protocolFee * (block.timestamp - timestamps[vault]) / (365 * 24 * 3600 * 1e6)`, proportional to both share supply and elapsed time since the last timestamp update.\
#### 🎯 Risk Manager
The Risk Manager contract defines and enforces asset deposit limits across the Vault and its associated Subvaults. It maintains internal accounting of balances, limits, and approved assets for each subvault.
This module is responsible for:
* Setting and enforcing share limits (practically representing approximate deposit limits) at both vault and subvault levels
* Permitting or restricting specific assets to be pulled into the Subvaults
* Tracking pending balances for Deposits that are not yet finalized
* Validating limits using Oracle price reports
**Technical Details**
* **Vault Limit**: Global cap across all assets managed by the vault (in shares).
* **Subvault Limit**: Individual cap per subvault, enforced independently (in shares).
* **Allowed Assets**: Only explicitly allowlisted assets are permitted for push / pull operations in a given subvault.
* **Pending Assets**: Temporarily tracked assets, e.g., during deposit queueing
* **Shares Conversion**: All balances are internally tracked in shares, calculated using latest report in the `Oracle` contract.
All vault and subvault-level limits are treated as **approximate** and computed using the most recent Oracle report available **at the time of the state update** (on Subvault pull/push event or Deposit/Redeem operations).
If actual balances deviate significantly from the stored `balance` values due to oracle drift, delayed execution, or protocol-side changes, a **trusted actor** can apply a ‘corrections’ to mitigate the difference:
* `modifyVaultBalance` for the Vault, or
* `modifySubvaultBalance` for individual Subvaults.
Since the system is expected to hold only correlated assets, such manual adjustments are assumed to be **rare** under normal operating conditions.\
#### 🔐Access Control
Granular Access Control (MellowACL) is a lightweight yet extensible layer built on an OpenZeppelin contract. It adds automatic tracking and enumeration of *active roles* to enhance governance transparency and enable dynamic role management.
Responsibilities include:
* Granting and revoking access control roles to addresses
* Maintaining a dedicated set of all active (assigned) roles
* Providing enumerable functions for external auditing of granted roles
* Emitting events when roles are assigned or fully revoked\
### Supported protocols and integrations
Core Vaults architecture supports integration with a wide range of apps, including both DeFi protocols and centralized exchanges. Most of the protocol integrations can work out of the box. Below is a brief example of potential connections to subvaults.
#### Major protocols:
* Aave (leverage & supply side LPing)
* Gearbox (leverage & supply side LPing)
* Curve, Uniswap (DEX liquidity provisioning)
* Cowswap (limit orders)
* Symbiotic, EigenLayer (provide liquidity for restaking rewards)
* Pendle (splitting and selling future yield or holding for boosted returns)
* Morpho (leverage & supply side LPing)
* Euler (leverage & supply side LPing)
* Fluid (leverage & supply side LPing)
* Hyperliquid
#### **CEXes** via custodial off-exchange solutions (Copper and Ceffu)
* Deribit
* ByBit
* Binance
* Most of tier 1 and 2 CEXes
## Case Study
**Prerequisites:**
The flow occurs within a Core Vault under the following conditions:
1. Two subvaults representing different yield sources: a delta-neutral trading strategy and restaking
2. Liquidity is evenly allocated 50%-50% between the subvaults
3. 1% management fee and 15% performance fee
4. Annual Percentage Rate (APR) of 10%
**Initial Action:**
A Liquidity Provider (LP) deposits 1,000,000 USDC into the Mellow Core Vault.
**Process Flow:**
1. Deposits are processed and transferred into the vault after passing through the time-buffered Deposit Queue.
2. The LP receives receipt tokens representing the 1,000,000 USDC position.
3. The LP can use these receipt tokens as collateral in various DeFi protocols to generate additional yield, such as leverage looping on Gearbox.
4. Liquidity is allocated from the Vault to the Subvaults according to the limit-based rules.
5. 50% of unallocated funds are pulled into a Subvault 1 by a Curator.
6. 50% of unallocated funds are pulled into a Subvault 2 by a Curator.
7. The Curator allocates funds from Subvault 1 to the delta-neutral strategy through integrated centralized exchanges.
8. Funds from Subvault 2 are allocated to the restaking strategy via Symbiotic.
9. After 12 months, the LP requests a full withdrawal.
10. Throughout the holding period, Protocol and Performance Fees are automatically accrued with each Oracle update, resulting in a management fee of 10,000 USDC and a performance fee of 15,000 USDC - both paid in vault receipt tokens.
11. The withdrawal request is queued and detected onchain.
12. At the end of the withdrawal interval, the Curator transfers 1,075,000 USDC (principal plus accrued yield) from the Subvaults back to the Core Vault Contract.
13. The LP redeems the full amount directly from the Redeem Queue.
*Process Flow for Curator Allocation:*
1. Curator asks Admin to add 2 subvaults with correct verifier configs:
* First one allowing liquidity transfer into a Copper or Ceffu account
* Second one allowing deposits, withdrawals, withdrawal claims and reward claims & swaps from Symbiotic
1. Curator pushes unallocated funds: `Vault.pushAssets(USDC, *500000*)`.
2. Allocates assets in Subvault 1 (Delta-Neutral strategy on ByBit via Copper ClearLoop).
* In UI, Curator clicks **New Call** → sets target to **Copper Subvault** → selects **USDC** asset and B**ybit Clearloop** as destination.
* Generate `VerificationPayload` via Mellow API.
* Executes call:
```solidity theme={null}
subvault1.call(
asset, // address of the asset (USDC) to be sent to the Copper account
0, // eth value
abi.encodeCall(
IERC20.tranfer, // transfer call encoding
(copperAccountAddress, 5e11) // (recipient, amount)
),
verificationPayload // extra data for Verifer contract
)
```
* Inside Bybit UI, strategy is realized by the Curator, with settlements occurring every few hours in the Copper Clearloop.
3. Allocate to Subvault 2 (Symbiotic Restaking).
* Pushes unallocated funds to the second subvault: `vault.pushAssets(subvault2, asset, 5e11)`.
* Clicks **New Call** → target = **Restaking Subvault** → calls `deposit(subvault, 5e11)`
* Gets the verification result and `VerificationPayload` from the Mellow API for this call.
* Executes call:
```solidity theme={null}
subvault2.call(
symbioticVault, // address of the symbiotic vault
0, // eth value
abi.encodeCall(
ISymbioticVault.deposit, // deposit call encoding
(subvault, 5e11) // (onBehalfOf, amount)
),
verificationPayload // extra data for Verifer contract
)
```
4. Curator regularly claims rewards from Symbiotic Restaking and swaps them into assets before depositing them using `subvault2.call`
5. Curator monitors Performance & Exit upon user request by repeating **New Call** steps to withdraw according to net returns.
# FenwickTreeLibrary
Source: https://metavaults.mellow.finance/architecture/libraries/fenwicktreelibrary
This library implements a **0-indexed** Fenwick Tree for tracking cumulative values over a dynamic array. It is suitable for systems where frequent prefix sum queries and point updates are required, such as time-based accounting, queuing systems, or share tracking.
### Design Characteristics
* `O(log n)` complexity for both updates and prefix sum queries.
* Storage-efficient using a `mapping(uint256 => int256)`, instead of an array.
* Only supports lengths that are exact powers of two (`2^k`), which simplifies internal logic and allows future extensions via `extend()`.
### Invariants and Constraints
* The tree must be initialized with a power-of-two length > 0 via `initialize(...)`.
* Index bounds are enforced — access beyond the current capacity reverts with `IndexOutOfBounds()`.
* To support dynamic resizing, `extend()` can double the current tree length (up to a safe limit).
* Use of negative values is supported in `modify(...)`, allowing decrement operations.
### Data Structures
```solidity theme={null}
struct Tree {
mapping(uint256 index => int256) _values; // Internal Fenwick Tree nodes.
uint256 _length; // Capacity of the tree (must be power of two).
}
```
### Functions
### `initialize(Tree storage tree, uint256 length_)`
Initializes the tree with the specified length.
* Reverts with `InvalidLength()` if `length_ == 0` or not a power of two.
* Only callable once (re-initialization is not allowed).
### `length(Tree storage tree) → uint256`
Returns the current capacity of the tree.
### `extend(Tree storage tree)`
Doubles the tree's capacity.
* Preserves prefix sum structure.
* Reverts with `InvalidLength()` on overflow.
### `modify(Tree storage tree, uint256 index, int256 value)`
Increments or decrements the value at a given index by `value`.
* Performs `tree[index] += value`.
* Reverts if index is out of bounds.
* No-op if `value == 0`.
### `get(Tree storage tree, uint256 index) → int256`
Returns the **prefix sum** for the range `[0, index]`.
* If `index >= length`, it is clamped to `length - 1`.
### `get(Tree storage tree, uint256 from, uint256 to) → int256`
Returns the sum over the range `[from, to]` (inclusive).
* Returns `0` if `from > to`.
### Internals
### `_modify(...)`
Low-level implementation of Fenwick update using bitwise operations:
* Updates `tree[index]` and propagates changes upward via `index |= index + 1`.
### `_get(...)`
Assembly-optimized prefix sum computation:
* Aggregates values by descending via `index := and(index, index + 1) - 1`.
### References
* [CP Algorithms: Fenwick Tree](https://cp-algorithms.com/data_structures/fenwick.html)
* [Wikipedia: Binary Indexed Tree](https://en.wikipedia.org/wiki/Fenwick_tree)
# Libraries
Source: https://metavaults.mellow.finance/architecture/libraries/index
In this directory, you will find a detailed per-contract overview of the libraries used in Core Vaults, including the following:
[FenwickTreeLibrary](/architecture/libraries/fenwicktreelibrary)
[ShareManagerLibrary](/architecture/libraries/sharemanagerlibrary)
[SlotLibrary](/architecture/libraries/slotlibrary)
[TransferLibrary](/architecture/libraries/transferlibrary)
# ShareManagerLibrary
Source: https://metavaults.mellow.finance/architecture/libraries/sharemanagerlibrary
This library helps pack multiple boolean flags and lockup durations into a compact `uint256` bitmask. It enables efficient storage and quick access to share manager configuration in vault systems.
Designed for the `ShareManager` component to control:
* Whether minting, burning, or transfers are paused
* Whether deposit/transfer whitelists are active
* How long global or user-specific lockups last
All data is packed into a single `uint256` using bit-level encoding for optimal storage and gas efficiency.
### Bitmask Layout
| Bit Range | Purpose |
| ---------- | ----------------------------- |
| `[0]` | `hasMintPause` (bool) |
| `[1]` | `hasBurnPause` (bool) |
| `[2]` | `hasTransferPause` (bool) |
| `[3]` | `hasWhitelist` (bool) |
| `[4]` | `hasTransferWhitelist` (bool) |
| `[5..36]` | `globalLockup` (uint32) |
| `[37..68]` | `targetedLockup` (uint32) |
### Functions
### `hasMintPause(uint256 mask) → bool`
Returns `true` if minting is paused (bit 0 is set).
### `hasBurnPause(uint256 mask) → bool`
Returns `true` if burning is paused (bit 1 is set).
### `hasTransferPause(uint256 mask) → bool`
Returns `true` if transfers are paused (bit 2 is set).
### `hasWhitelist(uint256 mask) → bool`
Returns `true` if a deposit whitelist is enabled (bit 3 is set).
### `hasTransferWhitelist(uint256 mask) → bool`
Returns `true` if a transfer whitelist is enabled (bit 4 is set).
### `getGlobalLockup(uint256 mask) → uint32`
Returns the **global lockup duration** in seconds (timestamp), encoded in bits `[5..36]`.
### `getTargetedLockup(uint256 mask) → uint32`
Returns the **targeted lockup duration** in seconds, encoded in bits `[37..68]`.
### `createMask(IShareManager.Flags calldata f) → uint256`
Encodes the values in a `Flags` struct into a single bitmask:
```solidity theme={null}
struct Flags {
bool hasMintPause;
bool hasBurnPause;
bool hasTransferPause;
bool hasWhitelist;
bool hasTransferWhitelist;
uint32 globalLockup;
uint32 targetedLockup;
}
```
# SlotLibrary
Source: https://metavaults.mellow.finance/architecture/libraries/slotlibrary
This library generates unique and collision-resistant storage slots for use in upgradeable Solidity contracts. It ensures that different modules or instances do not unintentionally overwrite each other’s storage, even when used via proxy or delegate calls.
### Storage Slot Strategy
* Based on EIP-7201
* Inputs include:
* Contract name (`contractName`)
* Human-readable name (`name`)
* Version number (`version`)
* Final slot:
```solidity theme={null}
keccak256(
abi.encode(
uint256(
keccak256(
abi.encodePacked(
"mellow.flexible-vaults.storage.",
contractName,
name, version
)
)
) - 1
)
) & ~bytes32(uint256(0xff));
```
This structure ensures:
* **Namespacing:** Prevents overlap between different modules (`ShareModule`, `FeeManager`, etc.)
* **Instance separation:** Multiple deployments with different names produce distinct slots
* **Versioning:** Upgrades can cleanly migrate to new versions without collision
### Function
### `getSlot(string contractName, string name, uint256 version) → bytes32`
**Description:**
Computes a deterministic, collision-resistant storage slot for a contract module.
**Parameters:**
* `contractName`: Logical name of the module (e.g., `"ShareModule"`)
* `name`: Instance identifier or label (e.g., `"Mellow"`)
* `version`: Numeric version for versioned slot separation
**Returns:**
* A `bytes32` value representing the computed storage slot
**Example:**
```solidity theme={null}
bytes32 slot = SlotLibrary.getSlot("FeeManager", "Mellow", 1);
```
# TransferLibrary
Source: https://metavaults.mellow.finance/architecture/libraries/transferlibrary
This utility abstracts away the differences between transferring native ETH and ERC20 tokens by introducing a unified interface for both sending and receiving assets. It also standardizes how native ETH is represented on-chain to simplify integration logic across different components.
### ETH Representation
The constant `ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` ([\*\*EIP-7528](https://ethereum-magicians.org/t/eip-7528-eth-native-asset-address-convention/15989))\*\* is used as a sentinel value to distinguish native ETH from ERC20 tokens.
### Errors
* `InvalidValue()`: Thrown when the contract expects a specific `msg.value` (for native ETH transfers) but receives a different amount.
### Constants
* `ETH`: Reserved address used to represent native Ether. When passed to `sendAssets` or `receiveAssets`, the function will process ETH instead of calling token functions.
### Functions
### `sendAssets(address asset, address to, uint256 assets)`
Sends the specified asset (`assets` amount) to the recipient `to`.
* If `asset == ETH`, the function uses `Address.sendValue` to transfer native ETH.
* If `asset` is an ERC20 token, it uses `IERC20.safeTransfer`.
**Parameters:**
* `asset`: Address of the asset to transfer. Should be `ETH` for native Ether or the ERC20 token address.
* `to`: Address to send the asset to.
* `assets`: Amount of the asset to transfer.
**Reverts if:** ETH transfer fails or ERC20 transfer fails via `SafeERC20`.
### `receiveAssets(address asset, address from, uint256 assets)`
Receives assets from the caller (or a third-party) into the current contract.
* If `asset == ETH`, verifies that `msg.value == assets`.
* If `asset` is an ERC20 token, calls `IERC20.safeTransferFrom` from `from` to the current contract.
**Parameters:**
* `asset`: Address of the asset to receive. Use `ETH` for native Ether or an ERC20 token address.
* `from`: Address sending the ERC20 tokens (ignored for ETH).
* `assets`: Expected amount of the asset to receive.
**Reverts if:**
* The contract receives an incorrect `msg.value` for ETH.
* The ERC20 transfer fails.
> Calling `receiveAssets(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, from, assets)` multiple times within a single function call will result in incorrect asset accounting.
>
> **DO NOT** use this function in scenarios like the following:
```solidity theme={null}
function func() external payable {
TransferLibrary.receiveAssets(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, msg.sender, 1 ether);
TransferLibrary.receiveAssets(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, msg.sender, 1 ether);
...
}
```
# BasicShareManager
Source: https://metavaults.mellow.finance/architecture/managers/basicsharemanager
### Overview
`BasicShareManager` is a concrete implementation of the abstract `ShareManager`, designed to provide native ERC20-style share accounting within a modular vault system. It handles minting, burning, and tracking balances of vault shares directly through a local ERC20-compatible storage layout, without exposing standard ERC20 interfaces.
This contract is intended for setups where shares are not tokenized on-chain as ERC20s but are still tracked internally using the ERC20Upgradeable storage schema.
### Key Features
* Uses `ShareManager` for permissioning, allocation, and whitelisting logic.
* Maintains balances and total supply using `ERC20Upgradeable.ERC20Storage`.
* Internal mint/burn logic emits `IERC20.Transfer` events (for transparency or compatibility).
* Fully decoupled from standard `ERC20` interface – share transfers are governed by vault queues and mint/burn logic only.
### Storage
ERC20-style balances and supply are stored at a fixed storage slot allowing for migrations BasicShareManager ↔ TokenizedShareManager:
```solidity theme={null}
bytes32 private constant ERC20StorageLocation = 0x52c6...ce00;
```
### Initialization
```solidity theme={null}
function initialize(bytes calldata data) external initializer
```
* Expects a single `bytes32 whitelistMerkleRoot` (used by `ShareManager`).
### View Functions
* `activeShares()`: Returns `_totalSupply` from ERC20 storage.
* `activeSharesOf(account)`: Returns balance of `account`.
### Internal Logic
### `_mintShares(address, uint256)`
* Checks if minting is allowed via `updateChecks`.
* Increments total supply and receiver's balance.
* Emits `IERC20.Transfer(address(0), account, value)`.
Reverts if:
* `account == address(0)`
* Minting is paused or restricted by lockup, whitelist, or blacklist
### `_burnShares(address, uint256)`
* Checks if burning is allowed via `updateChecks`.
* Decreases sender's balance and total supply.
* Emits `IERC20.Transfer(account, address(0), value)`.
Reverts if:
* `account == address(0)`
* `value > account balance`
* Burning is paused or blocked
### Design Notes
* This module deliberately avoids exposing the ERC20 interface, preventing any unintended external transfers or integrations.
* It is intended for internal share accounting within vault systems, where shares are tracked but not tokenized onchain.
* All permissioning logic, including minting, burning, whitelisting, and lockup enforcement, is delegated to the inherited `ShareManager`.
* This implementation is ideal when the vault owner requires non-transferable shares for internal logic, without compliance to ERC20 or ERC4626 standards.
* It is **not appropriate** for setups where shares must be externally transferable, interoperable with third-party protocols, or conform to token standards.
# FeeManager
Source: https://metavaults.mellow.finance/architecture/managers/feemanager
**Modular, upgradeable fee management contract for vaults.**
The `FeeManager` is responsible for managing and calculating various fee types in a vault system, including deposit, redemption, performance, and protocol fees. It uses a flexible architecture with deterministic storage slots (via `SlotLibrary`) and supports per-vault configurations.
### Key Responsibilities
* Configures and stores global fee settings (in D6 precision).
* Tracks vault-specific state (base asset, min price, timestamp).
* Computes:
* **Deposit Fee**: Fee charged on asset deposit.
* **Redeem Fee**: Fee charged on share redemption.
* **Performance Fee**: Fee based on drop in price (`assets * price = shares`)
* **Protocol Fee**: Time-based fee accrued per vault.
* Provides administrative controls to update fee settings and vault metadata.
### Storage
Uses an isolated storage slot per deployment instance, computed deterministically using `SlotLibrary.getSlot("FeeManager", name, version)`, ensuring safety and upgradability.
Each vault is associated with:
* `baseAsset`: Reference token used for performance fee calculation.
* `minPriceD18`: Minimum price recorded (used for performance fee calculation).
* `timestamps`: Last update timestamp for time-based fee accrual.
### Fee Calculation Logic
### `calculateDepositFee(uint256 shares) → uint256`
Computes a linear fee as `shares * depositFeeD6 / 1e6`.
### `calculateRedeemFee(uint256 shares) → uint256`
Computes a linear fee as `shares * redeemFeeD6 / 1e6`.
### `calculateFee(...) → uint256 shares`
Calculates the total fee to be charged based on:
* Performance: If current `priceD18` below `minPriceD18`, applies `performanceFeeD6` as `(minPriceD18 - priceD18) * performanceFeeD6 * totalShares / 1e24`.
* Protocol: Time-weighted fee based on `block.timestamp - timestamps[vault]`, computed as `totalShares * protocolFee * (block.timestamp - timestamps[vault]) / (365 * 24 * 3600 * 1e6)`
All fees are paid in **shares** of the vault (not assets).
### Access Control
* Only the `owner` (defined during initialization) can modify fee parameters or vault configurations.
* Calls to `initialize(...)` must come from the factory and include all required setup parameters.
### Events
* `Initialized(bytes data)`: Emitted after initialization.
* `SetFeeRecipient(address feeRecipient)`: On recipient update.
* `SetFees(...)`: On any fee change.
* `SetBaseAsset(...)`: When base asset is configured per vault.
* `UpdateState(...)`: On state update used for protocol/performance fees.
### Errors
* `ZeroAddress()`: Thrown if an address input is zero.
* `InvalidFees(...)`: When the combined fee rate exceeds 100% (`1e6` D6).
* `BaseAssetAlreadySet(...)`: Prevents base asset override if already set.
### Lifecycle
1. **Constructor**: Sets storage slot based on name/version.
2. **Initialization** (via `initialize(bytes)`): Sets owner, recipient, and fees.
3. **Fee Updates**: Admin can update recipient and fee parameters.
4. **Vault Hooks**:
* `updateState(asset, price)`: Vaults call this to refresh timestamp and min price.
* `setBaseAsset`: Called once per vault to register its performance reference token.
# Managers
Source: https://metavaults.mellow.finance/architecture/managers/index
In this directory, you will find a detailed per-contract overview of the "Managers" contract category, including the following contracts:
[FeeManager](/architecture/managers/feemanager)
[RiskManager](/architecture/managers/riskmanager)
[ShareManager](/architecture/managers/sharemanager)
[BasicShareManager](/architecture/managers/basicsharemanager)
[TokenizedShareManager](/architecture/managers/tokenizedsharemanager)
# RiskManager
Source: https://metavaults.mellow.finance/architecture/managers/riskmanager
**On-chain risk control and allocation policy manager for modular vaults.**
The `RiskManager` contract defines and enforces asset deposit limits across a `Vault` and its associated `Subvaults`. It maintains internal accounting of balances, limits and allowed subvault assets.
### Purpose
This contract is a centralized module responsible for:
* Defining and enforcing **deposit limits** at vault and subvault levels.
* Allowlisting or disallowing **specific assets** per subvault.
* Tracking **pending balances** (deposits/withdrawals that are not yet finalized).
* Validating risk assumptions through **oracle price reports**.
### Core Concepts
* **Vault Limit**: Global cap across all assets managed by the vault (in shares).
* **Subvault Limit**: Individual cap per subvault, enforced independently (in shares).
* **Allowed Assets**: Only explicitly allowlisted assets are permitted in a given subvault.
* **Pending Assets**: Temporarily tracked assets, e.g., during deposit queueing
* **Shares Conversion**: All balances are internally tracked in shares, calculated using latest report in the `Oracle` contract.
### Storage Slot
Utilizes a deterministic storage slot computed via `SlotLibrary.getSlot("RiskManager", name, version)` to ensure safe upgrades and modular deployment.
### Roles and Permissions
The contract uses fine-grained access roles:
* `SET_VAULT_LIMIT_ROLE`: Can modify global vault capacity.
* `SET_SUBVAULT_LIMIT_ROLE`: Can change limits on individual subvaults.
* `ALLOW_SUBVAULT_ASSETS_ROLE`: Can whitelist assets for specific subvaults.
* `DISALLOW_SUBVAULT_ASSETS_ROLE`: Can revoke asset approval from subvaults.
* `MODIFY_PENDING_ASSETS_ROLE`: Can manipulate pending balance delta.
* `MODIFY_VAULT_BALANCE_ROLE`: Can update the vault's live balance.
* `MODIFY_SUBVAULT_BALANCE_ROLE`: Can update a subvault’s internal balance.
Roles are verified via the vault's ACL module (`IACLModule`) or allowed queues (`IShareModule`).
### Key Methods
### View
* `vault()`: Returns the vault address.
* `vaultState()`: Returns the global vault state (limit, balance).
* `pendingBalance()`: Returns the pending share balance across all assets and deposit queues.
* `subvaultState(address)`: Returns per-subvault state.
* `pendingAssets(address)`: Returns currently pending asset amount.
* `pendingShares(address)`: Returns share-equivalent of pending assets.
* `allowedAssets(address)`: Count of allowed assets in subvault.
* `allowedAssetAt(address, index)`: Indexed lookup of allowed asset.
* `isAllowedAsset(address, asset)`: Checks asset permission for subvault.
* `convertToShares(asset, value)`: Converts amount to share units using oracle.
* `maxDeposit(subvault, asset)`: Calculates max deposit amount given limits and prices.
### Mutable
* `initialize(bytes data)`: Initializes vault-wide limit.
* `setVault(address)`: Assigns the vault address (one-time only).
* `setVaultLimit(int256 limit)`: Updates vault's global limit.
* `setSubvaultLimit(address, int256)`: Updates limit for a specific subvault.
* `allowSubvaultAssets(address, address[])`: Adds assets to subvault's allowlist.
* `disallowSubvaultAssets(address, address[])`: Removes assets from allowlist.
* `modifyPendingAssets(address, int256)`: Adjusts pending assets and updates internal shares.
* `modifyVaultBalance(address, int256)`: Applies a delta to vault's current balance (with limit checks).
* `modifySubvaultBalance(address, asset, int256)`: Same as above, but scoped to a specific subvault.
### Internal Mechanics
### Conversion to Shares
Conversion is done with oracle price data, where:
```solidity theme={null}
shares = (value * priceD18) / 1e18
```
### Assumptions
The system assumes that:
* The vault and its subvaults operate exclusively with **correlated assets**, and
* Protocol-level delegations performed by the curator **do not introduce extreme APR variance or significant principal loss**.
Given this, all vault- and subvault-level limits are treated as **approximate** and are computed using the most recent oracle report available **at the time of the state update** (e.g., on pull/push or deposit/redeem operations).
If actual balances deviate significantly from the stored `balance` values due to oracle drift, delayed execution, or protocol-side changes, a **trusted actor** can apply a ‘corrections’ to mitigate the difference:
* `modifyVaultBalance` for the Vault, or
* `modifySubvaultBalance` for individual Subvaults.
Since the system is expected to hold only correlated assets, such manual adjustments are assumed to be **rare** under normal operating conditions.
# ShareManager
Source: https://metavaults.mellow.finance/architecture/managers/sharemanager
### Overview
The `ShareManager` is an abstract upgradeable contract responsible for managing vault share issuance, allocation, whitelisting, permissions, and lockups in a modular vault system. It relies on a compact bitmask (`flags`) for enabling/disabling features and supports configurable per-account permissions.
### Key Responsibilities
* Tracking total and active share supply
* Managing global and per-account lockups
* Verifying whitelist and transfer permissions
* Enforcing mint/burn/transfer pauses
* Allocating and claiming shares through queues
* Emitting granular control events on state transitions
### Storage
Storage is accessed via a deterministic slot generated by `SlotLibrary.getSlot("ShareManager", name, version)`.
```solidity theme={null}
struct ShareManagerStorage {
address vault;
uint256 flags; // Encodes permissions and lockup durations
uint256 allocatedShares;
bytes32 whitelistMerkleRoot;
mapping(address => AccountInfo) accounts;
}
```
### Permission System
Controlled through roles defined as:
* `SET_FLAGS_ROLE`: Allows changing global flags (e.g., mint pause, whitelist enforcement).
* `SET_ACCOUNT_INFO_ROLE`: Grants permission to set per-account configuration.
* `SET_WHITELIST_MERKLE_ROOT_ROLE`: Grants permission to set new whitelist merkle root.
* All share-related actions are guarded via:
* `onlyQueue()`
* `onlyVaultOrQueue()`
* `onlyRole(...)`
### View Functions
* `vault()`: Returns the vault address.
* `sharesOf(account)`: Total shares (active + claimable).
* `activeSharesOf(account)`: Abstract; must be implemented by child.
* `claimableSharesOf(account)`: Reads from the `IShareModule`.
* `totalShares()`: `allocatedShares + activeShares()`
* `accounts(account)`: Returns `AccountInfo` struct (deposit/transfer flags, lockups, blacklisting).
* `flags()`: Decoded bitmask as `Flags` struct.
* `whitelistMerkleRoot()`: Current root used for off-chain whitelist proof verification.
* `isDepositorWhitelisted(account, proof)`: Verifies Merkle proof or checks local permission flags.
* `updateChecks(from, to)`: Reverts on violations (paused actions, lockups, blacklisting, etc.).
### Mutable Functions
* `setVault(...)`: One-time vault initialization.
* `setFlags(...)`: Updates global bitmask configuration.
* `setWhitelistMerkleRoot(…)`: Updates whitelist merkle root.
* `setAccountInfo(...)`: Sets access rights for an individual address.
* `claimShares(...)`: Claims shares from the vault’s `IShareModule`.
* `allocateShares(...)`: Allocates shares for future minting (only callable by a queue).
* `mintAllocatedShares(...)`: Mints shares from allocated pool to user.
* `mint(...)`: Mints shares to a user with optional lockup.
* `burn(...)`: Burns a user’s shares (only queue).
* `__ShareManager_init(...)`: Sets Merkle root at construction or upgrade.
### Internal Hooks (Implemented by Child)
```solidity theme={null}
function _mintShares(address account, uint256 value) internal virtual;
function _burnShares(address account, uint256 value) internal virtual;
```
These abstract functions allow concrete implementations to define how share balances are recorded or tokenized.
### Bitmask-Controlled Features
Controlled via `ShareManagerFlagLibrary`:
* `hasMintPause`
* `hasBurnPause`
* `hasTransferPause`
* `hasWhitelist`
* `hasTransferWhitelist`
* `globalLockup`
* `targetedLockup`
Lockups are enforced in `updateChecks`.
# TokenizedShareManager
Source: https://metavaults.mellow.finance/architecture/managers/tokenizedsharemanager
### Overview
* This module extends `ShareManager` and `ERC20Upgradeable`, making vault shares externally transferable and fully compliant with the ERC20 standard.
* It is intended for vaults that require tokenized shares usable across external protocols, wallets, or DeFi integrations.
* Core share logic (minting, burning, whitelisting, lockups) is delegated to the inherited `ShareManager`, preserving consistent permission enforcement.
* Whitelist enforcement, lockup mechanics, and share claim logic are integrated into the overridden `_update` hook, which ensures all token transfers pass necessary checks and call `claimShares` for non-zero actors.
* Suitable for use cases where share liquidity, composability, or token standard compatibility (e.g., ERC20, ERC4626 wrappers) is required.
# ACLModule
Source: https://metavaults.mellow.finance/architecture/modules/aclmodule
### Overview
Abstract module integrating role-based access control via `MellowACL`, providing permission management functionality.
## Internal Functions
### `__ACLModule_init`
```solidity theme={null}
function __ACLModule_init(address admin_) internal onlyInitializing
```
### Description
Initializes the module with an admin address by assigning the `DEFAULT_ADMIN_ROLE`. This sets up the foundational RBAC structure.
### Parameters
* `admin_`: Address to be granted the `DEFAULT_ADMIN_ROLE`.
### Requirements
* `admin_` must not be the zero address.
* Callable only during initialization (`onlyInitializing`).
# BaseModule
Source: https://metavaults.mellow.finance/architecture/modules/basemodule
## Overview
`BaseModule` is an abstract contract that acts as a foundational layer for modules within the system. It integrates shared logic such as initializer protection, reentrancy guard, IERC721Receiver compliance and low level storage access.
This module is intended to be inherited and extended by other functional modules.
## Constructor
```solidity theme={null}
constructor() {
_disableInitializers();
}
```
Prevents the contract from being initialized outside of proxy context. Ensures secure upgradeable deployments.
## Public & External Functions
### `getStorageAt(bytes32 slot)`
```solidity theme={null}
function getStorageAt(bytes32 slot) external pure returns (StorageSlot.Bytes32Slot memory)
```
Returns a reference to a custom storage slot. Enables advanced access to shared storage across upgradeable modules using the `StorageSlot` pattern.
**Parameters:**
* `slot` — The `bytes32` identifier of the storage slot.
**Returns:**
* A `StorageSlot.Bytes32Slot` struct pointing to the slot.
### `onERC721Received(...)`
```solidity theme={null}
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4)
```
ERC721 receiver hook implementation to support safe transfers of NFTs to the module. Returns the selector as required by `IERC721Receiver`.
**Returns:**
* `IERC721Receiver.onERC721Received.selector` — confirms compliance.
### `receive()`
```solidity theme={null}
receive() external payable {}
```
Allows the contract to receive native ETH transfers. This is typically used for vaults handling native tokens directly.
## Internal Functions
### `__BaseModule_init()`
```solidity theme={null}
function __BaseModule_init() internal onlyInitializing
```
Initializes internal dependencies and base upgradeable components. Should be called from derived contract initializers.
**Side Effects:**
* Calls `__ReentrancyGuard_init()` to initialize reentrancy protection.
# CallModule
Source: https://metavaults.mellow.finance/architecture/modules/callmodule
### Overview
Abstract contract extending `VerifierModule`, implementing low-level contract calls with verification via a pluggable verifier.
### `call`
```solidity theme={null}
function call(
address where,
uint256 value,
bytes calldata data,
IVerifier.VerificationPayload calldata payload
) external nonReentrant returns (bytes memory response)
```
### Description
Executes a low-level call to a target contract after validating the call parameters through an external `Verifier` contract. The verification logic is determined by the verification type specified in the `Verifier` contract.
For details on available verification types, refer to the [Verifier specification](https://www.notion.so/Verifier-23002ad8627680cfbab5e96defcdbe31?pvs=21).
### Parameters
* `where`: The address of the target contract.
* `value`: The ETH value to send along with the call.
* `data`: Calldata to pass to the target contract.
* `payload`: Encoded verification payload used to authorize the call.
### Returns
* `response`: The raw returned data from the target contract call.
### Requirements
* All the provided parameters must be externally verified via `verifier().verifyCall`.
* Reentrancy is prevented via `nonReentrant` modifier.
# Modules
Source: https://metavaults.mellow.finance/architecture/modules/index
In this directory, you will find a detailed per-contract overview of the "Modules" contract category, including the following modules:
[VerifierModule](/architecture/modules/verifiermodule)
[ACLModule](/architecture/modules/aclmodule)
[CallModule](/architecture/modules/callmodule)
[ShareModule](/architecture/modules/sharemodule)
[VaultModule](/architecture/modules/vaultmodule)
[SubvaultModule](/architecture/modules/subvaultmodule)
# ShareModule
Source: https://metavaults.mellow.finance/architecture/modules/sharemodule
### Purpose
`ShareModule` is a core module responsible for managing user interactions with a vault through structured deposit and redeem queues. It provides governance over queue creation, hook configurations, oracle-driven settlement, and fee accounting.
### Key Responsibilities
* Tracks and validates all deposit/redeem queue operations.
* Coordinates price reporting with oracle.
* Facilitates dynamic queue configuration and lifecycle.
* Integrates hooks for deposit/redeem processing customization.
* Central hub for protocol and performance fee minting, share claim logic and report handling.
### Roles
* `SET_HOOK_ROLE`: Grants the ability to modify per-queue and default hook addresses.
* `CREATE_QUEUE_ROLE`: Allows creation of new deposit/redeem queues.
* `SET_QUEUE_STATUS_ROLE`: Permits pausing/unpausing individual queues.
* `SET_QUEUE_LIMIT_ROLE`: Enables setting the max number of total queues.
* `REMOVE_QUEUE_ROLE`: Allows safe removal of queues with `canBeRemoved()` check.
### Core Storage Layout (`ShareModuleStorage`)
* `shareManager`: Reference to contract handling share minting/burning.
* `feeManager`: Reference to contract that calculates fees and stores fee-related data.
* `oracle`: Oracle contract providing price data and asset support status.
* `defaultDepositHook` / `defaultRedeemHook`: Global fallback hooks used by default in case custom hooks are not defined.
* `customHooks`: Per-queue override for custom hook logic.
* `queueCount`: Total existing queues.
* `queueLimit`: Global max limit for queues.
* `isDepositQueue`: Distinguishes deposit queues from redeem ones.
* `isPausedQueue`: Tracks paused queues.
* `queues`: Asset → queues mapping.
* `assets`: Registry of all assets with registered queues.
### View Functions
* `shareManager()`: Returns the `IShareManager` instance.
* `feeManager()`: Returns the `IFeeManager` instance.
* `oracle()`: Returns the `IOracle` instance.
* `depositQueueFactory()` / `redeemQueueFactory()`: Queue factory contracts.
* `queueLimit()`: Max allowed queues.
* `claimableSharesOf(account)`: Sum of claimable shares across all deposit queues for the `account`.
* `getLiquidAssets()`: Called by redeem queues to determine liquidity available for handling redemptions.
* `defaultDepositHook()` / `defaultRedeemHook()`: Global default hooks.
* `getHook(queue)`: Resolves hook for queue (custom or default fallback as a fallback).
* Asset/Queue helpers:
* `getAssetCount()`, `assetAt(index)`, `hasAsset(asset)`
* `hasQueue(queue)`, `isDepositQueue(queue)`, `isPausedQueue(queue)`
* `getQueueCount()` / `getQueueCount(asset)`
* `queueAt(asset, index)`
### Mutable Functions
* `claimShares(account)`: Claims all claimable shares from deposit queues for the specific account.
* `callHook(assets)`: Calls the queue’s associated hook. Transfers assets to the queue if redeem.
* `setCustomHook(queue, hook)`: Assigns per-queue hook.
* `setDefaultDepositHook(hook)` / `setDefaultRedeemHook(hook)`: Sets global hooks.
* `setQueueLimit(limit)`: Updates global queue cap.
* `setQueueStatus(queue, isPaused)`: Pauses/unpauses a queue.
* `createQueue(version, isDeposit, owner, asset, data)`: Deploys new queue for asset.
* `removeQueue(queue)`: Removes a queue that passed `canBeRemoved()`.
* `handleReport(asset, priceD18, depositTimestamp, redeemTimestamp)`:
* Called by the oracle.
* Distributes protocol fees.
* Propagates price report to all queues and calls hooks.
### Events
* `SharesClaimed(account)`
* `CustomHookSet(queue, hook)`
* `QueueCreated(queue, asset, isDepositQueue)`
* `QueueRemoved(queue, asset)`
* `HookCalled(queue, asset, assets, hook)`
* `QueueLimitSet(limit)`
* `SetQueueStatus(queue, isPaused)`
* `DefaultHookSet(hook, isDepositHook)`
* `ReportHandled(asset, priceD18, depositTimestamp, redeemTimestamp, fees)`
# SubvaultModule
Source: https://metavaults.mellow.finance/architecture/modules/subvaultmodule
### Purpose
The `SubvaultModule` represents an isolated child vault within a modular vault system. It is tightly controlled by its parent vault (typically a `VaultModule`) and is responsible for securely holding and releasing assets upon authenticated requests.
### Responsibilities
* Store and isolate a portion of vault assets
* Allow trusted actor (curator) to delegate liquidity from the Subvault to external protocol based on the `Verifier` setup for this specific subvault
* Respond to `pullAssets` calls from the parent vault only
### Storage Layout (`SubvaultModuleStorage`)
```solidity theme={null}
struct SubvaultModuleStorage {
address vault;
}
```
* `vault`: Address of the root vault that controls this subvault. Only this address can request asset withdrawals.
The layout is stored in a deterministic custom slot derived using:
```solidity theme={null}
SlotLibrary.getSlot("SubvaultModule", name_, version_)
```
### View Functions
### `vault() → address`
Returns the address of the parent vault that instantiated this subvault.
### Mutable Functions
### `pullAssets(asset: address, value: uint256)`
Allows the parent vault to withdraw a specified amount of an asset.
* **Access Control**: Can only be called by the `vault()` address
* **Reverts**: With `NotVault()` if the caller is not the vault
* **Transfer Behavior**: Uses `TransferLibrary.sendAssets()` to forward tokens or native ETH to the `Vault.sol` address
* **Emits**: `AssetsPulled(asset, vault, value)`
### Internal Initialization
### `__SubvaultModule_init(address vault_)`
Internal setup method to be called during construction or proxy initialization.
### Events
### `event AssetsPulled(address indexed asset, address indexed to, uint256 value)`
Triggered when assets are withdrawn by the parent vault.
* `asset`: Address of the ERC20 token or native ETH
* `to`: Always equals the `vault()` address
* `value`: Amount of the asset transferred
### Error Handling
* **`NotVault()`**: Raised when a non-vault caller attempts to call `pullAssets()`
# VaultModule
Source: https://metavaults.mellow.finance/architecture/modules/vaultmodule
### Purpose
`VaultModule` is a core component of the modular vault architecture. It manages liquidity routing between the [`Vault`](https://www.notion.so/Vault-23002ad86276805a88a4c52c48b7f677?pvs=21) and its connected [`Subvaults`](https://www.notion.so/Subvault-23002ad8627680ef88ebe91c30b2d1b4?pvs=21), enabling flexible strategy composition and modular upgrades. It supports hot-swapping of subvault contracts and ensures robust control over asset movement.
### Responsibilities
* Orchestrate liquidity push/pull operations between the vault and subvaults
* Create, disconnect, and reconnect subvaults
* Verify creations, removals and reconnections using external `Factory` contracts and local state
* Track and update risk exposure via `RiskManager`
### Roles
* `CREATE_SUBVAULT_ROLE`: Allows creation of new subvaults
* `DISCONNECT_SUBVAULT_ROLE`: Allows disconnection of active subvaults
* `RECONNECT_SUBVAULT_ROLE`: Allows reattachment of disconnected or new properly configured subvaults
* `PULL_LIQUIDITY_ROLE`: Grants permission to pull assets from subvaults
* `PUSH_LIQUIDITY_ROLE`: Grants permission to send assets to subvaults
### Storage Layout (`VaultModuleStorage`)
```solidity theme={null}
struct VaultModuleStorage {
address riskManager;
EnumerableSet.AddressSet subvaults;
}
```
* `riskManager`: Module used to track and limit exposure per asset/subvault
* `subvaults`: Enumerable set of currently connected subvaults
### View Functions
* `subvaultFactory()`: Returns `IFactory` used to deploy and check deployed subvaults
* `verifierFactory()`: Returns `IFactory` used to deploy and check deployed verifiers
* `subvaults()`: Returns the total number of connected subvaults
* `subvaultAt(index)`: Returns the subvault address at a specific index
* `hasSubvault(address)`: Checks if a given address is an active subvault
* `riskManager()`: Returns the address of the risk manager
### Mutable Functions
### Subvault Management
* `createSubvault(version, owner, verifier)`:
* Deploys a new subvault via the `subvaultFactory`
* Links it to the provided `verifier`
* Adds it to the vault's subvault list
* Emits `SubvaultCreated`
* `disconnectSubvault(subvault)`:
* Removes a subvault from the vault registry
* Emits `SubvaultDisconnected`
* Reverts with `NotConnected` if not already linked
* `reconnectSubvault(subvault)`:
* Re-adds a subvault to the vault registry
* Validates via `subvaultFactory` and `verifierFactory`
* Emits `SubvaultReconnected`
* Reverts with `InvalidSubvault`, `NotEntity`, or `AlreadyConnected` if checks fail
### Liquidity Movement
* `pushAssets(subvault, asset, value)`:
* Transfers assets from vault to subvault
* Updates internal risk manager state (adds exposure)
* Emits `AssetsPushed`
* `pullAssets(subvault, asset, value)`:
* Retrieves assets from a subvault
* Updates internal risk manager state (reduces exposure)
* Emits `AssetsPulled`
### Internal Liquidity Hooks
These can only be invoked by the vault itself (via hooks):
* `hookPushAssets(subvault, asset, value)`
* `hookPullAssets(subvault, asset, value)`
### Error Conditions
* `AlreadyConnected(subvault)`: When attempting to reconnect an already connected subvault
* `NotConnected(subvault)`: When attempting to disconnect a subvault that isn't connected
* `NotEntity(address)`: Provided contract is not a valid `IFactory`deployed entity
* `InvalidSubvault(address)`: Subvault fails verification (incorrect `subvault.vault()` address)
* `ZeroAddress()`: Passed `RiskManager` address is zero (used in `__VaultModule_init`)
* `Forbidden()`: Caller is not authorized (used in internal checks)
### Events
* `SubvaultCreated(subvault, version, owner, verifier)`
* `SubvaultDisconnected(subvault)`
* `SubvaultReconnected(subvault, verifier)`
* `AssetsPulled(asset, subvault, value)`
* `AssetsPushed(asset, subvault, value)`
### Security Considerations
* All critical functions gated by role-based ACL
* Uses factory-verified deployments for submodules
* Internal state (risk exposure) updated on every asset movement
* Only the vault contract itself may invoke `hook*` liquidity functions
### Initialization
```solidity theme={null}
function __VaultModule_init(address riskManager_) internal onlyInitializing
```
* Sets the `riskManager` address (must be non-zero)
* Should be invoked during deployment or upgrade setup
# VerifierModule
Source: https://metavaults.mellow.finance/architecture/modules/verifiermodule
## Overview
`VerifierModule` is an abstract extension of `BaseModule` designed to provide standardized access to a `Verifier` contract. It manages internal storage using a deterministic slot derived via `SlotLibrary`, supporting secure modular composition across multiple vault systems.
## Constructor
```solidity theme={null}
constructor(string memory name_, uint256 version_)
```
Computes and stores the custom storage slot used for verifier configuration based on a unique `(name_, version_)` pair.
**Parameters:**
* `name_` — Unique identifier used to namespace the storage slot.
* `version_` — Version number used for slot derivation.
## Public & External Functions
### `verifier()`
```solidity theme={null}
function verifier() public view returns (IVerifier)
```
Returns the address of the configured `Verifier` contract. It is retrieved from internal storage using a fixed slot.
**Returns:**
* `IVerifier` — The verifier contract associated with the module.
## Internal Functions
### `__VerifierModule_init(address verifier_)`
```solidity theme={null}
function __VerifierModule_init(address verifier_) internal onlyInitializing
```
Initializes the verifier module with the given verifier contract address. Validates non-zero address to prevent misconfiguration.
**Parameters:**
* `verifier_` — Address of the verifier contract.
**Reverts:**
* `ZeroAddress()` if verifier address is zero.
## Private Functions
### `_verifierModuleStorage()`
```solidity theme={null}
function _verifierModuleStorage() private view returns (VerifierModuleStorage storage)
```
Internal function to access the `VerifierModuleStorage` struct using the precomputed custom slot. Utilizes inline assembly for direct storage access.
**Returns:**
* `VerifierModuleStorage` — Storage struct holding verifier address.
# Oracle
Source: https://metavaults.mellow.finance/architecture/oracle
#### Overview
The `Oracle` contract is responsible for secure and configurable **price reporting** for supported assets. It is tightly coupled with a vault module (implementing `IShareModule`) and provides **price validation**, **deviation tracking**, and **rate-limited report submission**.
It enforces strong assumptions around **data integrity**, **report timing**, and **trust minimization** through roles and deviation thresholds. This oracle ensures consistent pricing across all queue, share, and vault-related computations.
#### Key Responsibilities
* **Report Submission**: Allows trusted accounts to submit price updates
* **Deviation Analysis**: Compares new prices against the last report for suspicious behavior
* **Timestamp-based Rate Limiting**: Prevents frequent or premature reports
* **Asset Management**: Controls which tokens are supported by the oracle
* **Oracle Price Validation**: Used by other modules (e.g., `SignatureQueue`) to verify incoming prices
#### Roles
| Role | Description |
| ------------------------------ | --------------------------------------------- |
| `SUBMIT_REPORTS_ROLE` | Permission to submit regular price reports |
| `ACCEPT_REPORT_ROLE` | Permission to accept suspicious reports |
| `SET_SECURITY_PARAMS_ROLE` | Can modify validation rules and intervals |
| `ADD_SUPPORTED_ASSETS_ROLE` | Can whitelist new assets for reporting |
| `REMOVE_SUPPORTED_ASSETS_ROLE` | Can remove assets and delete associated state |
#### **`SecurityParameters`**
```solidity theme={null}
struct SecurityParams {
uint224 maxAbsoluteDeviation;
uint224 suspiciousAbsoluteDeviation;
uint64 maxRelativeDeviationD18;
uint64 suspiciousRelativeDeviationD18;
uint32 timeout;
uint32 depositInterval;
uint32 redeemInterval;
}
```
* **Absolute Deviation**: Hard limits on price delta in price units
* **Relative Deviation**: Tolerance as a percentage (e.g., 5% = 0.05e18)
* **Timeout**: Minimum time between valid reports (ignored if the previous report is suspicious)
* **depositInterval**: Minimum age required for a deposit to be processed
* **redeemInterval**: Same, but for redemptions
#### **`Reports`**
```solidity theme={null}
struct Report {
address asset;
uint224 priceD18;
}
struct DetailedReport {
uint224 priceD18;
uint32 timestamp;
bool isSuspicious;
}
```
Used to validate asset prices and coordinate cross-queue processing.
#### Key Functions
#### View
| Function | Description |
| -------------------------------- | ---------------------------------------------------------------------- |
| `vault()` | Returns the linked vault (must implement `IShareModule`) |
| `securityParams()` | Current oracle thresholds and intervals |
| `supportedAssets()` | Count of whitelisted tokens |
| `supportedAssetAt(index)` | Token at a given index |
| `isSupportedAsset(address)` | Whether an asset is valid for reporting |
| `getReport(asset)` | Returns last report (price, timestamp, suspicious flag) |
| `validatePrice(priceD18, asset)` | Validates a given price against the current report and security params |
#### Mutable
| Function | Description |
| --------------------------------------- | ----------------------------------------------- |
| `initialize(params)` | Initializes with assets and security settings |
| `setVault(vault)` | Registers the vault for report consumption |
| `submitReports(reports[])` | Batch-submits prices for multiple assets |
| `acceptReport(asset, price, timestamp)` | Marks a previously suspicious report as trusted |
| `setSecurityParams(params)` | Updates thresholds and timing rules |
| `addSupportedAssets(assets[])` | Adds tokens to the supported set |
| `removeSupportedAssets(assets[])` | Removes tokens and clears their reports |
#### Reporting Logic
When calling `submitReports(...)`:
1. Each asset is checked for support
2. The previous report is evaluated:
* If `timeout` has not passed, and the report is not suspicious → **revert** `TooEarly`
3. Price is compared against previous:
* Too far off → **revert** `InvalidPrice`
* Moderately off → flagged `isSuspicious`
4. If the report is accepted:
* Triggers `vault.handleReport(...)` with adjusted deposit and redeem timestamps
* Emits `ReportsSubmitted`
#### Validation Logic
Prices are validated by:
* Calculating **absolute deviation**
* Calculating **relative deviation**
* Comparing against `max` and `suspicious` thresholds
A price is:
* **Rejected** if either deviation exceeds max
* **Accepted but suspicious** if above suspicious threshold
* **Accepted as normal** if within all limits
Used by:
* `SignatureDepositQueue`, `SignatureRedeemQueue`, `DepositQueue` and `RedeemQueue` contracts
* Vault's limit accounting (RiskManager)
#### Events
| Event | Purpose |
| ----------------------------------------- | ---------------------------------- |
| `ReportsSubmitted(Report[])` | Emitted when new prices are posted |
| `ReportAccepted(asset, price, timestamp)` | Suspicious report accepted |
| `SecurityParamsSet(params)` | Oracle thresholds changed |
| `SupportedAssetsAdded(addresses[])` | New tokens added |
| `SupportedAssetsRemoved(addresses[])` | Tokens delisted |
| `SetVault(address)` | Vault set |
#### Security Considerations
* Only trusted roles can push prices
* Suspicious reports cannot be accepted without explicit approval
* No pricing logic oracles trust external feeds — price validation is local
* Prevents manipulation by enforcing absolute & relative deviation constraints
# BitmaskVerifier
Source: https://metavaults.mellow.finance/architecture/permissions/bitmaskverifier
#### Purpose
The `BitmaskVerifier` is a customizable, low-level verifier module that enables **selective call authorization** using **bitmask-based hashing**. It allows a contract to validate whether a function call (defined by `who`, `where`, `value`, and `data`) conforms to a pre-authorized pattern.
It supports:
* Partial matching of calldata
* Exact or wildcard matching on sender, target, or ETH value
* Highly gas-efficient verification with minimal storage
#### Core Concept: Bitmask-Based Hashing
The `BitmaskVerifier` computes a hash over masked components of a transaction and compares it to a stored or expected hash.
The verification succeeds if:
```solidity theme={null}
calculateHash(bitmask, who, where, value, data) == expectedHash
```
#### Bitmask Format
The bitmask is a byte array with the following structure:
| Segment | Bytes | Targeted Field | Description |
| -------- | ------------- | -------------- | -------------------------------------------------------------- |
| \[0:32] | 32 bytes | `who` | Mask for the caller address (left-padded to 32 bytes) |
| \[32:64] | 32 bytes | `where` | Mask for the target contract address (left-padded to 32 bytes) |
| \[64:96] | 32 bytes | `value` | Mask for ETH value (uint256) |
| \[96:] | `data.length` | `data` | One byte per calldata byte; used to mask calldata selectively |
This structure allows the verifier to:
* Fully match addresses and value
* Partially match calldata (e.g. permit `approve(x, anyAmount)`)
#### Function: `calculateHash`
```solidity theme={null}
function calculateHash(
bytes calldata bitmask,
address who,
address where,
uint256 value,
bytes calldata data
) public pure returns (bytes32)
```
#### Logic
This function computes a `keccak256` hash over the masked versions of each input field:
1. `who`, masked by `bitmask[0:32]`
2. `where`, masked by `bitmask[32:64]`
3. `value`, masked by `bitmask[64:96]`
4. Each `data[i]` masked by `bitmask[96+i]`
#### Example Use
If a bitmask has `0xff` for a given byte, that byte is strictly matched. If `0x00`, the byte is ignored (wildcarded). Mixed values allow partial matching.
#### Function: `verifyCall`
```solidity theme={null}
function verifyCall(
address who,
address where,
uint256 value,
bytes calldata data,
bytes calldata verificationData
) public pure returns (bool)
```
#### Input: `verificationData`
This input must be ABI-encoded as:
```solidity theme={null}
abi.encode(bytes32 expectedHash, bytes bitmask)
```
#### Logic
1. Parses `expectedHash` and `bitmask` from the calldata
2. Verifies that the bitmask length matches `96 + data.length`
* 32 for `who`, 32 for `where`, 32 for `value`, and one byte per calldata byte
3. Calls `calculateHash()` and compares it to `expectedHash`
Returns:
* `true` if the masked call hash matches the expected hash
* `false` otherwise
#### Use Cases
This verifier enables granular control over contract interactions, for example:
* **Approvals to a specific contract**:
Allow `approve(farmContract, anyAmount)` but block other approvals.
* **Partial calldata authorization**:
Authorize only the first 4 bytes (function selector) of a call.
* **Curated access for specific addresses**:
Allow only specific curators to call `delegate(address)` with known targets.
* **Value-bound actions**:
Authorize only zero-ETH transactions or enforce a cap on `value`.
# Consensus
Source: https://metavaults.mellow.finance/architecture/permissions/consensus
#### Purpose
The `Consensus` contract manages a permissioned set of signers and enforces **multi-signature validation logic** using either EIP-712 or EIP-1271 signatures. It is a lightweight module designed for verifying **offchain consensus** before executing critical actions such as deposit and redemptions via SignatureQueues.
It supports:
* Threshold-based consensus
* Two signature modes: EIP712 (EOA) and EIP1271 (contract-based)
* Dynamic signer set management
* Stateless, reusable verification interface
#### Core Concepts
#### Threshold-Based Verification
To validate an action, a set of authorized signers must collectively submit signatures. The number of valid signatures must be **greater than or equal to** the configured `threshold`.
#### Signature Types
Each signer is associated with a `SignatureType`:
* `EIP712` – Used for externally owned accounts (standard `ECDSA.recover`)
* `EIP1271` – Used for contract accounts (via `isValidSignature()`)
#### Storage Layout
```solidity theme={null}
struct ConsensusStorage {
uint256 threshold;
EnumerableMap.AddressToUintMap signers;
}
```
* `threshold`: Minimum number of valid signatures required for verification to succeed.
* `signers`: Mapping of signer addresses → their configured signature type.
#### Initialization
```solidity theme={null}
function initialize(bytes calldata data)
```
* Expects `abi.encode(owner)` as input.
* Sets the initial owner using `OwnableUpgradeable`.
#### Signature Verification
#### checkSignatures
```solidity theme={null}
function checkSignatures(bytes32 orderHash, Signature[] calldata signatures) public view returns (bool)
```
* Returns `true` if:
* At least `threshold` signatures are present
* Each signature is:
* From an authorized signer
* Valid according to the signer’s configured signature type
* Returns `false` otherwise
Signature validation behavior:
* `EIP712`: Uses `ECDSA.recover(orderHash, sig)` and matches signer
* `EIP1271`: Calls `isValidSignature(orderHash, sig)` on the contract
#### requireValidSignatures
```solidity theme={null}
function requireValidSignatures(bytes32 orderHash, Signature[] calldata signatures) external view
```
* Same logic as `checkSignatures`, but reverts with `InvalidSignatures` error if validation fails
#### Signer Management (Owner-only)
#### setThreshold
```solidity theme={null}
function setThreshold(uint256 threshold_) external onlyOwner
```
* Sets a new threshold
* Must be `> 0` and `≤ signers.length()`
* Emits `ThresholdSet`
#### addSigner
```solidity theme={null}
function addSigner(address signer, uint256 threshold_, SignatureType sigType) external onlyOwner
```
* Adds a new signer with specified signature type
* Updates threshold (as part of signer addition)
* Reverts if:
* `signer == address(0)`
* Signer already exists
* Emits `SignerAdded` and `ThresholdSet`
#### removeSigner
```solidity theme={null}
function removeSigner(address signer, uint256 threshold_) external onlyOwner
```
* Removes signer from consensus set
* Updates threshold
* Reverts if signer not found
* Emits `SignerRemoved` and `ThresholdSet`
#### View Functions
| Function | Returns |
| ------------------- | ----------------------------------------- |
| `threshold()` | Current consensus threshold |
| `signers()` | Total number of signers |
| `signerAt(uint256)` | Signer address and type at index |
| `isSigner(address)` | Boolean indicating if address is a signer |
#### Events
* `Initialized(bytes)`
* `ThresholdSet(uint256)`
* `SignerAdded(address signer, SignatureType)`
* `SignerRemoved(address signer)`
* `InvalidSignatures(bytes32 hash, Signature[] signatures)` (used in revert)
#### Security Considerations
* Only the owner (via `OwnableUpgradeable`) may update signer set or threshold
* Signatures are stateless and externally verifiable
* Replay protection (e.g., nonce checks) must be handled by upstream systems (nonces)
* Signers using `EIP1271` are trusted for contract logic – contracts must not be mutable without governance
# Permissions
Source: https://metavaults.mellow.finance/architecture/permissions/index
In this directory, you will find a detailed per-contract overview of the "Permissions" contract category, including the following modules: \
\
[MellowACL](/architecture/permissions/mellowacl)
[Verifier](/architecture/permissions/verifier)
[BitmaskVerifier](/architecture/permissions/bitmaskverifier)
[Consensus](/architecture/permissions/consensus)
[protocols](/architecture/permissions/protocols)
# MellowACL
Source: https://metavaults.mellow.finance/architecture/permissions/mellowacl
### Purpose
`MellowACL` is a lightweight but extendable access control layer that wraps OpenZeppelin’s `AccessControlEnumerableUpgradeable`. It introduces automatic tracking and enumeration of *active roles* to improve governance transparency.
This contract is intended to be inherited by modules that require dynamic role management and storage-isolated initialization.
### Responsibilities
* Grant and revoke access control roles to addresses
* Keep track of all active (i.e., assigned) roles in a dedicated set
* Expose enumerable functions for external auditing of granted roles
* Emit structured events when roles are added or fully revoked
### Storage Layout
```solidity theme={null}
struct MellowACLStorage {
EnumerableSet.Bytes32Set supportedRoles;
}
```
* `supportedRoles`: A unique set of role identifiers (`bytes32`) currently assigned to any address
* Uses a dedicated storage slot derived from:
```solidity theme={null}
SlotLibrary.getSlot("MellowACL", name_, version_)
```
### View Functions
### `supportedRoles() → uint256`
Returns the number of currently active roles (i.e., roles with at least one member).
### `supportedRoleAt(index: uint256) → bytes32`
Returns the role identifier at the specified index from the active role set.
### `hasSupportedRole(role: bytes32) → bool`
Returns `true` if the role is currently active (i.e., assigned to at least one account).
### Internal Logic
### `_grantRole(role: bytes32, account: address) → bool`
Grants the specified role to an account. If the role was not previously active, it is added to `supportedRoles`, and `RoleAdded` is emitted.
* Inherits from `AccessControlUpgradeable._grantRole`
* Emits:
```solidity theme={null}
event RoleAdded(bytes32 indexed role)
```
### `_revokeRole(role: bytes32, account: address) → bool`
Revokes the specified role from an account. If the role has no remaining members afterward, it is removed from `supportedRoles`, and `RoleRemoved` is emitted.
* Inherits from `AccessControlUpgradeable._revokeRole`
* Emits:
```solidity theme={null}
event RoleRemoved(bytes32 indexed role)
```
### Constructor
```solidity theme={null}
constructor(string memory name_, uint256 version_)
```
* Computes a deterministic storage slot using `SlotLibrary`
* Disables initializer to prevent accidental direct deployment
* Should be initialized later via proxy-aware module constructor
### Events
* `event RoleAdded(bytes32 indexed role)`
* Emitted when a new role is introduced into the system
* `event RoleRemoved(bytes32 indexed role)`
* Emitted when the last holder of a role is revoked and the role becomes inactive
# EigenLayerVerifier
Source: https://metavaults.mellow.finance/architecture/permissions/protocols/eigenlayerverifier
### Overview
`EigenLayerVerifier` is a custom `ICustomVerifier` implementation tailored to securely authorize calls to **EigenLayer** contracts like `DelegationManager`, `StrategyManager`, and `RewardsCoordinator`. It uses strict role-based gating, exact calldata matching, and entity-specific validation to ensure that only authorized vaults and bots can interact with EigenLayer staking, delegation, withdrawal, and rewards workflows.
### Purpose
This verifier protects EigenLayer operations by:
* Ensuring only whitelisted entities (vaults, strategies, operators) can execute actions
* Verifying target contracts and function selectors precisely
* Enforcing exact calldata encoding to eliminate any ambiguity or abuse
### Role Definitions
| Role Constant | Description |
| ------------------- | --------------------------------------------------------------------- |
| `CALLER_ROLE` | Address allowed to initiate EigenLayer calls (typically curators) |
| `ASSET_ROLE` | Whitelisted ERC20 token allowed in strategy deposits or withdrawals |
| `STRATEGY_ROLE` | Whitelisted EigenLayer strategy contracts |
| `OPERATOR_ROLE` | Approved EigenLayer operator address for delegation |
| `MELLOW_VAULT_ROLE` | Whitelisted vaults acting as stakers or earners (usually `Subvault` ) |
| `RECEIVER_ROLE` | Authorized receivers for claimed rewards |
### Constructor
```solidity theme={null}
constructor(address delegationManager_, address strategyManager_, address rewardsCoordinator_, string memory name_, uint256 version_)
```
Initializes the verifier by:
* Setting immutable references to EigenLayer’s:
* `DelegationManager`
* `StrategyManager`
* `RewardsCoordinator`
* Inheriting access control via `OwnedCustomVerifier`
### `verifyCall`
```solidity theme={null}
function verifyCall(
address who,
address where,
uint256 value,
bytes calldata callData,
bytes calldata /* verificationData */
) external view override returns (bool)
```
### General Preconditions
* `who` must have `CALLER_ROLE`
* `value` must be 0 (no ETH allowed)
* `callData.length >= 4` (valid selector)
### Validated Targets & Selectors
### 1. **StrategyManager** – `depositIntoStrategy`
* `depositIntoStrategy(IStrategy, address asset, uint256 shares)`
* Strategy must have `STRATEGY_ROLE`
* Asset must have `ASSET_ROLE`
* Shares must be non-zero
* Calldata must match
### 2. **DelegationManager**
* **`delegateTo(address operator, SignatureWithExpiry signature, bytes32 salt)`**
* Operator must have `OPERATOR_ROLE`
* Calldata must match
* **`queueWithdrawals(QueuedWithdrawalParams[] params)`**
* Only **one** **`params.length == 1`** allowed
* Param must include:
* One strategy with `STRATEGY_ROLE`
* One deposit share > 0
* Calldata must match
* **`completeQueuedWithdrawal(Withdrawal, address[] tokens, bool receiveAsTokens)`**
* `receiveAsTokens` must be `true`
* Withdrawal must:
* Have only one strategy with `STRATEGY_ROLE`
* Have `staker` with `MELLOW_VAULT_ROLE`
* `tokens.length == 1` and token must have `ASSET_ROLE`
* Calldata must match
### 3. **RewardsCoordinator** – `processClaim`
* **Selector:** `processClaim(RewardsMerkleClaim claimData, address receiver)`
* **Checks:**
* `claimData.earnerLeaf.earner` must have `MELLOW_VAULT_ROLE`
* `receiver` must have `RECEIVER_ROLE`
* Calldata must match
### Security Properties
* **Role enforcement:** Prevents unauthorized usage of EigenLayer functions
* **Exact calldata match:** Avoids incorrect encoding or maliciously crafted data
* **Zero ETH transfers:** Disallows unexpected native token usage
* **Single param enforcement (withdrawals):** Minimizes complexity and risk surface
# ERC20Verifier
Source: https://metavaults.mellow.finance/architecture/permissions/protocols/erc20verifier
### Overview
`ERC20Verifier` is a role-driven `ICustomVerifier` implementation that enforces strict, granular permissioning over ERC20 `approve` and `transfer` function calls. It builds upon `OwnedCustomVerifier`, using `MellowACL`-style roles to validate the **caller**, **target asset**, and **recipient** of each operation.
This verifier is designed for use in **modular vaults** such as `SubVault` where only specific ERC20 operations should be allowed through a customizable permission matrix.
### Purpose
To allow or deny ERC20 `approve` and `transfer` calls based on:
* The **caller** (must have `CALLER_ROLE`)
* The **asset address** (must have `ASSET_ROLE`)
* The **recipient** (must have `RECIPIENT_ROLE`)
* Additionally:
* `transfer` must not be for zero amount
* `approve` allows any amount
* `value` sent with the call must be `0`
* Only exact calldata is accepted (no encoding variation or garbage data)
### Roles
Each permission check is mapped to a distinct `bytes32` role:
| Role Constant | Purpose |
| ---------------- | --------------------------------------------------------------------------------- |
| `ASSET_ROLE` | Marks which ERC20 tokens are allowed to be interacted with |
| `CALLER_ROLE` | Who is allowed to perform `approve` or `transfer` |
| `RECIPIENT_ROLE` | Who is allowed to receive tokens (for `transfer`) or get approval (for `approve`) |
These roles are expected to be configured via the `initialize()` function inherited from `OwnedCustomVerifier`.
### Contract Behavior
### Constructor
```solidity theme={null}
constructor(string memory name_, uint256 version_)
```
* Passes initialization parameters to `OwnedCustomVerifier` and disables further initializers
### `verifyCall` Function
```solidity theme={null}
function verifyCall(
address who,
address where,
uint256 value,
bytes calldata callData,
bytes calldata /* verificationData */
) external view override returns (bool)
```
### Summary:
Checks if a specific ERC20 call is authorized.
### Logic Steps:
1. **Pre-checks**:
* Must be a zero-ETH call: `value == 0`
* Calldata must be exactly 68 bytes: 4-byte selector + 32 bytes address + 32 bytes uint
* `where` (the token address) must have `ASSET_ROLE`
* `who` (the caller, usually curator) must have `CALLER_ROLE`
2. **Selector Validation**:
* Accepts only two ERC20 functions:
* `approve(address,uint256)`
* `transfer(address,uint256)`
3. **Recipient & Amount Validation**:
* `to` address must have `RECIPIENT_ROLE`
* For `transfer`:
* `amount` must not be zero
* `to` must not be zero address in any case
4. **Exact Calldata Matching**:
* Ensures call is not forged via alternate encodings:
```solidity theme={null}
keccak256(abi.encodeWithSelector(selector, to, amount)) == keccak256(callData)
```
### Returns:
* `true` if all checks pass
* `false` otherwise
### Security Considerations
* Prevents misuse of `approve` and `transfer` by enforcing:
* Strict role-based gating
* Zero ETH payload enforcement
* Calldata normalization to eliminate encoding ambiguity
* Ensures no contract or address receives funds or allowances without being explicitly whitelisted
# Protocols
Source: https://metavaults.mellow.finance/architecture/permissions/protocols/index
In this directory, you will find a detailed overview of the specific verifiers, including the following: \
\
[OwnedCustomVerifier](/architecture/permissions/protocols/ownedcustomverifier)
[ERC20Verifier](/architecture/permissions/protocols/erc20verifier)
[SymbioticVerifier](/architecture/permissions/protocols/symbioticverifier)
[EigenLayerVerifier](/architecture/permissions/protocols/eigenlayerverifier)
# OwnedCustomVerifier
Source: https://metavaults.mellow.finance/architecture/permissions/protocols/ownedcustomverifier
### Overview
`OwnedCustomVerifier` is an **abstract base contract** for implementing `ICustomVerifier`-compatible verifiers with configurable role-based access control. It integrates with `MellowACL` and provides a flexible initialization mechanism for dynamic permission setup.
This verifier is designed to be used in **`Verifier.sol`** as a custom verifier, \*\*\*\*where specific calls must pass access control checks based on predefined roles.
### Key Components
### Inherits:
* `ICustomVerifier`: Interface used by the `Verifier` contract for permission checks
* `MellowACL`: Upgradeable, role-based access control module compatible with OpenZeppelin’s `AccessControl`
### Constructor
```solidity theme={null}
constructor(string memory name_, uint256 version_) MellowACL(name_, version_)
```
* Initializes the underlying `MellowACL` module with `name_` and `version_`
* Disables further initialization to prevent misuse in logic contracts (`_disableInitializers()`)
### Initialization
```solidity theme={null}
function initialize(bytes calldata data) external initializer
```
* Initializes access control roles
* Decodes input as:
```solidity theme={null}
(address admin, address[] memory holders, bytes32[] memory roles)
```
* Logic:
* Sets `admin` as the contract’s `DEFAULT_ADMIN_ROLE`
* Grants each `roles[i]` to `holders[i]`
* Reverts with `ZeroValue` if:
* `admin == address(0)`
* Any holder is zero address
* Any role is `DEFAULT_ADMIN_ROLE`
### Usage Pattern
This base contract does **not** implement the `verifyCall()` method itself. Instead, it is expected to be **inherited and extended** by a concrete verifier contract that implements the permission logic based on role membership (e.g., checking `hasRole(role, who)`).
This allows teams to quickly implement custom verifiers that enforce arbitrary permissions (e.g., allow certain addresses to `approve`, `transfer`, or `delegate`) based on **assigned roles** instead of hardcoded logic.
# SymbioticVerifier
Source: https://metavaults.mellow.finance/architecture/permissions/protocols/symbioticverifier
### Overview
`SymbioticVerifier` is a custom `ICustomVerifier` implementation used to authorize interactions with the Symbiotic protocol. It restricts access to `deposit`, `withdraw`, `claim`, and `claimRewards` calls across Symbiotic vaults and farm contracts. All permissions are tightly scoped using role-based access control via `MellowACL`.
This verifier ensures that only allowed addresses (typically curators) can perform specific actions within the Symbiotic ecosystem.
### Purpose
The verifier ensures that:
* Only whitelisted vaults can act on behalf of themselves in Symbiotic vaults and farms
* All interactions are strictly validated against exact calldata to prevent misuse or encoding variation
* Only allowed selectors and targets can be used
### Role Definitions
| Role Constant | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------ |
| `CALLER_ROLE` | Who is allowed to initiate Symbiotic operations (typically curators) |
| `MELLOW_VAULT_ROLE` | Addresses that are allowed to be the recipient of deposits, withdrawals, or claims (usually Subvaults) |
| `SYMBIOTIC_VAULT_ROLE` | Contracts that are approved as Symbiotic vault |
| `SYMBIOTIC_FARM_ROLE` | Contracts that are approved as Symbiotic farm |
### Constructor
```solidity theme={null}
constructor(address vaultFactory_, address farmFactory_, string memory name_, uint256 version_)
```
### `verifyCall`
```solidity theme={null}
function verifyCall(
address who,
address where,
uint256 value,
bytes calldata callData,
bytes calldata /* verificationData */
) public view returns (bool)
```
### High-Level Behavior
* Verifies caller (`who`) has `CALLER_ROLE`
* Matches target contract (`where`) with either a Symbiotic vault or farm
* Validates exact function selector and arguments using full `keccak256(callData)` hash
* Rejects any calls with non-zero ETH value
### Supported Calls
| Target Type | Function | Signature | Additional Checks |
| --------------- | -------------------------------------- | ----------------------------------------------- | --------------------------------------------------------- |
| Symbiotic Vault | `deposit(onBehalfOf, amount)` | `ISymbioticVault.deposit.selector` | `onBehalfOf` must have `MELLOW_VAULT_ROLE`, `amount > 0` |
| Symbiotic Vault | `withdraw(claimer, amount)` | `ISymbioticVault.withdraw.selector` | `claimer` must have `MELLOW_VAULT_ROLE`, `amount > 0` |
| Symbiotic Vault | `claim(recipient, epoch)` | `ISymbioticVault.claim.selector` | `recipient` must have `MELLOW_VAULT_ROLE` |
| Symbiotic Farm | `claimRewards(recipient, token, data)` | `ISymbioticStakerRewards.claimRewards.selector` | `recipient` must have `MELLOW_VAULT_ROLE`, `token != 0x0` |
* For all calls, the calldata must exactly match the selector and parameters
* All other selectors or targets are denied
### Security Properties
* **Strict call gating**: Only explicitly allowed selectors, targets, and roles pass
* **Calldata hash check**: Enforces strict encoding to avoid alternate ABI variants or garbage data
* **Zero-value enforcement**: Prevents accidental ETH transfers
* **Factory pattern compatibility**: Target contracts can be validated indirectly via registries
# Verifier
Source: https://metavaults.mellow.finance/architecture/permissions/verifier
### Purpose
The `Verifier` contract is a multi-mode permissioning module for verifying and enforcing call-level access control across vault-connected modules. It supports:
* On-chain allowlists using hashed shortened calls (`CompactCall`)
* Merkle tree-based validation for compact merkle, extended merkle and custom verifier verification types
* Delegated verification logic through external custom verifiers (`ICustomVerifier`)
This contract enables secure and modular delegation of operational permissions
### Core Responsibilities
* Validates function calls from external actors (e.g., operators, curators) or strategy contracts (strategies)
* Grants or revokes execution rights using on-chain and off-chain mechanisms
* Ensures that only whitelisted or merkle-authenticated calls are allowed
* Integrates with vault-based role system via `IAccessControl`
### Roles and Access
* `SET_MERKLE_ROOT_ROLE`: Role allowed to update the active Merkle root
* `CALLER_ROLE`: Role required by initiators of authorized calls
* `ALLOW_CALL_ROLE`: Grants ability to add compact calls to allowlist
* `DISALLOW_CALL_ROLE`: Grants ability to remove compact calls from allowlist
### Storage Layout
```solidity theme={null}
struct VerifierStorage {
address vault;
bytes32 merkleRoot;
EnumerableSet.Bytes32Set compactCallHashes;
mapping(bytes32 => CompactCall) compactCalls;
}
```
* `vault`: Vault contract that owns the verifier (must support `IAccessControl`)
* `merkleRoot`: Merkle root for off-chain verified call proofs
* `compactCallHashes`: Set of hashes representing allowed compact calls
* `compactCalls`: Optional mapping for reverse lookup of call metadata by hash
### Verification Types
```solidity theme={null}
enum VerificationType {
ONCHAIN_COMPACT,
MERKLE_COMPACT,
MERKLE_EXTENDED,
CUSTOM_VERIFIER
}
```
* **ONCHAIN\_COMPACT**: Checks `CompactCall` (who | where | selector) hash against internal set
* **MERKLE\_COMPACT**: Verifies Merkle proof of `CompactCall` (who | where | selector) hash
* **MERKLE\_EXTENDED**: Verifies Merkle proof of `ExtendedCall` (who | where | value | callData) hash
* **CUSTOM\_VERIFIER:** Delegates full verification to an external verifier
### Call Structures
```solidity theme={null}
struct CompactCall {
address who;
address where;
bytes4 selector;
}
struct ExtendedCall {
address who;
address where;
uint256 value;
bytes data;
}
struct VerificationPayload {
VerificationType verificationType;
bytes verificationData;
bytes32[] proof;
}
```
* `CompactCall`: Encodes permissioned call using address and selector
* `ExtendedCall`: Encodes full call (selector + calldata + ETH value)
* `VerificationPayload`: Contains verification metadata and proof
### View Functions
* `vault()`: Returns the associated vault contract
* `merkleRoot()`: Returns current Merkle root
* `allowedCalls()`: Returns number of compact calls in allowlist
* `allowedCallAt(index)`: Returns `CompactCall` at index from internal set
* `isAllowedCall(who, where, callData)`: Checks if `CompactCall` is explicitly allowed
* `hashCall(CompactCall)`: Returns keccak256 hash of compact call
* `hashCall(ExtendedCall)`: Returns keccak256 hash of extended call
### Verification Functions
### `verifyCall(...)`
```solidity theme={null}
function verifyCall(
address who,
address where,
uint256 value,
bytes calldata data,
VerificationPayload calldata payload
) external view;
```
* Validates call permissions using the chosen `VerificationType`
* Reverts with `VerificationFailed` on failure
### `getVerificationResult(...) → bool`
```solidity theme={null}
function getVerificationResult(
address who,
address where,
uint256 value,
bytes calldata data,
VerificationPayload calldata payload
) external view returns (bool);
```
* Returns `true` if the verification succeeds, `false` otherwise
Verification decision logic:
* `ONCHAIN_COMPACT`: Validate hash against stored allowlist
* `MERKLE_COMPACT`: Validate Merkle proof of compact hash
* `MERKLE_EXTENDED`: Validate Merkle proof of full hash
* `CUSTOM_VERIFIER`: Validate Merkle proot of the verification payload and delegate validation to external contract
### Mutable Functions
* `initialize(bytes calldata initParams)`:
* Accepts `abi.encode(address vault_, bytes32 merkleRoot_)`
* Sets the vault address and initial Merkle root
* `setMerkleRoot(bytes32 root)`:
* Updates Merkle root (requires `SET_MERKLE_ROOT_ROLE`)
* `allowCalls(CompactCall[] calldata calls)`:
* Adds compact calls to allowlist
* Reverts on duplicates (calls already allowed)
* `disallowCalls(CompactCall[] calldata calls)`:
* Removes calls from allowlist
* Reverts if call is not found in allowlist
### Initialization
```solidity theme={null}
function initialize(bytes calldata initParams) external initializer;
```
* `initParams` format: `abi.encode(address vault_, bytes32 merkleRoot_)`
# DepositQueue
Source: https://metavaults.mellow.finance/architecture/queues/depositqueue
### Overview
The `DepositQueue` contract enables asynchronous asset deposits into vaults using a time-delayed, oracle-priced queuing mechanism. Deposits are not processed immediately. Instead, users submit requests that are later fulfilled when an external price oracle submits a valid report. This enables batching (based on Fenwick Tree data structure), mitigates front-running, and facilitates accurate share pricing.
### Deposit Lifecycle
### Step 1: **User Deposits**
* A user submits a deposit request via `deposit(assets, referral, merkleProof)`.
* The deposited amount is stored as a `(timestamp, value)` checkpoint under `requestOf[msg.sender]`.
* Each account can have **only one pending request** at a time.
* Deposits are validated via optional Merkle whitelist logic (using `merkleProof`) or onchain mapping (if `flags.hasWhitelist()` returns true).
* If a previous request exists, it must be claimed or canceled before creating a new one.
### Step 2: **Oracle Report**
* Oracle submits a report via the `handleReport(priceD18, timestamp)` method.
* The queue validates the report:
* It must be called by the vault.
* The timestamp must be in the past.
* Price must be non-zero.
* The queue handles deposit requests that are pending for longer than `depositInterval` seconds (the interval is specified in the oracle’s security params).
* The contract stores the price in `prices` and uses it to convert accumulated assets to shares.
* Converted shares are allocated but not minted yet.
* Events: `ReportHandled` is emitted.
### Step 3: **User Claims**
* A user calls `claim(account)` to mint and receive previously allocated shares.
* The number of shares is computed as:
```solidity theme={null}
uint256 shares = (request.assets * reducedByDepositFeePriceD18) / 1e18;
```
* Shares are minted to the user via `mintAllocatedShares`.
### Cancellation
* A user may cancel a pending request using `cancelDepositRequest()`.
* Cancellation reverts if the request has already become claimable (i.e., processed by an oracle report).
* Refund is issued in the original asset amount.
* Event: `DepositRequestCanceled` is emitted.
### Query Methods
* `claimableOf(address account)`: Returns how many shares are currently claimable for a given user.
* `requestOf(address account)`: Returns the `(timestamp, amount)` tuple of a user’s current pending request.
### Internal Mechanics
The system tracks all deposit requests and prices using the following structures:
* `Checkpoints.Trace224 prices`: Stores historical oracle-reported prices keyed by timestamp.
* `mapping(address => Checkpoints.Checkpoint224) requestOf`: Maps users to their active deposit requests.
* `FenwickTreeLibrary.Tree requests`: A prefix-sum data structure tracking asset totals across compressed timestamps.
* `uint256 handledIndices`: Tracks the last fully processed request index, ensuring each oracle report progresses the queue.
## Scalability Challenge
Vaults may face **thousands of deposit requests daily**. Processing each one individually leads to significant gas costs or even OOG. To optimize:
> A Fenwick Tree (Binary Indexed Tree) is used to efficiently manage aggregate deposit data by timestamp.
### Fenwick Tree Mechanics
* **On Deposit**:
When a user deposits an amount `A` at time `T`, the system performs:
`fenwickTree[T] += A`
* **On Cancellation**:
If the user cancels the request:
`fenwickTree[T] -= A`
* **On Oracle Report**:
During report at `reportTimestamp`, the system calculates:
```solidity theme={null}
fenwickTree.getSum(latestHandledTimestamp + 1, reportTimestamp - depositInterval)
```
This determines the **total amount** eligible for conversion into vault shares at the reported price.
### Lazy Propagation of Shares
Rather than eagerly updating each user balance during `handleReport`, the vault employs **lazy propagation**:
* Each user’s **claimable shares** are finalized only during subsequent calling `claim()`.
* This significantly reduces processing cost during batch report execution.
### Timestamp Compression
To minimize storage writes and reads used by `FenwickTree.sol` the system uses coordinate compression, storing only timestamps where actual deposit requests occurred.
This compression strategy ensures that the Fenwick Tree remains compact, even with high-frequency usage.
### Key **Invariants**
1. **Single Active Request**
Each user may have at most one unprocessed deposit request, user can not do new deposit till previous one is not claimed.
2. **Delayed Execution**
Deposit processing requires an oracle report submitted after a configured `depositInterval`.
3. **Lazy Claiming**
Deposits are converted to shares during oracle processing, but users must call `claim()` to receive them.
4. **Whitelist Enforcement**
Deposits may require Merkle proof for depositor whitelisting.
### Events
* `DepositRequested(address account, address referral, uint224 assets, uint32 timestamp)`: Emitted on new deposit submission.
* `DepositRequestCanceled(address account, uint256 assets, uint32 timestamp)`: Emitted when a request is canceled and assets refunded.
* `DepositRequestClaimed(address account, uint256 shares, uint32 timestamp)`: Emitted when deposit shares are successfully claimed.
* `ReportHandled(uint224 priceD18, uint32 timestamp)`: Emitted when an oracle report is processed.
### Errors
* `DepositNotAllowed()`: Depositor not whitelisted.
* `PendingRequestExists()`: Existing request not yet processed or claimed.
* `ClaimableRequestExists()`: Attempting to cancel after request has become claimable.
* `NoPendingRequest()`: No existing request to cancel.
* `ZeroValue()`: Input value is zero.
* `InvalidReport()`: Oracle report failed validation.
* `Forbidden()`: Unauthorized caller.
* `QueuePaused()`: Deposits disabled via vault pause mechanism.
# Queues
Source: https://metavaults.mellow.finance/architecture/queues/index
In this directory, you will find a detailed per-contract overview of the "Queues" contract category, including the following queues:
[Queue](/architecture/queues/queue)
[DepositQueue](/architecture/queues/depositqueue)
[RedeemQueue](/architecture/queues/redeemqueue)
[SignatureQueue](/architecture/queues/signaturequeue)
[SignatureDepositQueue](/architecture/queues/signaturedepositqueue)
[SignatureRedeemQueue](/architecture/queues/signatureredeemqueue)
# Queue
Source: https://metavaults.mellow.finance/architecture/queues/queue
### Overview
The `Queue` contract provides a shared foundation for **time-gated asset processing** in systems like `DepositQueue` and `RedeemQueue`. It tracks user requests via timestamped checkpoints and processes them using oracle-based pricing.
This abstract module is **not directly deployable** but is designed to be extended by concrete implementations, which define the behavior of `_handleReport` and allowed user actions (deposit / redeem functions).
### Purpose
* Serves as a **modular base** for deposit/redeem queues
* Enforces **Vault request processing initially triggered by Oracle**
* Stores **timestamped user action traces** via `Checkpoints.Trace224`
* Ensures **vault-controlled access** and proper **report validation**
### Derived contracts
* `DepositQueue`: handles queued deposits after a delay
* `RedeemQueue`: handles redemption requests similarly
### Use Cases
* Prevents manipulation by requiring **delayed processing** relative to price updates
### Storage Structure
```solidity theme={null}
struct QueueStorage {
address asset; // Token/ETH managed by this queue
address vault; // Vault that owns this queue
Checkpoints.Trace224 timestamps; // Timeline of requests
}
```
* **asset**: token used for this queue (ERC20 or native ETH)
* **vault**: only this address can call `handleReport(...)`
* **timestamps**: request history used in the implementations
### Initialization
```solidity theme={null}
function __Queue_init(address asset_, address vault_) internal
```
* Must be called by child contracts
* Initializes asset, vault, and creates a starting checkpoint
### Oracle Integration
```solidity theme={null}
function handleReport(uint224 priceD18, uint32 timestamp) external
```
* Called by the vault when an oracle report is available
* Verifies:
* Caller is the `vault`
* Price is non-zero
* Timestamp is in the past (timestamp \< block.timestamp)
* Internally delegates to `_handleReport(...)` (must be implemented by child)
### Abstract Hook
```solidity theme={null}
function _handleReport(uint224 priceD18, uint32 timestamp) internal virtual
```
Must be implemented by child classes to:
* Read and process requests from `_timestamps()`
* Apply pricing logic to convert shares↔assets
* Mint/burn shares, transfer tokens, etc.
### View Functions
| Function | Description |
| ---------------- | -------------------------------------------------- |
| `vault()` | Returns the controlling vault address |
| `asset()` | Returns the ERC20/native token used by the queue |
| `canBeRemoved()` | Not implemented in `Queue` (optional for children) |
### Internal Helpers
| Function | Description |
| ------------------- | ----------------------------------------------------- |
| `_timestamps()` | Returns the internal `Checkpoints.Trace224` structure |
| `_queueStorage()` | Loads queue storage using custom storage slot |
| `_queueStorageSlot` | Computed using SlotLibrary to prevent conflicts |
### Events
```solidity theme={null}
event ReportHandled(uint224 priceD18, uint32 timestamp)
```
* Emitted when `handleReport()` completes
* Signals that all eligible requests up to `timestamp` were processed
### Errors
| Error | Reason |
| ----------------- | ---------------------------------------------------- |
| `ZeroValue()` | Called with `0` address or value |
| `Forbidden()` | Caller not authorized (e.g., not the vault) |
| `InvalidReport()` | Oracle report is zero-priced or timestamp is invalid |
| `QueuePaused()` | Reserved for future ACL/pause integration |
# RedeemQueue
Source: https://metavaults.mellow.finance/architecture/queues/redeemqueue
### Purpose
The `RedeemQueue` contract enables delayed, batched redemptions of vault shares into underlying assets. Redemptions are processed in two phases:
1. **Oracle pricing** – Shares are priced via a trusted price report.
2. **Liquidity settlement** – Vault liquidity is allocated to fulfill priced requests.
This separation supports asynchronous liquidity management, gas efficiency, and protection against griefing.
### Overview
The `RedeemQueue` enables users to convert their vault shares into underlying assets, introducing a **time delay** enforced by an oracle-defined `redeemInterval`. It maintains the following core invariants:
1. **Request Format**: Each request is structured as a `(shares, timestamp)` pair.
2. **Non-Cancellable**: Redemption requests **cannot** be cancelled to prevent griefing (e.g., submitting and canceling after unstaking starts).
3. **Multiple Requests Allowed**: Users may submit multiple independent redemption requests.
Upon receiving an oracle report at `reportTimestamp`, the system processes all requests with:
```solidity theme={null}
timestamp <= reportTimestamp - redeemInterval
```
On the next step these vault shares are converted to assets at the price specified in the oracle report in this step.
### Liquidity Processing (Two-Stage)
To enable flexible and asynchronous liquidity management, redemption is handled in **two distinct phases**:
1. **Post-Request**:
Vault curators monitor and, if needed, pull liquidity from external protocols.
2. **Post-Oracle Report**:
* Once a valid report is submitted and sufficient liquidity is available,
* The vault curator (or any other trusted actor) invokes `handleBatches(n)` on the `RedeemQueue`,
* This action triggers the movement of required assets from the vault (and associated subvaults) to process redemption requests.
### Scalability Approach
Unlike deposits, **redemption requests are never cancelled**, which allows for a simplified and gas-efficient tracking model: A prefix sum array is used to efficiently manage cumulative share redemptions over time.
### Redemption Processing Logic
* **On Redemption**:
When a user redeems `amount` of shares at time `T`, the system logs:
`prefixSum[T] += amount`
* **On Oracle Report**:
At `reportTimestamp`, all requests with:
```solidity theme={null}
timestamp <= reportTimestamp - redeemInterval
```
are marked as processed.
* **Post-Processing**:
* The curator ensures the necessary asset liquidity is available,
* Then calls `handleBatches()` to finalize processing.
* **User Claim**:
After requests are processed, users can call:
```solidity theme={null}
claim(receiver, timestamps[])
```
to claim assets for their requested vault shares corresponding to each processed timestamp.
### Storage Layout
All internal state is maintained in `RedeemQueueStorage`, including:
| Field | Description |
| -------------------- | ----------------------------------------------------------------------- |
| `handledIndices` | Tracks number of oracle checkpoints that have been priced |
| `batchIterator` | Index of the next unfulfilled batch |
| `totalDemandAssets` | Total asset amount needed to fulfill pending batches |
| `totalPendingShares` | Total shares in requests that are not yet claimable |
| `requestsOf` | Maps `address → (timestamp → shares)` for pending user requests |
| `prefixSum` | Maps `timestamp index → shares` for batch creation and summation |
| `batches` | Array of `Batch` structs; each batch tracks fulfilled assets and shares |
| `prices` | Oracle-reported price checkpoints, indexed by timestamp |
### Structs
### `Request`
Represents a single redemption request from a user:
* `timestamp`: When the request was submitted.
* `shares`: Amount of vault shares being redeemed.
* `isClaimable`: Set to true after batch is fulfilled.
* `assets`: Amount of assets claimable for this request (set after pricing).
### `Batch`
Represents a priced redemption batch:
* `assets`: Total value fulfilled for the batch (via oracle `shares / report.price`).
* `shares`: Total shares matched in this batch.
### View Functions
### `requestsOf(account, offset, limit)`
Returns paginated redemption request data for the specified account. Each request includes:
* Timestamp
* Shares
* Claimable status
* Asset amount
### `batchAt(index)`
Returns the `(assets, shares)` for a given redemption batch.
### `getState()`
Returns core system state:
* Current `batchIterator` (next unfulfilled batch index)
* Total `batches`
* Total `demandedAssets` still awaiting liquidity
* Total `pendingShares` that are not yet claimable
### State Transition Guarantees
1. **Non-Cancellable Requests**
* Prevents griefing where a user requests redemption, causing curator to pull liquidity, then cancels.
2. **Price Separation**
* Oracle reports must be delayed by at least`redeemInterval` seconds from the original request.
3. **Asynchronous Fulfillment**
* Liquidity can be managed independently of oracle report submission.
### Events
* `RedeemRequested(account, shares, timestamp)`
* `RedeemRequestClaimed(account, receiver, assets, timestamp)`
* `RedeemRequestsHandled(counter, demand)`
# SignatureDepositQueue
Source: https://metavaults.mellow.finance/architecture/queues/signaturedepositqueue
### Purpose
`SignatureDepositQueue` extends `SignatureQueue` to enable **instant deposit** of assets into a vault, bypassing the standard on-chain `DepositQueue` mechanism. It leverages **off-chain approvals** signed by a trusted consensus group, using **EIP-712** or **EIP-1271**-compliant signatures, to authorize asset inflows and minting of vault shares.
This contract is optimized for high-trust environments requiring immediate asset onboarding while maintaining on-chain price safety guarantees.
### Key Features
* **Instant deposit execution** with no queuing delay
* **EIP-712 signed orders** with nonce-based replay protection
* **Vault share minting** at off-chain pre-agreed price
* **Fully integrated with vault accounting and share manager**
* **No deposit fee** applied (unlike possible fees in `DepositQueue`)
### Workflow
1. A consensus group signs an `Order` authorizing a user deposit:
* Includes asset amount (`ordered`) and shares to mint (`requested`)
* Binds the request to a specific queue and vault
* Includes a nonce and expiration timestamp
2. User submits the order on-chain by calling `deposit` function:
* The contract validates the order using signatures and price logic
* Receives tokens from the user
* Transfers these tokens to the vault
* Mints shares to the specified recipient
* Updates vault internal balance and executes post-deposit hook
### Function: `deposit`
```solidity theme={null}
function deposit(Order calldata order, IConsensus.Signature[] calldata signatures) external payable nonReentrant
```
### Parameters:
* `order`: A signed `Order` struct including deposit parameters
* `signatures`: Signatures from the off-chain consensus validating the order
### Steps:
1. `validateOrder(...)`:
* Confirms order is not expired
* Confirms order is intended for this queue
* Confirms correct asset, caller, and nonce
* Validates off-chain signatures
* Computes and validates asset/share price using vault oracle
2. Increments the caller’s nonce to prevent replay
3. Transfers `order.ordered` assets from the caller to this contract
4. Transfers these assets into the vault
5. Calls `vault.callHook(...)` for any optional strategy logic
6. Notifies the vault's `RiskManager` of the new deposit
7. Mints `order.requested` shares to `order.recipient`
8. Emits `OrderExecuted` event
### Security Considerations
* **Consensus signatures** are required to prevent unauthorized deposits
* **Oracle validation** ensures price sanity even in trusted setups
* **Replay protection** enforced using per-user nonces
* No deposit can proceed if:
* Asset or queue mismatch
* Caller mismatch
* Nonce is reused
* Off-chain price is out of oracle bounds
# SignatureQueue
Source: https://metavaults.mellow.finance/architecture/queues/signaturequeue
### Purpose
The `SignatureQueue` enables **instant** user deposits or redemptions using **off-chain signed approvals** from a trusted consensus group. This queue type bypasses the normal time-delayed queuing mechanism (e.g., `DepositQueue`, `RedeemQueue`) by verifying orders via **EIP-712** or **EIP-1271** signatures, offering fast-lane access for users while preserving oracle-bound price safety.
### Key Features
* **Instant execution** without waiting for oracle price reports
* **Nonce-based signature protection** to prevent replay attacks
* **EIP-712/EIP-1271 compatible** signed orders
* **Oracle price validation** enforced on-chain
* **Stateless and removable** (i.e., does not accumulate shares or process claims)
* **Fee-bypassed**: No `depositFee` or `redeemFee` is charged for actions via this queue
### High-Level Workflow
1. Off-chain consensus actors (e.g., operators, curators, admins) generate signed `Order` messages.
2. A user submits this order to the `SignatureQueue` contract for execution.
3. The queue:
* Verifies the order signature
* Validates nonce, queue address, asset match, deadline, and caller
* Computes the implied asset/share price and checks it against the vault's oracle
4. If all checks pass, the order is executed atomically.
### Order Structure
Each order encapsulates all the necessary metadata for verification:
```solidity theme={null}
struct Order {
uint256 orderId; // Off-chain tracking ID
address queue; // Must match queue address
address asset; // Token involved in deposit/redeem
address caller; // Must match msg.sender
address recipient; // Recipient of assets or shares
uint256 ordered; // Assets in (deposit) or shares out (redeem)
uint256 requested; // Shares out (deposit) or assets in (redeem)
uint256 deadline; // Expiration timestamp
uint256 nonce; // Unique per caller
}
```
### Signature Validation
* Orders are signed by a quorum of validators registered in the `Consensus` contract.
* Signatures can conform to:
* **EIP-712** (structured message hashing)
* **EIP-1271** (smart contract-based signature schemes)
The order hash is computed via:
```solidity theme={null}
keccak256(
abi.encode(
ORDER_TYPEHASH,
order.orderId,
order.queue,
order.asset,
order.caller,
order.recipient,
order.ordered,
order.requested,
order.deadline,
order.nonce
)
);
```
### Price Safety Check
After signature verification, `SignatureQueue` uses the vault’s `Oracle` to validate the price:
* **Deposits**: `price = requestedShares / depositedAssets`
* **Redemptions**: `price = burnedShares / redeemedAssets`
* Oracle must confirm:
* Price is within allowed bounds
* Price is not marked as suspicious
Otherwise, the operation is rejected with `InvalidPrice`.
### Storage Layout
```solidity theme={null}
struct SignatureQueueStorage {
address consensus; // Signature validator contract
address vault; // Parent vault (Vault.sol)
address asset; // Supported ERC20 token or native ETH
mapping(address => uint256) nonces; // Per-user nonces
}
```
### Interface Compatibility
Despite not using claimable balances, `SignatureQueue` implements stub methods for compatibility with the `IQueue` interface:
* `claimableOf(...) → 0`
* `claim(...) → false`
* `handleReport(...)`: no-op
* `canBeRemoved() → true`: confirms it has no persistent state
### Events
```solidity theme={null}
event OrderExecuted(Order order, IConsensus.Signature[] signatures);
```
Emitted after a signed order is successfully executed.
### Security Assumptions
* Only **trusted off-chain actors** (consensus group) are authorized to sign orders.
* Price quotes must match oracle-defined asset/share rates.
* Users cannot reuse old signatures due to nonce tracking.
* Orders must be executed before `deadline`.
### Use Cases
* **Instant UX**: bypassing delays in `DepositQueue` or `RedeemQueue`
* **Institutional integrations**: where trusted relayers or coordinators pre-sign valid actions
* **Fallback mechanism**: during oracle lags or downtime
### Limitations
* Does not charge fees (unlike time-delayed queues)
* Requires trusted off-chain actors
* Less decentralized if consensus actors are not well-audited or rotated
# SignatureRedeemQueue
Source: https://metavaults.mellow.finance/architecture/queues/signatureredeemqueue
### Purpose
`SignatureRedeemQueue` extends `SignatureQueue` to enable **instant share redemption** from a vault without the usual delay of on-chain oracle processing. It leverages **off-chain consensus signatures** conforming to EIP-712 or EIP-1271 to authorize redemptions, allowing trusted users to convert shares to assets in a fast and secure manner.
This module provides a **low-latency redemption path** under stronger trust assumptions, useful in environments where responsiveness is critical and participants are whitelisted by a governance consensus.
### Key Features
* Off-chain authorized redemptions using signed `Order` messages
* Oracle-bound price validation to prevent manipulation
* EIP-712 structured data signature verification
* Direct burning of shares and asset pulling from the vault and payout
* Nonce-based replay protection
* Vault hook execution and balance tracking
* **No redeem fee** applied (unlike possible fees in `RedeemQueue`)
### Function: `redeem`
```solidity theme={null}
function redeem(Order calldata order, IConsensus.Signature[] calldata signatures) external payable nonReentrant
```
### Parameters:
* `order`: A signed redemption `Order` struct specifying asset amount and recipient
* `signatures`: Validator signatures from the consensus group
### Workflow
1. **Validation** via `validateOrder(...)`:
* Signature freshness (`deadline`)
* Queue and asset correctness
* Caller authenticity and correct nonce
* Signatures validated by registered `Consensus` contract
* Price computed from `ordered` and `requested` values and verified via oracle
2. **Nonce incremented** for the caller to prevent signature reuse
3. **Vault liquid asset check**:
* Ensures enough liquidity is available for the redemption
* Reverts with `InsufficientAssets` if funds are lacking
4. **Redemption Processing**:
* Burns `order.ordered` shares from the user via `shareManager`
* Calls `vault.callHook(...)` for any strategy exit logic
* Transfers `order.requested` assets to the user
* Updates internal vault balance via the `RiskManager`
5. **Event Emitted**:
* `OrderExecuted(order, signatures)` confirms successful execution
### Error: `InsufficientAssets`
```solidity theme={null}
error InsufficientAssets(uint256 requested, uint256 available);
```
Thrown when the vault does not have enough liquid assets to fulfill the request. Ensures safety during instantaneous exits.
# Vaults
Source: https://metavaults.mellow.finance/architecture/vaults/index
In this directory, you will find a detailed per-contract overview of the "Vaults" contract category, including the following contracts:
[Vault](/architecture/vaults/vault)
[Subvault](/architecture/vaults/subvault)
[VaultConfigurator](/architecture/vaults/vaultconfigurator)
# Subvault
Source: https://metavaults.mellow.finance/architecture/vaults/subvault
## Overview
The `Subvault` contract represents a modular, permissioned vault component designed to manage delegated asset strategies within a parent `Vault`. It enables curated logic for permissioned calls and asset management without exposing external deposit or redemption interfaces.
This contract combines callable and verifiable logic to serve as a secure, controlled execution unit within a system.
## Inheritance Structure
```solidity theme={null}
contract Subvault is IFactoryEntity, CallModule, SubvaultModule
```
The `Subvault` inherits:
* `CallModule`: Enables arbitrary low-level calls to external contracts (used by curator of the vault), and verification through a verifier module.
* `SubvaultModule`: Handles vault linkage and liquidity handling.
* `IFactoryEntity`: Standard initialization interface for factory deployment compatibility.
The constructor explicitly calls:
```solidity theme={null}
VerifierModule(name_, version_)
SubvaultModule(name_, version_)
```
This indicates that both modules rely on deterministic storage and versioned deployment identifiers via `SlotLibrary`.
## Constructor
```solidity theme={null}
constructor(string memory name_, uint256 version_)
```
### Parameters:
* `name_`: A unique string identifier for the deployment (e.g., “Mellow”).
* `version_`: A version number used to derive storage slots and allow upgradeable logic.
### Behavior:
Passes the `name_` and `version_` arguments into the constructors of `VerifierModule` and `SubvaultModule`.
## External Functions
### `initialize`
```solidity theme={null}
function initialize(bytes calldata initParams) external initializer
```
Initializes the subvault contract. This function can only be called once due to the `initializer` modifier.
### Parameters:
* `initParams`: ABI-encoded as `(address verifier_, address vault_)`
### Initialization Steps:
1. Decodes `verifier_` and `vault_` from the calldata.
2. Calls `__VerifierModule_init(verifier_)` to link the external verifier (used for strategy proof or access control).
3. Calls `__SubvaultModule_init(vault_)` to register this subvault with the parent vault.
4. Emits the `Initialized(initParams)` event for transparency.
## Design Notes
* **Modular Strategy Execution**: The `CallModule` enables arbitrary external calls, useful for delegating assets into other protocols or yield strategies.
* **Trust-Minimized Calls**: External strategy actions are gated via a `VerifierModule`, which can enforce logic like off-chain signatures or time-based constraints.
* **Parent Vault Registration**: Initialization ensures the `vault` address is securely set once and governs access and lifecycle.
* **Upgradeable Architecture**: Follows the shared pattern of using deterministic storage slots (via `SlotLibrary`) to remain safely upgradeable and composable.
## Events
### `Initialized(bytes data)`
Emitted once after a successful `initialize` call. Contains the raw ABI-encoded input for auditing or debugging.
# Vault
Source: https://metavaults.mellow.finance/architecture/vaults/vault
## Overview
The `Vault` contract is the central entry point in the Flexible Vault system. It composes three foundational modules:
* `ACLModule`: Role-based access control.
* `ShareModule`: Management of user-facing shares, including deposit and redemption processes.
* `VaultModule`: Subvault management.
This contract allows secure, extensible, and upgradeable vault implementations by coordinating all external and internal interactions within the system. It is typically instantiated through `Factory` or `VaultConfigurator` and initialized with all the required components and role assignments in a single atomic transaction.
## Inheritance Structure
```solidity theme={null}
contract Vault is IFactoryEntity, VaultModule, ShareModule, ACLModule
```
The contract inherits three modules:
* `ACLModule`: Admin and permission management
* `ShareModule`: Deposits, redemptions, share management
* `VaultModule`: Subvault delegation control
It also implements the `IFactoryEntity` interface for standard factory-based deployment patterns.
## Constructor
```solidity theme={null}
constructor(
string memory name_,
uint256 version_,
address depositQueueFactory_,
address redeemQueueFactory_,
address subvaultFactory_,
address verifierFactory_
)
```
### Parameters:
* `name_`: Unique name identifier for the vault instance
* `version_`: Configuration version of the vault
* `depositQueueFactory_`: Address of the factory used to deploy deposit queues
* `redeemQueueFactory_`: Address of the factory used to deploy redemption queues
* `subvaultFactory_`: Address of the factory used to deploy subvaults
* `verifierFactory_`: Address of the factory for deploying verifier contracts
### Behavior:
Passes these arguments to the parent module constructors:
* `ACLModule(name_, version_)`
* `ShareModule(name_, version_, depositQueueFactory_, redeemQueueFactory_)`
* `VaultModule(name_, version_, subvaultFactory_, verifierFactory_)`
## Structs
### `RoleHolder`
```solidity theme={null}
struct RoleHolder {
bytes32 role;
address holder;
}
```
Used to batch-assign multiple roles during initialization. Each entry maps a role identifier to a designated address.
## External Functions
### `initialize`
```solidity theme={null}
function initialize(bytes calldata initParams) external initializer
```
Initializes the vault instance. Can only be called once due to the `initializer` modifier.
### `initParams` structure (ABI-encoded):
```solidity theme={null}
(
address admin_,
address shareManager_,
address feeManager_,
address riskManager_,
address oracle_,
address defaultDepositHook_,
address defaultRedeemHook_,
uint256 queueLimit_,
RoleHolder[] roleHolders
)
```
### Initialization Logic:
* Calls `__ACLModule_init(admin_)` to configure the default admin.
* Calls `__ShareModule_init(...)` to link share management and hook modules.
* Calls `__VaultModule_init(riskManager_)` to initialize risk management.
* Iterates over `roleHolders` and grants each role using `_grantRole(...)`.
* Emits `Initialized(initParams)`.
## Design Notes
* **Modular Composition**: The vault is composed by inheriting three upgradeable modules, enabling reuse and flexible configuration.
* **Factory-Compatible**: The contract is factory-deployable and supports atomic configuration during creation.
* **Centralized Control Layer**: Acts as a trusted coordinator for hooks, queues, shares, and strategy logic.
* **Role Assignment**: Enables full delegation of operational control via batched `RoleHolder` entries.
* **Upgradeable and Isolated**: Each module manages its own storage via deterministic slots (`SlotLibrary`) to support safe upgrades.
## Events
### `Initialized(bytes data)`
Emitted after successful initialization. Includes all parameters passed for transparency.
# VaultConfigurator
Source: https://metavaults.mellow.finance/architecture/vaults/vaultconfigurator
## Overview
The `VaultConfigurator` contract provides a streamlined and modular deployment mechanism for setting up a new `Vault` instance and its associated managers. It orchestrates the creation and initialization of the following components:
* `Vault`
* `ShareManager`
* `FeeManager`
* `RiskManager`
* `Oracle`
It ensures that all components are correctly wired together by setting appropriate references between them.
## Purpose
This contract is designed to be used by an actor that need to deploy and configure fully functional vaults in a deterministic and upgradeable way, using versioned module factories.
## Contract Structure
### State Variables
```solidity theme={null}
IFactory public immutable shareManagerFactory;
IFactory public immutable feeManagerFactory;
IFactory public immutable riskManagerFactory;
IFactory public immutable oracleFactory;
IFactory public immutable vaultFactory;
```
Each of these holds a reference to a factory contract responsible for creating a specific type of contract.
### Constructor
```solidity theme={null}
constructor(
address shareManagerFactory_,
address feeManagerFactory_,
address riskManagerFactory_,
address oracleFactory_,
address vaultFactory_
)
```
Initializes the configurator with references to module factories.
## InitParams Struct
```solidity theme={null}
struct InitParams {
uint256 version;
address proxyAdmin;
address vaultAdmin;
uint256 shareManagerVersion;
bytes shareManagerParams;
uint256 feeManagerVersion;
bytes feeManagerParams;
uint256 riskManagerVersion;
bytes riskManagerParams;
uint256 oracleVersion;
bytes oracleParams;
address defaultDepositHook;
address defaultRedeemHook;
uint256 queueLimit;
Vault.RoleHolder[] roleHolders;
}
```
### Fields:
* `version`: Version of the `Vault` implementation to deploy.
* `proxyAdmin`: Address to be set as `ProxyAdmin` for upgradeable proxies.
* `vaultAdmin`: Address to be set as the vault's owner (admin).
* `_Version`: Specific implementation version to use for each module (used in the corresponding factory).
* `_Params`: ABI-encoded initialization parameters for each module.
* `defaultDepositHook`: Address of the default deposit hook to attach to queues.
* `defaultRedeemHook`: Address of the default redeem hook to attach to queues.
* `queueLimit`: Maximum number of queued operations per deposit/redeem queue.
* `roleHolders`: List of role assignments for vault-level access control.
## External Functions
### `create`
```solidity theme={null}
function create(InitParams calldata params)
external
returns (
address shareManager,
address feeManager,
address riskManager,
address oracle,
address vault
)
```
### Description:
Creates and initializes a new vault instance along with all dependent modules using the provided factory addresses and parameters.
### Steps:
1. **Deploy ShareManager**:
* Uses `shareManagerFactory` to deploy a versioned `ShareManager` proxy.
2. **Deploy FeeManager**:
* Uses `feeManagerFactory` to deploy a versioned `FeeManager`.
3. **Deploy RiskManager**:
* Uses `riskManagerFactory` to deploy a versioned `RiskManager`.
4. **Deploy Oracle**:
* Uses `oracleFactory` to deploy a versioned `Oracle`.
5. **Deploy Vault**:
* Prepares encoded initialization calldata and calls `vaultFactory.create()` with the version and proxy admin.
6. **Post-deployment Wiring**:
* Sets the `vault` address in each of the deployed components using:
* `IShareManager(shareManager).setVault(vault)`
* `IRiskManager(riskManager).setVault(vault)`
* `IOracle(oracle).setVault(vault)`
### Returns:
* `shareManager`: Address of the deployed share manager contract
* `feeManager`: Address of the deployed fee manager contract
* `riskManager`: Address of the deployed risk manager contract
* `oracle`: Address of the deployed oracle contract
* `vault`: Address of the newly created vault
# Deployments
Source: https://metavaults.mellow.finance/deployments
Earn Vaults: user-facing
| Component | Address |
| -------------------------- | ------------------------------------------ |
| Vault | 0x6a37725ca7f4CE81c004c955f7280d5C704a249e |
| DepositQueue (ETH) | 0x1db7094Ef0D994B0b62f6Cd67dB801ad194999A8 |
| SyncDepositQueue (ETH) | 0xb99394f8b95d426Cb2F013B857C74aCC924b20D5 |
| DepositQueue (WETH) | 0x3Fc48660d02e59fBedD0a5Cc18a5580D1f8dD6A4 |
| SyncDepositQueue (WETH) | 0xCe6C2505fEF74d2dE10FCF1d534cB73eCc837976 |
| DepositQueue (wstETH) | 0xe39EED9A454C4918F8d0682062777cB251cd513F |
| SyncDepositQueue (wstETH) | 0xECD2Bfe725fa14f5Ed86e9bDcc0eA4b34A4ed522 |
| RedeemQueue (wstETH) | 0x095bFAca9f1c6F2B063Cd67C6d6bfcd0c3aaB7b4 |
| DepositQueue (GG) | 0x411172F1E5310d03b38128F2a294F2e33c691B30 |
| SyncDepositQueue (GG) | 0x2792004b709E3E88b8FCCb06c3C5e1A6dff0EC2B |
| DepositQueue (strETH) | 0x268ea1cc674cdaE200c4609E7b09d03Dc618E663 |
| SyncDepositQueue (strETH) | 0xA4F23f56442C01a478af20fe06b9F5f8f05aDD96 |
| DepositQueue (DVstETH) | 0x4bDd2Ea1E20acb13f2758190c92a84175107A86f |
| SyncDepositQueue (DVstETH) | 0xA80f247b92C79740b0610b754403D5cb0bf216b5 |
| Oracle | 0xAda1f4c24603aB2fe5aBd35BCD12370e98A20358 |
| ShareManager | 0xBBFC8683C8fE8cF73777feDE7ab9574935fea0A4 |
| FeeManager | 0xed4Fac879eE86F3aB0101993A3713e7cAA0488E1 |
| RiskManager | 0xa2a4C4ecE27229aF51c546844AB752824Ccb557e |
| Subvault 0 | 0xC5901C2481ca9C26398A9Da258b13717894bfebF |
| Verifier 0 | 0xBc46B79d79fCac1F4232D4Da1BA31aCED0AABFE0 |
| Subvault 1 | 0x7F515C80fA4C1FCFF34F0329141A9C3b20468FE5 |
| Verifier 1 | 0xc0FC0B74923A80Af21B1E49633cAA309f432140F |
| Timelock controller | 0x363Ba8843d06BA5968f55C26aB055162eDd62189 |
| OracleSubmitter | 0xFbD83f7C531D35D99392a5A20bb5F1e75E97076e |
| Component | Address |
| ------------------------ | ------------------------------------------ |
| Vault | 0x014e6DA8F283C4aF65B2AA0f201438680A004452 |
| DepositQueue (USDC) | 0xC75E7E73B25fEa8bB23EB55CC48BA55067b5be76 |
| SyncDepositQueue (USDC) | 0xf6AFAf6afcAe116dD37A779D50fE6c5fa6f8C8f5 |
| RedeemQueue (USDC) | 0x9e36A74FE278906a76e7615263e46a83fC40c47F |
| DepositQueue (USDT) | 0xEeC5041c47Cba1e31321AC6941Bf09Ad60645B73 |
| SyncDepositQueue (USDT) | 0x534d0bEb82C47cf703BFb9E959297658b65Ec8E9 |
| AsyncRedeemQueue (USDT) | 0x95092A7a86715246Be6395b8D514B3d60A270Cd3 |
| AsyncDepositQueue (USDe) | 0xeec37568b01e0c4d5028501a49e024b475e2d7ca |
| Oracle | 0x827044735c9708a2cf850e7Ea37EBa43bc786028 |
| ShareManager | 0x4Ce1ac8F43E0E5BD7A346A98aF777bF8fbeA1981 |
| FeeManager | 0x72fa23f40e08eB9E45953233b2Dd9665E347e8Dc |
| RiskManager | 0x7b1e06C46d4510277FC37a37bBeF65F3794fdDE4 |
| Subvault 0 | 0x77B9441d5Cb89fca435190A9B6D108ad4B00ccFd |
| Verifier 0 | 0xB65A8E0937c77a76C3f4F86A1110f81A299CB481 |
| Subvault 1 | 0xe3e0111e31FA3AEB7A528128F2DbAe1C15397242 |
| Verifier 1 | 0xBEa44cd2f58f3CC6f37aaeC82A2dee57911d0b36 |
| Timelock Controller | 0xdA6Da82DFF8cD29D828e4775Cc003f504A968845 |
| OracleSubmitter | 0xB105DaEeFEb1390ce49172c99E3e12C607367156 |
earnUSDc Vaults: operational
| Actor | Address |
| -------------- | ------------------------------------------ |
| ProxyAdmin | 0x81698f87C6482bF1ce9bFcfC0F103C4A0Adf0Af0 |
| LazyAdmin | 0x0Dd73341d6158a72b4D224541f1094188f57076E |
| ActiveAdmin | 0x982aB69785f5329BB59c36B19CBd4865353fEf10 |
| Curator | 0x9745F161b0160a99924845BeFCE1d7b9Daee6899 |
| OracleUpdater | 0x93a797643d74fC81e7A51F3f84a9D78F930435D1 |
| OracleAccepter | 0x0Dd73341d6158a72b4D224541f1094188f57076E |
| Treasury | 0xcCf2daba8Bb04a232a2fDA0D01010D4EF6C69B85 |
| LidoPauser | 0xA916fD5252160A7E56A6405741De76dc0Da5A0Cd |
| MellowPauser | 0x6E887aF318c6b29CEE42Ea28953Bd0BAdb3cE638 |
#### **Addresses**
| Component | Address |
| ----------------------- | ------------------------------------------ |
| SwapModule 0 | 0x28c4c26b4d4eA70434906C270cF26B995583c08C |
| Subvault 0 | 0xD6D3a0f4dd3d48bF3653c0549aac6b3516dD933B |
| Verifier 0 | 0x1e87c6fba77966A5Ff6BF83FAc4Ea76D978A733f |
| Vault | 0xDF0fb76Df2c21F79798949A4E886cd22D1C085d7 |
| SyncDepositQueue (USDT) | 0xeC3EE7F7669b7ce0aC91c4638e4b89c9F40E179F |
| SyncDepositQueue (USDC) | 0xEdcd9C257719799435B05C28ff9a8a34e6872bE0 |
| RedeemQueue (USDC) | 0x59fC26AFFF725eBb77Db8E1de14572e5eA9e87EB |
| SyncRedeemQueue(USDC) | 0x395d3230B47c8EAd7b4152c670945f6545efe3c9 |
| SyncRedeemQueue(USDT) | 0xe04A1c2D63e6964f5629B3c08BF08D2472faaB09 |
| Oracle | 0x8d229B565A0c6Bf2d693C343bea0Ec96103dEF5f |
| ShareManager | 0xd9543AfF8A859F6B34f80A9A230B277c89ACdda4 |
| FeeManager | 0x31325D52B763B1a43d5564114FA4A4ce62148716 |
| RiskManager | 0x24d5300b6503a7358581EE5bb3651b5bC3F6f835 |
| Timelock controller | 0xD0e9094E7E26ff133C349ACd9993743DCc15cA5c |
| OracleSubmitter | 0x03852b7138c6704F8F46e87768399616D31Cf733 |
**Actors**
| Role | Address |
| -------------- | ------------------------------------------ |
| ProxyAdmin | 0x81698f87C6482bF1ce9bFcfC0F103C4A0Adf0Af0 |
| LazyAdmin | 0x0Dd73341d6158a72b4D224541f1094188f57076E |
| ActiveAdmin | 0x982aB69785f5329BB59c36B19CBd4865353fEf10 |
| Curator | 0x9745F161b0160a99924845BeFCE1d7b9Daee6899 |
| OracleUpdater | 0x93a797643d74fC81e7A51F3f84a9D78F930435D1 |
| OracleAccepter | 0x0Dd73341d6158a72b4D224541f1094188f57076E |
| Treasury | 0xcCf2daba8Bb04a232a2fDA0D01010D4EF6C69B85 |
| LidoPauser | 0xA916fD5252160A7E56A6405741De76dc0Da5A0Cd |
| MellowPauser | 0x6E887aF318c6b29CEE42Ea28953Bd0BAdb3cE638 |
**Addresses**
| Component | Address |
| ------------------- | ------------------------------------------ |
| Subvault 0 | 0x6CD300d6D848cb315EfaE2d874b2Ec8Ad4897977 |
| Verifier 0 | 0x67276a558638073D43068CbD91e2E6b478963742 |
| Vault | 0xb7651cae9c8de82B188990CFD44aB728dC2Fa061 |
| Oracle | 0x0C7E836EB1d30E2f2f02D0F85e64449D355ecFd0 |
| ShareManager | 0xcfe8DbC6a2df2d8eb7F0a4d7e915C253A78B0754 |
| FeeManager | 0x25958e9965B76f0B3a7809FcCc934066Aa80A540 |
| RiskManager | 0xc93f1B04CDEFB5C7F86f7F2f3df4CA26c5a098Ce |
| Timelock Controller | 0x0555306F5063f62a3A7896A9eaBA0754c1185a67 |
| OracleSubmitter | 0x8A6a1648A39C7F3dE64282e8bF2fcD783CCF08b0 |
earnUSDe Vaults: operational
#### Actors
| Role | Address |
| -------------- | ------------------------------------------ |
| ProxyAdmin | 0x81698f87C6482bF1ce9bFcfC0F103C4A0Adf0Af0 |
| LazyAdmin | 0x0Dd73341d6158a72b4D224541f1094188f57076E |
| ActiveAdmin | 0x982aB69785f5329BB59c36B19CBd4865353fEf10 |
| Curator | 0x9745F161b0160a99924845BeFCE1d7b9Daee6899 |
| OracleUpdater | 0x93a797643d74fC81e7A51F3f84a9D78F930435D1 |
| OracleAccepter | 0x0Dd73341d6158a72b4D224541f1094188f57076E |
| Treasury | 0xcCf2daba8Bb04a232a2fDA0D01010D4EF6C69B85 |
| LidoPauser | 0xA916fD5252160A7E56A6405741De76dc0Da5A0Cd |
| MellowPauser | 0x6E887aF318c6b29CEE42Ea28953Bd0BAdb3cE638 |
#### Addresses
| Component | Address |
| ----------------------- | ------------------------------------------ |
| SwapModule 0 | 0xC99DaA2dC366cFd115130a0b7D21Df01CB5FcF7b |
| Subvault 0 | 0x31B7d5A2B1CE1871Dd642F6aeCC0Ef68d126B95A |
| Verifier 0 | 0x87631dbf0224234107B593c874f63f577e1336Da |
| Vault | 0x0cC65147BF7F615A8dD9E78e2c53158F8E01754d |
| SyncDepositQueue (USDC) | 0xBb647898e0CF0aE81Ac480d04A8a9973763eBD2D |
| RedeemQueue (USDC) | 0x8B857170F2a6C10Ce64ec8b920428ca977fb7710 |
| SyncDepositQueue (USDT) | 0xa01aEfeC7A3384C8440e99084458030BDbdD7404 |
| SyncDepositQueue (USDe) | 0xAcb2E510e8FcdaB3808cC5B9d206374cAB527947 |
| Oracle | 0xBcdFaf92783B2C391A1c80682e75Bb6EF47B9c3C |
| ShareManager | 0x3D561e1E0204d47b45C23B65356a4536c36d1AF6 |
| FeeManager | 0xE90b8D7DfFB816b2895DE67307b4A8f9061CEF52 |
| RiskManager | 0x5D7e237BD77d3671fFb33FC9bC8c37d49aAE6153 |
| Timelock controller | 0x7589b8645F61F151D6c28Eaf8cE2fD9F23E09AbF |
| OracleSubmitter | 0xDa5508789B5f93fb49b644c87Ef9D8CddB699d59 |
#### Actors
| Role | Address |
| -------------- | ------------------------------------------ |
| ProxyAdmin | 0x81698f87C6482bF1ce9bFcfC0F103C4A0Adf0Af0 |
| LazyAdmin | 0x0Dd73341d6158a72b4D224541f1094188f57076E |
| ActiveAdmin | 0x982aB69785f5329BB59c36B19CBd4865353fEf10 |
| Curator | 0x9745F161b0160a99924845BeFCE1d7b9Daee6899 |
| OracleUpdater | 0x93a797643d74fC81e7A51F3f84a9D78F930435D1 |
| OracleAccepter | 0x0Dd73341d6158a72b4D224541f1094188f57076E |
| Treasury | 0xcCf2daba8Bb04a232a2fDA0D01010D4EF6C69B85 |
| LidoPauser | 0xA916fD5252160A7E56A6405741De76dc0Da5A0Cd |
| MellowPauser | 0x6E887aF318c6b29CEE42Ea28953Bd0BAdb3cE638 |
#### Addresses
| Component | Address |
| ------------------- | ------------------------------------------ |
| SwapModule 0 | 0x351C5644D4d8502385b28Fe3Ef36B44C4b4cEb1c |
| Subvault 0 | 0xa11BE438F1961dB47F6660BDAF59b05C0200ADC5 |
| Verifier 0 | 0xeF886da65AA032ce51517a555540339d08effd83 |
| Subvault 1 | 0xbaaE2F02d3a4f33eC164902e5A9E980Cb4c71afB |
| Verifier 1 | 0xDCa60DBDa06418dB843779486D61cc89208634DE |
| Vault | 0x49DAb986A4288bE616f44733d56397d3410fD331 |
| Oracle | 0xdB8837c1946f28d9766d9CA7470160B663198DD9 |
| ShareManager | 0x906703a4e566D04828845b6C2918B1767E24752A |
| FeeManager | 0x4FD8e72bEA84dc3B947672E49734e457a196bbdb |
| RiskManager | 0x6B2EaDFD25947b6eD2657f9DCb5bf4413113cc9E |
| Timelock controller | 0xFC950F8C0064071a5D762783Cf726Fa0CC2722Fe |
| OracleSubmitter | 0x9d84510ED5dA4adc6Be2726F6C27B3AD68fDAd92 |
#### Actors
| Role | Address |
| -------------- | ------------------------------------------ |
| ProxyAdmin | 0x81698f87C6482bF1ce9bFcfC0F103C4A0Adf0Af0 |
| LazyAdmin | 0x0Dd73341d6158a72b4D224541f1094188f57076E |
| ActiveAdmin | 0x982aB69785f5329BB59c36B19CBd4865353fEf10 |
| Curator | 0x9745F161b0160a99924845BeFCE1d7b9Daee6899 |
| OracleUpdater | 0x93a797643d74fC81e7A51F3f84a9D78F930435D1 |
| OracleAccepter | 0x0Dd73341d6158a72b4D224541f1094188f57076E |
| Treasury | 0xcCf2daba8Bb04a232a2fDA0D01010D4EF6C69B85 |
| LidoPauser | 0xA916fD5252160A7E56A6405741De76dc0Da5A0Cd |
| MellowPauser | 0x6E887aF318c6b29CEE42Ea28953Bd0BAdb3cE638 |
#### Addresses
| Component | Address |
| ------------------- | ------------------------------------------ |
| SwapModule 0 | 0x72b4c5Dc7E7e26BD077d78D5417F0Bf5b86a00EA |
| Subvault 0 | 0x18b50CbAAf4C48855b29E548E4d0248C71A15392 |
| Verifier 0 | 0xd6d0cA0e4d5dE8df1BD3cb9c96E2ae1c473e9bf7 |
| Vault | 0xF9DD401ff0f806b71dE62a936c34B930d0876022 |
| Oracle | 0x6020b7dEa8df4C82Fe3EbD202FF76bcdbcBe12BA |
| ShareManager | 0x266E1084a88c78D18D42152b6a29873F67F2B586 |
| FeeManager | 0x25958e9965B76f0B3a7809FcCc934066Aa80A540 |
| RiskManager | 0xc93f1B04CDEFB5C7F86f7F2f3df4CA26c5a098Ce |
| Timelock controller | 0x3032f5eCf95B2F8FA216Df50d588E2aAe4256f33 |
| OracleSubmitter | 0xbe580d9C5C24b0A06C19660c058937BB8434BBa5 |
# MetaVaults
Source: https://metavaults.mellow.finance/index
### Overview
MetaVaults provide an **aggregation layer for onchain strategies**, enabling a single vault to allocate liquidity across multiple onchain destinations under a unified interface. They are designed to power portfolio-style products where capital is distributed across several strategies, vaults, or apps and protocols while users interact with only one vault position.
A MetaVault abstracts strategy composition and capital routing, allowing allocation logic to be expressed onchain without exposing users to operational complexity.
### Design Goals and Key Characteristics
MetaVaults are built with the following principles in mind:
* **Strategy aggregation**\
Each MetaVault represents a portfolio of underlying strategies rather than a single execution path.
* **Composable allocations**\
Capital can be routed to multiple onchain destinations, including other vaults and direct protocol integrations, in a modular and extensible way.
* **Unified user interface**\
Users deposit into and redeem from a single vault, regardless of how many strategies are used internally.
* **Onchain transparency**\
All allocations, balances, and interactions are executed and accounted for onchain, enabling full auditability and integration with external tooling.
* **Scalable orchestration**\
MetaVaults enable reuse of existing strategies and integrations, supporting rapid iteration without changes to the user-facing vault interface.
### High-Level Functionality
At a high level, a MetaVault functions as follows:
* Users deposit assets into the MetaVault and receive shares representing proportional ownership of the aggregated strategy.
* Deposited liquidity is allocated across a configurable set of underlying onchain destinations according to the vault’s strategy logic.
* Allocation can be adjusted over time as the strategy evolves, while users remain exposed through a single vault position.
* The value of MetaVault shares reflects the combined performance of all underlying allocations, and redemptions are handled at the MetaVault level.
# Referral Tracking
Source: https://metavaults.mellow.finance/referral-tracking
MetaVault deposits can include **additional parameters embedded directly in the onchain transaction**, enabling reliable attribution and access control without relying on offchain tracking.
#### Deposit Parameters
Two optional fields may be provided during deposit:
**`assets`** - the quantity of tokens being deposited, expressed in the token’s smallest unit.\
For example, a deposit of **1 wstETH** is represented as **`1e18`**.
**`referral`** - a tracking code used to attribute the source of the deposit (e.g., partner, campaign, or distribution channel).
**`merkleProof`** - a proof used to validate **whitelisted deposits**, ensuring that only approved addresses or allocations can participate when whitelist restrictions are enabled.
These parameters are written directly into the deposit queue contract and become part of the onchain record for the transaction.
For the exact implementation and parameter structure, see the contract source:
[DepositQueue.sol](https://github.com/mellow-finance/flexible-vaults/blob/main/src/queues/DepositQueue.sol#L64)
#### Recommended Integration Approach
For accurate attribution, it is **strongly recommended to use a custom UI or integration layer** that:
* Injects the correct referral code into the deposit transaction
* Ensures parameters are consistently formatted and recorded onchain
Because the data is embedded in the transaction itself, this method provides deterministic and verifiable attribution.
### How to integrate the referral address
You can set up referral attribution by sharing a **vault link with a personalized web parameter** that includes your wallet address as the referral ID.
#### How to use it
1. **Copy your wallet address**\
This address acts as your unique referral identifier.
2. **Add it to the vault link**\
Attach the following parameter to the URL:
```
?referral=YOUR_WALLET_ADDRESS
```
\
**Example:**
```
https://app.mellow.finance/vaults/earneth?referral=0xAbC123...
```
3. **Share the link**\
When someone opens your link and deposits through the interface,\
your wallet address is recorded onchain as the referral source.
#### Limitations of URL-Based Referrals
Web referrals passed via URL parameters (e.g., `?referral={code}`) are technically possible but **not fully reliable in the crypto space**.
Many users interact through:
* Privacy-focused browsers
* Wallet in-app browsers
* Direct contract interactions
These flows often **strip or ignore URL query parameters**, leading to incomplete or incorrect attribution.
# Security
Source: https://metavaults.mellow.finance/security
### Audits
Security is a core design priority of the MetaVaults architecture. The system is built with a strong emphasis on **defensive design, explicit constraints, and onchain enforceability**, aiming to minimize risk while operating in complex and adversarial environments.
Main security partners for the architecture are [Nethermind Security](https://x.com/NethermindSec) and [Sherlock](https://x.com/sherlockdefi).
To support ongoing security and encourage continuous review of live code, MetaVaults participate in a public bug bounty program hosted by Sherlock.
Live bug bounty on the Sherlock platform.
Sherlock Contest
Nethermind audit
Nethermind audit
Nethermind audit
Nethermind audit
Nethermind audit
Nethermind audit
Nethermind audit
Nethermind audit
Nethermind audit
Nethermind audit
Mixbytes audit
Nethermind audit