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 {
_lockState.increaseLock(msg.sender, amount);
_updateBoostState(msg.sender, locks[msg.sender].amount);
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
@> userLock.amount + amount,
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();
if (lock.amount + additionalAmount > state.maxLockAmount) revert AmountExceedsLimit();
@> lock.amount += additionalAmount;
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;
await veRAACToken.connect(users[0]).lock(initialAmount, duration);
await veRAACToken.connect(users[0]).increase(additionalAmount);
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);
console.log("User 0 Power:", user_0_power);
console.log("User 1 Power:", user_1_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
);