Core Contracts

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

Incorrect update of `_boostState.votingPower` in `veRAACToken.sol::_updateBoostState()` function.

Summary

Contract - veRAAC.sol

The _updateBoostState() function is as follow -

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();
}
  • By defination votingPower of a boost state, represents the voting power of all user (in boost period).

  • But in current implementation, the voting power only accounts for 1 user, which is wrong.

Vulnerability Details

  1. wrong updation of _boostState.votingPower can be very drastic.

  2. _boostState.votingPower is designed to store collecting voting power of all user, in the particular boost period.

Let's go through function flow (only relevant ones) -

  1. lock(uint256 amount, uint256 duration) ->

  2. _updateBoostState(msg.sender, amount) ->

  3. updateBoostPeriod(BoostState storage state) ->

  4. updateValue(Period storage self, uint256 newValue, uint256 timestamp)


  1. verRAAC.sol::lock()

function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
if (amount == 0) revert InvalidAmount();
if (amount > MAX_LOCK_AMOUNT) revert AmountExceedsLimit();
if (totalSupply() + amount > MAX_TOTAL_SUPPLY) revert TotalSupplyLimitExceeded();
if (duration < MIN_LOCK_DURATION || duration > MAX_LOCK_DURATION)
revert InvalidLockDuration();
// Do the transfer first - this will revert with ERC20InsufficientBalance if user doesn't have enough tokens
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// Calculate unlock time
uint256 unlockTime = block.timestamp + duration;
_lockState.createLock(msg.sender, amount, duration);
@-> _updateBoostState(msg.sender, amount);
(int128 bias, int128 slope) = _votingState.calculateAndUpdatePower(
msg.sender,
amount,
unlockTime
);
// Update checkpoints
uint256 newPower = uint256(uint128(bias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Mint veTokens
_mint(msg.sender, newPower);
emit LockCreated(msg.sender, amount, unlockTime);
}
  1. veRAAC.sol::_updateBoostState()

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. BoostCalculator.sol::updateBoostPeriod()

function updateBoostPeriod(
BoostState storage state
) internal {
if (state.boostWindow == 0) revert InvalidBoostWindow();
if (state.maxBoost < state.minBoost) revert InvalidBoostBounds();
uint256 currentTime = block.timestamp;
uint256 periodStart = state.boostPeriod.startTime;
// If no period exists, create initial period starting from current block
if(periodStart > 0) {
if (currentTime >= periodStart + state.boostWindow) {
TimeWeightedAverage.createPeriod(
state.boostPeriod,
currentTime,
state.boostWindow,
state.votingPower,
state.maxBoost
);
return;
}
// Update existing period
@-> state.boostPeriod.updateValue(state.votingPower, currentTime);
return;
}
// If no period exists, create initial period starting from current block
TimeWeightedAverage.createPeriod(
state.boostPeriod,
currentTime,
state.boostWindow,
state.votingPower,
state.maxBoost
);
}
  1. TimeWeightedAverage.sol::updateValue()

function updateValue(
Period storage self,
uint256 newValue,
uint256 timestamp
) internal {
if (timestamp < self.startTime || timestamp > self.endTime) {
revert InvalidTime();
}
unchecked {
uint256 duration = timestamp - self.lastUpdateTime;
if (duration > 0) {
// self.value is wrong approch because voting power will decrease with time.
uint256 timeWeightedValue = self.value * duration;
if (timeWeightedValue / duration != self.value) revert ValueOverflow();
self.weightedSum += timeWeightedValue;
self.totalDuration += duration;
}
}
@-> self.value = newValue;
self.lastUpdateTime = timestamp;
}

Impact

  • The problem here is that self.value is being updated with voting power of a user, which is wrong. state.value should
    account for all users during that boost duration.

  • Wheneven a new user creates a new lock, self.value will again be reupdated to only account for new user.

  • Boost state value or self.value is being used to calculate user Reward, it's incorrect value means incorrect reward to user.

Tools Used

Manual

Recommendations

Instead of updating self.value for 1 user only, built as functionality that account for all user in that boost period.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!