Lucene search

K
code423n4Code4renaCODE423N4:2022-04-XTRIBE-FINDINGS-ISSUES-23
HistoryApr 25, 2022 - 12:00 a.m.

FlywheelCore.setBooster() can be used to steal unclaimed rewards

2022-04-2500:00:00
Code4rena
github.com
7

Lines of code

Vulnerability details

Impact

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.

Proof of Concept

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       }

<https://github.com/fei-protocol/flywheel-v2/blob/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/FlywheelCore.sol#L182-L187&gt;

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;

<https://github.com/fei-protocol/flywheel-v2/blob/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/FlywheelCore.sol#L258-L266&gt;

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);

<https://github.com/fei-protocol/flywheel-v2/blob/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/FlywheelCore.sol#L119-L125&gt;

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        }

<https://github.com/fei-protocol/flywheel-v2/blob/77bfadf388db25cf5917d39cd9c0ad920f404aad/src/rewards/BaseFlywheelRewards.sol#L25-L31&gt;

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

Tools Used

Code inspection

Recommended Mitigation Steps

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