One Shot: Reloaded

First Flight #47
Beginner FriendlyNFT
100 EXP
Submission Details
Impact: medium
Likelihood: medium

Weak Randomness in Battle Outcomes

Author Revealed upon completion

Root + Impact

Description

  • Battle outcomes should be fairly random based on skills, resistant to prediction or manipulation.

  • The specific issue is using timestamp::now_seconds() % total_skill as rnd, which is predictable and timing-manipulable.

// In rap_battle.move
let total_skill = defender_skill + challenger_skill;
//@> let rnd = timestamp::now_seconds() % (if (total_skill == 0) { 1 } else { total_skill });
let winner = if (rnd < defender_skill) { defender_addr } else { chall_addr };

Risk

Likelihood: Medium

  • Attackers simulate timestamp-based rnd before tx submission.

  • Front-running or timing exploits occur in low-latency environments.

Impact: Medium

  • Unfair battle outcomes favoring manipulators.

  • Economic loss for honest players in bets.

Proof of Concept

  • Predict current timestamp, compute rnd offline, only submit challenge tx if favorable.

#[test(module_owner = @battle_addr, player1 = @0x123, player2 = @0x456)]
fun test_weak_randomness_predictable(module_owner: &signer, player1: &signer, player2: &signer) acquires battle_addr::rap_battle::BattleArena, battle_addr::one_shot::Collection, battle_addr::one_shot::RapperStats {
// Setup: Initialize modules
battle_addr::cred_token::init_module(module_owner);
battle_addr::one_shot::init_module(module_owner);
battle_addr::rap_battle::init_module(module_owner);
// Mint rappers with known skills
battle_addr::one_shot::mint_rapper(module_owner, signer::address_of(player1));
battle_addr::one_shot::mint_rapper(module_owner, signer::address_of(player2));
let token_id1 = /* assume */;
let token_id2 = /* assume */;
let skill1 = battle_addr::one_shot::skill_of(token_id1); // e.g., 65
let skill2 = battle_addr::one_shot::skill_of(token_id2); // e.g., 65
let total = skill1 + skill2;
// Set timestamp to known value
aptos_framework::timestamp::set_time_has_started_for_testing(module_owner);
let now = 1000; // Predictable
aptos_framework::timestamp::update_global_time_for_test_secs(now);
// Simulate battle: rnd = now % total
let rnd = now % total;
let winner = if (rnd < skill1) { signer::address_of(player1) } else { signer::address_of(player2) };
// Demonstrate predictability: Attacker can simulate and only challenge if favorable
assert!(/* check actual battle matches predicted winner */, 0);
}

Recommended Mitigation

  • The following mitigate the vulnerability, by usage of a more roboust randomness.

- let rnd = timestamp::now_seconds() % total_skill;
+ let rnd = aptos_framework::randomnessrandomness::u64_range(0, total_skill);

Support

FAQs

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