Beginner FriendlyGameFi
100 EXP
View results
Submission Details
Severity: medium
Valid

Predictable Randomness in Airdrop Allocation

Root + Impact

Description

  • Describe the normal behavior in one or more sentences

    Answer: When distributing randomized rewards (e.g., 100–500 APT per claim), the randomness should be unpredictable to all parties, including contract owner and participants, to ensure fairness.


  • Explain the specific issue or problem in one or more sentences

    Answer: The contract derives randomness from predictable sources such as the current block timestamp, sequence number, or transaction hash. Since these are publicly visible and often manipulatable by validators or miners, attackers can predict or manipulate the random output to always receive the maximum slice.

module pizzadrop::airdrop {
use std::timestamp;
use std::math;
public entry fun claim(caller: &signer) acquires Pool {
let addr = signer::address_of(caller);
/// @> Problem: Predictable source of randomness
let now = timestamp::now_seconds();
let slice = (now % 401) + 100;
withdraw_from_pool(slice, addr);
}
}

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

    Answer: Every claim transaction relies on a fully predictable timestamp.


  • Reason 2

    Answer: Attackers can simulate possible values off-chain before submitting their transaction.

Impact:

  • Impact 1

    Answer: Attackers repeatedly manipulate transaction timing until they always receive the maximum allocation (500 APT).


  • Impact 2

    Answer: Entire fairness of the airdrop pool is compromised, draining the pool disproportionately fast.

Proof of Concept

Step 1: Attacker precomputes rewards for future timestamps.

Step 2: Attacker submits transaction only when conditions favor a maximum payout.

Step 3: Contract behaves deterministically, proving randomness is broken.

#[test_only]
module pizzadrop::poc_exploit {
use pizzadrop::airdrop;
use std::timestamp;
use std::debug;
#[test(attacker = @0xBAD)]
fun test_predictable_randomness(attacker: &signer) {
// Step 1: Attacker predicts randomness off-chain
let fake_time = 1699999999; // attacker simulates this timestamp
let predicted_slice = (fake_time % 401) + 100;
// Step 2: Attacker waits until on-chain timestamp matches
// then calls claim()
let actual_slice = (timestamp::now_seconds() % 401) + 100;
debug::print(&predicted_slice); // attacker knows the exact reward
debug::print(&actual_slice); // matches predicted value
}
}

Recommended Mitigation

Replace timestamp-based randomness with a secure randomness source, such as Aptos’ built-in aptos_framework::random module.

Alternatively, use a commit–reveal scheme:

Users commit to a secret before the claim period.

Secret is later revealed, and combined with on-chain entropy to generate randomness.

This ensures unpredictability and prevents attackers from gaming the system.

- remove this code
- let now = timestamp::now_seconds();
- let slice = (now % 401) + 100;
+ add this code
+ /// Use a verifiable random oracle or commit-reveal scheme
+ /// Example: use on-chain randomness module (if available)
+ let randomness = aptos_framework::random::rand_u64();
+ let slice = (randomness % 401) + 100;
Updates

Appeal created

bube Lead Judge 12 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Predictable randomness

The `get_random_slice` function should only be called by the owner via the `register_pizza_lover` function. Also, the `owner` is trusted and will not choose a specific time for a new user to register. Therefore, I disagree with the claim of most reports in this group that an attacker can manipulate the random number of pizza slices. But I agree with the root cause of the reports in this group, that the random distribution is not completely random.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.