Alert Source Discuss
🎉 Final Standards Track: SRC

SRC-7575: Multi-Asset SRC-4626 Vaults

Extended SRC-4626 Interface enabling Multi-Asset Vaults

Authors Jeroen Offerijns (@hieronx), Alina Sinelnikova (@ilinzweilin), Vikram Arun (@vikramarun), Joey Santoro (@joeysantoro), Farhaan Ali (@0xfarhaan)
Created 2023-12-11
Requires SIP-20, SIP-165, SIP-2771, SIP-4626

Abstract

The following standard adapts SRC-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 SRC-20 dependency to be externalized.

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

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

Motivation

One missing use case that is not supported by SRC-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 SRC-4626 to itself be an SRC-20.

Specification

Definitions:

The existing definitions from SRC-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 SRC-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 SRC-7575 Vaults MUST implement SRC-4626 excluding the SRC-20 methods and events.

share

The address of the underlying share received on deposit into the Vault. MUST return an address of an SRC-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 SRC-20.

Pipes

Pipes convert between a single asset and share which are both SRC-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 SRC-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

SRC-165 support

Vaults implementing SRC-7575 MUST implement the SRC-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 SRC-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 SRC-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 SRC-20 Dependency

By allowing share != address(this), the Vault can have an external contract managing the SRC-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 SRC-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 SRC-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 SRC-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

SRC-7575 Vaults are not fully compatible with SRC-4626 because the SRC-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 SRC20 {
        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 SRC7575 {
        address public share = address(Share);
        address public asset = address(TokenA);

        // SRC4626 implementation

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

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

        // SRC4626 implementation

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

Security Considerations

SRC-20 non-compliant Vaults must take care with supporting a redeem flow where owner is not msg.sender, since the SRC-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 SRC-2771.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Jeroen Offerijns (@hieronx), Alina Sinelnikova (@ilinzweilin), Vikram Arun (@vikramarun), Joey Santoro (@joeysantoro), Farhaan Ali (@0xfarhaan), "SRC-7575: Multi-Asset SRC-4626 Vaults," Sila Improvement Proposals, no. 7575, December 2023. Available: https://sips.sila.org/SIPS/sip-7575.