#59928 [SC-Low] incorrect period calculation periodattimestamp function
Submitted on Nov 17th 2025 at 02:26:03 UTC by @Le_Rems for Audit Comp | Firelight
Report ID: #59928
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 in FirelightVault is designed to return the period number corresponding to any given timestamp. However, the implementation incorrectly uses the current block timestamp instead of the provided parameter when calculating the period number. This causes the function to return incorrect results for any timestamp that differs from the current time.
Vulnerability Details
The FirelightVault contract implements a period-based system where time is divided into discrete periods, each defined by a PeriodConfiguration containing an epoch timestamp, duration, and starting period number.
The periodAtTimestamp() function is a public view function intended to determine which period number corresponds to a specific timestamp. This function should enable querying periods for any point in time—past, present, or future—which is essential for historical queries, period validation, and integration with external systems.
The function implementation is as follows:
The function first correctly retrieves the appropriate PeriodConfiguration for the given timestamp by calling periodConfigurationAtTimestamp(timestamp). However, when calculating the actual period number, it calls _sinceEpoch(periodConfiguration.epoch), which is defined as:
The root cause is that _sinceEpoch() always uses Time.timestamp() (the current block timestamp) rather than accepting and using the timestamp parameter provided to periodAtTimestamp(). This means:
When querying the current period, the function works correctly because
timestampequalsTime.timestamp()When querying a past timestamp, the function retrieves the correct configuration but calculates the period number using the current time, returning a period that would be correct now but incorrect for the queried time
When querying a future timestamp, the calculation is similarly incorrect
For example, consider a scenario where:
The vault has been operational for 100 periods, each lasting 30 days
A period configuration has an epoch at timestamp T0 and duration of 30 days
At timestamp T0 + 15 days, the period number should be
startingPeriod + 0(still in the first period)At timestamp T0 + 45 days (15 days into the future from the current time T0 + 30 days), if someone queries
periodAtTimestamp(T0 + 15), the function would:Correctly identify the period configuration for T0 + 15 ✓
But calculate using
(T0 + 30) - epochinstead of(T0 + 15) - epoch, returningstartingPeriod + 1instead ofstartingPeriod + 0✗
The issue affects the contract's API contract: the function signature and documentation promise to return the period for "the timestamp given," but the implementation violates this promise for any timestamp other than the current time.
Impact Details
The impact manifests in several ways:
1. The function doesn't behave as expected
Any external system or smart contract that queries
periodAtTimestamp()with historical timestamps will receive incorrect period numbers
2. Future Functionality Risks
If the contract is upgraded or extended to validate withdrawals, deposits, or claims based on historical periods (e.g., validating that a withdrawal was requested in a specific past period), the bug would cause incorrect validations
Proof of Concept
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.28;
import {Test, console} from "forge-std/Test.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {FirelightVault} from "../contracts/FirelightVault.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract FirelightVaultTest is Test { // Contracts FirelightVault public firelightVault; FirelightVault public firelightVaultImplementation; ERC1967Proxy public proxy; IERC20 public tokenContract; address public assetManager; // Mock asset manager for FAsset
}
Was this helpful?