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.
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:
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:
#[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);
}
#[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
}
#[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
) {
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);
cred_token::init(&module_owner);
rap_battle::init(&module_owner);
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);
streets::stake(&alice, object::address_to_object<token::Token>(alice_rapper_1));
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.