Core Contracts

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

Lock with Small Amount Does Not Decay Over Time

Summary

The veRAACToken contract's voting power calculation for locked tokens (calculateAndUpdatePower) can result in non-decaying voting power when the locked amount is too small. This occurs due to integer division truncation in the slope calculation, leading to a slope of zero. Consequently, the voting power remains constant over time, violating the intended linear decay mechanism.

Vulnerability Details

In the veRAACToken::lock, veRAACToken::increase, and veRAACToken::extend functions, when the locked amount is too small (specifically, less than MAX_LOCK_DURATION), the slope calculation in calculateAndUpdatePower will be 0. As a result, the voting power does not decay over time, which contradicts the intended behavior of the protocol.

Here is the code of VotingPowerLib::calculateAndUpdatePower:

// 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

If amount < MAX_LOCK_DURATION, the slope calculation will result in slope = 0, causing the voting power to remain constant.

Impact

Users can lock multiple small amounts to maintain full voting power until unlock, bypassing the intended decay. This allows unfair governance influence by accumulating non-decaying voting power, distorting voting outcomes.

Proof of Concept

it.only("Lock with Small Amount Does Not Decay Over Time", async () => {
const amount = 365 * 24 * 3600 * 4 - 1;
const duration = 365 * 24 * 3600 * 4; // 4 years
await veRAACToken.connect(users[0]).lock(amount, duration);
const initialPower = await veRAACToken.getVotingPower(users[0].address);
console.log("Test Initial Power: ", initialPower);
await time.increase(duration / 2); // Advance time by half the lock duration
const midPower = await veRAACToken.getVotingPower(users[0].address);
console.log("Test Mid Power: ", midPower);
// Voting power remains unchanged
expect(midPower).to.equal(initialPower);
});

and the output of the test is:

Test Initial Power: 126143999n
Test Mid Power: 126143999n

Tools Used

Manual Review

Recommendations

To mitigate this issue, enforce a minimum effective lock amount that ensures the slope calculation does not truncate to zero.

Updates

Lead Judging Commences

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

VotingPowerLib::calculateAndUpdatePower results in zero slope for small amounts (<MAX_LOCK_DURATION), creating non-decaying voting power that violates linear decay mechanism

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

VotingPowerLib::calculateAndUpdatePower results in zero slope for small amounts (<MAX_LOCK_DURATION), creating non-decaying voting power that violates linear decay mechanism

Support

FAQs

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