Skip to content

Latest commit

 

History

History
423 lines (296 loc) · 18.7 KB

File metadata and controls

423 lines (296 loc) · 18.7 KB
title Multi-Asset Standard
description A standard interface for multi-asset vaults with USD-based accounting and optional governance token wrapping
author Bro (@Sun-of-Siberia)
discussions-to https://github.com/Sun-of-Siberia/BOLD-APEX/issues/35
status Draft
type Standards Track
category ERC
created 2025-11-20
requires 20, 165, 2535
license CC0-1.0

Abstract

This EIP proposes a standard interface for Multi-Asset Vaults (MAS), enabling the creation of vault contracts that hold multiple ERC-20 tokens simultaneously with USD-based accounting. Unlike single-asset vault standards (e.g., ERC-4626), MAS supports multi-asset portfolios with proportional withdrawals, non-transferable shares by default, and optional wrapping into governance tokens. The standard is implementation-agnostic and can be deployed as personal vaults, pooled vaults, or other configurations.

Motivation

Current vault standards like ERC-4626 are designed for single-asset vaults where all users deposit the same underlying token. While this model works well for many use cases, it has several limitations:

  1. Single Asset Limitation: ERC-4626 requires a single underlying asset, preventing multi-asset strategies
  2. Complex Multi-Asset Workarounds: Implementing multi-asset support requires multiple ERC-4626 instances or custom solutions
  3. Accounting Complexity: Multi-asset portfolios need USD-based valuation for fair share pricing
  4. Limited Flexibility: No standard for non-transferable shares or governance token wrapping

Multi-Asset Standard (MAS) addresses these limitations by providing:

  • Multi-Asset Support: Vaults can hold and manage multiple tokens simultaneously
  • USD-Based Accounting: Share valuation based on total USD value using oracle price feeds
  • Proportional Withdrawals: Users receive proportional amounts of all vault assets
  • Non-Transferable Shares: Default behavior prevents speculation while allowing optional governance token wrapping
  • Implementation Flexibility: Can be used for personal vaults, pooled vaults, or other configurations
  • Composability: Compatible with modular architectures like ERC-2535 Diamond standard

This standard is valuable for:

  • Multi-asset portfolio management
  • Yield strategies requiring multiple token positions
  • Institutional vaults managing diversified assets
  • Applications requiring fair share pricing across multiple assets

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Overview

A MAS-compliant vault is a smart contract that:

  1. Holds one or more ERC-20 tokens (multi-asset portfolio)
  2. Tracks user ownership via non-transferable shares
  3. Generates yield through modular strategies
  4. Optionally wraps shares into transferable governance tokens

Recommended Architecture: MAS-compliant vaults SHOULD implement ERC-2535 (Diamond) to support modular strategy facets, though simpler single-contract implementations are possible for basic use cases.

Core Interface

Every MAS-compliant contract MUST implement the following interface:

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;

interface IERC_MAS {
    /// @notice Emitted when assets are deposited into the vault
    /// @param user The address depositing assets
    /// @param assets Array of token addresses deposited
    /// @param amounts Array of amounts deposited (same order as assets)
    /// @param sharesMinted Number of vault shares minted to user
    /// @param totalValue Total USD value of deposited assets
    event Deposited(
        address indexed user,
        address[] assets,
        uint256[] amounts,
        uint256 sharesMinted,
        uint256 totalValue
    );

    /// @notice Emitted when assets are withdrawn from the vault
    /// @param user The address withdrawing assets
    /// @param assets Array of token addresses withdrawn
    /// @param amounts Array of amounts withdrawn (same order as assets)
    /// @param sharesBurned Number of vault shares burned from user
    /// @param totalValue Total USD value of withdrawn assets
    event Withdrawn(
        address indexed user,
        address[] assets,
        uint256[] amounts,
        uint256 sharesBurned,
        uint256 totalValue
    );

    /// @notice Emitted when shares are wrapped into governance tokens
    /// @param user The address wrapping shares
    /// @param sharesBurned Number of vault shares burned
    /// @param governanceTokensMinted Number of governance tokens minted
    /// @param usdValue USD value of wrapped shares
    event SharesWrapped(
        address indexed user,
        uint256 sharesBurned,
        uint256 governanceTokensMinted,
        uint256 usdValue
    );

    /// @notice Emitted when governance tokens are unwrapped back to shares
    /// @param user The address unwrapping governance tokens
    /// @param governanceTokensBurned Number of governance tokens burned
    /// @param sharesMinted Number of vault shares minted
    /// @param usdValue USD value of unwrapped governance tokens
    event SharesUnwrapped(
        address indexed user,
        uint256 governanceTokensBurned,
        uint256 sharesMinted,
        uint256 usdValue
    );

    /// @notice Deposit assets into the vault and receive shares
    /// @dev MUST emit Deposited event
    /// @dev MUST revert if arrays length mismatch
    /// @dev MUST revert if any amount is zero
    /// @dev MUST calculate shares based on total USD value of assets
    /// @param assets Array of ERC-20 token addresses to deposit
    /// @param amounts Array of amounts to deposit (same order as assets)
    /// @return shares Number of vault shares minted to caller
    function deposit(address[] calldata assets, uint256[] calldata amounts)
        external
        returns (uint256 shares);

    /// @notice Withdraw assets from the vault by burning shares
    /// @dev MUST emit Withdrawn event
    /// @dev MUST revert if shares exceed caller's balance
    /// @dev MUST withdraw proportional amounts of all vault assets
    /// @param shares Number of vault shares to burn
    /// @return assets Array of token addresses withdrawn
    /// @return amounts Array of amounts withdrawn (same order as assets)
    function withdraw(uint256 shares)
        external
        returns (address[] memory assets, uint256[] memory amounts);

    /// @notice Get the total number of shares in the vault
    /// @return Total supply of vault shares
    function totalShares() external view returns (uint256);

    /// @notice Get the number of shares owned by an address
    /// @param user Address to query
    /// @return Number of shares owned by user
    function sharesOf(address user) external view returns (uint256);

    /// @notice Get the total USD value of all assets in the vault
    /// @dev MUST use oracle prices for valuation
    /// @dev MUST return value in 18 decimals
    /// @return Total vault value in USD (18 decimals)
    function totalValue() external view returns (uint256);

    /// @notice Get the USD value of a user's shares
    /// @param user Address to query
    /// @return USD value of user's shares (18 decimals)
    function valueOfShares(address user) external view returns (uint256);

    /// @notice Get list of all assets held by the vault
    /// @return Array of ERC-20 token addresses
    function getAssets() external view returns (address[] memory);

    /// @notice Get the balance of a specific asset in the vault
    /// @param asset ERC-20 token address
    /// @return Balance of the asset
    function getAssetBalance(address asset) external view returns (uint256);

    /// @notice Wrap vault shares into transferable governance tokens
    /// @dev MUST emit SharesWrapped event
    /// @dev MUST revert if shares exceed caller's balance
    /// @dev MUST burn vault shares and mint governance tokens
    /// @dev Governance token address MUST be set via setGovernanceToken()
    /// @param shares Number of vault shares to wrap
    /// @return governanceTokens Number of governance tokens minted
    function wrapShares(uint256 shares) external returns (uint256 governanceTokens);

    /// @notice Unwrap governance tokens back into vault shares
    /// @dev MUST emit SharesUnwrapped event
    /// @dev MUST revert if governance tokens exceed caller's balance
    /// @dev MUST burn governance tokens and mint vault shares
    /// @param governanceTokens Number of governance tokens to unwrap
    /// @return shares Number of vault shares minted
    function unwrapShares(uint256 governanceTokens) external returns (uint256 shares);

    /// @notice Get the governance token address
    /// @return Address of the governance token (address(0) if not set)
    function governanceToken() external view returns (address);

    /// @notice Check if shares are transferable
    /// @dev MUST return false for standard MAS vaults (non-transferable shares)
    /// @return True if shares can be transferred, false otherwise
    function sharesTransferable() external view returns (bool);
}

Share Accounting

  1. Share Minting: When assets are deposited, shares MUST be minted based on the total USD value of deposited assets relative to the vault's total value.

    If totalShares == 0:
        shares = depositValueUSD  // depositValueUSD is in 18 decimals; 1 share = 1 USD (18 decimals)
    Else:
        shares = (depositValueUSD * totalShares) / totalValue
    
  2. Share Burning: When shares are withdrawn, the vault MUST return a proportional amount of each asset.

    For each asset:
        withdrawAmount = (shares * assetBalance) / totalShares
    
  3. Non-Transferable: Vault shares MUST NOT be transferable by default. This prevents speculation and ensures shares represent actual vault ownership.

  4. Governance Token Wrapping: Users MAY wrap their non-transferable shares into transferable governance tokens at a 1:1 USD value ratio. The governance token address MUST be set by the vault owner.

Multi-Asset Support

  1. Asset Management: Vaults MUST support holding multiple ERC-20 tokens simultaneously.

  2. Valuation: All asset values MUST be calculated in USD using oracle price feeds (e.g., Chainlink).

  3. Proportional Withdrawals: When withdrawing, users MUST receive a proportional share of ALL vault assets, not just a single asset.

Modular Strategies (Optional)

  1. Diamond Standard (Recommended): MAS-compliant vaults SHOULD implement ERC-2535 (Diamond) to support modular strategy facets. This enables:

    • Adding new strategy facets via diamondCut()
    • Removing strategy facets via diamondCut()
    • Upgrading strategies without redeploying the vault
  2. Alternative Implementations: Simple MAS implementations MAY use a single contract with hardcoded strategies for basic use cases.

  3. Common Facet Types (for Diamond-based implementations):

    • Deposit/Withdraw facets (core functionality)
    • Rebalance facets (portfolio management)
    • Yield facets (lending protocols, LST, restaking)
    • Risk management facets (stop-loss, drawdown protection)
    • Governance facets (voting, proposals)

Security Requirements

  1. Ownership: Each MAS-compliant vault MUST have a single owner (ERC-173).

  2. Access Control: Only the vault owner MUST be able to:

    • Add/remove facets
    • Update strategy parameters
    • Set governance token address
    • Transfer vault ownership
  3. Reentrancy Protection: All state-changing functions MUST be protected against reentrancy attacks.

  4. Oracle Security: Price feeds MUST include staleness checks and circuit breakers.

Optional Extensions

  1. ERC-165 Interface Detection: Contracts SHOULD implement ERC-165 to allow interface detection.

  2. Batch Operations: Contracts MAY implement batch deposit/withdraw functions for gas optimization.

  3. Emergency Pause: Contracts MAY implement emergency pause functionality for security incidents.

Rationale

Why Personal Vaults Instead of Pooled Vaults?

Self-Custody: Personal vaults eliminate custody risk. Users maintain full control of their assets and can transfer vault ownership at any time.

Customization: Each user can configure their own risk parameters, strategy mix, and asset allocation without affecting others.

Multi-Asset Portfolios: Unlike ERC-4626 which requires a single underlying asset, MAS supports complex multi-asset strategies (e.g., 40% WBTC + 40% WETH + 20% PAXG).

Regulatory Compliance: Personal vaults may have different regulatory treatment than pooled funds in some jurisdictions.

Why Non-Transferable Shares?

Prevent Speculation: Non-transferable shares ensure the vault is used for yield generation, not speculation.

Simplify Accounting: Without transfers, share accounting is simpler and more gas-efficient.

Governance Separation: Users who want transferability can wrap shares into governance tokens, separating utility from governance.

Why Diamond Standard (ERC-2535)?

Modularity: Facets allow users to add/remove strategies without redeploying the entire vault.

Upgradeability: New yield strategies can be added as they become available (e.g., new LST protocols, restaking opportunities).

Gas Efficiency: Diamond pattern allows large contracts to be split into smaller facets, avoiding contract size limits.

Composability: Facets from different developers can be combined, creating an ecosystem of reusable strategy modules.

Why USD-Based Valuation?

Multi-Asset Comparison: USD provides a common denominator for valuing different assets (BTC, ETH, Gold).

Share Calculation: USD-based shares ensure fair minting/burning regardless of which assets are deposited/withdrawn.

Governance Token Parity: 1 governance token = 1 USD worth of shares provides clear, stable voting power.

Backwards Compatibility

This EIP introduces a new standard and does not break compatibility with existing standards.

Relationship to ERC-4626: MAS is NOT compatible with ERC-4626 due to fundamental differences:

  • ERC-4626: Single asset, pooled vault, transferable shares
  • MAS-compliant vaults: Multi-asset, personal vault, non-transferable shares

Projects using ERC-4626 cannot directly migrate to MAS without architectural changes.

Relationship to ERC-2535: MAS-compliant vaults SHOULD implement ERC-2535 (Diamond) for maximum modularity and upgradeability. However, the core IERC_MAS interface can also be implemented in simpler single-contract architectures for basic use cases (see MinimalMAS reference implementation).

Relationship to ERC-20: Governance tokens wrapped from MAS vault shares SHOULD implement ERC-20 for maximum compatibility with existing DeFi protocols.

Test Cases

Test cases are available in the reference implementation repository:

  • test/erc-MAS/compliance.spec.ts - Comprehensive MAS compliance tests for the MinimalMAS reference implementation (33 tests covering all interface functions, share accounting, multi-asset operations, governance wrapping, security, and edge cases)
  • test/erc-MAS/diamond-compliance.spec.ts - MAS compliance tests for the Diamond-based example implementation (34 tests covering interface compliance, share accounting, multi-asset support, governance token wrapping, security, and edge cases)

Reference Implementation

Two reference implementations are available at: example implementation

1. Minimal MAS Vault (Simple Single-Contract)

A minimal single-contract implementation demonstrating the core IERC_MAS interface:

  • contracts/examples/MinimalMAS.sol - Complete MAS vault implementation in a single contract
  • contracts/interfaces/IERC_MAS.sol - Standard interface definition

Use Case: Educational reference, simple vaults without modular strategies.

2. example implementation Diamond (Diamond-Based Vault)

A full-featured Diamond-based implementation with modular strategies:

  • contracts/Diamond.sol - Diamond proxy implementation
  • contracts/facets/basic/DepositWithdrawFacet.sol - Core deposit/withdraw logic
  • contracts/facets/basic/RebalanceFacet.sol - Portfolio rebalancing
  • contracts/facets/governance/WrapUnwrapFacet.sol - Governance token wrapping
  • contracts/tokens/GovernanceToken.sol - Non-transferable governance token
  • contracts/libraries/LibAppStorage.sol - Shared storage pattern

Use Case: Deployments requiring modularity, upgradeability, and advanced strategies.

Note: The Diamond implementation includes additional features beyond the minimal MAS interface described above (e.g., APEX inverse volatility strategy, fee discounts, treasury buyback).

Security Considerations

Oracle Manipulation

Risk: Malicious or compromised price oracles could allow attackers to mint shares at incorrect valuations.

Mitigation:

  • Use multiple oracle sources (e.g., Chainlink + Uniswap TWAP)
  • Implement price deviation checks (reject prices that differ >X% from previous)
  • Add staleness checks (reject prices older than Y minutes)
  • Use circuit breakers to pause deposits during extreme volatility

Reentrancy Attacks

Risk: External calls to ERC-20 tokens during deposit/withdraw could enable reentrancy.

Mitigation:

  • Use checks-effects-interactions pattern
  • Implement reentrancy guards on all state-changing functions
  • Update internal state before external calls

Facet Upgrade Risks

Risk: Malicious vault owner could add a facet that steals user funds.

Mitigation:

  • Users MUST trust the vault owner (it's their own vault)
  • For shared vaults, implement timelock + multisig for facet changes
  • Emit events for all facet additions/removals
  • Consider immutable core facets for critical functionality

Share Inflation Attacks

Risk: First depositor could manipulate share price by depositing 1 wei and donating large amounts.

Mitigation:

  • Require minimum initial deposit (e.g., 1000 USD)
  • Use virtual shares/assets (similar to ERC-4626 mitigation)
  • Implement share price bounds checks

Governance Token Wrapping

Risk: If governance token is compromised, attackers could mint unlimited tokens.

Mitigation:

  • Governance token MUST only be mintable by the MAS vault contract
  • Implement strict access controls on wrap/unwrap functions
  • Consider making governance token address immutable after initial set

Multi-Asset Withdrawal Risks

Risk: If one asset becomes illiquid, users cannot withdraw their proportional share.

Mitigation:

  • Implement emergency withdrawal mode (withdraw available assets only)
  • Allow partial withdrawals of specific assets
  • Monitor asset liquidity and pause deposits if needed

Copyright

Copyright and related rights waived via CC0.