Skip to content

Instantly share code, notes, and snippets.

@fubuloubu
Last active May 9, 2022 00:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fubuloubu/61dd062f6508852895e31319d8ab2cd4 to your computer and use it in GitHub Desktop.
Save fubuloubu/61dd062f6508852895e31319d8ab2cd4 to your computer and use it in GitHub Desktop.
Potential Design of ERC4626 Yield-bearing Vault Tokens Standard
# ERC4626: Yield-Bearing Vaults
# Definitions:
# - asset: The underlying token managed by the Vault.
# Has units defined by the corresponding ERC20 contract.
# - share: The token of the Vault. Has a ratio of underlying tokens
# exchanged on deposit/withdraw (defined by the vault)
# - slippage: any difference between advertised share price and economic realities of
# depositto or withdrawal from the Vault, which is not accounted by fees
# - deposit fee: fee charged on deposit to Vault
# - performance fee: fee charged on the yield generated by the Vault
# - management fee: fee charged on deposited tokens while in Vault
# - withdrawal fee: fee charged on exit to the Vault
# NOTE: This standard does not consider any more complex fee structures than the above
implements:
# All ERC4626 implementations must be use ERC20 to represent shares
- ERC20
# ERC4626 users should consider using the Metadata extension of ERC20,
# and represent `name` and `symbol` as an extension to the underlying
- ERC20Metadata
# ERC4626 users should consider using ERC165 to inform outside integrators
# that they indeed comply to the ERC4626 interface, to ensure that
# upstream contracts do not add a Vault that does not comply to the
# interface, risking loss of funds for their contracts
- ERC165
ERC4626:
# The address of the underlying token used for the Vault
- name: asset
type: function
stateMutability: view
outputs:
- name: assetTokenAddress
type: address # Must be an ERC20
# The current exchange rate of shares to tokens
# NOTE: Tokens per unit share e.g. `10**Vault.decimmals()`
# NOTE: Should be inclusive of any management or performance fee
# NOTE: Should *not* be inclusive of any deposit or withdrawal fee
# NOTE: In certain types of fee calculations, this calculation will
# not reflect the "per-user" price-per-share, and instead
# should reflect the "average-user" price-per-share, meaning
# what the average user should expect to exchange to and from.
# `assetsOf` should be used for more accurate calculations.
# NOTE: This value may not be completely accurate according to
# slippage or other on-chain conditions.
- name: pricePerShare
type: function
stateMutability: view
outputs:
- name: assetsPerUnitShare
type: uint256
# Total number of underyling assets that managed by Vault
# NOTE: This should include any compounding that occurs from yield
# NOTE: This is the Value that any management fees should be charged against
- name: totalAssets
type: function
stateMutability: view
outputs:
- name: totalAssets
type: uint256
# Total number of underlying tokens that a depositor's shares represent
# NOTE: This function is more accurate than using `pricePerShare` or
# `totalAssets / vault.totalSupply` for certain fee calculations
# NOTE: This value may not be completely accurate according to
# slippage or other on-chain conditions.
- name: assetsOf
type: function
stateMutability: view
inputs:
- name: depositor
type: address
outputs:
- name: assets
type: uint256
# Allow an on-chain or off-chain user to simulate the effects of their
# deposit at the current block and on-chain conditions.
# NOTE: This value should simulate as closely as possible the real
# outcome of a deposit. Integrators to this contract expect the
# represented slippage or loss to be under 1 basis point of accuracy.
- name: previewDeposit
type: function
stateMutability: view
inputs:
# Maximum number of assets they wish to deposit
# NOTE: Vault will simulate attempt to deposit up to `maxAssets`
- name: maxAssets
type: uint256
outputs:
# Number of assets that Vault has simulated as deposited
# NOTE: This number may be less than `maxAssets` to communicate a
# "deposit limit" or other such concept, where less than
# `maxAssets` was taken from depositor, and the rest was either
# refunded or simply not transferred in the first place
# e.g. `depositedAssets <= maxAssets`
- name: depositedAssets
type: uint256
# Number of shares issued for `depositedAssets`
# NOTE: any discrepency between `shares * pricePerShare`
# and `depositedTokens` should be considered slippage in share
# price or some other type of condition, meaning the depositor
# will lose assets by depositing
# NOTE: can also be used to communicate a deposit fee
- name: shares
type: uint256
# Perform a deposit of assets from the caller to the vault
# NOTE: Conditions of deposit should match as closely as possible to
# those described by `previewDeposit`. Any discrepency could
# cause a revert due to tight slippage bounds by caller.
# NOTE: caller should pre-approve this deposit with `asset.approve`
- name: deposit
type: function
stateMutability: nonpayable
inputs:
# Party who should receive the deposited shares
- name: receiver
type: address
# Maximum number of tokens to process in the deposit
- name: assetsToDeposit
type: uint256
outputs:
# Number of shares created and given to `receiver`
# NOTE: Should revert if `sharesCreated < minShares`
- name: sharesCreated
type: uint256
# Allow an on-chain or off-chain user to simulate the effects of their
# mint at the current block and on-chain conditions.
# NOTE: This value should simulate as closely as possible the real
# outcome of a share mint. Integrators to this contract expect the
# represented slippage or loss to be under 1 basis point of accuracy.
- name: previewMint
type: function
stateMutability: view
inputs:
# The maximuim number of shares the depositor wishes to create
# NOTE: Vault will simulate attempt to deposit and create up to `maxShares`
- name: maxShares
type: uint256
outputs:
# Number of assets that need to deposited to mint
# NOTE: any discrepency between `pricePerShare / minAssets`
# and `sharesCreated` should be considered slippage in share
# price or some other type of condition, meaning the depositor
# will lose assets by depositing
# NOTE: can also be used to communicate a deposit fee
- name: minAssets
type: uint256
# Exact number of shares created for `minAssets`
# NOTE: This number may be less than `maxShares` to communicate a
# "deposit limit" or other such concept, where less than
# the expected number of shares were created.
# e.g. `sharesCreated <= maxShares`
- name: sharesCreated
type: uint256
# Perform a mint of shares from the Vault to the caller
# NOTE: Conditions of mint should match as closely as possible to
# those described by `previewMint`. Any discrepency could
# cause a revert due to tight slippage bounds by caller.
# NOTE: caller should pre-approve this minting with `asset.approve`
- name: mint
type: function
stateMutability: nonpayable
inputs:
# Party who should receive the deposited shares
- name: receiver
type: address
# Maximum number of shares to create in the mint
- name: sharesToMint
type: uint256
outputs:
# Number of assets deposited to the Vault
- name: assetsAccepted
type: uint256
# Event emitted when tokens are deposited into the vault
- name: Deposit
type: event
inputs:
# The user who triggered the deposit
- name: sender
indexed: true
type: address
# The user who is able to withdraw the created shares
- name: receiver
indexed: true
type: address
# Number of underlying tokens sent to the vault
- name: value
indexed: false
type: uint256
# Allow an on-chain or off-chain user to simulate the effects of their
# withdrawal at the current block and on-chain conditions.
# NOTE: This value should simulate as closely as possible the real
# outcome of a withdrawal. Integrators to this contract expect the
# represented slippage or loss to be under 1 basis point of accuracy.
- name: previewWithdraw
type: function
stateMutability: view
inputs:
# Maximum number of shares they wish to withdraw
# NOTE: Vault will simulate attempt to withdraw up to `maxShares`
- name: maxShares
type: uint256
outputs:
# Number of shares that Vault has redeemed
# NOTE: This number may be less than `maxShares` to communicate a
# "withdrawal limit" or other such concept, where less than
# `maxShares` was redeemed by depositor, and the rest was left
# untouched e.g. `redeemedShares <= maxShares`
- name: redeemedShares
type: uint256
# Number of tokens transferred out for `redeemedShares`
# NOTE: any discrepency between `maxAssets / pricePerShare`
# and `redeemedShares` should be considered slippage in share
# price meaning the depositor has lost tokens by withdrawing
# NOTE: `maxTokens` is only an *estimate* of the amount that will be
# withdrawn, and should not have to fully perform a withdrawal,
# but simply be as accurate as possible to the withdrawal amount
# so as to be useful for depositors to gauge potential slippage.
# NOTE: can also be used to communicate a withdrawal fee
- name: assets
type: uint256
# Perform a redemption of shares from the caller's balance
# NOTE: Conditions of withdrawal should match as closely as possible to
# those described by `previewWithdraw`. Any discrepency could
# cause a revert due to tight slippage bounds by caller.
- name: withdraw
type: function
stateMutability: nonpayable
inputs:
# Number of shares they wish to redeem
- name: sharesToRedeem
type: uint256
# Party who should receive redeemed tokens
- name: receiver
type: address
outputs:
# Number of tokens redeemed and sent to receiver
# NOTE: Integrators to untrusted Vaults should indepedently check
# token balance increase
- name: tokensWithdrawn
type: uint256
# Same as `withdraw`, but shares are being redeemed on behalf of
# another party who has previous approved via Vault's ERC20 approval flow
- name: withdrawFrom
type: function
stateMutability: nonpayable
inputs:
# Owner who shares should be redeemed from
# NOTE: `owner` should pre-approve this redemption with Vault
- name: owner
type: address
# Same as `withdraw`
- name: receiver
type: address
# Same as `withdraw`
- name: sharesToRedeem
type: uint256
outputs:
# Same as `withdraw`
- name: tokensWithdrawn
type: uint256
# Event emitted when tokens are withdrawn from the vault by a depositor.
- name: Withdraw
type: event
inputs:
# The user who triggered the withdrawal
- name: owner
indexed: true
type: address
# The user who received the withdrawn tokens
- name: receiver
indexed: true
type: address
# The number of underlying tokens sent out of the vault
- name: value
indexed: false
type: uint256
ERC4626SlippageProtection:
# Add this optional extension if you would like to give users
# the ability to specify what amount of slippage loss they are
# comfortable with.
# NOTE: Not needed in many cases.
# Same as `ERC4626.deposit`
- name: deposit
type: function
stateMutability: nonpayable
inputs:
# Same as `ERC4626.deposit`
- name: receiver
type: address
# Same as `ERC4626.deposit`
- name: assetsToDeposit
type: uint256
# Minimum number of shares to be accepted by depositor
# Default:
# `minShares = assetsToDeposit / pricePerShare`
# NOTE: Only used as a guide for untrusted Vaults, integrators
# should enforce that `minShares <= sharesCreated` afterwards
- name: minShares
optional: true
type: uint256
outputs:
# Same as `ERC4626.deposit`
# NOTE: Should revert if `sharesCreated < minShares`
- name: sharesCreated
type: uint256
# Same as `ERC4626.withdraw`
- name: withdraw
type: function
stateMutability: nonpayable
inputs:
# Same as `ERC4626.withdraw`
- name: sharesToRedeem
type: uint256
# PSame as `ERC4626.withdraw`
- name: receiver
type: address
# Minimum number of tokens to be accepted by depositor
# Default:
# `minTokens = sharesToRedeem * pricePerShare`
# NOTE: Only used as a guide for untrusted Vaults, integrators
# should enforce that `minTokens <= tokensWithdrawn` afterwards
- name: minTokens
optional: true
type: uint256
outputs:
# Same as `ERC4626.withdraw`
# NOTE: Should revert if `tokensWithdrawn < minTokens`
- name: tokensWithdrawn
type: uint256
@aallamaa
Copy link

aallamaa commented Jan 26, 2022

Would it be okay to use tokensOf(depositor) to represent this more explicit case, and then use pricePerShare to represent the time- and size-weighted average of all depositors' share prices?

Yes I think it would be ok, over which period of time would this price be calculated ?
I also was thinking of adding an APY function.

@fubuloubu
Copy link
Author

Yes I think it would be ok, over which period of time would this price be calculated ?

The current average of all depositors, probably tracked by a continuously updating variable. I meant for that function to sort of serve as an "instantanous" gauge of current value, but I guess on average. Actually, maybe it makes more sense just to get rid of it, if it's contextual, it's better off just using tokensOf(depositor) instead to get an accurate reading.

I also was thinking of adding an APY function.

APY functions are really hard. And if you make it contextual to the depositor... ngmi lol. I think it should perhaps be an extension or separate EIP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment