One Shot: Reloaded

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

No input validation for bet_amount in go_on_stage_or_battle function will cause the CRED prize pool to be zero.

Author Revealed upon completion

Root + Impact

Description

  • The go_on_stage_or_battle function in the battle_addr::rap_battle module does not validate the bet_amount parameter before processing it. Specifically, it fails to check if bet_amount is positive (i.e., greater than zero).

  • When a first player initially submits bet_amount = 0, the transaction will proceed without depositing any CRED into the prize pool. Then when the next player calls the function, he will have to submit the same zero value as bet_amount, as the game requires the two players match equal CRED bets.

// Root cause in the codebase with @> marks to highlight the relevant section
public entry fun go_on_stage_or_battle(
player: &signer,
rapper_token: Object<Token>,
bet_amount: u64
) acquires BattleArena {
let player_addr = signer::address_of(player);
let arena = borrow_global_mut<BattleArena>(@battle_addr);
// No validation for bet_amount > 0
if (arena.defender == @0x0) {
assert!(arena.defender_bet == 0, E_BATTLE_ARENA_OCCUPIED);
arena.defender = player_addr;
arena.defender_bet = bet_amount;
// ...
let first_bet = coin::withdraw<cred_token::CRED>(player, bet_amount);
// ...
} else {
assert!(arena.defender_bet == bet_amount, E_BETS_DO_NOT_MATCH);
let defender_addr = arena.defender;
let chall_addr = player_addr;
// ...
}
}

Risk

Likelihood:

  • The likelihood of this bug being exploited is high, as the go_on_stage_or_battle function is a public entry point, accessible to any user interacting with the contract, and there are no barriers (e.g., access control) preventing a user from submitting a bet_amount of zero.

Impact:

  • This will result to the winner gaining zero CRED coin when the battle ends, disrupting the intended game mechanics.

Proof of Concept

A player calls go_on_stage_or_battle with bet_amount = 0 and a valid rapper_token.
The function proceeds to the "go on stage" branch (if arena.defender == @0x0).
No validation checks for bet_amount > 0, so coin::withdraw<cred_token::CRED>(player, 0) is called, which succeeds (as withdrawing zero coins is typically allowed).
The player becomes the defender, locking their rapper_token in the contract without depositing any CRED into the prize_pool.
Result: The arena is occupied, but the prize_pool remains empty, breaking the economic model of the contract.

Recommended Mitigation

  • Add a validation check for a positive bet amount in go_on_stage_or_battle function before processing the bet:

// add this code
+ assert!(bet_amount > 0, E_INVALID_BET_AMOUNT);

Support

FAQs

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