Summary
A critical vulnerability exists in the veRAACToken::increase
function of the veRAACToken
contract, leading to an incorrect calculation of voting power when users increase their locked token amount. This results in double-counting the increased amount, granting users excess voting power.
Vulnerability Details
The root cause lies in the veRAACToken::increase
function's handling of the lock amount update and subsequent voting power calculation. After increasing the lock via _lockState.increaseLock
, the code retrieves the updated userLock.amount
and erroneously adds the amount
parameter again when invoking _votingState.calculateAndUpdatePower
. This double-addition leads to inflated voting power.
Here is the code of veRAACToken::increase
:
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
userLock.amount + amount,
userLock.end
);
Impact
Users receive 2x the intended voting power for their increased lock amount (e.g., +1000 tokens yields +2000 power instead of +1000)
Proof of Concept
it.only("H02 - Double-Counting in `veRAACToken::increase` Leads to Incorrect Voting Power Calculation", async () => {
const alice = users[0];
const amount = ethers.parseEther("1000");
const duration = 365 * 24 * 3600 * 4;
console.log("Creating lock for alice...");
const alice1Tx = await veRAACToken.connect(alice).lock(amount, duration);
await alice1Tx.wait();
const positionFirstLock = await veRAACToken.getLockPosition(alice.address);
console.log("Alice Position after lock creation: ");
console.log(" - amount: ", positionFirstLock.amount);
console.log(" - power: ", positionFirstLock.power);
expect(positionFirstLock.amount).to.equal(amount);
expect(positionFirstLock.power).to.equal(amount);
console.log("Increasing lock amount for alice...");
const alice2Tx = await veRAACToken.connect(alice).increase(amount);
await alice2Tx.wait();
const positionSecondLock = await veRAACToken.getLockPosition(alice.address);
console.log("Alice Position after increase: ");
console.log(" - amount: ", positionSecondLock.amount);
console.log(" - power: ", positionSecondLock.power);
expect(positionSecondLock.amount).to.equal(amount + amount);
expect(positionSecondLock.power).to.equal(amount + amount);
});
Here is the output of the test:
Creating lock for alice...
Alice Position after lock creation:
- amount: 1000000000000000000000n
- power: 1000000000000000000000n
Increasing lock amount for alice...
Alice Position after increase:
- amount: 2000000000000000000000n
- power: 2999999833523592085235n
Tools Used
Manual Review
Recommendations
Use the already-updated userLock.amount
without adding the amount
parameter again
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
userLock.amount,
userLock.end
);