52277 sc low race condition in streak calculation leads to unfair streak reset for users spinning near utc day change
Submitted on: Aug 9th 2025 at 11:19:27 UTC by @Orionn for Attackathon | Plume Network
Report ID: #52277
Report Type: Smart Contract
Severity: Low
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/spin/Spin.sol
Impacts: Contract fails to deliver promised returns, but doesn't lose value
A race condition in streak calculation can unfairly reset a user's daily streak when the oracle callback is mined after a UTC day boundary, even if the user initiated their spin before the boundary.
Description
Brief / Intro
A race condition exists in the daily streak calculation logic within Spin.sol. The _computeStreak function uses the block.timestamp from the oracle's callback transaction (handleRandomness) to determine the day of the spin, rather than the block.timestamp of the user's initial startSpin transaction. If a user starts a spin shortly before a UTC day change, and the oracle callback is mined after the day change, the user's streak can be unfairly reset.
Vulnerability Details
The daily streak mechanic relies on comparing the day of the last spin (lastDaySpun) with the day of the current spin (today). The contract treats the oracle callback time as the spin time, which is asynchronous and outside the user's control.
User's Transaction (
startSpin): user initiates spin at timestamp A (the user's intended spin time).Oracle's Transaction (
handleRandomness): oracle callback occurs at timestamp B (potentially later).Root Cause:
_computeStreakis called inhandleRandomnessand usesnowTs(the callback's block.timestamp) to computetoday. If A and B fall on different UTC days, the streak calculation can be incorrect and penalize the user.
Vulnerable code excerpt: Spin::_computeStreak
uint256 lastDaySpun = lastSpinTs / SECONDS_PER_DAY;
uint256 today = nowTs / SECONDS_PER_DAY; // `nowTs` is the block.timestamp of the handleRandomness callback
if (today == lastDaySpun + 1) { // This check fails if `today` is `lastDaySpun + 2` due to the timing gap
return userData[user].streakCount + streakAdjustment;
}
return 0 + streakAdjustment; // The user's streak is unfairly resetImpact Details
Loss of Value: Users can lose their accumulated daily streak. Higher streaks yield higher "Raffle Ticket" rewards (
baseRaffleMultiplier * streakForReward), so breaking a streak reduces future rewards.Loss of Opportunity: Users may lose eligibility for weekly Jackpot rewards that require a minimum
streakCount.
References
https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/spin/Spin.sol?utm_source=immunefi
Proof of Concept
Step 4 — Bug triggers in streak computation
_computeStreakis called insidehandleRandomnessusingnowTsfrom the callback (Day 11).Calculations:
lastDaySpunderived from Alice'slastSpinTimestamp→ Day 9.todayderived fromnowTs→ Day 11.Check
if (today == lastDaySpun + 1)=> checks11 == 9 + 1→ false.
Result: The function treats the spin as breaking the streak and returns
1.
Was this helpful?