One Shot: Reloaded

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

BattleArena resource is never published, blocking all battles

Author Revealed upon completion

Initialization Bug in rap_battle.move

Description

  • Normal behavior: Before any battles, the BattleArena resource should exist at @battle_addr so players can join and fight.

  • Actual behavior:

    • The module defines a private init_module, but there is no entry/public call that publishes BattleArena.

    • Any call to rap_battle::go_on_stage_or_battle immediately aborts because the resource does not exist.

// rap_battle.move
public entry fun go_on_stage_or_battle(player: &signer, rapper_token: Object<Token>, bet_amount: u64) acquires BattleArena {
@> let arena = borrow_global_mut<BattleArena>(@battle_addr); // aborts if BattleArena not published
...
}

Risk

Likelihood:

  • This always occurs on a fresh deployment, since nothing publishes BattleArena.

  • First player attempting to start a battle will consistently hit the abort.

Impact:

  • Battles cannot start at all.

  • Protocol is effectively bricked until redeployment or manual resource publishing.

Proof of Concept

/// Confirm that calling go_on_stage_or_battle without initializing BattleArena
/// aborts due to missing resource (arena not set up).
#[test(battle_addr = @0x42, alice = @0xa11ce)]
#[expected_failure]
public fun test_go_on_stage_without_init(
battle_addr: &signer,
alice: &signer,
) {
// Mint Rapper for Alice
let (alice_obj, _) = one_shot::test_mint_to_player_and_return_object(battle_addr, alice);
// Directly try to go on stage without rap_battle::test_init
// → should abort with MISSING_DATA (BattleArena not published)
rap_battle::go_on_stage_or_battle(alice, alice_obj, 0);
}

Test confirms: go_on_stage_or_battle aborts immediately when BattleArena has not been initialized.

Recommended Mitigation

Explicitly publish BattleArena on deployment or add lazy-init logic:

public entry fun go_on_stage_or_battle(player: &signer, rapper_token: Object<Token>, bet_amount: u64) acquires BattleArena {
- let arena = borrow_global_mut<BattleArena>(@battle_addr);
+ if (!exists<BattleArena>(@battle_addr)) {
+ init_module(player); // or better: require @battle_addr to init during deployment
+ };
+ let arena = borrow_global_mut<BattleArena>(@battle_addr);
...
}

Support

FAQs

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