Description
Inside veRAACToken.sol
the correct implementation of increase() ought to be:
File: contracts/core/tokens/veRAACToken.sol
251: function increase(uint256 amount) external nonReentrant whenNotPaused {
252: // Increase lock using LockManager
253: _lockState.increaseLock(msg.sender, amount);
254: _updateBoostState(msg.sender, locks[msg.sender].amount);
255:
256: // Update voting power
257: LockManager.Lock memory userLock = _lockState.locks[msg.sender];
258: (int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
259: msg.sender,
- 260: userLock.amount + amount,
+ 260: userLock.amount,
261: userLock.end
262: );
263:
264: // Update checkpoints
265: uint256 newPower = uint256(uint128(newBias));
266: _checkpointState.writeCheckpoint(msg.sender, newPower);
267:
268: // Transfer additional tokens and mint veTokens
269: raacToken.safeTransferFrom(msg.sender, address(this), amount);
270: _mint(msg.sender, newPower - balanceOf(msg.sender));
271:
272: emit LockIncreased(msg.sender, amount);
273: }
This is because on L252 the call to _lockState.increaseLock(msg.sender, amount)
already increases the userLock.amount
by amount
.
Impact
User gets a much higher voting power than expected.
Proof of Concept
Add this inside test/unit/core/tokens/veRAACToken.test.js
and run to see it fail with the following output:
describe("Voting Power Calculations with Lock and Increase", () => {
it("should calculate correct voting power after lock and increase", async () => {
const initialAmount = ethers.parseEther("1000");
const duration = 4 * 365 * 24 * 3600;
await veRAACToken.connect(users[0]).lock(initialAmount, duration);
const initialVotingPower = await veRAACToken.getVotingPower(users[0].address);
console.log("Initial voting power:", ethers.formatEther(initialVotingPower));
const initialPosition = await veRAACToken.getLockPosition(users[0].address);
console.log("Initial lock position:", {
amount: ethers.formatEther(initialPosition.amount),
end: initialPosition.end.toString(),
power: ethers.formatEther(initialPosition.power)
});
await veRAACToken.connect(users[0]).increase(initialAmount);
const newVotingPower = await veRAACToken.getVotingPower(users[0].address);
console.log("New voting power:", ethers.formatEther(newVotingPower));
const newPosition = await veRAACToken.getLockPosition(users[0].address);
console.log("New lock position:", {
amount: ethers.formatEther(newPosition.amount),
end: newPosition.end.toString(),
power: ethers.formatEther(newPosition.power)
});
const expectedRatio = 2;
const actualRatio = Number(newVotingPower) / Number(initialVotingPower);
console.log("Actual power ratio:", actualRatio);
expect(actualRatio).to.be.closeTo(expectedRatio, 0.1);
});
});
Output:
veRAACToken
Voting Power Calculations with Lock and Increase
Initial voting power: 1000.0
Initial lock position: { amount: '1000.0', end: '1865269225', power: '1000.0' }
New voting power: 3000.0 ❌
New lock position: { amount: '2000.0', end: '1865269225', power: '3000.0' } ❌ <--- power should have doubled to '2000.0' since amount had doubled
Actual power ratio: 3
1) should calculate correct voting power after lock and increase
0 passing (12s)
1 failing
1) veRAACToken
Voting Power Calculations with Lock and Increase
should calculate correct voting power after lock and increase:
AssertionError: expected 3 to be close to 2 +/- 0.1