ERC-4626 unexpectedly large change in share:asset conversion rate when vault emptied and refilled

Hi, I'm seeing an unexpected big change in the share:asset conversion rate when an ERC-4626 vault is emptied and refilled. When the vault is first created, the share:asset ratio is 1:1 and the vault has no share nor assets. When I run through a series of deposits and donations (explained below), then empty the vault and refill it, I'm seeing the share:asset conversion jump to 1:2.

I'm wondering is this expected behaviour, or do I need to protect the vault against storing tiny amounts of asset when shares is zero. For example if the vault shares ever drops to zero then the vault could burn any tiny asset values remaining.

Example

  1. Assume a vault using OZ v5.02 and the standard ERC-4626 contract. All values are default, there are no fees.
  2. The vault is for an asset called ASSET which has 6 decimals.
  3. An investor puts in 800_000 ASSET, gets back 800_000 SHARE.
  4. The vault earns +10% more ASSET, so 80_000 ASSETS gets donated in.
  5. The vault now has 880_000 ASSET and 800_000 SHARE.
  6. Investor wants to cash out, they want to convert all their shares to assets.
  7. They call vault.redeem(800_000 SHARE);
  8. They get back 879_999 ASSET, and 1 ASSET remains in the vault. The fact they get not all assets is due to rounding inside the calculation in _convertToAssets. This makes sense I think because the vault must never have too little assets, so rounding is done in favour of the vault.
  9. The vault now has 1 ASSET and 0 SHARE. It is empty of shares, but still has 1 ASSET.
  10. The investor decides to re-invest in the vault. They take 800_000 ASSET and deposit it.
  11. This time they get back 400_000 SHARE! Why is this? It is because the vault calculates the "share per asset" as floor of [ totalAssets + 1 / totalSupply (ie total shares) + 1 ] assets per share. This calculation becomes floor [ 1 + 1 / 0 + 1 ] = 2.

Hi @NethDote,

Your analysis is correct, the vault always rounds in favor of itself, so emptying the vault is essentially impossible given there will always be 1 asset in the vault. A more in-depth analysis of why this rounding is added and how to configure it through a virtual offset is available in our documentation

This is somewhat expected behavior since it's not affecting the security of your vault, though we always recommend setting a virtual offset. However, it's uncommon to fill a vault, empty it, and then refill it.

1 Like

Thanks Ernesto. I don't think this is an issue, but I'll update the results here in case it helps anyone else:

I experimented with various values of _decimalsOffset() and the behaviour of the share conversion rate "jumping" remains the same. For example, if I add a decimals offset of 3, then the share gets 9 decimals, whilst the asset remains at 6 decimals. Then in the example above, everything behaves as before, just the SHARE values become 1000x larger. So in step 3 the investor got back 800_000_000 SHARE and in step 11 the investor got back the unexpected 400_000_000 SHARE. The ASSET values all remained the same.

As you say, it doesn't affect the security of the vault.

What I did find, though, is that leaving a little ASSET in the vault seems to make the vault behave more like I'd expect. If I leave 1 whole natural unit of the ASSET in the vault, so 1_000_000 ASSET in this example, then in step 11 the investor got the same shares they got in step 3.

The principle is, don't completely empty the vault, and that ties in with your comment that that is an uncommon thing to do.