[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
touint256
, 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:
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!).