Core Contracts

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

Truncated Voting Power Undermines RAAC's Real Estate Governance Model

Summary

The veRAACToken's voting power calculation exhibits truncation issues that break the expected linear relationship between lock duration and voting power. This violates invariant where voting power should scale proportionally with lock duration. The impact undermines the entire governance weight system.

Vulnerability Details

RAAC's governance system relies on vote-escrowed tokens (veRAACToken) to align long-term incentives. The core principle is simple, the longer you lock your RAAC tokens, the more voting power you receive. This relationship should be perfectly linear, like a straight line on a graph.

The problem appears in how voting power is calculated. On how the veRAACToken contract performs this critical calculation: calculateVeAmount() function

function calculateVeAmount(uint256 amount, uint256 lockDuration) external pure returns (uint256) {
// [1] Basic input validation
if (amount == 0 || lockDuration == 0) return 0;
if (lockDuration > MAX_LOCK_DURATION) lockDuration = MAX_LOCK_DURATION;
// [2] Vulnerability: Integer division truncates voting power
// Example: 1000 tokens * 400 days / 1460 days = 273.97... truncated to 273
return (amount * lockDuration) / MAX_LOCK_DURATION;
}

This division operation hides a significant flaw. Like say when Alice locks 1000 RAAC for 400 days, the contract calculates her voting power as (1000 * 400) / 1460. The division truncates any remainder, effectively reducing her actual governance weight.

Means that users systematically receive less voting power than the protocol design intended. The impact is particularly pronounced for:

  • Community members with smaller holdings

  • Medium-term locks that should provide proportional voting rights

  • Strategic voters trying to maximize their governance influence

Think of it like a voting machine that always rounds down your vote weight, it technically works, but subtly undermines the democratic process we're trying to build.

Impact

When a real estate fund locks 1000 RAAC for 400 days, their actual governance weight gets truncated through integer division. The impact ripples through the entire dual-gauge system. RWA yields and RAAC emissions both depend on precise voting power calculations.

Looking at the GaugeController.sol integration, we see how this affects real-world protocol governance: vote() function

function vote(address gauge, uint256 weight) external override whenNotPaused {
// [1] First validation layer
if (!isGauge(gauge)) revert GaugeNotFound();
if (weight > WEIGHT_PRECISION) revert InvalidWeight();
// [2] Critical Point: Gets truncated voting power from veRAACToken
uint256 votingPower = veRAACToken.balanceOf(msg.sender);
if (votingPower == 0) revert NoVotingPower();
// [3] State Updates
uint256 oldWeight = userGaugeVotes[msg.sender][gauge];
userGaugeVotes[msg.sender][gauge] = weight;
// [4] Propagates truncated voting power into gauge weights
_updateGaugeWeight(gauge, oldWeight, weight, votingPower);
emit WeightUpdated(gauge, oldWeight, weight);
}

Recommendations

The vulnerability flow connects through these contracts

// veRAACToken.sol
function calculateVeAmount(uint256 amount, uint256 lockDuration) external pure returns (uint256) {
// [1] Initial validation gates
if (amount == 0 || lockDuration == 0) return 0;
if (lockDuration > MAX_LOCK_DURATION) lockDuration = MAX_LOCK_DURATION;
// [2] Root of truncation: Integer division without precision scaling
// Example: 1000 RAAC * 400 days / 1460 days = 273 (losing 0.97...)
return (amount * lockDuration) / MAX_LOCK_DURATION;
}
// IveRAACToken.sol
interface IveRAACToken {
// [3] Interface defines the expected linear relationship
function calculateVeAmount(uint256 amount, uint256 lockDuration) external pure returns (uint256);
function getVotingPower(address account) external view returns (uint256);
}
// GaugeController.sol
function vote(address gauge, uint256 weight) external override whenNotPaused {
// [4] Validation checks
if (!isGauge(gauge)) revert GaugeNotFound();
if (weight > WEIGHT_PRECISION) revert InvalidWeight();
// [5] Uses truncated voting power from veRAACToken
uint256 votingPower = veRAACToken.balanceOf(msg.sender);
if (votingPower == 0) revert NoVotingPower();
// [6] Propagates truncated power into gauge weights
uint256 oldWeight = userGaugeVotes[msg.sender][gauge];
userGaugeVotes[msg.sender][gauge] = weight;
_updateGaugeWeight(gauge, oldWeight, weight, votingPower);
}

The truncation cascades from veRAACToken's calculation through the interface and into the gauge voting system, affecting the entire governance weight mechanism.

The fix needs to be applied in the veRAACToken.sol contract, specifically in the calculateVeAmount function. Is where the root of the truncation issue originates.

// Current vulnerable implementation
function calculateVeAmount(uint256 amount, uint256 lockDuration) external pure returns (uint256) {
if (amount == 0 || lockDuration == 0) return 0;
if (lockDuration > MAX_LOCK_DURATION) lockDuration = MAX_LOCK_DURATION;
return (amount * lockDuration) / MAX_LOCK_DURATION;
}
// Fixed implementation with precision scaling
function calculateVeAmount(uint256 amount, uint256 lockDuration) external pure returns (uint256) {
if (amount == 0 || lockDuration == 0) return 0;
if (lockDuration > MAX_LOCK_DURATION) lockDuration = MAX_LOCK_DURATION;
return (amount * lockDuration * PRECISION) / MAX_LOCK_DURATION;
}

We automatically resolve the truncation issues that propagate through IveRAACToken interface and into the GaugeController's voting mechanism. The PRECISION scaling factor ensures accurate voting power representation throughout the entire governance system.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!