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.
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
Impact: Medium
Proof of Concept
#[test(module_owner = @battle_addr, staker = @0x123)]
#[expected_failure]
fun test_staking_limited_one_per_address(module_owner: &signer, staker: &signer) {
battle_addr::one_shot::init_module(module_owner);
battle_addr::one_shot::mint_rapper(module_owner, signer::address_of(staker));
battle_addr::one_shot::mint_rapper(module_owner, signer::address_of(staker));
let rapper1 = /* assume */
battle_addr::streets::stake(staker, rapper1);
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