Core Contracts

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

Incorrect amount calculation in `veRAACToken::increase` leads to inflated veRAAC token minting and voting power

Summary

In contracts::core::tokens::veRAACToken.sol, a miscalculation in the increase function causes the voting power calculation to use an inflated amount, leading to excessive voting power allocation. This occurs because userLock.amount + amount is passed to calculateAndUpdatePower, whereas userLock.amount has already been updated earlier in the function. This results in users receiving twice the intended increasing veRAAC tokens and voting power, potentially inflating their rewards and compromising governance integrity.

Vulnerability Details

In increase, the function increases the amount of locked RAAC tokens. After increasing lock using the LockManager library, the incorrect amount of RAAC tokens is used to calculate and update voting power.

In contracts/core/tokens/veRAACToken.sol#L251-L262:

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, // ❌ Incorrect: `userLock.amount` is already updated
userLock.end
);

We can see that amount is added into userLock.amount, which results in double counting because userLock.amount already includes amount after increaseLock is called.

In contracts/libraries/governance/LockManager.sol#L152-L166:

function increaseLock(
LockState storage state,
address user,
uint256 additionalAmount
) internal {
Lock storage lock = state.locks[user];
if (!lock.exists) revert LockNotFound();
if (lock.end <= block.timestamp) revert LockExpired();
// Maximum lock amount
if (lock.amount + additionalAmount > state.maxLockAmount) revert AmountExceedsLimit();
// Maximum total locked amount
// if (state.totalLocked + additionalAmount > state.maxTotalLocked) revert AmountExceedsLimit();
@> lock.amount += additionalAmount; // `additionalAmount` is already added into user's lock position here

A brief PoC is shown below:

Place the following code into test/unit/core/tokens/veRAACToken.test.js, in describe("veRAACToken") scope and in describe("Lock Mechanism") scope:

it("should allow users to increase lock amount with inflated voting power", async () => {
const initialAmount = ethers.parseEther("1000");
const additionalAmount = ethers.parseEther("500");
const duration = 365 * 24 * 3600; // 1 year
// For simplicity, we will assume `users[0]` locks and increases at the same time.
await veRAACToken.connect(users[0]).lock(initialAmount, duration);
await veRAACToken.connect(users[0]).increase(additionalAmount);
// We also let `users[1]` lock `initialAmount + additionalAmount` amount of RAAC tokens, equal to the total amount of tokens that `users[0]` has locked.
await veRAACToken.connect(users[1]).lock(initialAmount + additionalAmount, duration);
const user_0_power = await veRAACToken.balanceOf(users[0].address);
const user_1_power = await veRAACToken.balanceOf(users[1].address);
// Since `users[0]` and `users[1]` have the same amount of RAAC tokens locked for the same duration, they should have the same power.
console.log("User 0 Power:", user_0_power);
console.log("User 1 Power:", user_1_power);
// But `users[0]` has more due to the incorrect amount of RAAC tokens used to calculate power
expect(user_0_power).to.be.gt(user_1_power);
})

Run npx hardhat test --grep "should allow users to increase lock amount with inflated voting power"

veRAACToken
Lock Mechanism
User 0 Power: 500000000000000000000n
User 1 Power: 375000000000000000000n
✔ should allow users to increase lock amount with inflated voting power
1 passing (2s)

Impact

Users can unfairly increase their veRAAC token balance and voting power, allowing them to inflate their rewards and influence decisions beyond their actual amount of locked RAAC tokens.

Tools Used

Manual Review

Recommendations

Modify the function to ensure the correct amount is passed:

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.amount, // ✅ Correct: Only use the updated `userLock.amount`
userLock.end
);
Updates

Lead Judging Commences

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

veRAACToken::increase doubles the voting power of users

Support

FAQs

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