Vulnerability : Inconsistent Balance Tracking Leads to Potential DoS
Root + Impact
Description
The contract uses a state variable balance
to manually track the funds in the resource account. This internal counter can become out of sync with the true APT balance, and the claim_pizza_slice
function relies on this potentially inaccurate balance
for its E_INSUFFICIENT_FUND
check.
public entry fun fund_pizza_drop(owner: &signer, amount: u64) acquires ModuleData, State {
// ...
coin::transfer<AptosCoin>(owner, resource_addr, amount);
@> state.balance = state.balance + amount;
}
public entry fun claim_pizza_slice(user: &signer) acquires ModuleData, State {
// ...
@> assert!(state.balance >= amount, E_INSUFFICIENT_FUND);
// ...
@> state.balance = state.balance - amount;
}
Risk
Likelihood:
-
This will occur when funds are transferred directly to the resource account's address, bypassing the fund_pizza_drop
function.
-
The contract's internal state will not reflect the new funds, leading to incorrect validation.
Impact:
Denial of Service (DoS): If the internal balance
is incorrectly lower than the actual balance, legitimate users will be blocked from claiming their airdrop, as the assertion state.balance >= amount
will fail even when enough funds are present.
Proof of Concept
A mismatch between the tracked and actual balance can lock legitimate users out of their funds.
public entry fun claim_pizza_slice(user: &signer) acquires ModuleData, State {
let user_addr = signer::address_of(user);
let state = borrow_global_mut<State>(get_resource_address());
- // ...
- // Check if contract has sufficient balance
- assert!(state.balance >= amount, E_INSUFFICIENT_FUND);
+ // POC:
+ // 1. Internal state.balance is 0.
+ // 2. 10,000 APT is sent directly to the resource account. Actual balance is 10,000.
+ // 3. User tries to claim. The check `assert!(0 >= 300, ...)` fails, blocking the claim.
}
Recommended Mitigation
Remove the manual balance
variable and check the real-time balance of the resource account directly.
- // In the `State` struct:
- balance: u64,
- // In `fund_pizza_drop`:
- state.balance = state.balance + amount;
- // In `claim_pizza_slice`:
- assert!(state.balance >= amount, E_INSUFFICIENT_FUND);
- state.balance = state.balance - amount;
+ // In `claim_pizza_slice`:
+ assert!(get_actual_apt_balance() >= amount, E_INSUFFICIENT_FUND);