One Shot: Reloaded

First Flight #47
Beginner FriendlyNFT
100 EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

Self-challenge exploit (stat inflation / free wins)

Root + Impact

Description

  • Normal behavior:
    A defender should place a CRED bet, and a different player should act as the challenger. The battle outcome is determined by Rapper stats and RNG, and the winner receives the prize pool.

    Issue:
    The contract never checks that the challenger’s address is different from the defender’s. Because of this, the same user can play both roles in a battle.
    This creates two problems:

    1. Stat Inflation: Players can farm unlimited “wins” for their own Rappers without risk. Since wins are supposed to signal competitive success, this breaks the reputation system.

    2. Economic Abuse: Users can stage fake battles with themselves to unlock staking rewards or trigger unintended side-effects, without actually putting capital at risk.

// Root cause in the codebase with @> marks to highlight the relevant section
// Root cause: missing check on challenger vs defender
public entry fun go_on_stage_or_battle(
defender: &signer,
rapper_token: object::Object<Rapper>,
bet: coin::Coin<CRED>,
) acquires RapBattleArena, RapperStats {
...
@> // No check that challenger != defender
let challenger_addr = signer::address_of(defender);
...
}

Risk

Likelihood:

  • Any player can do this immediately, no special conditions.

Impact:

  • Fake wins, stat inflation, possible manipulation of prize pools.

Proof of Concept

Alice stakes her Rapper.
Alice also challenges her own Rapper using the same signer.
Regardless of the outcome, Alice still controls both sides and can repeatedly generate “wins.”
This breaks the competitive fairness.

Recommended Mitigation

- remove this code
+ add this code
public entry fun go_on_stage_or_battle(
defender: &signer,
rapper_token: object::Object<Rapper>,
bet: coin::Coin<CRED>,
) acquires RapBattleArena, RapperStats {
let defender_addr = signer::address_of(defender);
...
- // missing check
+ let challenger_addr = signer::address_of(defender);
+ assert!(challenger_addr != defender_addr, E_SELF_BATTLE);
Updates

Lead Judging Commences

bube Lead Judge 18 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

The defender and challenger can be the same person

Even if the defender and challenger are not the same address. The player can use two different addresses and still be both defender and challenger at the same time. The intended behavior here is the same Rapper tokens to be not able to battle themself.

Support

FAQs

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