Core Contracts

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

Missing Boost State Update in extend() and withdraw()

Summary

The _updateBoostState() function is missing at the end of extend() and withdraw() in veRAACToken.sol. Since these functions significantly affect totalSupply() and user voting power, failing to update the boost state leads to outdated calculations. This issue distorts governance calculations dependent on the latest update of _boostState.

Vulnerability Details

Users extending their lock is poised to increase their decaying voting power after invoking _votingState.calculateAndUpdatePower() because duration has increased due to extended unlockTime:

VotingPowerLib.sol#L89-L90

uint256 duration = unlockTime - block.timestamp;
uint256 initialPower = (amount * duration) / MAX_LOCK_DURATION; // Normalize by max duration

It may increase or decrease totalSupply() as is evidenced from the logic below:

veRAACToken.sol#L297-L302

// Update veToken balance
if (newPower > oldPower) {
_mint(msg.sender, newPower - oldPower);
} else if (newPower < oldPower) {
_burn(msg.sender, oldPower - newPower);
}

They will be needed to separately update _boostState.votingPower and _boostState.totalVotingPower, but is missing in the function logic to invoke _updateBoostState():

veRAACToken.sol#L568-L575

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();
}

The same issue is also found in the logic of withdraw() where the user's voting has supposedly decayed to zero and totalSupply() is now reduced by the currentPower burned.

Impact

All calculations protocol wide dependent on _boostState will be affected. As a paralleled example, BaseGauge._applyBoost() internally invoked by getUserWeight() which is being triggered by earned() is crucially needed when updating reward state for an account. As is evidenced in the code logic entailed, totalVotingPower: boostState.totalVotingPower and votingPower: boostState.votingPower are part of the params serving as the third input parameter for BoostCalculator.calculateBoost():

BaseGauge.sol#L236-L250

// Create BoostParameters struct from boostState
BoostCalculator.BoostParameters memory params = BoostCalculator.BoostParameters({
maxBoost: boostState.maxBoost,
minBoost: boostState.minBoost,
boostWindow: boostState.boostWindow,
totalWeight: boostState.totalWeight,
totalVotingPower: boostState.totalVotingPower,
votingPower: boostState.votingPower
});
uint256 boost = BoostCalculator.calculateBoost(
veBalance,
totalVeSupply,
params
);

Tools Used

Manual

Recommendations

Consider making the following fix:

veRAACToken.sol#L297-L302

// Update veToken balance
if (newPower > oldPower) {
_mint(msg.sender, newPower - oldPower);
} else if (newPower < oldPower) {
_burn(msg.sender, oldPower - newPower);
}
+ _updateBoostState(msg.sender, locks[msg.sender].amount);

veRAACToken.sol#L327-L329

// Burn veTokens and transfer RAAC
_burn(msg.sender, currentPower);
raacToken.safeTransfer(msg.sender, amount);
+ _updateBoostState(msg.sender, locks[msg.sender].amount);
Updates

Lead Judging Commences

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

veRAACToken::_updateBoostState not called in extend/withdraw

Support

FAQs

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

Give us feedback!