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

Predictable Random Slice Size Allows Owner to Manipulate Rewards, Granting Maximum Slice (500 APT) For Designated Users.

Root + Impact

Description

  • From the readme.md, the airdrop contract should give each registered user a random slice size between 100–500 APT, but the get_random_slice function uses the timestamp (timestamp::now_microseconds()) to generate a pseudo-random value for determining the random slice allocated to a user.

  • This timestamp-based randomness is predictable and manipulable, allowing the module’s owner to register users at specific timestamps to guarantee the maximum allocation (500 APT).

  • For example, the owner, who calls register_pizza_lover (which invokes get_random_slice), can time their transaction to ensure time % 401 == 400, resulting in random_val = 400 and random_amount = 500 for a chosen user. This undermines the fairness of the airdrop and could lead to abuse.

// Root cause in the codebase with @> marks to highlight the relevant section
entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
let time = timestamp::now_microseconds();
let random_val = time % 401;
let random_amount = 100 + random_val; // 100-500 APT (in Octas: 10^8 smallest unit)
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}

Risk:

  • Moderate. Precise timing requires automation but is feasible in a testnet or low-traffic network. In a mainnet with multiple validators, the exploit is harder but still possible with validator collusion or repeated transaction submissions.

Impact:

  • Users do not receive fair random allocations.

  • The contract owner can register addresses at carefully chosen timestamps to guarantee maximum slices (500 APT).

  • Validators can reorder or delay transactions to skew randomness outcomes.

  • Over time, this could drain the pool prematurely and break trust in the airdrop process.

Proof of Concept

1. Deploy the pizza_drop::airdrop module on an Aptos testnet.
2. Fund the module with APT using fund_pizza_drop.
3. Monitor the blockchain’s timestamp using an Aptos node API (e.g., querying timestamp::now_microseconds() via a view function).
4. Identify a timestamp T where T % 401 == 400 (e.g., 1,735,516,800,000,400 µs).
5. Submit a register_pizza_lover transaction for a target user at the precise moment the block timestamp is expected to be T. This can be automated with a script that polls the timestamp and submits the transaction.
6. Verify that get_random_slice assigns random_amount = 500 by checking get_claimed_amount(user).
7. Repeat for multiple users to demonstrate consistent maximum allocations.

Recommended Mitigation

Replace timestamp::now_microseconds() with a built-in on-chain randomness module in Aptos:

// remove this code
- let time = timestamp::now_microseconds();
- let random_val = time % 401;
// add this code
+ let random_val = aptos_framework::randomness::generate_random_u64() % 401;
Updates

Appeal created

bube Lead Judge about 1 month 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.