One Shot: Reloaded

First Flight #47
Beginner FriendlyNFT
100 EXP
Submission Details
Impact: low
Likelihood: medium

[L-05] Missing Input Validation for Token IDs

Author Revealed upon completion

Root + Impact

Description

Several functions accept token IDs without validating they exist in the system, which could lead to unexpected behavior or wasted gas. Functions like read_stats(), skill_of(), and balance_of() don't verify token existence before attempting to access them.

This lack of validation could result in runtime errors or wasted computation on non-existent tokens.

// In one_shot.move
@> public fun balance_of(addr: address): u64 acquires RapperStats {
@> if (!exists<RapperStats>(@battle_addr)) return 0;
@> let stats_res = borrow_global<RapperStats>(@battle_addr);
@> if (table::contains(&stats_res.owner_counts, addr)) {
@> *table::borrow(&stats_res.owner_counts, addr)
@> } else {
@> 0
@> }
@> }

Risk

Likelihood:

  • Users could query non-existent tokens

  • Wasted gas on invalid operations

  • Potential runtime errors

Impact:

  • Wasted computational resources

  • Poor user experience

  • Potential system confusion

Proof of Concept

This PoC demonstrates the lack of validation:

// Demonstrate lack of validation
let invalid_token = @0x9999999999999999; // Non-existent token
let user_address = @0x1234567890abcdef;
// These calls don't validate token existence
let stats = one_shot::read_stats(invalid_token); // May panic
let skill = one_shot::skill_of(invalid_token); // May panic
let balance = one_shot::balance_of(user_address); // Returns 0, no validation
// Result: Functions may panic or return misleading results
// for non-existent tokens

Recommended Mitigation

The mitigation adds proper input validation:

+ const E_INVALID_TOKEN: u64 = 13;
+
public(friend) fun read_stats(token_id: address): (bool, bool, bool, bool, u64) acquires RapperStats {
+ let stats_res = borrow_global<RapperStats>(@battle_addr);
+ assert!(table::contains(&stats_res.stats, token_id), E_INVALID_TOKEN);
+
- let stats_res = borrow_global<RapperStats>(@battle_addr);
let s = table::borrow(&stats_res.stats, token_id);
(s.weak_knees, s.heavy_arms, s.spaghetti_sweater, s.calm_and_ready, s.battles_won)
}
+
+ public(friend) fun skill_of(token_id: address): u64 acquires RapperStats {
+ let stats_res = borrow_global<RapperStats>(@battle_addr);
+ assert!(table::contains(&stats_res.stats, token_id), E_INVALID_TOKEN);
+
+ let s = table::borrow(&stats_res.stats, token_id);
+ // ... rest of calculation
+ }

This validation prevents operations on non-existent tokens and improves error handling.

Support

FAQs

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