Flywheel index mismatch issue during optOut
Description
The optOut
function is called to stop accruing bribes for each gauge.
The userGaugeFlywheels[msg.sender][strategy]
keeps the all flywheel
addresses that were optIn
for the msg.sender
and strategy
address. Also, the userGaugeflywheelId[msg.sender][strategy][flywheel]
keeps the corresponding index of flywheel
in the userGaugeFlywheels
array.
During the optOut
function execution, there is an optimization if the index of the flywheel
address is not the last in the userGaugeFlywheels[msg.sender][strategy]
array. To optimize the deletion of the flywheel
address from userGaugeFlywheels[msg.sender][strategy]
, this flywheel
address will be rewritten by the last flywheel
address and the last element will be pop
ped from the array.
The problem is that userGaugeflywheelId[msg.sender][strategy][flywheel]
, where flywheel
is the address of the last address inside userGaugeFlywheels[msg.sender][strategy]
, continues to store the index of the last element and is not updated to the new index of the moved address of the flywheel
.
function optOut(ERC20 strategy, FlywheelCore flywheel) external {
FlywheelCore[] storage bribeFlywheels = userGaugeFlywheels[msg.sender][strategy];
uint256 userFlywheelId = userGaugeflywheelId[msg.sender][strategy][flywheel];
if (userFlywheelId == 0) revert NotOptedIn();
flywheel.accrue(strategy, msg.sender);
flywheelStrategyGaugeWeight[strategy][flywheel] = flywheelStrategyGaugeWeight[strategy][flywheel]
- bHermesGauges(owner()).getUserGaugeWeight(msg.sender, address(strategy));
uint256 length = bribeFlywheels.length;
if (length != userFlywheelId) bribeFlywheels[userFlywheelId - 1] = bribeFlywheels[bribeFlywheels.length - 1];
bribeFlywheels.pop();
userGaugeflywheelId[msg.sender][strategy][flywheel] = 0;
}
Impact
The function could experience a malfunction. And if invoked by a user in the future, it might result in the function being reverted, preventing the user from stopping the accrual of their tokens.
Recommendations
We recommend adding code that will update the index value to a new one.
function optOut(ERC20 strategy, FlywheelCore flywheel) external {
...
uint256 length = bribeFlywheels.length;
if (length != userFlywheelId) bribeFlywheels[userFlywheelId - 1] = bribeFlywheels[bribeFlywheels.length - 1];
+ userGaugeflywheelId[msg.sender][strategy][bribeFlywheels[length - 1]] = userFlywheelId;
...
}
Remediation
This issue has been acknowledged by Maia DAO, and a fix was implemented in commit 607537d2↗.