Core Contracts

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

Inconsistent Voting Power Usage

Summary

In BoostController.sol, there is a critical inconsistency between how voting power is calculated in internal vs external functions, leading to different boost values for the same user.

Vulnerable Code

// Internal calculation uses raw balance
function _calculateBoost(address user, address pool, uint256 amount) internal view returns (uint256) {
// ...
uint256 userBalance = IERC20(address(veToken)).balanceOf(user);
uint256 totalSupply = IERC20(address(veToken)).totalSupply();
// Uses raw balance for calculations
(uint256 boostBasisPoints, uint256 boostedAmount) = BoostCalculator.calculateTimeWeightedBoost(
params,
userBalance, // RAW BALANCE
totalSupply, // RAW SUPPLY
amount
);
// ...
}
// External calculation uses voting power
function calculateBoost(address user, address pool, uint256 amount)
external view override returns (uint256 boostBasisPoints, uint256 boostedAmount)
{
// ...
uint256 userVotingPower = veToken.getVotingPower(user, block.timestamp);
return BoostCalculator.calculateTimeWeightedBoost(
params,
userVotingPower, // VOTING POWER
totalVotingPower, // TOTAL VOTING POWER
amount
);
}

Impact

  1. Calculation Discrepancy

function demonstrateDiscrepancy() public {
address user = address(1);
address pool = address(2);
uint256 amount = 1000e18;
// Internal calculation (via updateUserBoost)
controller.updateUserBoost(user, pool);
uint256 internalBoost = controller.getWorkingBalance(user, pool);
// External calculation
(_, uint256 externalBoost) = controller.calculateBoost(user, pool, amount);
// Results differ due to different power calculations
assert(internalBoost != externalBoost);
}
  1. Economic Impact

  • Users receive different boosts depending on which function is called

  • Rewards distribution becomes unpredictable

  • Gaming opportunity through function call selection

Proof of Concept

contract BoostDiscrepancyTest {
IveRAACToken veToken;
BoostController controller;
function testBoostDiscrepancy() public {
// Setup
address user = address(1);
address pool = address(2);
uint256 lockAmount = 1000e18;
uint256 lockDuration = 365 days;
// Create lock position
veToken.createLock(lockAmount, lockDuration);
// Compare calculations
uint256 rawBalance = IERC20(address(veToken)).balanceOf(user);
uint256 votingPower = veToken.getVotingPower(user, block.timestamp);
// votingPower will be higher due to time multiplier
assert(votingPower > rawBalance);
// This leads to different boost calculations
uint256 amount = 1000e18;
controller.updateUserBoost(user, pool); // Uses rawBalance
(_, uint256 externalBoost) = controller.calculateBoost(user, pool, amount); // Uses votingPower
uint256 storedBoost = controller.getWorkingBalance(user, pool);
assert(storedBoost < externalBoost); // Demonstrates the discrepancy
}
}

Recommended Mitigation

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();
// Use voting power instead of raw balance
uint256 userVotingPower = veToken.getVotingPower(user, block.timestamp);
if (userVotingPower == 0 || totalVotingPower == 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
});
return BoostCalculator.calculateTimeWeightedBoost(
params,
userVotingPower,
totalVotingPower,
amount
);
}

Additional Recommendations

  1. Add voting power caching:

mapping(address => VotingPowerSnapshot) public votingPowerSnapshots;
struct VotingPowerSnapshot {
uint256 power;
uint256 timestamp;
}
  1. Implement consistency checks:

function validateBoostCalculation(uint256 internalBoost, uint256 externalBoost) internal pure {
require(
(internalBoost * 100) / externalBoost >= 95 &&
(internalBoost * 100) / externalBoost <= 105,
"Boost calculation deviation too high"
);
}
  1. Add events for monitoring:

event BoostCalculationPerformed(
address indexed user,
address indexed pool,
uint256 votingPower,
uint256 boost,
bool isInternal
);
Updates

Lead Judging Commences

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

BoostController uses inconsistent input values in calculateBoost (voting power) vs _calculateBoost (token balance), creating misleading boost estimates

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

BoostController uses inconsistent input values in calculateBoost (voting power) vs _calculateBoost (token balance), creating misleading boost estimates

Support

FAQs

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

Give us feedback!