Abstract

The following standard adapts ERC-4626 to support multiple assets or entry points for the same share token. This also enables Vaults which don’t have a true share token but rather convert between two arbitrary external tokens.

It adds a new share method to the Vault, to allow the ERC-20 dependency to be externalized.

It also adds Vault-to-Share lookup to the share token.

Lastly, it enforces ERC-165 support for Vaults and the share token.

Motivation

One missing use case that is not supported by ERC-4626 is Vaults which have multiple assets or entry points such as liquidity provider (LP) Tokens. These are generally unwieldy or non-compliant due to the requirement of ERC-4626 to itself be an ERC-20.

Specification

Definitions:

The existing definitions from ERC-4626 apply. In addition, this spec defines:

  • Multi-Asset Vaults: A Vault which has multiple assets/entry points. The Multi-Asset Vault refers to the group of ERC-7575 contracts with the entry points for a specific asset, linked to one common share token.
  • Pipe: A converter from one token to another (unidirectional or bidirectional)

Methods

All ERC-7575 Vaults MUST implement ERC-4626 excluding the ERC-20 methods and events.

share

The address of the underlying share received on deposit into the Vault. MUST return an address of an ERC-20 share representation of the Vault.

share MAY return the address of the Vault itself.

If the share returns an external token i.e. share != address(this):

  • entry functions MUST increase the share balance of the receiver by the shares amount. i.e. share.balanceOf(receiver) += shares
  • exit functions MUST decrease the share balance of the owner by the shares amount. i.e. share.balanceOf(owner) -= shares

MUST NOT revert.

- name: share
  type: function
  stateMutability: view

  inputs: []
  outputs:
    - name: shareTokenAddress
      type: address

Multi-Asset Vaults

Multi-Asset Vaults share a single share token with multiple entry points denominated in different asset tokens.

Multi-Asset Vaults MUST implement the share method on each entry point. The entry points SHOULD NOT be ERC-20.

Pipes

Pipes convert between a single asset and share which are both ERC-20 tokens outside the Vault.

A Pipe MAY be either unidirectional or bidirectional.

A unidirectional Pipe SHOULD implement only the entry function(s) deposit and/or mint, not redeem and/or withdraw.

The entry points SHOULD lock or burn the asset from the msg.sender and mint or transfer the share to the receiver. For bidirectional pipes, the exit points SHOULD lock or burn the share from the owner and mint or transfer the asset to the receiver.

Share-to-Vault lookup

The ERC-20 implementation of share SHOULD implement a vault method, that returns the address of the Vault for a specific asset.

SHOULD emit the VaultUpdate event when a Vault linked to the share changes.

- name: vault
  type: function
  stateMutability: view

  inputs: 
    - name: asset
      type: address
    
  outputs:
    - name: vault
      type: address

ERC-165 support

Vaults implementing ERC-7575 MUST implement the ERC-165 supportsInterface function. The Vault contract MUST return the constant value true if 0x2f0a18c5 is passed through the interfaceID argument.

The share contract SHOULD implement the ERC-165 supportsInterface function. The share token MUST return the constant value true if 0xf815c03d is passed through the interfaceID argument.

Events

VaultUpdate

The Vault linked to the share has been updated.

- name: VaultUpdate
  type: event

  inputs:
    - name: asset
      indexed: true
      type: address
    - name: vault
      indexed: false
      type: address

Rationale

This standard is intentionally flexible to support both existing ERC-4626 Vaults easily by the introduction of a single new method, but also flexible to support new use cases by allowing separate share tokens.

Ability to externalize ERC-20 Dependency

By allowing share != address(this), the Vault can have an external contract managing the ERC-20 functionality of the Share. In the case of Multi-Asset, this avoids the confusion that might arise if each Vault itself were required to be an ERC-20, which could confuse integrators and front-ends.

This approach also enables the creation of new types of Vaults, such as Pipes, which facilitate the conversion between two external ERC-20 tokens. These Pipes could be unidirectional (i.e. only for assets to shares via deposit/mint, or shares to assets via redeem/withdraw) or bidirectional for both entry and exit flows.

Including Share-to-Vault lookup optionally

The vault method is included to look up a Vault for a share by its asset, combined with the VaultUpdate event and ERC-165 support. This enables integrations to easily query Multi-Asset Vaults.

This is optional, to maintain backward compatibility with use cases where the share is an existing deployed contract.

Backwards Compatibility

ERC-7575 Vaults are not fully compatible with ERC-4626 because the ERC-20 functionality has been removed.

Reference Implementation

    // This code snippet is incomplete pseudocode used for example only and is no way intended to be used in production or guaranteed to be secure

    contract Share is ERC20 {
        mapping (address asset => address) vault;

        function updateVault(address asset, address vault_) public {
            vault[asset] = vault_;
            emit UpdateVault(asset, vault_);
        }

        function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
            return interfaceId == 0xf815c03d || interfaceId == 0x01ffc9a7;
        }
    }

    contract TokenAVault is ERC7575 {
        address public share = address(Share);
        address public asset = address(TokenA);

        // ERC4626 implementation

        function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
            return interfaceId == 0x2f0a18c5 || interfaceId == 0x01ffc9a7;
        }
    }

    contract TokenBVault is ERC7575 {
        address public share = address(Share);
        address public asset = address(TokenB);

        // ERC4626 implementation

        function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
            return interfaceId == 0x2f0a18c5 || interfaceId == 0x01ffc9a7;
        }
    }

Security Considerations

ERC-20 non-compliant Vaults must take care with supporting a redeem flow where owner is not msg.sender, since the ERC-20 approval flow does not by itself work if the Vault and share are separate contracts. It can work by setting up the Vault as a Trusted Forwarder of the share token, using ERC-2771.

Copyright and related rights waived via CC0.