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

Spec/units mismatch: “100–500 APT” are actually **100–500 octas** (≃ 0.000001–0.000005 APT)

Root + Impact

Description

  • Normal behavior:
    The airdrop promises 100–500 APT per eligible user.

  • Issue:
    The contract assigns and transfers the raw u64 amount for AptosCoin, which is denominated in octas (1 APT = 100_000_000 octas). As a result, users actually receive 100–500 octas (≈ 0.000001–0.000005 APT), not “100–500 APT” as advertised.

#[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 OCTAS, not APT
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}
public entry fun claim_pizza_slice(user: &signer) acquires ModuleData, State {
...
@> let amount = *table::borrow(&state.users_claimed_amount, user_addr); // OCTAS
assert!(state.balance >= amount, E_INSUFFICIENT_FUND);
...
@> transfer_from_contract(user_addr, amount); // sends OCTAS 1:1
...
}

Risk

Likelihood:

  • AptosCoin is octa-denominated; the code never multiplies by OCTAS_PER_APT

  • The mismatch persists for all users and all claims

Impact:

  • Trust/UX: Users expecting 100–500 APT receive ~0.000001–0.000005 APT; perceived as deceptive or broken

  • Ops/metrics: Pool spend and dashboards are off; “unused pool” and per-user payouts don’t match plan

  • Security escalation: If corrected to verdadero APT (×1e8), H2A (predictable RNG) immediately becomes financially severe

Proof of Concept

#[test(deployer = @pizza_drop, user = @0xABC, framework = @0x1)]
fun poc_unit_scale_is_octas_not_full_APT(
deployer: &signer,
user: &signer,
framework: &signer
) acquires State, ModuleData {
use aptos_framework::timestamp;
use aptos_framework::account;
use aptos_framework::aptos_coin;
use aptos_framework::coin;
timestamp::set_time_has_started_for_testing(framework);
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);
timestamp::update_global_time_for_test(42);
let u = signer::address_of(user);
register_pizza_lover(deployer, u);
let assigned = get_claimed_amount(u);
assert!(assigned >= 100 && assigned <= 500, 10); // OCTAS range
assert!(assigned < 100_000_000, 11); // strictly < 1 APT
coin::register<aptos_coin::AptosCoin>(deployer);
let owner_coins = coin::mint<aptos_coin::AptosCoin>(assigned, &mint_cap);
coin::deposit<aptos_coin::AptosCoin>(@pizza_drop, owner_coins);
fund_pizza_drop(deployer, assigned);
claim_pizza_slice(user);
let bal = coin::balance<aptos_coin::AptosCoin>(u);
assert!(bal == assigned, 12); // received OCTAS 1:1
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}

Recommended Mitigation

+ // Choose one of the two: (A) pay real APT; (B) clarify that amounts are OCTAS.
+ // (A) Pay 100–500 APT (scale by 1e8):
+ const OCTAS_PER_APT: u64 = 100_000_000;
+ const MIN_APT: u64 = 100;
+ const MAX_APT: u64 = 500;
+ // Use a secure RNG source (see H2A) returning a u64 'r'
+ let apt_amount: u64 = MIN_APT + (r % (MAX_APT - MIN_APT + 1));
+ let amount_octas: u64 = apt_amount * OCTAS_PER_APT; // 100..500 APT in octas
- table::add(&mut state.users_claimed_amount, user_addr, random_amount);
+ table::add(&mut state.users_claimed_amount, user_addr, amount_octas);
+ // (B) If the intent is 100–500 OCTAS, explicitly document it and update UI/spec:
+ // - Rename fields/messages to “octas”
+ // - Show amounts in APT with proper decimal conversion (divide by 1e8) to avoid confusion

Note: Fixing units to real APT should be done together with fixing predictable timestamp-based “randomness”. Otherwise the change immediately increases financial risk.

Updates

Appeal created

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

Incorrect APT value

Support

FAQs

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