Core Contracts

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

Incorrect Boost State Update Timing in veRAACToken.sol

Summary

The _updateBoostState() function in veRAACToken.sol is invoked in the code logic of lock() and increase() before _votingState.calculateAndUpdatePower() and _mint(), leading to outdated calculations of totalSupply() and user's voting power. This results in an inaccurate boost calculation, affecting governance calculations dependent on the latest update of _boostState. To ensure correctness, _updateBoostState() should be called after _mint() to reflect the latest totalSupply() and updated voting power.

Vulnerability Details

In lock(), _updateBoostState() is called right after lock creation but before calculating initial voting power and minting veTokens:

veRAACToken.sol#L225-L241

// Create lock position
_lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount);
// Calculate initial voting power
(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);

At the point of triggering _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();
}

_boostState.votingPower will assuredly be assigned 0 because point.timestamp is 0 since _votingState.calculateAndUpdatePower() has not be invoked yet to update state.points[user]:

VotingPowerLib.sol#L254-L271

RAACVoting.Point memory point = state.points[account];
if (point.timestamp == 0) return 0;

Additionally, the minted veTokens isn't reflected in totalSupply(). Hence, _boostState.totalVotingPower will be updated with a smaller value.

Similar issue is being seen in increase() with _boostState.votingPower assigned based on the existing userLock.amount instead of userLock.amount + amount. And apparently, _boostState.totalVotingPower will be updated with an outdated value.

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#L225-L241

// Create lock position
_lockState.createLock(msg.sender, amount, duration);
- _updateBoostState(msg.sender, amount);
// Calculate initial voting power
(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);
+ _updateBoostState(msg.sender, amount);

veRAACToken.sol#L252-L270

// 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 7 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.

Give us feedback!