Platform: Code4rena
Start Date: 19/10/2021
Pot Size: $30,000 ETH
Total HM: 5
Participants: 13
Period: 3 days
Judge: GalloDaSballo
Total Solo HM: 4
Id: 43
League: ETH
Rank: 7/13
Findings: 2
Award: $695.14
🌟 Selected for report: 2
🚀 Solo Findings: 0
0.1639 ETH - $586.30
gpersoon
The function depositRewardTokens divides the "amount" of tokens by allocatedTokensPerEpoch to calculate the endEpoch. When "amount" isn't a multiple of allocatedTokensPerEpoch the result of the division will be rounded down, effectively losing a number of tokens for the rewards.
For example if allocatedTokensPerEpoch is set to 3e18 and "amount" is 100e18 then endEpoch will be increased with 33e18 and the last 1e18 tokens are lost.
A similar problem occurs here:
In depositRewardTokens() add, in the beginning of function, before the if statement: require(amount % allocatedTokensPerEpoch == 0,"Not multiple");
In takeOutRewardTokens() add: require(amount % allocatedTokensPerEpoch == 0,"Not multiple");
Update setAllocatedTokensPerEpoch() to something like:
if (endEpoch != 0) {
...
uint128 futureRewards = ...
require(futureRewards % amount ==0,"Not multiple");
...
} else { // to prevent issues with _stake()
require(rewardsLocked % allocatedTokensPerEpoch==0,"Not multiple");
}
#0 - kitti-katy
2021-10-21T18:44:11Z
Agreed, the original assumption was that the owner would always make sure the take out and deposit amount is multiple of emission rate. But yes, this is good to add the check. Also it is not that risky since the emission rate wouldn't be that high per epoch and the loss will always be less than the emission rate.
#1 - GalloDaSballo
2021-11-02T01:25:47Z
Agree with the finding, since it's a rounding error the max loss in rewards can at most be 1 less than the denominator
That said, this is a Medium Severity Finding as per the doc:
2 — Med: Assets not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements.
Where in this case the rounding is a way to leak value (loss of yield)
🌟 Selected for report: gpersoon
0.0304 ETH - $108.84
gpersoon
The function _stake() initializes endEpoch using the value of rewardsLocked. Afterwards rewardsLocked is no longer used (because now endEpoch !=0)
So you can set rewardsLocked to 0 save a bit of gas.
Update to code of _stake() to: if (endEpoch == 0) { endEpoch = uint128(block.number) + rewardsLocked / allocatedTokensPerEpoch; rewardsLocked = 0; // no longer used and saves a bit of gas }
#0 - GalloDaSballo
2021-11-01T17:02:01Z
I believe gas refunds have bee removed when EIP-1559 made it to mainnet, that said, the sponsor has applied the improvement