Core Contracts

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

veRAACToken increase() Function can be DoSed when New Power is Less Than Current Balance Due to Decay

Summary

veRAACToken provides increase() function enabling users to add extra tokens to their lock positions. However, the increase() function incorrectly assumes that new voting power will always be greater than the current balance when increasing lock amount, failing to account for voting power decay over time. This can cause the function to revert due to underflow, making it impossible for users to increase their lock amounts in certain scenarios.

Vulnerability Details

The root cause of this issue is that in increase() function, when _mint() veRAACToken, it does not consider newPower can be less than balanceOf(msg.sender).

contracts/core/tokens/veRAACToken.sol#L270

function increase(uint256 amount) external nonReentrant whenNotPaused {
...
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
userLock.amount + amount,
userLock.end
);
uint256 newPower = uint256(uint128(newBias));
...
// Issue here: Assumes newPower >= balanceOf(msg.sender)
_mint(msg.sender, newPower - balanceOf(msg.sender));
emit LockIncreased(msg.sender, amount);
}

The newPower value is determined in calculateAndUpdatePower() function by calculation (amount * (unlockTime - block.timestamp)) / MAX_LOCK_DURATION;. Even though the amount is increased, when the remaining lock duration is short, eventually the newPower value will be less than balanceOf(msg.sender).

This can cause the function to revert due to underflow error in _mint().

PoC

The following PoC test case demonstrates that:

  1. A user creates an initial lock with 1000 RAACToken for 1 year

  2. Fast forwards time to 60% of the lock duration

  3. Attempts to increase the lock amount by 100 RAAC but reverts

it("should revert when increasing amount when new power is Less than current balance due to decay", async () => {
const amount = ethers.parseEther("1000");
const additionalAmount = ethers.parseEther("100");
const duration = 365 * 24 * 3600; // 1 year
// Create initial lock
await veRAACToken.connect(users[0]).lock(amount, duration);
const initialPower = await veRAACToken.balanceOf(users[0].address);
console.log("Initial veRAACToken balance:", initialPower);
// Move time forward close to lock end (60% of duration)
await time.increase(Math.floor(duration * 0.6));
// Try to increase the lock amount
// This will fail because the new power calculated will be less than current power
// Due to being very close to lock end, even with more tokens locked
await expect(
veRAACToken.connect(users[0]).increase(additionalAmount)
).to.be.revertedWithPanic(
0x11 // Arithmetic operation overflowed outside of an unchecked block
);
});

The PoC can be run by appending to the original veRAACToken.test.js test file.

Impact

The impact is high as user cannot increase their lock amounts in a common senario when newPower < balanceOf(msg.sender). This affects core protocol functionality around governance participation.

Tools Used

Manual code review

Recommendations

Update increase() function to handle both increasing and decreasing voting power scenarios:

function increase(uint256 amount) external nonReentrant whenNotPaused {
...
uint256 oldPower = balanceOf(msg.sender);
uint256 newPower = uint256(uint128(newBias));
// Handle both power increase and decrease
if (newPower > oldPower) {
_mint(msg.sender, newPower - oldPower);
} else if (newPower < oldPower) {
_burn(msg.sender, oldPower - newPower);
}
...
}
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!