A malicious authorized user can steal all unclaimed rewards and break the reward accounting
Even if the authorized user is benevolent the fact that there is a rug vector available may negatively impact the protocolโs reputation. Furthermore since this contract is meant to be used by other projects, the trustworthiness of every project cannot be vouched for.
By setting a booster that returns zero for all calls to boostedBalanceOf() where the user address is not under the attackerโs control, and returning arbitrary values for those under his/her control, an attacker can choose specific amounts of rewardToken to assign to himself/herself. The attacker can then call claimRewards() to withdraw the funds. Any amounts that the attacker assigns to himself/herself over the amount that normally would have been assigned, upon claiming, is taken from other usersโ unclaimed balances, since tokens are custodied by the flywheelRewards address rather than per-user accounts.
File: flywheel-v2/src/FlywheelCore.sol
182 /// @notice swap out the flywheel booster contract
183 function setBooster(IFlywheelBooster newBooster) external requiresAuth {
184 flywheelBooster = newBooster;
185
186 emit FlywheelBoosterUpdate(address(newBooster));
187 }
File: flywheel-v2/src/FlywheelCore.sol
258 uint256 supplierTokens = address(flywheelBooster) != address(0)
259 ? flywheelBooster.boostedBalanceOf(strategy, user)
260 : strategy.balanceOf(user);
261
262 // accumulate rewards by multiplying user tokens by rewardsPerToken index and adding on unclaimed
263 uint256 supplierDelta = (supplierTokens * deltaIndex) / ONE;
264 uint256 supplierAccrued = rewardsAccrued[user] + supplierDelta;
265
266 rewardsAccrued[user] = supplierAccrued;
File: flywheel-v2/src/FlywheelCore.sol
119 function claimRewards(address user) external {
120 uint256 accrued = rewardsAccrued[user];
121
122 if (accrued != 0) {
123 rewardsAccrued[user] = 0;
124
125 rewardToken.safeTransferFrom(address(flywheelRewards), user, accrued);
Projects also using BaseFlywheelRewards or its child contrats, are implicitly approving infinite transfers by the core
File: flywheel-v2/src/rewards/BaseFlywheelRewards.sol
25 constructor(FlywheelCore _flywheel) {
26 flywheel = _flywheel;
27 ERC20 _rewardToken = _flywheel.rewardToken();
28 rewardToken = _rewardToken;
29
30 _rewardToken.safeApprove(address(_flywheel), type(uint256).max);
31 }
The attacker need not keep the booster set this way - he/she can set it, call accrue() for his/her specific user, and unset it, all in the same block
Code inspection
Make flywheelRewards immutable, or only allow it to change if there are no current users
The text was updated successfully, but these errors were encountered:
All reactions