Core Contracts

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

Inconsistent `totalVotingPower` Calculation in `veRAACToken`

Summary

The veRAACToken contract incorrectly updates _boostState.totalVotingPower before minting tokens, leading to a discrepancy where the total voting power is one step behind the actual minted supply. This results in incorrect boost calculations, affecting user rewards and governance voting power.

Vulnerability Details

The lock as well as increase functions in veRAACToken updates _boostState.totalVotingPower using totalSupply() before minting the corresponding veRAAC tokens. As a result, the voting power calculations do not account for the newly minted tokens in the current transaction, causing a underestimation of total voting power.

Here is the code of veRAACToken::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;
// Create lock position
_lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount); // audit - totalVotingPower is updated with totalSupply() before minting the tokens
// 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);
emit LockCreated(msg.sender, amount, unlockTime);
}

Impact

Any calculation that relies on _boostState.totalVotingPower or veRAACToken.getBoostState will be incorrect.

Proof of Concept

Here is the test case that shows the issue:

it.only("H04 - Inconsistent totalVotingPower Calculation in veRAACToken", async () => {
const alice = users[0];
const bob = users[1];
const amount = ethers.parseEther("1000");
const amount2 = ethers.parseEther("1500");
const duration = 365 * 24 * 3600 * 4; // 4 years in seconds
// Step 1: Alice creates the first lock
console.log("Creating first lock with amount 1000...");
const alice1Tx = await veRAACToken.connect(alice).lock(amount, duration);
await alice1Tx.wait();
const boostState = await veRAACToken.getBoostState();
console.log("Total Voting Power after first lock: ", boostState[3]);
console.log("totalWeight after first lock: ", boostState[4]);
// Step 2: Bob creates a new lock near the end of the boost period
console.log("Creating second lock with amount 1500...");
const bob1Tx = await veRAACToken.connect(bob).lock(amount2, duration);
await bob1Tx.wait();
const boostState2 = await veRAACToken.getBoostState();
console.log("Total Voting Power after second lock: ", boostState2[3]);
console.log("totalWeight after second lock: ", boostState2[4]);
});

and the output of the test is:

Creating first lock with amount 1000...
Total Voting Power after first lock: 0n
totalWeight after first lock: 1000000000000000000000n
Creating second lock with amount 1500...
Total Voting Power after second lock: 1000000000000000000000n
totalWeight after second lock: 2500000000000000000000n

Tools Used

Manual Review

Recommendations

Ensure veRAACToken::updateBoostState() is called after minting tokens.

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.