Core Contracts

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

`veRAACToken::lock` and `veRAACToken::increase` Functions Can Be Blocked Due to Incorrect Boost Period Updates

Summary

A vulnerability arises because the TimeWeightedAverage::updateValue() function erroneously increments the totalDuration field, incorrectly extending the current period’s duration. This increment incorrectly treats the elapsed time within the current period as part of the overall duration, causing new period creation (via TimeWeightedAverage::createPeriod) to revert with PeriodNotElapsed(). Consequently, once the period is "stretched out," any subsequent calls to start a new period - such as those triggered by additional veRAACToken::lock() or veRAACToken::increase() calls - will indefinitely fail until the artificially extended period time finally elapses.

Vulnerability Details

Where It Happens

  • In the BoostCalculator::updateBoostPeriod() function, if the current period has not yet ended, it calls:

    state.boostPeriod.updateValue(newVotingPower, currentTime)
  • Inside TimeWeightedAverage::updateValue(), the code erroneously does:

    self.totalDuration += duration;

    Here, duration is the time elapsed since the last update. By adding that elapsed time to totalDuration, the current period’s duration is unintentionally extended.

Why It's Problematic

totalDuration is used to determine whether a new period can start:

if (self.startTime != 0 && startTime < self.startTime + self.totalDuration) {
revert PeriodNotElapsed();
}

Because totalDuration grows every time TimeWeightedAverage::updateValue() is called:

  • The contract behaves as though the current period never ends.

  • Any lock() or increase() calls that attempt to create a new period will revert with PeriodNotElapsed().

  • This effectively locks the system into an infinite extension loop until the artificially extended duration fully elapses.

Real-World Impact in the Code

  • Users who create or increase locks within the original period will unintentionally extend totalDuration, causing the period to be considered "not over" due to the accumulated duration.

  • Once in this state, no further lock or increase operations can be performed successfully. The contract will reject all new actions until the artificially prolonged period naturally expires.

  • This issue repeats for every new period, meaning the protocol will be locked up in every cycle.

Impact

Denial of Service (DoS) on lock() and increase() operations

  • Calls to lock() or increase() will always revert when a new period should be created but is prevented by PeriodNotElapsed().

  • The lockout duration can last as long as the boost period, making it impossible for users to create/increase locks.

  • This issue repeats every period, meaning the protocol will repeatedly face disruption in every new cycle.

Tools Used

Manual Review

POC

Here is a POC for the issue:

// add this test to test/unit/core/tokens/veRAACToken.test.js
it.only("locks/increase can be DoS", async () => {
console.log("H03 - locks/increase can be DoS");
// Define users
const alice = users[0];
const bob = users[1];
const charlie = users[2];
// Define locking parameters
const amount = ethers.parseEther("1000");
const duration = 365 * 24 * 3600 * 4; // 4 years in seconds
const boostWindow = await veRAACToken.getBoostWindow(); // Retrieve the boost window duration
let currentTime = BigInt(await time.latest());
let elapsedTime = 0n;
console.log("Boost window: ", boostWindow);
console.log("Current time: ", currentTime);
// Check initial boost period state
const totalDuration0 = await veRAACToken.getBoostPeriodTotalDuration();
const startTime0 = await veRAACToken.getBoostPeriodStartTime();
const endTime0 = await veRAACToken.getBoostPeriodEndTime();
console.log("Initial Boost Period State: ");
console.log(" - Total Duration: ", totalDuration0);
console.log(" - Start Time: ", startTime0);
console.log(" - End Time: ", endTime0);
// Step 1: Alice creates the first lock
console.log("Creating first lock for Alice...");
const alice1Tx = await veRAACToken.connect(alice).lock(amount, duration);
await alice1Tx.wait();
// Retrieve new boost period state after Alice's lock
const totalDuration1 = await veRAACToken.getBoostPeriodTotalDuration();
const startTime1 = await veRAACToken.getBoostPeriodStartTime();
const endTime1 = await veRAACToken.getBoostPeriodEndTime();
console.log("Alice's Position after first lock:");
console.log(" - Total Duration: ", totalDuration1);
console.log(" - Start Time: ", startTime1);
console.log(" - End Time: ", endTime1);
// Step 2: Move close to the end of the boost period
await time.increase(boostWindow - 10n);
currentTime = BigInt(await time.latest());
elapsedTime = currentTime - BigInt(startTime1);
console.log("Time advanced close to boost period end:");
console.log(" - Current Time: ", currentTime);
console.log(" - Elapsed Time: ", elapsedTime);
// Step 3: Bob creates a new lock near the end of the boost period
console.log("Creating first lock for Bob...");
const bob1Tx = await veRAACToken.connect(bob).lock(amount, duration);
await bob1Tx.wait();
// Retrieve boost period state after Bob's lock
const totalDuration2 = await veRAACToken.getBoostPeriodTotalDuration();
const startTime2 = await veRAACToken.getBoostPeriodStartTime();
const endTime2 = await veRAACToken.getBoostPeriodEndTime();
console.log("Bob's Position after first lock:");
console.log(" - Total Duration: ", totalDuration2);
console.log(" - Start Time: ", startTime2);
console.log(" - End Time: ", endTime2);
// Step 4: Move close to the end of the newly extended boost period
await time.increase(boostWindow - 10n);
currentTime = BigInt(await time.latest());
elapsedTime = currentTime - BigInt(startTime2);
console.log("Time advanced close to the extended boost period end:");
console.log(" - Current Time: ", currentTime);
console.log(" - Elapsed Time: ", elapsedTime);
// Uncomment to test the effect of another lock right at the period's end
/*
console.log("Creating first lock for Charlie...");
const charlie1Tx = await veRAACToken.connect(charlie).lock(amount, duration);
await charlie1Tx.wait();
const totalDuration3 = await veRAACToken.getBoostPeriodTotalDuration();
const startTime3 = await veRAACToken.getBoostPeriodStartTime();
console.log("Charlie's Position after first lock:");
console.log(" - Total Duration: ", totalDuration3);
console.log(" - Start Time: ", startTime3);
*/
// Step 5: Alice tries to increase her lock amount
// This should fail due to the artificially extended period
console.log("Increasing lock amount for Alice...");
const alice2Tx = await veRAACToken.connect(alice).increase(amount);
await alice2Tx.wait();
});

and the test's output:

H03 - locks/increase can be DoS
Boost window: 604800n
Current time: 1740138005n
Initial Boost Period State:
- Total Duration: 0n
- Start Time: 0n
- End Time: 0n
Creating first lock for Alice...
Alice's Position after first lock:
- Total Duration: 604800n
- Start Time: 1740138007n
- End Time: 1740742807n
Time advanced close to boost period end:
- Current Time: 1740742797n
- Elapsed Time: 604790n
Creating first lock for Bob...
Bob's Position after first lock:
- Total Duration: 1209590n
- Start Time: 1740138007n
- End Time: 1740742807n
Time advanced close to the extended boost period end:
- Current Time: 1741347587n
- Elapsed Time: 1209580n
Increasing lock amount for Alice...
1) H03 - locks/increase can be DoS
0 passing (36s)
1 failing
1) veRAACToken
Lock Mechanism
H03 - locks/increase can be DoS:
Error: VM Exception while processing transaction: reverted with custom error 'PeriodNotElapsed()'
at veRAACToken.createPeriod (contracts/libraries/math/TimeWeightedAverage.sol:118)

Recommendations

In TimeWeightedAverage::updateValue(), remove the line:

self.totalDuration += duration;
Updates

Lead Judging Commences

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

veRAACToken can be DOSed when totalDuration exceeds boostWindow in TimeWeightedAverage, preventing new users from locking tokens until extended duration elapses

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

veRAACToken can be DOSed when totalDuration exceeds boostWindow in TimeWeightedAverage, preventing new users from locking tokens until extended duration elapses

Support

FAQs

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