Summary
The veRAACToken#increase() incorrectly increases the voting power calculation twice when a user locks additional tokens. This results in the over-minting of veRAAC tokens, which can inflate voting power unfairly and break governance integrity.
Vulnerability Details
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
);
uint256 newPower = uint256(uint128(newBias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
raacToken.safeTransferFrom(msg.sender, address(this), amount);
_mint(msg.sender, newPower - balanceOf(msg.sender));
emit LockIncreased(msg.sender, amount);
}
_lockState.increaseLock(msg.sender, amount); already adds amount to the user's locked balance.
Then, _votingState.calculateAndUpdatePower() is called and adds amount again.
Since amount is already added in _lockState.increaseLock(), adding it again here results in an excessive increase in voting power.
This leads to the over-minting of veRAAC tokens, allowing users to gain disproportionate voting power and potentially manipulate governance.
Testcode is written in veRAACToken.test.js
describe("Newspace POC", () => {
it("double increase lock amount", async () => {
const initialAmount = ethers.parseEther("1000");
const additionalAmount = ethers.parseEther("500");
const duration = 365 * 24 * 3600;
await veRAACToken.connect(users[0]).lock(initialAmount, duration);
console.log(await veRAACToken.balanceOf(users[0].address));
await expect(veRAACToken.connect(users[0]).increase(additionalAmount))
.to.emit(veRAACToken, "LockIncreased")
.withArgs(users[0].address, additionalAmount);
const position = await veRAACToken.getLockPosition(users[0].address);
expect(position.amount).to.equal(initialAmount + additionalAmount);
console.log(position.power);
});
});
Output:
250000000000000000000n 499999857305936073059n
Exact Result:
250000000000000000000n
374999892979452054794n
As seen output, only increasing 500e18 tokens are same as 1000e18 tokens. This causes double increase.
Impact
veRAACTokens can be inflating leading to disrupt the protocol.
Tools Used
manual, hardhat
Recommendations
Update increase() to prevent double increase amount.
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,
userLock.end
);
// Update checkpoints
uint256 newPower = uint256(uint128(newBias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Transfer additional tokens and mint veTokens
raacToken.safeTransferFrom(msg.sender, address(this), amount);
_mint(msg.sender, newPower - balanceOf(msg.sender));
emit LockIncreased(msg.sender, amount);
}