EIP-4626: Yield Bearing Vault Standard

To revert, or not to revert, that is the question.

The specifications does not explicitly state if a deposit, mint, withdraw, redeem should revert or not revert if zero amounts are returned.

Looking at the various implementations, most are going with they should not revert unless there is an integer overflow. For example, rounding down the result by subtracting it by one when the value is an unsigned zero will revert.

See Should ERC4626 txs revert with zero amounts? · Issue #314 · transmissions11/solmate · GitHub

I’m too late for this comment, given the standard has been finalized and is now seeming to get some traction (yay!). So I guess I’m asking for advice. In accounting, Assets - Liabilities == Equity. Given many vaults don’t have liabilities, Assets == Equity. However, for vaults that do have liabilities (accrued fees, or senior loans to the vault), they are not the same. I’d love to see totalLiabilities() and totalEquity() as part of the standard, but I’m a little late for that. Here’s the advice I’d like: seems like a lot of clients might make the assumption that totalAssets() == convertToAssets(totalSupply()). So I have a choice: implement so totalAssets() is actually equity value (assets - liabilities), so that assumption is correct, and provide a nonstandard totalLiabilities() function so clients could add them to get actual total assets. Or, totalAssets() can be the total amount managed by the vault, and there’s a separate function totalEquity() which equals convertToAssets(totalSupply()). Thoughts?

Could someone point out a compliant implementation of ERC4626 with fees? We don’t know how to interpret the specification’s wording about functions being “inclusive of fees”.

There are many different types of fees that you could use, but some common ones are:

  1. Deposit fee, typically a % of assets deposited is taken as a fee, with shares issued being against the remaining portion
  2. Withdrawal fee, a % of shares (or assets) is levied as a fee for exiting the vault, with a portion of the assets represented by those shares being returned
  3. Performance fee, a % of total yield is deducted
  4. Management fee, a % of the assets your shares represent reduces over time at a specific rate (like how a Trust works e.g. GBTC)

In all cases, we designed 4626 to be inclusive of fees so that apples to apples comparisons of protocols are more possible. For example, on deposit and withdrawal, the language enforces that it must be the total amount coming in, and the final amount issued as well as the total amount redeemed and the final amount sent out. This means that withdrawal and deposit fees should be levied internally to the code, and the amount post fee is given as an output. The end result is the effective exchange rate you paid is worse, which means the vault would accurately describe the exchange of assets to shares and back again that is occurring.

Management fees and performance fees are a little easier, both are basically the rate at which the conversion functions change their output. The guidance there is that both preview and convert functions should be inclusive of these types of fees so that their answer remains accurate to what real depositors would see.

Originally we tried to be more specific about fees, but that’s a hard battle because there are so many different ways of imposing them. The guidance provided by ERC4626 is more general, essentially just try to make your functions be as honest as possible to what real depositors should expect.

2 Likes

It is unclear to me what the events should be when dealing with fees.

Lets take an example, where there is a 5% deposit fee,

  • a user calls deposits with 100 DAI
  • 5 DAI go to the feeRecipient (whoever that is)
  • 95 DAI go to the vault
  • 95 shares are minted in exchange.

In that context, previewDeposit(100) is 95. But what should the use for assets ? 100? 95?

Same question for a mint:

  • a user calls mint with 100 shares
    • these 100 shares need 100 DAI, plus 6 DAI of fees
  • 6 DAI go to the feeRecipient (whoever that is)
  • 100 DAI go to the vault
  • 100 shares are minted in exchange.

In that context, previewMint(100) is 106. But what should the use for assets in the event ? 100? 106?

FIY, the event is described as

Deposit

sender has exchanged assets for shares, and transferred those shares to owner.

MUST be emitted when tokens are deposited into the Vault via the mint and deposit methods.

- name: Deposit
  type: event
  inputs:
    - name: sender
      indexed: true
      type: address
    - name: owner
      indexed: true
      type: address
    - name: assets
      indexed: false
      type: uint256
    - name: shares
      indexed: false
      type: uint256

said otherwize, should the “assets” params in these event reflect:

  • How much the user paid/got
  • How much the vault gained/lost

How much the user paid/got

After some discussion we think this is the right option according to the guideline shared above:

1 Like

Please who can help me with a list of platforms that talks with ERC 4626 standard? I mean platforms that expects to integrate dapps built on ERC 4626 → ERC-4626: Tokenized Vaults

1 Like

One major failure of this standard, in hindsight, was not standardizing whether deposits and withdrawals must emit Transfer events from/to the zero address. Some 4626 vaults emit these Transfer events and some do not. This complicates accounting.