51218 sc high oracle callback timing vulnerability causes jackpot prize loss

Submitted on Aug 1st 2025 at 01:25:02 UTC by @KlosMitSoss for Attackathon | Plume Network

  • Report ID: #51218

  • Report Type: Smart Contract

  • Report severity: High

  • Target: https://github.com/immunefi-team/attackathon-plume-network/blob/main/plume/src/spin/Spin.sol

  • Impacts:

    • Theft of unclaimed yield

Description

Brief/Intro

When a callback from the Supra Router is executed, the current week is calculated based on the current timestamp rather than when the spin was initiated. This creates a timing vulnerability where users can start a spin during an active campaign, but the callback executes after the campaign has ended. When such delayed spins win the jackpot, users receive zero rewards despite legitimately winning and paying the spin fee.

Vulnerability Details

Users who initiate spins in the final weeks of the campaign risk losing legitimately won jackpots due to oracle callback delays beyond their control. The vulnerability occurs in the handleRandomness() function:

function determineReward(uint256 randomness, uint256 streakForReward) internal view returns (string memory, uint256) {
    ... ...
    uint8 weekNumber = uint8(getCurrentWeek()); // Uses current timestamp, not spin initiation time
    
    if (probability < jackpotThreshold) {
        return ("Jackpot", jackpotPrizes[weekNumber]); // Returns 0 for weeks > 11
    }
    ... ...
}

These delays cannot be controlled and will lead to unfair situations for users that didn't do anything wrong and started their spin during the campaign.

To mitigate this, consider using the timestamp of the startSpin() call such that the current week only relies on the user and not on the delay between the two calls.

Impact Details

The won jackpot cannot be claimed even though the user started the spin while the campaign was ongoing. This leads to users losing legitimately won jackpots which are currently worth up to 100,000 PLUME tokens.

References

Code references are provided throughout the report.

Proof of Concept

1

Step

Alice calls startSpin() in week 11 of the campaign, which is the last valid week (https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/spin/Spin.sol#L174-L195).

2

Step

The callback is executed in week 12, meaning the campaign has already ended. Alice wins the jackpot.

3

Step

In handleRandomness(), determineReward() is called. Inside this function, the week number is calculated based on the current timestamp, not the timestamp of the startSpin() call (https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/spin/Spin.sol#L285-L286).

4

Step

This week number is used to retrieve the jackpot prize for the week (https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/spin/Spin.sol#L293). However, since the week number is greater than 11 and the campaign only lasts 12 weeks (weeks 0-11), the jackpot prize for week 12 will be 0. The following comment also confirms this: https://github.com/immunefi-team/attackathon-plume-network/blob/580cc6d61b08a728bd98f11b9a2140b84f41c802/plume/src/spin/Spin.sol#L451

5

Step

As a result, Alice wins the jackpot but cannot claim anything, even though she submitted the spin on time while the campaign was ongoing.

Was this helpful?