Contract fails to deliver promised returns, but doesn't lose value
Description
Summary
The periodAtTimestamp() function contains a logic error where it uses the current block timestamp instead of the provided timestamp parameter when calculating period numbers. This causes two potential failure modes:
Underflow Reverts: When querying future timestamps before the epoch is reached, the function reverts with an arithmetic underflow
Silent Incorrect Returns: When querying future timestamps after the epoch is reached, the function returns an incorrect period number based on current time instead of the requested timestamp
Both failures break the contract's ability to provide accurate period information, and this falls in the "Contract fails to deliver promised returns, but doesn't lose value" low-severity impact.
Finding Description
The vulnerability exists in the interaction between two functions:
periodAtTimestamp(uint48 timestamp):
_sinceEpoch(uint48 epoch):
The _sinceEpoch() function ignores the caller-supplied timestamp parameter and always uses Time.timestamp() (current block time). This creates an incorrect calculation path.
Impact
As discussed above, the function returns incorrect value and satisfies the low severity criteria.
Recommendation
Modify _sinceEpoch() to accept and use the actual timestamp parameter:
Then update respective callers.
Proof of Concept
Proof of Concept
We update period_update.js and add another test under describe('Period update test', function() section:
Running the test would fail. This demonstrates a different return value is from expected value.
function periodAtTimestamp(uint48 timestamp) public view returns (uint256) {
PeriodConfiguration memory periodConfiguration = periodConfigurationAtTimestamp(timestamp);
return periodConfiguration.startingPeriod + _sinceEpoch(periodConfiguration.epoch) / periodConfiguration.duration;
}
function _sinceEpoch(uint48 epoch) private view returns (uint48) {
return Time.timestamp() - epoch; // @audit: Uses current time, not the timestamp parameter
}
function _sinceEpoch(uint48 epoch, uint48 timestamp) private pure returns (uint48) {
return timestamp - epoch;
}
it("periodAtTimestamp should map to the same period", async () => {
// choose a timestamp ahead of the current block time
const t0 = await time.latest();
const ts = t0 + 2 * PERIOD_CONFIGURATION_DURATION;
const duration = await current_period_duration();
expect(duration).to.equal(PERIOD_CONFIGURATION_DURATION);
// snapshot the computed period before time movement
const initial = await firelight_vault.periodAtTimestamp(ts);
// move forward exactly one period
await time.increase(PERIOD_CONFIGURATION_DURATION);
// query again after the time jump
const shifted = await firelight_vault.periodAtTimestamp(ts);
expect(shifted).to.equal(initial);
});