Skip to content

Latest commit

 

History

History
84 lines (62 loc) · 3.87 KB

045.md

File metadata and controls

84 lines (62 loc) · 3.87 KB

Magic Eggshell Stallion

Medium

Oracle Pause Guardian Indefinitely Grants All Stakers Full Rewards

Summary and Impact

https://github.com/withtally/staker/blob/90802966475239c3eb8aafc3dbf18edd5a0b6b1b/src/BinaryEligibilityOracleEarningPowerCalculator.sol#L130-L137 This vulnerability allows the oraclePauseGuardian role to forcefully pause the oracle logic, thereby triggering fallback behavior that grants every staker 100% earning power—regardless of their delegate’s real governance score. While the Tally ARB Staker is designed to distribute rewards only to stakers whose delegatees meet a certain eligibility threshold, pausing the oracle or allowing it to become stale bypasses this mechanism entirely.

By keeping the system perpetually paused (or failing to unpause), malicious or negligent parties undermine the primary incentive structure of the protocol. This effectively cancels out any differential rewards intended to encourage active governance participation. Although this scenario requires specific role access (i.e., the oraclePauseGuardian must be compromised, malicious, or coerced), the resulting system failure is severe enough to warrant a medium classification: the exploit is straightforward to carry out but somewhat limited by role-based privileges.

Vulnerability Detail

The core contracts (notably BinaryEligibilityOracleEarningPowerCalculator) rely on the following logic to calculate staker eligibility:

function getEarningPower(
    uint256 _amountStaked,
    address,
    address _delegatee
) external view returns (uint256) {
    if (_isOracleStale() || isOraclePaused) return _amountStaked;
    return _isDelegateeEligible(_delegatee) ? _amountStaked : 0;
}

Here, _isOracleStale() is:

function _isOracleStale() internal view returns (bool) {
    return block.timestamp - lastOracleUpdateTime > STALE_ORACLE_WINDOW;
}

And pausing is done by the oraclePauseGuardian via:

function setOracleState(bool _pauseOracle) public {
    if (msg.sender != oraclePauseGuardian) {
        revert BinaryEligibilityOracleEarningPowerCalculator__Unauthorized(
            "not oracle pause guardian", msg.sender
        );
    }
    emit OraclePausedStatusUpdated(isOraclePaused, _pauseOracle);
    isOraclePaused = _pauseOracle;
}

Problematic Code Path

  1. Pausing: isOraclePaused = true;
  2. No Score Updates: With the oracle paused (or stale past STALE_ORACLE_WINDOW), _isOracleStale() returns true.
  3. Everyone Eligible: getEarningPower() subsequently returns _amountStaked in all scenarios.

Test Code Snippet (from the exploit demonstration)

// 2. Malicious "pauseGuardian" forcibly pauses the oracle.
vm.prank(pauseGuardian);
calc.setOracleState(true);

// 3. Move forward in time > STALE_ORACLE_WINDOW => oracle becomes "stale".
vm.warp(block.timestamp + STALE_ORACLE_WINDOW + 1);

// 4. Everyone gets full earning power, ignoring real scores.
uint256 badEarningAfterPause = stakingCaller.getEarningPower(
    100 ether, stakerWithBadDelegate, address(0xBadDelegatee)
);
// badEarningAfterPause == 100 ether

Invariants dictate that “only stakers whose delegatees meet a certain eligibility score can earn rewards.” By making the system stale or paused indefinitely, this invariant is violated: all stakers are treated as meeting the threshold. This effectively defeats the protocol’s core governance-based reward distribution design.

Tool used

Manual Review and foundry

Recommendation

  1. Restrict or Decentralize Pause Authority

    • Require a multisig, DAO vote, or time-delayed mechanism for pausing. This ensures no single address can permanently subvert the protocol.
  2. Restrict or Decentralize Pause Authority

    • Require a multisig, DAO vote, or time-delayed mechanism for pausing. This ensures no single address can permanently subvert the protocol.