Omniscia Stakewise Audit
RewardEthToken Manual Review Findings
RewardEthToken Manual Review Findings
RET-01M: Circumvention of Checkpointing Mechanism
| Type | Severity | Location |
|---|---|---|
| Logical Fault | Major | RewardEthToken.sol:L173 |
Description:
The _updateRewardCheckpoint function assigns the existing rewardPerToken to a user should they have a zero balance, meaning that the checkpointing system can be circumvented to take advantage of a huge delta between newRewardPerToken and cp.rewardPerToken on new accounts, acquiring a disproportionate reward.
Example:
contracts/tokens/RewardEthToken.sol
159function _updateRewardCheckpoint(address account, uint128 newRewardPerToken) internal {160 Checkpoint memory cp = checkpoints[account];161 if (newRewardPerToken == cp.rewardPerToken) return;162
163 uint256 stakedEthAmount;164 if (account == address(0)) {165 // fetch merkle distributor current principal166 stakedEthAmount = stakedEthToken.distributorPrincipal();167 } else {168 stakedEthAmount = stakedEthToken.balanceOf(account);169 }170 if (stakedEthAmount == 0) {171 checkpoints[account] = Checkpoint({172 reward: cp.reward,173 rewardPerToken: cp.rewardPerToken174 });175 } else {176 uint256 periodRewardPerToken = uint256(newRewardPerToken).sub(cp.rewardPerToken);177 checkpoints[account] = Checkpoint({178 reward: _calculateNewReward(cp.reward, stakedEthAmount, periodRewardPerToken).toUint128(),179 rewardPerToken: newRewardPerToken180 });181 }182}Recommendation:
We advise the rewardPerToken to be set to the latest one whenever the target account has no ETH staked to ensure such a circumvention is not possible.
Alleviation:
The code now properly introduces a checkpoint of the latest value when the stakedEthAmount is equal to 0, alleviating this exhibit.