Core Contracts

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

Incorrect State Update Order in `veRAACToken.increase` Leads to Stale Boost Calculations

Summary

The veRAACToken contract contains a critical state management flaw where boost parameters are updated before completing dependent voting power calculations and token minting operations. This incorrect execution sequence in the veRAACToken.increase function causes boost calculations to use stale voting power data and pre-mint token supply values. The resulting miscalculations propagate to all boost-dependent systems including gauge weight distributions and reward allocations, creating protocol-wide inaccuracies in governance power tracking and incentive distributions until the next update cycle.

Vulnerability Details

The vulnerability arises from incorrect state update sequencing in the veRAACToken increase lock mechanism. The veRAACToken.increase function (veRAACToken.sol#L254) updates boost state parameters before completing critical voting power calculations and token minting operations:

  1. Premature Boost State Update _updateBoostState() is called before:

  • Voting power calculation via _votingState.calculateAndUpdatePower()

  • veToken minting operation _mint()

contract veRAACToken is ERC20, Ownable, ReentrancyGuard, IveRAACToken {
function increase(uint256 amount) external nonReentrant whenNotPaused {
// Increase lock using LockManager
_lockState.increaseLock(msg.sender, amount);
@> _updateBoostState(msg.sender, locks[msg.sender].amount);
// Update voting power
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
@> (int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
userLock.amount + amount,
userLock.end
);
// Update checkpoints
uint256 newPower = uint256(uint128(newBias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Transfer additional tokens and mint veTokens
raacToken.safeTransferFrom(msg.sender, address(this), amount);
@> _mint(msg.sender, newPower - balanceOf(msg.sender));
emit LockIncreased(msg.sender, amount);
}
function _updateBoostState(address user, uint256 newAmount) internal {
// Update boost calculator state
@> _boostState.votingPower = _votingState.calculatePowerAtTimestamp(user, block.timestamp);
@> _boostState.totalVotingPower = totalSupply();
_boostState.totalWeight = _lockState.totalLocked;
_boostState.updateBoostPeriod();
}
}
  1. Stale Data Dependency
    The boost calculation in _updateBoostState() depends on:

  • _votingState.points[user] updated by _votingState.calculateAndUpdatePower()

  • totalSupply() value changed by _mint()

library VotingPowerLib {
function calculateAndUpdatePower(
VotingPowerState storage state,
address user,
uint256 amount,
uint256 unlockTime
) internal returns (int128 bias, int128 slope) {
// ...
// Calculate initial voting power that will decay linearly to 0 at unlock time
uint256 duration = unlockTime - block.timestamp;
uint256 initialPower = (amount * duration) / MAX_LOCK_DURATION; // Normalize by max duration
bias = int128(int256(initialPower));
slope = int128(int256(initialPower / duration)); // Power per second decay
// ...
@> state.points[user] = RAACVoting.Point({
@> bias: bias,
@> slope: slope,
@> timestamp: block.timestamp
});
// ...
}
function calculatePowerAtTimestamp(
VotingPowerState storage state,
address account,
uint256 timestamp
) internal view returns (uint256) {
@> RAACVoting.Point memory point = state.points[account];
if (point.timestamp == 0) return 0;
if (timestamp < point.timestamp) {
return 0;
}
@> uint256 timeDelta = timestamp - point.timestamp;
@> int128 adjustedBias = point.bias - (point.slope * int128(int256(timeDelta)));
return adjustedBias > 0 ? uint256(uint128(adjustedBias)) : 0;
}
}

This results in boost calculations being based on pre-operation state values, systematically underreporting the user's actual voting power during the lock creation transaction.

Impact

This sequencing error creates systemic inaccuracies in three critical protocol mechanisms:

  1. Reward Miscalculations
    Boost-dependent reward distributions (in gauges and pools) will use stale voting power data, leading to:

    • Under-allocation of rewards for new locks

    • Disproportionate rewards for existing positions

    • Cumulative errors in long-term reward accounting

  2. Protocol Parameter Inaccuracies
    Key system metrics relying on boost calculations will be compromised:

    • Total voting power tracking

    • Gauge weight distributions

These effects compound over time, creating growing discrepancies between actual and recorded protocol states, ultimately undermining core protocol functionality and user trust in the RAAC governance system.

Tools Used

Manual Review

Recommendations

Move _updateBoostState() call after voting power calculation and token minting in veRAACToken.increase:

// Increase lock using LockManager
_lockState.increaseLock(msg.sender, amount);
- _updateBoostState(msg.sender, locks[msg.sender].amount);
// Update voting power
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
userLock.amount + amount,
userLock.end
);
// Update checkpoints
uint256 newPower = uint256(uint128(newBias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Transfer additional tokens and mint veTokens
raacToken.safeTransferFrom(msg.sender, address(this), amount);
_mint(msg.sender, newPower - balanceOf(msg.sender));
+ _updateBoostState(msg.sender, locks[msg.sender].amount);
Updates

Lead Judging Commences

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

veRAACToken::_updateBoostState should be called later inside lock/increase

Support

FAQs

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