morpho-org / morpho-v1-optimizers-vaults

ERC4626 vaults to ease interaction with Morpho Optimizers.

Home Page:https://vaults.morpho.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[R&D review] `index` unit for rewards management (aave-v3)

MathisGD opened this issue · comments

index is in unit 1e36 in the supply vault for aave-v3.

First, the comment is wrong:

        uint128 index; // User rewards index for a given reward token (in wad).

And this is very unusual. I would use wad, or ray IFF there are some good reasons to use it (more precision), and they should be explicited. It would also allow to use the rayMaths lib.

I think we wanted to have the least diff with Aave's code, so we used the same base
Though I understand it's strange, I don't have a strong opinion over this. Both are ok to me but I do have a preference for the ray-based unit which feels more natural

In parallel: as the vaults are deployed and used, isn't it too late for the change?

In parallel: as the vaults are deployed and used, isn't it too late for the change?

It is only for Aave-v3 so no it is ok

We'll not sure wad as discussed on Friday @MathisGD, why not ray

I prefer using ray for indexes in general as I think more precision is appropriate and can make a material difference for some tokens.

Actually on second thought, it is probably correct to use WAD in this case because the index is a uint128. The max value of this is ~3e38 which to me is too uncomfortably close to overflowing when using RAYs.

Hmm SCALE = 1e36 is only used during operations on uint256. The issue is the following:

if (supply > 0 && claimedAmount > 0) {
    rewardsIndexMem =
       rewardsIndex[rewardToken] +
       claimedAmount.mulDivDown(SCALE, supply).safeCastTo128();
    rewardsIndex[rewardToken] = rewardsIndexMem;
} else rewardsIndexMem = rewardsIndex[rewardToken];

If SCALE = WAD then, the rewards index is no updated if:
claimedAmount.mulDivDown(SCALE, supply) == 0 <=>
claimedAmount * WAD / supply <= 1<=>
claimedAmount <= supply / WAD

This can happen if the supply is every large compared to claimedAmount. Perhaps this is not an issue but I think it's worth mentioning.

Actually on second thought, it is probably correct to use WAD in this case because the index is a uint128. The max value of this is ~3e38 which to me is too uncomfortably close to overflowing when using RAYs.

Well the index can be at 1e11 before overflowing (whereas it's not even supposed to be greater than the first order of magnitude after RAY: 1e28) so I think we've got enough buffer (ie it's not uncomfortable to me)

The issue raised by @MerlinEgalite is concerning though

If I understand that issue correctly, then using RAY would make that issue worse, right?

If I understand that issue correctly, then using RAY would make that issue worse, right?

I thought so at first glance but no: using RAY decreases the threshold supply / SCALE at which if claimedAmount is lower then rewards are not accrued. So using a larger SCALE decreases the chances of this issue happening

Another solution and to make things safer is to pass from uint128 to uint256, we lose in term of gas but at least we remove these doubts

Another solution and to make things safer is to pass from uint128 to uint256, we lose in term of gas but at least we remove these doubts

I don't think it's necessary as multiplication are performed on uint256 and then divided by the supply which brings back the unsigned int to a 128 bits range

Btw this is not related to this issue. I think your suggestion solves another issue not yet existing, related to overflow (that I suggest we create if we want to discuss further about it)

I think this issue is directly concerned by overflow. What we want; an index that accrues well over time (no loss of information) but does fit in a uint<x> at the same time.

Moreover, here there's no division this is multiplication by SCALE. So the overflow could break the rewards accrual and the vaults entirely since we update rewards at each interaction. So I think my message is still relevant to this issue.

I can create an issue for that but not sure it will ease the discussion.

For one comment you did:

Well the index can be at 1e11 before overflowing (whereas it's not even supposed to be greater than the first order of magnitude after RAY: 1e28) so I think we've got enough buffer (ie it's not uncomfortable to me)

I'm not sure how did you compute it. Can you elaborate it?

When I'm doing the math let's say the largest value is 3e38, then you want that:
claimedAmount * 1e36 / supply < 3e38 <=> claimedAmount / supply < 3e2

This should hold when you sum over all interactions:
$\sum_{i=0} \frac{claimedAmount_i}{supply_i} &lt; 3e2$

This might hold since claimedAmount / supply > 0 means that the rewards are distributed very quickly...

So because we can assume that claimedAmount < 300 * supply, you've just proven that overflowing using uint128 cannot happen even when using SCALE = 1e36?

EDIT: ok I just get that the index is in the scale of SCALE which is 1e36. We still have 300.0 buffer which is enough to me (the index not supposed to even grow past 2.0...)

But for safety we can cast it to 256 bits as you suggested

So after some research, I think moving to RAY is safer.

If we take SCALE = 1e18, let's say:

  • totalSupply = 1e25 (10M of shares)
  • claimedAmount = 1e6 (1 USDC)

Then reward index accrued is 0, because claimedAmount.SCALE / totalSupply = 0. If you trigger the update when only one dollar is accrued this dollar is lost.

If we take SCALE = 1e36 there's no such problem (unless you're taking un reasonable values) as described here.

Thus, I think RAY is a good middle ground. If we take the example above with SCALE = 1e27, then even with 100M of shares (totalSupply = 1e26), you can still accrue dust rewards claimedAmount = 1 (0.00001 USDC!).