Description
The protocol has fundamentally incorrect state variable naming and getter implementation that returns invalid state information. The get_claimed_amount()
function returns values from a misnamed users_claimed_amount
table which actually stores assigned amounts, not claimed amounts. This creates a critical disconnect between function behavior and state representation, as users can have "claimed amounts" returned even when they haven't claimed anything.
Root Cause
Multiple layers of incorrect state management:
Misnamed state variable: users_claimed_amount
actually stores assigned amounts, not claimed amounts
Invalid getter logic: get_claimed_amount()
returns assigned amounts regardless of actual claim status
State inconsistency: The actual claim status in claimed_users
table is completely ignored by the getter
struct State has key {
users_claimed_amount: Table<address, u64>,
claimed_users: Table<address, bool>,
}
#[view]
public fun get_claimed_amount(user: address): u64 acquires ModuleData, State {
let state = borrow_global<State>(get_resource_address());
if (!table::contains(&state.users_claimed_amount, user)) {
return 0
};
let amount = table::borrow(&state.users_claimed_amount, user);
*amount
}
The actual claim happens here but the getter doesn't check this:
table::add(&mut state.claimed_users, user_addr, true);
Impact
Proof of Concept
register_pizza_lover(owner, user);
assert!(get_claimed_amount(user) == 300, 1);
assert!(!table::contains(&state.claimed_users, user), 2);
claim_pizza_slice(user);
assert!(get_claimed_amount(user) == 300, 3);
assert!(table::contains(&state.claimed_users, user), 4);
Recommended Mitigation
Fix the state variable naming and implement correct getter logic:
struct State has key {
- users_claimed_amount: Table<address, u64>,
+ users_assigned_amount: Table<address, u64>, // Correct naming
claimed_users: Table<address, bool>,
// ...
}
#[view]
- public fun get_claimed_amount(user: address): u64 acquires ModuleData, State {
+ public fun get_assigned_amount(user: address): u64 acquires ModuleData, State {
let state = borrow_global<State>(get_resource_address());
- if (!table::contains(&state.users_claimed_amount, user)) {
+ if (!table::contains(&state.users_assigned_amount, user)) {
return 0
};
- let amount = table::borrow(&state.users_claimed_amount, user);
+ let amount = table::borrow(&state.users_assigned_amount, user);
*amount
}
+ // Correctly implement get_claimed_amount that returns actual claimed amounts
+ #[view]
+ public fun get_claimed_amount(user: address): u64 acquires ModuleData, State {
+ let state = borrow_global<State>(get_resource_address());
+ // Only return amount if user has actually claimed (use existing has_claimed_slice)
+ if (table::contains(&state.claimed_users, user)) {
+ if (table::contains(&state.users_assigned_amount, user)) {
+ return *table::borrow(&state.users_assigned_amount, user)
+ }
+ };
+ 0 // Return 0 if not claimed
+ }
This ensures:
State variables have correct, descriptive names
Getters return accurate state information
Clear distinction between assigned and claimed amounts