#59007 [SC-Low] periodattimestamp returns current period instead of historical

Submitted on Nov 7th 2025 at 17:27:43 UTC by @Tomioka for Audit Comp | Firelight

  • Report ID: #59007

  • Report Type: Smart Contract

  • Report severity: Low

  • Target: https://github.com/firelight-protocol/firelight-core/blob/main/contracts/FirelightVault.sol

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Brief/Intro

The periodAtTimestamp() function is designed to return the period number for a given historical timestamp. However, due to the implementation of the private helper function _sinceEpoch(), it always uses Time.timestamp() (current block timestamp) instead of the provided timestamp parameter. This causes periodAtTimestamp() to always return the current period number regardless of what historical timestamp is passed to it.

Vulnerability Details

The Core Flaw:

The periodAtTimestamp() function at line 246-250 calls _sinceEpoch() to calculate elapsed time:

// Line 246-250: periodAtTimestamp function
function periodAtTimestamp(uint48 timestamp) public view returns (uint256) {
    PeriodConfiguration memory periodConfiguration = periodConfigurationAtTimestamp(timestamp);
    // solhint-disable-next-line max-line-length
    return periodConfiguration.startingPeriod + _sinceEpoch(periodConfiguration.epoch) / periodConfiguration.duration;
}

The _sinceEpoch() helper function is defined at line 795-797:

Why This Is Wrong:

The periodAtTimestamp(uint48 timestamp) function receives a timestamp parameter that should be used to calculate the period number at that point in time. However:

  1. Line 247 correctly retrieves the periodConfiguration for the provided timestamp

  2. Line 249 calls _sinceEpoch(periodConfiguration.epoch) which calculates Time.timestamp() - epoch

  3. Time.timestamp() returns the current block timestamp, not the timestamp parameter passed to the function

  4. The result is always calculated based on the current time, making the input parameter effectively ignored

The Correct Implementation Should Be:

Root Cause Analysis:

The bug stems from the design of _sinceEpoch() as a helper function that always uses the current timestamp. This pattern works correctly for functions like currentPeriod(), currentPeriodStart(), and currentPeriodEnd() which are meant to operate on the current time. However, it breaks the contract when used in periodAtTimestamp(), which needs to calculate periods for arbitrary historical (or future) timestamps.

Functions Affected:

The following functions use _sinceEpoch():

  • periodAtTimestamp(uint48 timestamp) : BROKEN, Should use provided timestamp

  • currentPeriodStart() : Correct: Intended to use current time

  • currentPeriodEnd() : Correct: Intended to use current time

Impact Details

Primary Impact: Historical Query Failure

The primary consequence is that any attempt to query the period number for a historical timestamp will fail to return accurate historical data. Instead, it will always return the current period number.

Secondary Impact: Dependent System Failures

Any off chain systems, integrations, or internal logic that relies on periodAtTimestamp() to:

  • Look up historical period numbers

  • Validate timestamps against period boundaries

  • Reconstruct historical state

  • Perform period based calculations for past events

will receive incorrect data, leading to potential cascading failures.

Tertiary Impact: User Confusion

Users or integrators who call periodAtTimestamp() with a historical timestamp will receive confusing results:

  • The function signature promises to return "the period number for the timestamp given"

  • The return value will always be the current period

  • No revert or error occurs, making the bug silent and hard to detect

Practical Example:

Affected Users:

  • Off chain systems querying historical period data

  • Analytics tools and dashboards

  • Third party integrations that rely on period lookups

  • Smart contracts that call this function externally

References

  • Vulnerable code: contracts/FirelightVault.sol:246-250

  • Helper function: contracts/FirelightVault.sol:795-797

  • OpenZeppelin Time library: https://docs.openzeppelin.com/contracts/5.x/api/utils#Time

  • NatSpec documentation at line 241-245 (describes intended behavior)


Proof of Concept

Proof of Concept

The complete runnable PoC : test/poc_period_bug.js.

Execution

Full PoC

Key Assertions

Was this helpful?