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
Copyright and related rights waived via CC0 (opens new window).