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

Deterministic Randomness Contradicts Intended Behavior

Description

The get_random_slice function uses timestamp-based pseudorandomness instead of the recommended Aptos randomness API. While practical exploitation is limited due to consensus-driven timestamps and trusted owner controls, this function is by definition not random as claimed in the README

Root Cause

The implementation uses timestamp::now_microseconds() % 401 for randomness generation instead of the cryptographically secure aptos_framework::randomness API, despite having the correct #[randomness] attribute.

#[randomness]
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(); // @audit-issue Consensus-driven but still predictable
let random_val = time % 401; // @audit-info Deterministic calculation
let random_amount = 100 + random_val; // 100-500 APT range
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}

Risk

Likelihood: Low - Requires sophisticated timing analysis and malicious trusted owner

Impact: Medium - Affects distribution fairness in airdrop scenario

Impact

Medium severity because:

  • Deviates from Aptos secure alternatives

  • While timestamps are consensus-driven (not manipulable by single validator), they remain predictable

  • Owner is typically trusted, reducing likelihood of malicious exploitation

  • Limited scope to airdrop distribution rather than core protocol security

Proof of Concept

Demonstrates the predictable nature of timestamp-based randomness:

First add this setup helper to the pizza_drop.move file

#[test_only]
fun setup_test(
deployer: &signer,
user: &signer,
user2: &signer,
framework: &signer
): (coin::BurnCapability<AptosCoin>, coin::MintCapability<AptosCoin>) {
use aptos_framework::account;
use aptos_framework::timestamp;
use aptos_framework::aptos_coin;
// Setup framework
timestamp::set_time_has_started_for_testing(framework);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(framework);
// Create test accounts
account::create_account_for_test(@pizza_drop);
account::create_account_for_test(signer::address_of(user));
account::create_account_for_test(signer::address_of(user2));
// Initialize the pizza drop module
init_module(deployer);
debug::print(&utf8(b"Pizza drop module initialized"));
let funding_amount = 100000;
let deployer_coins = coin::mint<AptosCoin>(funding_amount, &mint_cap);
coin::register<AptosCoin>(deployer);
coin::deposit<AptosCoin>(@pizza_drop, deployer_coins);
(burn_cap, mint_cap)
}

Then run the test

#[test(deployer= @pizza_drop, user = @0x123, user2 = @0x124, framework = @0x1)]
fun test_predictable_distribution(deployer: &signer, user: &signer, user2: &signer, framework: &signer) {
let (burn_cap, mint_cap) = setup_test(deployer, user, user2, framework);
// Demonstrate predictability in test environment
timestamp::update_global_time_for_test(1000000000);
let user1_addr = signer::address_of(user);
register_pizza_lover(deployer, user1_addr);
timestamp::update_global_time_for_test(1000000001);
let user2_addr = signer::address_of(user2);
register_pizza_lover(deployer, user2_addr);
// Results are mathematically deterministic:
// User1: 1000000000 % 401 = 235 → 335 APT
// User2: 1000000001 % 401 = 236 → 336 APT
let amount1 = get_claimed_amount(user1_addr); // Always 335
let amount2 = get_claimed_amount(user2_addr); // Always 336
}

Recommended Mitigation

Use the proper Aptos randomness API to align with security best practices:

#[randomness]
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_val = aptos_framework::randomness::u64_range(0, 401);
let random_amount = 100 + random_val;
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}
+#[lint::allow_unsafe_randomness] // Required to bypass compiler security check
public entry fun register_pizza_lover(owner: &signer, user: address) {
// Only trusted owner can call this function
}

This provides cryptographically secure randomness that follows Aptos framework recommendations and eliminates deterministic reward distribution.

Updates

Appeal created

bube Lead Judge 9 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.