One Shot: Reloaded

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

Battle Ownership Bug — Winner Takes Both NFTs

Root + Impact

Description

  • Expected behavior: The winner should receive the pooled CRED tokens, and only their Rapper’s stats should be updated. Both NFTs should be returned to their respective owners.

  • Issue: The battle logic transfers both Rapper NFTs to the winner using transfer_record_only.

// rap_battle.move
if (winner == defender_addr) {
coin::deposit(defender_addr, pool);
one_shot::increment_wins(arena.defender_token_id);
one_shot::transfer_record_only(arena.defender_token_id, @battle_addr, defender_addr);
one_shot::transfer_record_only(chall_token_id, @battle_addr, defender_addr); // @> BUG
} else {
coin::deposit(chall_addr, pool);
one_shot::increment_wins(chall_token_id);
one_shot::transfer_record_only(arena.defender_token_id, @battle_addr, chall_addr); // @> BUG
one_shot::transfer_record_only(chall_token_id, @battle_addr, chall_addr);
}

Risk

Likelihood:

  • Occurs in every battle resolution, since the code always executes these transfers.

Impact:

  • The loser’s Rapper NFT is permanently reassigned to the winner.

  • Contradicts expected design, causing loss of assets and trust.

Proof of Concept

// After battle:
assert_eq!(one_shot::get_owner(rapperDefender), winner);
assert_eq!(one_shot::get_owner(rapperChallenger), winner);
// Both NFTs are recorded under winner’s ownership instead of original owners.

Recommended Mitigation

if (winner == defender_addr) {
- one_shot::transfer_record_only(arena.defender_token_id, @battle_addr, defender_addr);
- one_shot::transfer_record_only(chall_token_id, @battle_addr, defender_addr);
+ one_shot::transfer_record_only(arena.defender_token_id, @battle_addr, defender_addr);
+ one_shot::transfer_record_only(chall_token_id, @battle_addr, chall_addr);
} else {
- one_shot::transfer_record_only(arena.defender_token_id, @battle_addr, chall_addr);
- one_shot::transfer_record_only(chall_token_id, @battle_addr, chall_addr);
+ one_shot::transfer_record_only(arena.defender_token_id, @battle_addr, defender_addr);
+ one_shot::transfer_record_only(chall_token_id, @battle_addr, chall_addr);
}
Updates

Lead Judging Commences

bube Lead Judge 16 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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