59226 sc low logic flaw in periodattimestamp function breaks historical queries returning current period instead
Submitted on Nov 10th 2025 at 07:06:38 UTC by @theboiledcorn for Audit Comp | Firelight
Report ID: #59226
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 view function has a critical logic flaw where it ignores its timestamp parameter. The function incorrectly uses the current block time (Time.timestamp()) for its calculation instead of the provided timestamp. Consequently, any query for a historical period will fail and incorrectly return the current period number. This breaks all historical lookup functionality, as the function provides a demonstrably incorrect value to any user or contract that calls it.
Vulnerability Details
The vulnerability is a logic error in how periodAtTimestamp calculates the period number. The function correctly uses the timestamp parameter to find the right PeriodConfiguration, but it fails to use that timestamp in the final calculation.
Here is the flawed function:
function periodAtTimestamp(uint48 timestamp) public view returns (uint256) {
PeriodConfiguration memory periodConfiguration = periodConfigurationAtTimestamp(timestamp);
// ...
// THE FLAW IS HERE:
return periodConfiguration.startingPeriod + _sinceEpoch(periodConfiguration.epoch) / periodConfiguration.duration;
}The function passes the periodConfiguration.epoch to the _sinceEpoch helper, but it omits the timestamp variable.
The _sinceEpoch helper function is defined as:
Because _sinceEpoch uses Time.timestamp(), the periodAtTimestamp function also inadvertently uses Time.timestamp().
Proof of Concept Scenario
This can be proven with a simple test:
Assume the first period configuration is set up at block.timestamp = 1 with a duration of 604800.
2. * Warp the blockchain time far into the future, for example, into Period 5. vm.warp((604800 * 5) + 1); // block.timestamp is now 3,024,001 * Call the function, asking for the period at a time in Period 1. uint48 pastTimestamp = 1_000_000; uint256 result = vault.periodAtTimestamp(pastTimestamp);
Expected (Correct) Result: The function should return 1, as
1,000,000falls within Period 1.Actual (Buggy) Result: The function will calculate
(Time.timestamp() - epoch) / duration=(3024001 - 1) / 604800= 5. The function incorrectly returns 5.
Impact Details
The direct impact is a failure of the contract's core logic. The periodAtTimestamp function does not perform its advertised behavior, making it useless for its intended purpose of historical data retrieval. Any trust placed in this function by users or other contracts is broken.
This bug maps to the following categories from the program's in-scope impacts:
Contract fails to deliver promised returns, but doesn't lose value: The function promises (via its name and parameters) to return a specific historical period number. It fails to deliver this value, instead returning the current (and incorrect) period number.
Any user, UI, or integrated contract that relies on this function for historical data will receive incorrect values. This can cause downstream logic to fail, transactions to revert, or UIs to display false information. This is a clear case of "damage to the users" as the protocol's functionality is broken, even if there is no direct profit motive for an attacker.
References
function periodAtTimestamp(uint48 timestamp)function _sinceEpoch(uint48 epoch)
Proof of Concept
Proof of Concept
The following test case demonstrates the vulnerability. It establishes an initial period configuration, warps time far into the future to create a disparity, and then queries a historical timestamp. The test confirms that the function incorrectly returns the current period instead of the historical one.
Step-by-Step Explanation
The
setUp()function initializes the vault. Theinitializefunction (called fromsetUp) adds the firstPeriodConfiguration. Based on theFirelightVault.solcontract'sinitializefunction, this first configuration is:{ epoch: 1, duration: 604800, startingPeriod: 0 }.The test warps the block time far into the future to Period 5 (timestamp
3,024,001). This makes the "current period" 5.The test then calls
vault.periodAtTimestamp()with apastTimestampof1,000,000. This timestamp clearly falls within Period 1 (which spans[604_801, 1_209_601)).The test asserts two conditions:
It confirms that the
buggyResultis 5 (the current period), which is incorrect.It confirms that the
buggyResultis not 1 (the correct historical period).
Test Code
Test Output
The test passes, confirming the bug. The trace shows vault.periodAtTimestamp(1000000) incorrectly returning 5.
Was this helpful?