59100 sc low periodattimestamp returns current period instead of queried historical period

Submitted on Nov 8th 2025 at 17:03:24 UTC by @jayx for Audit Comp | Firelight

  • Report ID: #59100

  • 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(uint48 timestamp) function in FirelightVault is advertised to return the period number corresponding to a given timestamp parameter, enabling historical period queries as part of the vault's "Historical tracking" feature. However, due to an implementation error in the helper function _sinceEpoch(), the function ignores the timestamp parameter and always calculates the period based on the current block timestamp (Time.timestamp()), effectively returning the current period for any input. This breaks the entire historical tracking feature for period-based queries, preventing users, external contracts, and off-chain systems from determining which period a historical timestamp belonged to, thereby rendering period-correlated historical data analysis impossible and causing operational failures in UIs, keepers, and analytics systems.

Vulnerability Details

The root cause lies in the interaction between periodAtTimestamp() and its helper function _sinceEpoch():

/**
 * @notice Returns the period number for the timestamp given.
 * @dev Return value may be unreliable if period number given is far away in the future
 * @dev given that new period configurations can be added after nextPeriodEnd().
 * @return The period number corresponding to the given timestamp.
 */
function periodAtTimestamp(uint48 timestamp) public view returns (uint256) {
    PeriodConfiguration memory periodConfiguration = periodConfigurationAtTimestamp(timestamp);
    // BUG: Uses _sinceEpoch() which calls Time.timestamp(), not the parameter 'timestamp'
    return periodConfiguration.startingPeriod + _sinceEpoch(periodConfiguration.epoch) / periodConfiguration.duration;
}

function _sinceEpoch(uint48 epoch) private view returns (uint48) {
    return Time.timestamp() - epoch;  // ← Always uses current time, never query timestamp
}

The function correctly retrieves the periodConfiguration for the queried timestamp via periodConfigurationAtTimestamp(timestamp), but then calculates the period offset using _sinceEpoch(periodConfiguration.epoch), which internally calls Time.timestamp() - epoch instead of using timestamp - epoch.

Expected behavior:

Actual behavior:

This causes the function to always return the current period, regardless of whether the queried timestamp is in the past, present, or future.

Impact Details

Primary Impact: Contract Fails to Deliver Promised Returns

The FirelightVault audit documentation explicitly lists "Historical tracking" as a core feature. The periodAtTimestamp() function is part of this advertised feature set but is completely non-functional for historical queries, constituting a failure to deliver promised functionality.

Concrete Consequences

1. Historical Balance-to-Period Correlation Broken

The vault provides balanceOfAt(address, timestamp) to query historical balances, but there is no way to determine which period that balance belonged to:

Any logic attempting to correlate historical balances with period-based data (such as pending withdrawals) will query the wrong period.

References

https://github.com/firelight-protocol/firelight-core/blob/db36312f1fb24efc88c3fde15a760defbc3e6370/contracts/FirelightVault.sol#L249C8-L249C123

Proof of Concept

Proof of Concept

Was this helpful?