Core Contracts

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

Inaccurate calculation for voting power in the flow of increasing lock

Summary

The function veRAACToken::increase() increases an user's lock position, in which position's voting power is computed and updated accordingly. However, the computed voting power is inaccurately calculated according to the current formula

Vulnerability Details

veRAACToken::increase() calls VotingPowerLib::calculateAndUpdatePower() to calculate and update user lock position.

The function calculateAndUpdatePower() computes the voting power based on amount and the lock duration, which does not consider user's current voting power. This causes the result of voting power calculation bias at a random time can definitely less than the current voting power, resulting the function veRAACToken::increase() to fail at the line _mint(msg.sender, newPower - balanceOf(msg.sender)) because of arithmetic underflow

// VotingPowerLib
function calculateAndUpdatePower(
VotingPowerState storage state,
address user,
uint256 amount,
uint256 unlockTime
) internal returns (int128 bias, int128 slope) {
if (amount == 0 || unlockTime <= block.timestamp) revert InvalidPowerParameters();
uint256 MAX_LOCK_DURATION = 1460 days; // 4 years
// FIXME: Get me to uncomment me when able
// bias = RAACVoting.calculateBias(amount, unlockTime, block.timestamp);
// slope = RAACVoting.calculateSlope(amount);
// Calculate initial voting power that will decay linearly to 0 at unlock time
@> uint256 duration = unlockTime - block.timestamp;
@> uint256 initialPower = (amount * duration) / MAX_LOCK_DURATION; // Normalize by max duration
@> bias = int128(int256(initialPower));
slope = int128(int256(initialPower / duration)); // Power per second decay
uint256 oldPower = getCurrentPower(state, user, block.timestamp);
state.points[user] = RAACVoting.Point({
@> bias: bias,
slope: slope,
timestamp: block.timestamp
});
_updateSlopeChanges(state, unlockTime, 0, slope);
emit VotingPowerUpdated(user, oldPower, uint256(uint128(bias)));
@> return (bias, slope);
}
// veRAACToken
function increase(uint256 amount) external nonReentrant whenNotPaused {
// Increase lock using LockManager
_lockState.increaseLock(msg.sender, amount);
_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,
userLock.end
);
// 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);
}

PoC

Add the test to test/unit/core/tokens/veRAACToken.test.js

describe("Lock Mechanism", () => {
...
it.only("increase lock updates wrong voting power", async function(){
const amount = ethers.parseEther("1000");
const year = 365n * 24n * 3600n
const duration = 3n * year; // 3 years
// lock 1000, in 3 years
await veRAACToken.connect(users[0]).lock(amount, duration);
// 2 years pass
let newBlockTime = BigInt(await time.increase(year * 2n));
const lock = await veRAACToken.getLockPosition(users[0].address)
const currentVotingPower = lock.power;
const MAX_LOCK_DURATION = 126144000n
// increase amount = 500
const increaseAmount = amount / 2n;
// compute new voting power
const newVotingPower = (amount + increaseAmount) * (lock.end - newBlockTime) / MAX_LOCK_DURATION
// current > new
console.log(currentVotingPower)
console.log(newVotingPower)
// hence, this call reverts
await veRAACToken.connect(users[0]).increase(amount / 2n); // <<< revert
})

Run the test and console shows:

veRAACToken
Lock Mechanism
750000000000000000000n
375000000000000000000n
1) increase lock updates wrong voting power
0 passing (2s)
1 failing
1) veRAACToken
Lock Mechanism
increase lock updates wrong voting power:
Error: VM Exception while processing transaction: reverted with panic code 0x11 (Arithmetic operation overflowed outside of an unchecked block)
at veRAACToken.increase (contracts/core/tokens/veRAACToken.sol:291)

Impact

  • Incorrect voting power

  • Users can be unable to increase lock

  • Incorrect checkpoints

Tools Used

Manual

Recommendations

Take into account the current voting power of a position to calculate the new voting power

Updates

Lead Judging Commences

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

veRAACToken::increase underflows on newPower - balanceOf(msg.sender)

Support

FAQs

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

Give us feedback!