In the case where Alice has already claimed her reward, and Bob increases his voting power before claiming his reward, Bob will not be able to claim the reward, since the changed voting power will determine Bob's share as greater than what is available.
function _calculatePendingRewards(address user) internal view returns (uint256) {
# here current voting power is used
@> uint256 userVotingPower = veRAACToken.getVotingPower(user);
if (userVotingPower == 0) return 0;
# here current total voting power is used
@> uint256 totalVotingPower = veRAACToken.getTotalVotingPower();
if (totalVotingPower == 0) return 0;
uint256 share = (totalDistributed * userVotingPower) / totalVotingPower;
return share > userRewards[user] ? share - userRewards[user] : 0;
}
pragma solidity 0.8.28;
import {Test} from "forge-std/Test.sol";
import {RAACToken} from "src/core/tokens/RAACToken.sol";
import {veRAACToken} from "src/core/tokens/veRAACToken.sol";
import {FeeCollector} from "src/core/collectors/FeeCollector.sol";
contract FeeCollectorTestVotingPowerChange is Test {
RAACToken public raacToken;
veRAACToken public veToken;
FeeCollector public feeCollector;
function setUp() public {
raacToken = new RAACToken(address(this), 0, 0);
raacToken.setMinter(address(this));
veToken = new veRAACToken(address(raacToken));
feeCollector = new FeeCollector(
address(raacToken),
address(veToken),
makeAddr("treasury"),
makeAddr("repairFund"),
address(this));
raacToken.manageWhitelist(address(feeCollector), true);
raacToken.manageWhitelist(address(veToken), true);
raacToken.manageWhitelist(address(this), true);
}
function test_votingPowerChange() public {
address alice = makeAddr("alice");
address bob = makeAddr("bob");
raacToken.mint(address(this), 1000);
raacToken.mint(alice, 1000);
raacToken.mint(bob, 2000);
vm.startPrank(alice);
raacToken.approve(address(veToken), 1000);
veToken.lock(1000, 365 days);
vm.stopPrank();
vm.startPrank(bob);
raacToken.approve(address(veToken), 2000);
veToken.lock(1000, 365 days);
vm.stopPrank();
raacToken.approve(address(feeCollector), 1000);
feeCollector.collectFee(1000, 0);
feeCollector.distributeCollectedFees();
feeCollector.claimRewards(alice);
vm.prank(bob);
veToken.increase(1000);
feeCollector.claimRewards(bob);
}
}
This vulnerability can be used to manipulate the rewards distribution.
Store timestamp when rewards were distributed and use it to query the user's and total voting power at that moment.