One Shot: Reloaded

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

Staking Limited to One NFT Per Address (Implicit and Unenforced)

Root + Impact

Description

  • Users should be able to stake multiple Rapper NFTs per address.

  • The specific issue is StakeInfo stored at staker_addr without token_id keying, overwriting on multiple stakes and limiting to one implicitly.

// In streets.move
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);
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: Medium

  • User attempts to stake a second NFT.

  • move_to aborts on existing StakeInfo.

Impact: Medium

  • Users limited to one stake per address.

  • Failed txs and confusion for multi-NFT holders.

Proof of Concept

  • Stake first NFT: succeeds.

  • Stake second: aborts due to existing StakeInfo at address.

#[test(module_owner = @battle_addr, staker = @0x123)]
#[expected_failure] // Expect abort on move_to duplicate resource
fun test_staking_limited_one_per_address(module_owner: &signer, staker: &signer) {
// Setup: Initialize modules
battle_addr::one_shot::init_module(module_owner);
// Mint two rappers
battle_addr::one_shot::mint_rapper(module_owner, signer::address_of(staker));
battle_addr::one_shot::mint_rapper(module_owner, signer::address_of(staker));
// Stake first
let rapper1 = /* assume */;
battle_addr::streets::stake(staker, rapper1);
// Attempt stake second: Aborts on move_to to existing StakeInfo
let rapper2 = /* assume */;
battle_addr::streets::stake(staker, rapper2);
}

Recommended Mitigation

- struct StakeInfo has key, store { ... } // No token_id
+ struct StakeInfo has key { stakes: table::Table<address, StakeInfoEntry> } // Add table for multi-stake
Updates

Lead Judging Commences

bube Lead Judge 16 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.