Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

Not Accounting For Time Decay in `veRAACToken::getTotalVotingPower`, Will Cause A Discrepancy Between Total and Sum of Individual Voting Powers, Causing A Percentage Of Users' Rewards in FeeCollector To Remain Unclaimable

Summary

The veRAACToken contract's implementation of voting power decay creates a discrepancy between the total voting power reported by getTotalVotingPower() and the actual sum of all users' voting power. This discrepancy grows over time and results in a portion of users' rewards in `FeeCollector` to become permanently unclaimable.

Vulnerability Details

The issue is due to how the individual voting power and total voting power are calculated. The discrepancy occurs because individual voting power is calculated after accounting for the voting power decay of each token over time, while the total voting power doesn't account for this decay and instead just returns the total tokens in circulation. The difference grows larger as more time passes causing a loss for users' when claiming their tokens in `FeeCollector::claimRewards`.

POC

To use foundry in the codebase, follow the hardhat guide here: Foundry-Hardhat hybrid integration by Nomic foundation

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {FeeCollector} from "../../../../contracts/core/collectors/FeeCollector.sol";
import {RAACToken} 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; //1%
uint256 initialBurnTaxRate = 50; //0.5%
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 testUserVotingPowerDoesntAddUpToTotalVotingPowerCausingLossOfUserFees() public {
//Two users deposit raacToken and mints veRaacToken
address user1 = makeAddr("user1");
address user2 = makeAddr("user2");
uint256 mintAmount = 1e18;
uint256 maxDuration = veRAACTok.MAX_LOCK_DURATION();
vm.startPrank(admin);
raacToken.mint(user1, mintAmount);
raacToken.mint(user2, mintAmount);
vm.stopPrank();
vm.startPrank(user1);
raacToken.approve(address(veRAACTok), mintAmount);
veRAACTok.lock(mintAmount, maxDuration);
console.log("User1 veRaac balance: ", veRAACTok.balanceOf(user1));
vm.stopPrank();
vm.startPrank(user2);
raacToken.approve(address(veRAACTok), mintAmount);
veRAACTok.lock(mintAmount, maxDuration);
console.log("User2 veRaac balance: ", veRAACTok.balanceOf(user2));
vm.stopPrank();
//move time, get and add both users voting power
vm.warp(7 days);
uint256 expectedTotalVotingPower = veRAACTok.getTotalVotingPower();
uint256 actualTotalVotingPower = veRAACTok.getVotingPower(user1) + veRAACTok.getVotingPower(user2);
//the users' voting power doesn't add up to the total returned by the veRAACToken contract
assertGt(expectedTotalVotingPower, actualTotalVotingPower);
}
}

Impact

A portion of fees allocated to veRAACToken holders becomes unclaimable and this percentage of unclaimable rewards grows over time.

Tools Used

Manual review, foundry test suite

Recommendations

Modify `getTotalVotingPower` to account for voting decay

Updates

Lead Judging Commences

inallhonesty Lead Judge 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Time-Weighted Average Logic is Not Applied to Reward Distribution in `FeeCollector`

inallhonesty Lead Judge 2 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Time-Weighted Average Logic is Not Applied to Reward Distribution in `FeeCollector`

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.