Core Contracts

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

Incorrect calculation of voting power in the veRAACToken contract when we increase user RAAC tokens lock amount

Summary

Whenever we increase the amount of locked RAAC tokens in the veRAACToken contract by calling the increase function we get an incorrect higher calculation of the voting power due to incorrectly adding userLock.amount twice.

function increase(uint256 amount) external nonReentrant whenNotPaused {
console.log("User voting power before increase: %s", _votingState.getCurrentPower(msg.sender, block.timestamp));
console.log("User lock amount before increase: %s", _lockState.locks[msg.sender].amount);
// Increase lock using LockManager
_lockState.increaseLock(msg.sender, amount);
_updateBoostState(msg.sender, locks[msg.sender].amount);
console.log("=======================================================");
console.log("User voting power after increase: %s", _votingState.getCurrentPower(msg.sender, block.timestamp));
console.log("User lock amount after increase: %s", _lockState.locks[msg.sender].amount);
// Update voting power
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
//@Audit the amount was already increased when we called increaseLock se we are doing a doulbe increase here
userLock.amount + amount,
userLock.end
);
console.log("=======================================================");
console.log("User voting power after calculate and update: %s", _votingState.getCurrentPower(msg.sender, block.timestamp));
// 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);
}

Vulnerability Details

Due to an incorrect double addition of the amount of RAAC tokens that the user is depositing the calculated voting power for the veRAAC token holder is higher than it is supposed to. The issue happens when we call _votingState.calculateAndUpdatePowerand pass the userLock.amount + amount, which is incorrect as the userLock.amount was already incremented in the _lockState.increaseLock call and holds the new amount the user has locked.

This can be exploited to get higher voting power and in the same time invest less than requiered RAAC tokens to do so.

Impact

Unfair gain of voting power whenever RAAC token amount locked is increased for a user. This in turn can allow user to vote with a bigger weight when calling the castVote function in the Governance contract and make a propolsals pass.

Tools Used

  • Manual Review

  • Uint test

PoC

This test should be put in the veRAACToken.test.js file in the describe("Lock Mechanism") test suite group.

it("PoC user voting power is incorrectly calculated when lock is increased", async () => {
const initialAmount = ethers.parseEther("1000");
const additionalAmount = ethers.parseEther("500");
const duration = 365 * 24 * 3600; // 1 year
//We lock 1000 worth of ETH tokens into the veRAACToken contract
//Voting power at this point is (1000 ETH * 1 year)/4 years = 250 ETH worth
await veRAACToken.connect(users[0]).lock(initialAmount, duration);
console.log("User voting power before call to lock increase: ", await veRAACToken.getVotingPower(users[0].address));
console.log("=====================================================")
//We increase the lock by 500 worth of ETH tokens
//Voting power should now become (1500 ETH * 1 year)/4 years = 375 ETH worth
await expect(veRAACToken.connect(users[0]).increase(additionalAmount))
.to.emit(veRAACToken, "LockIncreased")
.withArgs(users[0].address, additionalAmount);
const userVotingPower = await veRAACToken.getVotingPower(users[0].address);
//Voting power was incorrectly increased to 500 ETH worth instead of 375 ETH worth
expect(userVotingPower).to.equal(ethers.parseEther("500"));
});

Console log output:

User voting power before call to lock increase: 250000000000000000000n
=====================================================
User voting power before lock increase: 250000000000000000000
User lock amount before increase: 1000000000000000000000
=======================================================
User voting power after lock increase but before recalculation: 250000000000000000000
User lock amount after increase: 1500000000000000000000
=======================================================
User voting power after calculate and update: 500000000000000000000

Recommendations

Removed the unesecarry double addition of the lock amount when calculating the voting power:

function increase(uint256 amount) external nonReentrant whenNotPaused {
console.log("User voting power before increase: %s", _votingState.getCurrentPower(msg.sender, block.timestamp));
console.log("User lock amount before increase: %s", _lockState.locks[msg.sender].amount);
// Increase lock using LockManager
_lockState.increaseLock(msg.sender, amount);
_updateBoostState(msg.sender, locks[msg.sender].amount);
console.log("=======================================================");
console.log("User voting power after increase: %s", _votingState.getCurrentPower(msg.sender, block.timestamp));
console.log("User lock amount after increase: %s", _lockState.locks[msg.sender].amount);
// Update voting power
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
//@Audit the amount was already increased when we called increaseLock se we are doing a doulbe increase here
- userLock.amount + amount,
+ userLock.amount,
userLock.end
);
console.log("=======================================================");
console.log("User voting power after calculate and update: %s", _votingState.getCurrentPower(msg.sender, block.timestamp));
// 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);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 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.

Give us feedback!