The example below describes why the return statement is problematic. This is for a user with 50% voting power.
pragma solidity ^0.8.19;
import {FeeCollector} from "../../../../contracts/core/collectors/FeeCollector.sol";
import {RAACToken, PercentageMath} from "../../../../contracts/core/tokens/RAACToken.sol";
import {veRAACToken} from "../../../../contracts/core/tokens/veRAACToken.sol";
import {Test, console} from "forge-std/Test.sol";
contract TestSuite is Test {
FeeCollector feeCollector;
RAACToken raacToken;
veRAACToken veRAACTok;
address treasury;
address repairFund;
address admin;
uint256 initialSwapTaxRate = 100;
uint256 initialBurnTaxRate = 50;
function setUp() public {
treasury = makeAddr("treasury");
repairFund = makeAddr("repairFund");
admin = makeAddr("admin");
raacToken = new RAACToken(admin, initialSwapTaxRate, initialBurnTaxRate);
veRAACTok = new veRAACToken(address(raacToken));
feeCollector = new FeeCollector(address(raacToken), address(veRAACTok), treasury, repairFund, admin);
vm.startPrank(admin);
raacToken.setFeeCollector(address(feeCollector));
raacToken.setMinter(admin);
vm.stopPrank();
}
function testUsersLoseSomeFeesDueToBadReturnStatement() public {
address user = makeAddr("user");
address user2 = makeAddr("user2");
uint256 firstAmountDistributed = 1e18;
uint256 secondAmountDistributed = 2e18;
uint256 maxDuration = veRAACTok.MAX_LOCK_DURATION();
vm.startPrank(admin);
raacToken.mint(admin, firstAmountDistributed + secondAmountDistributed);
raacToken.mint(user, firstAmountDistributed);
raacToken.mint(user2, firstAmountDistributed);
raacToken.approve(address(feeCollector), firstAmountDistributed + secondAmountDistributed);
feeCollector.collectFee(firstAmountDistributed, 0);
vm.stopPrank();
vm.startPrank(user);
raacToken.approve(address(veRAACTok), firstAmountDistributed);
veRAACTok.lock(firstAmountDistributed, maxDuration);
vm.stopPrank();
vm.startPrank(user2);
raacToken.approve(address(veRAACTok), firstAmountDistributed);
veRAACTok.lock(firstAmountDistributed, maxDuration);
vm.stopPrank();
vm.prank(admin);
feeCollector.distributeCollectedFees();
vm.prank(user);
feeCollector.claimRewards(user);
console.log("User voting power at first claim: ", veRAACTok.getVotingPower(user));
console.log("Total voting power at first claim: ", veRAACTok.getTotalVotingPower());
vm.warp(7 days + 1);
vm.prank(admin);
feeCollector.collectFee(secondAmountDistributed, 0);
uint256 totalFees = feeCollector._calculateTotalFees();
uint256[4] memory shares = feeCollector._calculateDistribution(totalFees);
vm.prank(admin);
feeCollector.distributeCollectedFees();
uint256 userVotingPower = veRAACTok.getVotingPower(user);
uint256 totalVotingPower = veRAACTok.getTotalVotingPower();
console.log("User voting power at second claim: ", userVotingPower);
console.log("Total voting power at second claim: ", totalVotingPower);
uint256 expectedUserShareSecond = (shares[0] * userVotingPower) / totalVotingPower;
uint256 actualUserShareSecond = feeCollector._calculatePendingRewards(user);
assertGt(expectedUserShareSecond, actualUserShareSecond);
}
}
function _calculatePendingRewards(address user) internal view returns (uint256) {
uint256 userVotingPower = veRAACToken.getVotingPower(user);
if (userVotingPower == 0) return 0;
uint256 totalVotingPower = veRAACToken.getTotalVotingPower();
if (totalVotingPower == 0) return 0;
// Calculate share of new distributions only
+ uint256 newDistributions = totalDistributed - userRewards[user];
+ uint256 share = (newDistributions * userVotingPower) / totalVotingPower;
+ return share;
- uint256 share = (totalDistributed * userVotingPower) / totalVotingPower;
- return share > userRewards[user] ? share - userRewards[user] : 0;
}