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

Invalid reward set in Octas not in APT

Description

The protocol intends to reward users with 100-500 APT tokens per slice, but due to a missing unit conversion, users receive 100-500 Octas instead. Since 1 APT = 10^8 Octas, users receive rewards that are 100 million times smaller than intended, making claims economically unfeasible due to gas costs exceeding reward values.

Root Cause

The get_random_slice function stores reward amounts directly as Octas (100-500) without converting to APT units:

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; // Should be (100-500) * 10^8 Octas
table::add(&mut state.users_claimed_amount, user_addr, random_amount);
}

When claiming, the program transfers the raw Octas amount without conversion:

public entry fun claim_pizza_slice(user: &signer) acquires ModuleData, State {
...
let amount = *table::borrow(&state.users_claimed_amount, user_addr);
...
transfer_from_contract(user_addr, amount);
...
}
fun transfer_from_contract(to: address, amount: u64) acquires ModuleData {
...
coin::transfer<AptosCoin>(&resource_signer, to, amount);
...
}

Risk

Likelihood: High - Affects every user claim transaction

Impact: High - Users receive rewards 100 million times smaller than intended, making protocol unusable

Impact

  • Users lose ~99.999999% of intended rewards

  • Claims become economically unviable (gas > reward)

  • Protocol funds remain locked due to lack of recovery mechanism

  • Severe reputation damage to the project

Proof of Concept

In this example the owner fund the program with 1000 APT and the user got only 100 Octas.

#[test(deployer = @pizza_drop, user = @0x123, framework = @0x1)]
#[expected_failure(abort_code = 3)]
fun test_invalid_decimals_apt(deployer: &signer, user: &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 (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(user));
debug::print(&utf8(b"=== PIZZA DROP WITH APT TEST ==="));
// Initialize the pizza drop module
init_module(deployer);
debug::print(&utf8(b"Pizza drop module initialized"));
let decimal_base = 100000000;
// Mint APT to deployer for funding
let funding_amount = 10000 * decimal_base; // 10000 APT
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);
debug::print(&utf8(b"Deployer APT balance: "));
debug::print(&deployer_balance);
// Fund the pizza drop contract
let contract_funding = 1000 * decimal_base; // 1000 APT
fund_pizza_drop(deployer, contract_funding);
let contract_balance = get_actual_apt_balance();
debug::print(&utf8(b"Contract APT balance: "));
debug::print(&contract_balance);
assert!(contract_balance == contract_funding, 1);
// Register a user for the airdrop
let user_addr = signer::address_of(user);
register_pizza_lover(deployer, user_addr);
assert!(is_registered(user_addr), 2);
debug::print(&utf8(b"User registered successfully"));
// Check the assigned amount
let assigned_amount = get_claimed_amount(user_addr);
debug::print(&utf8(b"User assigned amount: "));
debug::print(&assigned_amount);
assert!(assigned_amount >= 100 * decimal_base && assigned_amount <= 500 * decimal_base, 3); // 100 to 500 APT
coin::destroy_burn_cap(burn_cap);
coin::destroy_mint_cap(mint_cap);
}

The test failed as the amount received by the user is in Octas and not in APT

Recommended Mitigation

Convert random amounts to proper APT units in Octas:

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;
+ let random_amount = (100 + random_val) * 100000000; // Convert APT to Octas
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:

Incorrect APT value

Support

FAQs

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