Core Contracts

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

Incorrect Voting Power and Weight Calculation in BoostController Contract

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/governance/boost/BoostController.sol#L385-L395
https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/governance/boost/BoostController.sol#L88-L131
https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/governance/boost/BoostController.sol#L141-L169

Summary

The BoostController contract relies on the updateTotalWeight function to calculate voting power and total weight for boost calculations. However, the updateTotalWeight function incorrectly assumes that the BoostController contract itself has locked RAAC tokens to receive veRAAC tokens and voting power. Since the BoostController contract does not lock any RAAC tokens, the voting power and weight calculations will always return 0, leading to incorrect boost calculations.


Vulnerability Details

Explanation

The updateTotalWeight function is used in two critical functions (calculateBoost and _calculateBoost) to determine the boost multiplier for users. However, the function incorrectly assumes that the BoostController contract has locked RAAC tokens to receive veRAAC tokens and voting power. As a result:

  1. Voting Power is Always 0: The BoostController contract does not lock any RAAC tokens, so its voting power will always be 0.

  2. Incorrect Boost Calculations: The boost calculations in calculateBoost and _calculateBoost rely on the voting power and weight returned by updateTotalWeight. Since these values are incorrect, the boost calculations will also be incorrect.

Root Cause in the Contract Function

The issue lies in the following lines of the updateTotalWeight function:

function updateTotalWeight()
internal
view
returns (uint256 totalWeight, uint256 totalVotingPower, uint256 votingPower)
{
return (
veToken.getLockPosition(address(this)).amount, //@audit-issue Not locking any position
veToken.getTotalVotingPower(),
veToken.getVotingPower(address(this), block.timestamp) //@audit-issue This contract did not receive tokens RAAC tokens to lock in veRAACToken contract, so it will be 0
);
}
  • veToken.getLockPosition(address(this)).amount: This will return 0 because the BoostController contract has not locked any RAAC tokens.

  • veToken.getVotingPower(address(this), block.timestamp): This will return 0 because the BoostController contract has no veRAAC tokens or voting power.

Usage

function calculateBoost(address user, address pool, uint256 amount)
external
view
override
returns (uint256 boostBasisPoints, uint256 boostedAmount)
{
if (!supportedPools[pool]) revert UnsupportedPool();
// Get current weights without modifying state
@> (uint256 totalWeight, uint256 totalVotingPower, uint256 votingPower) = updateTotalWeight();
uint256 userVotingPower = veToken.getVotingPower(user, block.timestamp);
// Create parameters struct for calculation
BoostCalculator.BoostParameters memory params = BoostCalculator.BoostParameters({
maxBoost: boostState.maxBoost,
minBoost: boostState.minBoost,
boostWindow: boostState.boostWindow,
totalWeight: totalWeight,
totalVotingPower: totalVotingPower,
votingPower: votingPower
});
return BoostCalculator.calculateTimeWeightedBoost(params, userVotingPower, totalVotingPower, amount);
}
function _calculateBoost(address user, address pool, uint256 amount) internal view returns (uint256) {
if (amount == 0) revert InvalidBoostAmount();
if (!supportedPools[pool]) revert PoolNotSupported();
// Get current weights without modifying state
@> (uint256 totalWeight, uint256 totalVotingPower, uint256 votingPower) = updateTotalWeight();
uint256 userBalance = IERC20(address(veToken)).balanceOf(user);
uint256 totalSupply = IERC20(address(veToken)).totalSupply();
if (userBalance == 0 || totalSupply == 0) {
return amount;
}
// Create parameters struct for calculation
BoostCalculator.BoostParameters memory params = BoostCalculator.BoostParameters({
maxBoost: boostState.maxBoost,
minBoost: boostState.minBoost,
boostWindow: boostState.boostWindow,
totalWeight: totalWeight,
totalVotingPower: totalVotingPower,
votingPower: votingPower
});
(uint256 boostBasisPoints, uint256 boostedAmount) =
BoostCalculator.calculateTimeWeightedBoost(params, userBalance, totalSupply, amount); //@audit-issue using userBalance instead of veToken.getVotingPower(user, block.timestamp);
if (boostedAmount < amount) {
return amount;
}
uint256 maxBoostAmount = amount * MAX_BOOST / 10000;
if (boostedAmount > maxBoostAmount) {
return maxBoostAmount;
}
return boostedAmount;
}

Proof of Concept

Scenario Example

  1. User Requests Boost: A user requests a boost calculation using the calculateBoost or _calculateBoost function.

  2. Incorrect Voting Power: The updateTotalWeight function returns 0 for voting power and weight because the BoostController contract has no veRAAC tokens.

  3. Incorrect Boost Calculation: The boost calculation is based on incorrect values, leading to an incorrect boost multiplier.


Impact

  • Incorrect Boost Calculations: The boost calculations in calculateBoost and _calculateBoost will be incorrect due to the incorrect voting power and weight values returned by updateTotalWeight.

  • Operational Inefficiency: The protocol's boost mechanism becomes unreliable, as it cannot provide meaningful boost calculations.


Tools Used

  • Manual Code Review: The vulnerability was identified through a manual review of the BoostController contract.


Recommendations

  1. Lock RAAC Tokens in BoostController:

    • Lock RAAC tokens in the BoostController contract to receive veRAAC tokens and voting power. This will ensure that the updateTotalWeight function returns meaningful values.

    function initializeLock(uint256 amount, uint256 duration) external onlyOwner {
    IERC20(raacToken).safeTransferFrom(msg.sender, address(this), amount);
    veToken.lock(amount, duration);
    }
Updates

Lead Judging Commences

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

BoostController::updateTotalWeight queries its own nonexistent lock position and voting power when calculating boosts, resulting in zero values that break all boost calculations

Support

FAQs

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