51878 sc high timing misalignment between campaign days and calendar days allows double spinning on high probability jackpot days
Submitted on Aug 6th 2025 at 12:09:12 UTC by @vivekd for Attackathon | Plume Network
Report ID: #51878
Report Type: Smart Contract
Report severity: High
Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/spin/Spin.sol
Impacts:
Smart contract unable to operate due to lack of token funds
Description
Brief/Intro
The Spin contract uses inconsistent day calculation methods for spin cooldowns versus jackpot probability determination, creating timing windows where a single high-probability campaign day spans multiple calendar days.
This allows users to bypass the intended daily spin limit by spinning on consecutive calendar days during the same profitable campaign day period, effectively doubling their chances at high-value jackpots and disrupting the lottery's economic balance.
Vulnerability Details
The vulnerability stems from two different day calculation systems used within the same contract.
Calendar Days (for spin restrictions)
The canSpin() modifier uses the DateTime library for actual calendar date boundaries:
// Lines 149-162 in Spin.sol
(uint16 lastSpinYear, uint8 lastSpinMonth, uint8 lastSpinDay) = (
dateTime.getYear(_lastSpinTimestamp),
dateTime.getMonth(_lastSpinTimestamp),
dateTime.getDay(_lastSpinTimestamp)
);
(uint16 currentYear, uint8 currentMonth, uint8 currentDay) =
(dateTime.getYear(block.timestamp), dateTime.getMonth(block.timestamp), dateTime.getDay(block.timestamp));
if (isSameDay(lastSpinYear, lastSpinMonth, lastSpinDay, currentYear, currentMonth, currentDay)) {
revert AlreadySpunToday();
}Campaign Days (for jackpot probabilities)
The reward system uses timestamp arithmetic from campaign start:
// Lines 285-290 in Spin.sol
uint256 daysSinceStart = (block.timestamp - campaignStartDate) / 1 days;
uint8 dayOfWeek = uint8(daysSinceStart % 7);
uint256 jackpotThreshold = jackpotProbabilities[dayOfWeek];The Critical Misalignment
When campaignStartDate is not aligned with midnight UTC boundaries, campaign day periods can span across multiple calendar days. This creates exploitation windows where users can spin multiple times during a single high-probability campaign day period.
Example scenario:
Campaign starts Sunday 12:00 PM UTC.
Campaign Day 6 (highest probability = 20) runs from Sunday 12:00 PM to Monday 12:00 PM — spanning the calendar day boundary at midnight.
A user can spin Sunday 11:59 PM (calendar Sunday) and again Monday 12:01 AM (calendar Monday) while both spins fall into the same campaign day (Day 6) and thus enjoy identical high jackpot odds.
Economic Impact
The jackpot probabilities array [1, 2, 3, 5, 7, 10, 20] shows dramatic variation, with the highest probability day offering 20x better odds than the lowest. Users exploiting this timing can effectively double their spins on the most profitable days of the week, accelerating reward pool depletion and potentially causing operational issues.
Impact: Fast reward pool depletion and potential operational shutdown of the lottery system due to excessive payouts or drained funds.
Proof of Concept
Prerequisites:
Campaign starts at a non-midnight time creating calendar/campaign day misalignment
Jackpot probabilities vary significantly across days of the week
User has sufficient PLUME tokens for multiple spin fees
References
https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/spin/Spin.sol#L137-L165
https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/spin/Spin.sol#L267-L304
Was this helpful?