59335 sc low periodattimestamp function returns current period instead of queried period leading to temporary freezing of funds
Submitted on Nov 11th 2025 at 08:59:36 UTC by @legion for Audit Comp | Firelight
Report ID: #59335
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/firelight-protocol/firelight-core/blob/main/contracts/FirelightVault.sol
Impacts:
Temporary freezing of funds
Description
Brief/Intro
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:
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 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 return0(period at deploy)periodAtTimestamp(2,209,600)→ should return2(period after 2 weeks)periodAtTimestamp(7,048,000)→ should return10(future period)
Actual behavior:
periodAtTimestamp(1,000,000)→ returns5(current period)periodAtTimestamp(2,209,600)→ returns5(current period)periodAtTimestamp(7,048,000)→ returns5(current 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
References
Vulnerable function:
FirelightVault.sol#L246-250-periodAtTimestamp()Root cause:
FirelightVault.sol#L794-796-_sinceEpoch()Related functions correctly using
_sinceEpochfor current time:currentPeriodStart()- Line 274currentPeriodEnd()- Line 282
Proof of Concept
Proof of Concept
Was this helpful?