One Shot: Reloaded

First Flight #47
Beginner FriendlyNFT
100 EXP
View results
Submission Details
Severity: low
Valid

Users can stake only one NFT at a time

Root + Impact

Description

In every NFT staking applications, users should be able to stake any amount of NFTs to receive rewards, unless specified in the documentation.

However, staking a rapper in streets::stake can only be done once at a time. This is because move_to sets resources for an address and aborts if the resource already exists, in order to not overwrite it.

Because of that behaviour, it is not possible to stake multiple rappers at the same time.

// in streets::stake
public entry fun stake(staker: &signer, rapper_token: Object<Token>) {
let staker_addr = signer::address_of(staker);
let token_id = object::object_address(&rapper_token);
// @audit `move_to` reverts if the resource already exists
move_to(staker, StakeInfo {
start_time_seconds: timestamp::now_seconds(),
owner: staker_addr,
});
one_shot::transfer_record_only(token_id, staker_addr, @battle_addr);
object::transfer(staker, rapper_token, @battle_addr);
event::emit(StakedEvent {
owner: staker_addr,
token_id,
start_time: timestamp::now_seconds(),
});
}

Risk

Likelihood:

Happens when users try to stake multiple rappers.

Impact:

The transaction aborts, only one rapper can be staked at a time. This cuts rewards for users.

Proof of Concept

You will first need to add test_only functions in the code:

// In cred_token:
#[test_only]
public fun mint_cred(to: address,amount: u64) acquires CredCapabilities {
let caps = borrow_global<CredCapabilities>(@battle_addr);
let coins = coin::mint(amount, &caps.mint_cap);
coin::deposit(to, coins);
}
#[test_only]
public entry fun init(account: &signer) {
init_module(account);
}
// In one_shot:
#[test_only]
public fun get_mint_rapper_event_minter(event: &MintRapperEvent): address {
event.minter
}
#[test_only]
public fun get_mint_rapper_event_token_id(event: &MintRapperEvent): address {
event.token_id
}
// In rap_battle:
#[test_only]
public entry fun init(sender: &signer) {
init_module(sender);
}

Add this code in the test file:

#[test(
module_owner = @battle_addr, alice = @0x999, aptos_framework = @aptos_framework
)]
public fun test_can_stake_only_rapper(
module_owner: signer, alice: signer, aptos_framework: signer
) {
// Initialize the test framework
timestamp::set_time_has_started_for_testing(&aptos_framework);
aptos_coin::ensure_initialized_with_apt_fa_metadata_for_test();
let alice_address = signer::address_of(&alice);
// Initialize modules
cred_token::init(&module_owner);
rap_battle::init(&module_owner);
// Alice gets 2 rappers
one_shot::mint_rapper(&module_owner, alice_address);
let events = event::emitted_events<one_shot::MintRapperEvent>();
let event = events.borrow(0);
let alice_rapper_1 = one_shot::get_mint_rapper_event_token_id(event);
one_shot::mint_rapper(&module_owner, alice_address);
let events = event::emitted_events<one_shot::MintRapperEvent>();
let event = events.borrow(1);
let alice_rapper_2 = one_shot::get_mint_rapper_event_token_id(event);
// Can stake 1 rapper
streets::stake(&alice, object::address_to_object<token::Token>(alice_rapper_1));
// Can't stake the 2nd because the ressource already exists
streets::stake(&alice, object::address_to_object<token::Token>(alice_rapper_2));
}

Recommended Mitigation

Update the staking mechanics in order to enable staking of mulitple rappers for a user.

Updates

Lead Judging Commences

bube Lead Judge 19 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Only one Rapper NFT can be staked at a time

Support

FAQs

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