One Shot: Reloaded

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

Defenders can have their funds locked if challengers don't have sufficient CRED tokens to battle them

Defenders can have their funds locked if challengers don't have sufficient CRED tokens to battle them

Description

The function rap_battle::go_on_stage_or_battle has the following line :

assert!(arena.defender_bet == bet_amount, E_BETS_DO_NOT_MATCH);

This means that if the challenger doesn't have the require bet_amount, the defender will need to wait for a challenger that meet the requirement, and it could append after some hours, days or months or never.
It will depends on the amount that he/she bets on the first place.

Risk

The risk is that the funds are locked for an unknown amount of time and maybe after some time the defender would like to withdraw but there is no mechanism for that.

impact: Fund can be lock for months or forever if the bet is really big. (Medium-High)

likelyhood: The likelihood for this to happen is low; it depends on the amount of staking phase the defender had made. (Low)

Proof of Concept

The scenario is simple.

  1. An user mint a rapper

  2. He/She does 50 - 60 phase of staking and unstaking of 4 days each(Total : 200-240 days) and earn 200 to 240 CRED token

  3. Make a bet of 200 CRED for the battle.

Possible scenario others challengers doesn't have this amount to bet again this serious player, and the funds are locked for months or a year.

Recommended Mitigation

Add the possibility to the defenders to withdraw when they think that they wait for to long.(with the help of the module owner or not).

Add the following possibility in rap_battle::go_on_stage_or_battle :

if (arena.defender == @0x0) {
assert!(arena.defender_bet == 0, E_BATTLE_ARENA_OCCUPIED);
arena.defender = player_addr;
arena.defender_bet = bet_amount;
let token_id = object::object_address(&rapper_token);
arena.defender_token_id = token_id;
let first_bet = coin::withdraw<cred_token::CRED>(player, bet_amount);
coin::merge(&mut arena.prize_pool, first_bet);
one_shot::transfer_record_only(token_id, player_addr, @battle_addr);
object::transfer(player, rapper_token, @battle_addr);
event::emit(OnStage {
defender: player_addr,
token_id,
cred_bet: bet_amount,
});
+ } else if (player_addr == @battle_addr && bet_amount == 0){
+ let pool = coin::extract_all(&mut arena.prize_pool);
+ coin::deposit(arena.defender, pool);
+ one_shot::transfer_record_only(arena.defender_token_id, @battle_addr, arena.defender);
+ arena.defender = @0x0;
+ arena.defender_bet = 0;
+ arena.defender_token_id = @0x0;
} else {
assert!(arena.defender_bet == bet_amount, E_BETS_DO_NOT_MATCH);
let defender_addr = arena.defender;
let chall_addr = player_addr;
let chall_token_id = object::object_address(&rapper_token);
one_shot::transfer_record_only(chall_token_id, chall_addr, @battle_addr);
object::transfer(player, rapper_token, @battle_addr);
let chall_coins = coin::withdraw<cred_token::CRED>(player, bet_amount);
coin::merge(&mut arena.prize_pool, chall_coins);
let defender_skill = one_shot::skill_of(arena.defender_token_id);
let challenger_skill = one_shot::skill_of(chall_token_id);
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 };
event::emit(Battle {
challenger: chall_addr,
challenger_token_id: chall_token_id,
winner,
});
let pool = coin::extract_all(&mut arena.prize_pool);
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);
} 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);
one_shot::transfer_record_only(chall_token_id, @battle_addr, chall_addr);
};
arena.defender = @0x0;
arena.defender_bet = 0;
arena.defender_token_id = @0x0;
}

In this scenario, the module owner can withdraw for the defender.

Updates

Lead Judging Commences

bube Lead Judge 20 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

The defender can't cancel the battle if there is no challenger

There is no security impact on the protocol from this issue. The defender should wait until the challenger joins, this is intended behavior.

Support

FAQs

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