one_shot::mint_rapper() does not store token_id to the "to" address + Players can't access their rapper token_ids
Description
-
one_shot.move
should provide a function for players to access their rapper token_id
's.
-
one_shot::mint_rapper() mints a rapper token with a unique token_id.
-
However there is no function provided by one_shot.move to retrieve a player's token_id
which is needed for retreiving their rapper token objects needed for staking or for battles.
public entry fun mint_rapper(module_owner: &signer, to: address)
acquires Collection, RapperStats {
let owner_addr = signer::address_of(module_owner);
assert!(owner_addr == @battle_addr, 1 );
if (!exists<Collection>(@battle_addr)) {
init_module(module_owner);
};
if (!exists<RapperStats>(@battle_addr)) {
let stats_table = table::new<address, StatsData>();
let owner_table = table::new<address, u64>();
move_to(module_owner, RapperStats { stats: stats_table, owner_counts: owner_table });
};
let _ = borrow_global<Collection>(@battle_addr);
let tok_ref = token::create(
module_owner,
string::utf8(COLLECTION_NAME),
string::utf8(b"A new rapper enters the scene."),
string::utf8(b"Rapper"),
option::none(),
string::utf8(b""),
);
let token_obj = object::object_from_constructor_ref<token::Token>(&tok_ref);
let token_id = object::address_from_constructor_ref(&tok_ref);
let stats_res = borrow_global_mut<RapperStats>(@battle_addr);
table::add(&mut stats_res.stats, token_id, StatsData {
owner: to,
weak_knees: true,
heavy_arms: true,
spaghetti_sweater: true,
calm_and_ready: false,
battles_won: 0,
});
if (table::contains(&stats_res.owner_counts, to)) {
let cnt = table::borrow_mut(&mut stats_res.owner_counts, to);
*cnt = *cnt + 1;
} else {
table::add(&mut stats_res.owner_counts, to, 1);
};
event::emit(MintRapperEvent { minter: to, token_id });
object::transfer(module_owner, token_obj, to);
@>
}
Risk
Likelihood:
Impact:
Proof of Concept
Place this code into tests/one_shot_tests.move
.
#[test(player1 = @0x100)]
public fun test_no_access_to_token_id(
player1: &signer,
) {
let module_owner = account::create_account_for_test(@battle_addr);
let player1_address = signer::address_of(player1);
account::create_account_for_test(player1_address);
one_shot::mint_rapper(&module_owner, player1_address);
let balance1 = one_shot::balance_of(player1_address);
assert!(balance1 == 1, 1);
*
* The rapper_token can be retrieved using token_id and the function
* object::address_to_object<token::Token>()
*/
}
On line 21, there is no way for the player to retrieve their rapper token_id
which can be passed into function object::address_to_object<token::Token>()
to get back the rapper token object for staking and battling.
Run with:
aptos move test --filter test_no_access_to_token_id
Output:
Running Move unit tests
[ PASS ] 0x42::one_shot_tests::test_no_access_to_token_id
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
"Result": "Success"
}
Recommended Mitigation
-
Modify one_shot::mint_rapper()
to store the minted rapper token_id
's to the player's global storage.
-
Provide a view function for the player to later retrieve their rapper token_id
from their global storage.
+ use std::vector;
+ struct TokenIDs has key {
+ ids: vector<address>,
+ }
public entry fun mint_rapper(module_owner: &signer, to: &signer)
- acquires Collection, RapperStats {
+ acquires Collection, RapperStats, TokenIDs {
let owner_addr = signer::address_of(module_owner);
assert!(owner_addr == @battle_addr, 1 );
if (!exists<Collection>(@battle_addr)) {
init_module(module_owner);
};
if (!exists<RapperStats>(@battle_addr)) {
let stats_table = table::new<address, StatsData>();
let owner_table = table::new<address, u64>();
move_to(module_owner, RapperStats { stats: stats_table, owner_counts: owner_table });
};
let _ = borrow_global<Collection>(@battle_addr);
let tok_ref = token::create(
module_owner,
string::utf8(COLLECTION_NAME),
string::utf8(b"A new rapper enters the scene."),
string::utf8(b"Rapper"),
option::none(),
string::utf8(b""),
);
let token_obj = object::object_from_constructor_ref<token::Token>(&tok_ref);
let token_id = object::address_from_constructor_ref(&tok_ref);
+ let to_address = signer::address_of(to);
let stats_res = borrow_global_mut<RapperStats>(@battle_addr);
table::add(&mut stats_res.stats, token_id, StatsData {
+ owner: to,
+ owner: to_address,
weak_knees: true,
heavy_arms: true,
spaghetti_sweater: true,
calm_and_ready: false,
battles_won: 0,
});
- if (table::contains(&stats_res.owner_counts, to)) {
+ if (table::contains(&stats_res.owner_counts, to_address)) {
- let cnt = table::borrow_mut(&mut stats_res.owner_counts, to);
+ let cnt = table::borrow_mut(&mut stats_res.owner_counts, to_address);
*cnt = *cnt + 1;
} else {
- table::add(&mut stats_res.owner_counts, to, 1);
+ table::add(&mut stats_res.owner_counts, to_address, 1);
};
- event::emit(MintRapperEvent { minter: to, token_id });
+ event::emit(MintRapperEvent { minter: to_address, token_id });
- object::transfer(module_owner, token_obj, to);
+ object::transfer(module_owner, token_obj, to_address);
+ if (exists<TokenIDs>(to_address)) {
+
+ let token_ids = borrow_global_mut<TokenIDs>(to_address);
+ vector::push_back(&mut token_ids.ids, token_id);
+ } else {
+
+ let v = vector::empty<address>();
+ vector::push_back(&mut v, token_id);
+ let token_ids = TokenIDs{
+ ids: v
+ };
+ move_to(to,token_ids);
+ };
}
+ #[view]
+ public fun get_token_ids(player_address: address): vector<address> acquires TokenIDs {
+ assert!(exists<TokenIDs>(player_address), E_NO_TOKEN_IDS);
+ let token_ids = borrow_global<TokenIDs>(player_address);
+ token_ids.ids
+ }
Test function in one_shot_tests.move
:
#[test_only]
use std::string::{Self, String};
use std::debug;
use std::vector;
fun p<T>(s: &T) {
debug::print(s);
}
fun p_vector_u8(s: vector<u8>) {
p(&string::utf8(s));
}
fun p_vector_address(s: &vector<address>) {
debug::print(s);
}
#[test(player1 = @0x100, framework = @0x1)]
public fun test_access_to_token_ids(
player1: &signer,
framework: &signer
) {
timestamp::set_time_has_started_for_testing(framework);
let module_owner = account::create_account_for_test(@battle_addr);
let player1_address = signer::address_of(player1);
account::create_account_for_test(player1_address);
one_shot::mint_rapper(&module_owner, player1);
one_shot::mint_rapper(&module_owner, player1);
let balance1 = one_shot::balance_of(player1_address);
assert!(balance1 == 2, 1);
let ids = one_shot::get_token_ids(player1_address);
p_vector_u8(b"ids");
p_vector_address(&ids);
let token_id0 = vector::borrow(&ids, 0);
p_vector_u8(b"token_id0");
p(token_id0);
let token_object0 = object::address_to_object<token::Token>(*token_id0);
p_vector_u8(b"token_object0");
debug::print(&token_object0);
p(&token_object0);
let now_secs = timestamp::now_seconds();
p_vector_u8(b"now_secs");
p(&now_secs);
streets::stake(player1, token_object0);
}
Run with:
aptos move test --filter test_access_to_token_ids
Output:
Running Move unit tests
[debug] "ids"
[debug] [ @0xe46a3c36283330c97668b5d4693766b8626420a5701c18eb64026075c3ec8a0a, @0xfab16b00983f01e5c2b7682472a4f4c3e5929fbba987958570b6290c02817df2 ]
[debug] "token_id0"
[debug] @0xe46a3c36283330c97668b5d4693766b8626420a5701c18eb64026075c3ec8a0a
[debug] "token_object0"
[debug] 0x1::object::Object<0x4::token::Token> {
inner: @0xe46a3c36283330c97668b5d4693766b8626420a5701c18eb64026075c3ec8a0a
}
[debug] 0x1::object::Object<0x4::token::Token> {
inner: @0xe46a3c36283330c97668b5d4693766b8626420a5701c18eb64026075c3ec8a0a
}
[debug] "now_secs"
[debug] 0
[ PASS ] 0x42::one_shot_tests::test_access_to_token_ids
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
"Result": "Success"
}