One Shot: Reloaded

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

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

Author Revealed upon completion

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.

Support

FAQs

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