đź“– This EIP is in the review stage. It is subject to changes and feedback is appreciated.

EIP-4626: Tokenized Vault Standard Source

A standard for tokenized vaults with a single underlying ERC-20 token.

AuthorJoey Santoro, t11s, Jet Jadeja, Alberto Cuesta Cañada
Discussions-Tohttps://ethereum-magicians.org/t/eip-4626-yield-bearing-vault-standard/7900
StatusReview
TypeStandards Track
CategoryERC
Created2021-12-22

Abstract

The following standard allows for the implementation of a standard API for tokenized vaults with a single underlying ERC-20 token 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 ERC-20. If a vault is to be non-transferrable, it MAY revert on calls to transfer or transferFrom. The ERC-20 operations balanceOf, transfer, totalSupply, etc. operate on the vault “shares” which represent a claim to ownership on a fraction of the vault’s underlying holdings.

Methods

deposit

function deposit(address to, uint256 value) public returns (uint256 shares)

Mints shares amount of vault tokens to to by depositing exactly value underlying tokens.

shares MUST equal the return value of a previewMint call, with value as a parameter, executed immediately before deposit in the same transaction.

MUST support the ERC-20 transferFrom flow where the vault has at least value approval over msg.sender’s balance of underlying.

MAY support an additional flow in which the underlying tokens are owned by the vault contract before the deposit execution, and are accounted for during deposit.

MUST emit the Deposit event.

mint

function mint(address to, uint256 shares) public returns (uint256 value)

Mints exactly shares amount of vault tokens to to by depositing value underlying tokens.

value MUST equal the return value of a previewMint call, with shares as a parameter, executed immediately before withdraw in the same transaction.

MUST support the ERC-20 transferFrom flow where the vault has at least value approval over msg.sender’s balance of underlying.

MAY support an additional flow in which the underlying tokens are owned by the vault contract before the mint execution, and are accounted for during mint.

MUST emit the Deposit event.

withdraw

function withdraw(address from, address to, uint256 value) public returns (uint256 shares)

Burns shares vault tokens from from, withdrawing exactly value underlying tokens to to.

shares MUST equal the return value of a previewWithdraw call, with value as a parameter, executed immediately before mint in the same transaction.

MUST support the ERC-20 flow in which the from address holds the shares vault tokens being burned. If from != msg.sender, then msg.sender MUST have ERC-20 approval over at least shares vault tokens from from.

MAY support an additional flow in which the vault tokens being burned are owned by the vault contract before the withdraw execution, instead of being owned by from.

MUST emit the Withdraw event.

redeem

function redeem(address from, address to, uint256 shares) public returns (uint256 value)

Burns exactly shares vault tokens from from, withdrawing value underlying tokens to to.

value MUST equal the return value of a previewRedeem call, with shares as a parameter, executed immediately before redeem in the same transaction.

MUST support the ERC-20 flow in which the from address holds the shares vault tokens being burned. If from != msg.sender, then msg.sender MUST have ERC-20 approval over at least shares vault tokens from from.

MAY support an additional flow in which the vault tokens being burned are owned by the vault contract before the redeem execution, instead of being owned by from.

MUST emit the Withdraw event.

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.

totalUnderlying

function totalUnderlying() public view returns (uint256)

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

balanceOfUnderlying

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

Returns the value in underlying terms of the vault tokens held by owner. Equivalent to previewRedeem(balanceOf(owner)).

exchangeRate

function exchangeRate() public view returns (uint256)

Returns the value in underlying terms of one vault token. Equivalent to previewRedeem(10 ** decimals()).

previewDeposit

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

Returns the amount of vault tokens that would be obtained if depositing a given amount of underlying tokens in a deposit call.

previewMint

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

Returns the amount of underlying tokens that need to be given to the vault to obtain a given amount of vault tokens in a mint call.

previewWithdraw

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

Returns the amount of vault tokens that need to be burned to obtain a given amount of underlying tokens in a withdraw call.

previewRedeem

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

Returns the amount of underlying tokens that would be obtained by redeeming a given amount of vault tokens in a redeem call.

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.

ERC-20 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 ERC-20 use cases in addition to ERC-4626.

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.

A single exchangeRate() function can only be guaranteed to be exact with one of the four mutable methods, unless significant conditions are placed on the use cases that can comply with this standard. Use cases that require to know the value of a vault position need to know the result of a redeem call, without executing it. On the other hand, integrators that intend to call withdraw on vaults with the user approving only the exact amount of underlying need the result of a withdraw call. Similar use cases can be found for deposit and mint.

As such, the exchangeRate() function has been kept for ease of integration on part of the simpler use cases, but preview functions have been included for each one of the four mutable methods. In each case, the value of a preview function is only guaranteed to equal the return value of the relted mutable function if called immediately before in the same transaction.

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

See Solmate ERC6426: a minimal and opinionated implementation of the standard with hooks for developers to easily insert custom logic into deposits and withdrawals.

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.

Citation

Please cite this document as:

Joey Santoro, t11s, Jet Jadeja, Alberto Cuesta Cañada, "EIP-4626: Tokenized Vault Standard," Ethereum Improvement Proposals, no. 4626, December 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4626.