EIP4626 - Tokenized Vault Standard

# Abstract

The following standard allows for the implementation of a standard API for tokenized vaults with a single underlying ERC-20 within smart contracts. This standard is an extension on the ERC-20 token that provides basic functionality for depositing and withdrawing tokens and reading balances.

# Motivation

Tokenized vaults have a lack of standardization leading to diverse implementation details. Some various examples include lending markets (Compound, Aave, Fuse), aggregators (Yearn, Rari Vaults, Idle), and intrinsically interest bearing tokens (xSushi). This makes integration difficult at the aggregator or plugin layer for protocols which need to conform to many standards. This forces each protocol to implement their own adapters which are error prone and waste development resources.

A standard for tokenized vaults will allow for a similar cambrian explosion to ERC-20, unlocking access to yield and other strategies in a variety of applications with little specialized effort from developers.

# Specification

All tokenized vaults MUST implement ERC20. If a vault is to be non-transferrable, it MAY revert on calls to transfer or transferFrom. The ERC20 operations balanceOf, transfer, totalSupply, etc. operate on the vault "shares" which represent ownership in the underlying.

# Methods

# deposit

function deposit(address _to, uint256 _value) public returns (uint256 _shares)

Deposits exactly _value tokens into the vault and grants ownership of the corresponding _shares amount to _to.

MUST support ERC-20 tranferFrom deposit flow where the vault has at least _value approval over msg.sender's balance of underlying. MAY support an additional deposit flow that is checked beforehand such as a skimbatch approach.

MUST emit the Deposit event.

# mint

function mint(address _to, uint256 _shares) public returns (uint256 _value)

Mints exactly _shares amount of ownership to _to by depositing _value tokens.

MUST support ERC-20 tranferFrom deposit flow where the vault has at least _value approval over msg.sender's balance of underlying. MAY support an additional deposit flow such as a skimbatch approach.

MUST emit the Deposit event.

# withdraw

function withdraw(address _from, address _to, uint256 _value) public returns (uint256 _shares)

Withdraws _value tokens from owner _from and transfers them to _to.

MUST support withdrawal flow where the _from address holds _shares, where the shares are burned. If _from != msg.sender, then msg.sender MUST have ERC-20 approval over _from shares. MAY support an additional withdrawal flow such as a skimbatch approach.

MUST emit the Withdraw event.

# redeem

function redeem(address _from, address _to, uint256 _shares) public returns (uint256 _value)

Redeems a specific number of _shares from owner _from for underlying tokens and transfers them to _to.

MUST support withdrawal flow where the _from address holds _shares, where the shares are burned. If _from != msg.sender, then msg.sender MUST have ERC-20 approval over _from shares. MAY support an additional withdrawal flow such as a skimbatch approach.

MUST emit the Withdraw event.

# totalUnderlying

function totalUnderlying() public view returns (uint256)

Returns the total amount of underlying tokens held/managed by the vault.

# balanceOfUnderlying

function balanceOfUnderlying(address _owner) public view returns (uint256)

Returns the total amount underlying tokens held in the vault for _owner.

# underlying

function underlying() public view returns (address)

Returns the address of the token the vault uses for accounting, depositing, and withdrawing.

MUST return the address of a token implementing the ERC-20 standard.

# calculateShares

function calculateShares(uint256 underlyingAmount) public view returns (uint256 shareAmount)

Returns the amount of vault shares corresponding to a given underlying amount.

calculateShares(calculateUnderlying(shareAmount)) MUST equal shareAmount

# calculateUnderlying

function calculateUnderlying(uint256 shareAmount) public view returns (uint256 underlyingAmount);

Returns the amount of underlying tokens corresponding to a given amount of vault shares.

calculateUnderlying(calculateShares(underlyingAmount)) MUST equal underlyingAmount

# Events

# Deposit

MUST be emitted when tokens are deposited into the vault.

event Deposit(address indexed _from, address indexed _to, uint256 _value)

Where _from is the user who triggered the deposit for _value underlying tokens to the vault, and _to is the user who is able to withdraw the deposited tokens.

# Withdraw

MUST be emitted when tokens are withdrawn from the vault by a depositor.

event Withdraw(address indexed _from, address indexed _to, uint256 _value)

Where _from is the owner who and held _value underlying tokens in the vault, and _to is the user who received the withdrawn tokens.

# Rationale

The vault interface is designed to be optimized for integrators with a feature complete yet minimal interface. Details such as accounting and allocation of deposited tokens are intentionally not specified, as vaults are expected to be treated as black boxes on-chain and inspected off-chain before use.

ERC20 is forced because implementation details like token approval and balance calculation directly carry over to the shares accounting. This standardization makes the vaults immediately compatible with all ERC20 use cases in addition to ERC4626.

The vaults are opinionated on a default deposit/withdraw flow because it gives integrators a default pattern to implement. Special use cases can be overloaded on these functions by including additional logic.

The mint function was included for symmetry and feature completeness. Most current use cases of shares based vaults do not ascribe special meaning to the shares such that a user would optimize for a specific number of shares (mint) rather than specific amount of underlying (deposit). However, it is easy to imagine future vault strategies which would have unique and independently useful share representations.

# Backwards Compatibility

ERC-4626 is fully backward compatible with the ERC-20 standard and has no known compatibility issues with other standards. For production implementations of vaults which do not use ERC-4626, wrapper adapters can be developed and used.

# Reference Implementation

Solmate Minimal Implementation (opens new window) - a tokenized vault using the ERC-20 extension with hooks for developers to add logic in deposit and withdraw.

Rari Vaults (opens new window) are an implementation that is nearly ready for production release. Any discrepancies between the vaults abi and this ERC will be adapted to conform to the ERC before mainnet deployment.

# Security Considerations

This specification has similar security considerations to the ERC-20 interface. Fully permissionless yield aggregators, for example, could fall prey to malicious implementations which only conform to the interface but not the specification.

Copyright and related rights waived via CC0 (opens new window).

▲ Powered by Vercel