The periodAtTimestamp(uint48 timestamp) view function in FirelightVault.sol contains a critical logic error where it always returns the current period number regardless of the timestamp parameter passed to it. This function is designed to allow users and off-chain systems to determine which period corresponds to any given historical or future timestamp. However, due to incorrect use of Time.timestamp() instead of the function parameter in the calculation, all queries return the same value—the currently active period. This breaks withdrawal scheduling automation, historical analytics, and user-facing dashboards, potentially causing users' mature withdrawals to remain temporarily frozen beyond their intended claim window because automated systems cannot correctly determine when periods end.
Vulnerability Details
Root Cause
The vulnerability exists in the periodAtTimestamp function at line 246-250 of FirelightVault.sol:
The function correctly retrieves the appropriate PeriodConfiguration for the given timestamp parameter using periodConfigurationAtTimestamp(timestamp). However, it then calculates the elapsed time by calling the private helper function _sinceEpoch:
The _sinceEpoch function always uses Time.timestamp() (the current block timestamp) rather than the timestamp parameter passed to periodAtTimestamp. This means the elapsed time calculation is always based on the current moment, not the queried timestamp.
Concrete Example
Consider a vault deployed with a 1-week (604800 seconds) period duration:
Timeline:
Deploy time (T₀): timestamp = 1,000,000
Current time (T_now): timestamp = 4,024,000 (5 weeks later, period = 5)
Expected behavior:
periodAtTimestamp(1,000,000) → should return 0 (period at deploy)
periodAtTimestamp(2,209,600) → should return 2 (period after 2 weeks)
periodAtTimestamp(7,048,000) → should return 10 (future period)
All three calls return the same value despite querying different timestamps.
Impact Detail
Temporary Freezing of Funds The FirelightVault implements an asynchronous withdrawal system where users call withdraw() or redeem() to queue a withdrawal for the next period (currentPeriod() + 1). Once that period ends, users can call claimWithdraw(period) to receive their funds. The claimWithdraw function requires that period < currentPeriod(), meaning the period must have ended. This can lead to : Broken off-chain automation: Dashboards, bots, and integrations rely on periodAtTimestamp() to calculate:
"When will my withdrawal become claimable?"
"What period will be active at timestamp X?"
Historical period lookups for analytics and auditing