One Shot: Reloaded

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

Missing Ownership Assertion — State Corruption Risk

Author Revealed upon completion

Root + Impact

Description

  • Expected behavior: transfer_record_only should confirm that the from address matches the current owner before updating.

  • Issue: No assertion is made, so the function decrements from’s count even if they are not the actual owner.

// one_shot.move
public fun transfer_record_only(token_id: address, from: address, to: address) acquires Stats {
let s = table::borrow_mut(&mut stats_res.stats, token_id);
// @> Missing assert!(s.owner == from)
s.owner = to;
let c_from = table::borrow_mut(&mut stats_res.owner_counts, from);
*c_from = *c_from - 1;
...
}

Risk

Likelihood:

  • Can occur anytime this function is called with mismatched arguments.

  • More likely in future integrations or refactors.

Impact:

  • Corrupted state (wrong counts, wrong owners).

  • Silent asset misattribution.

Proof of Concept

// Calling transfer_record_only with from != s.owner
// decreases from’s count even though they weren’t the owner.

Recommended Mitigation

public fun transfer_record_only(token_id: address, from: address, to: address) acquires Stats {
let s = table::borrow_mut(&mut stats_res.stats, token_id);
+ assert!(s.owner == from, E_NOT_OWNER); // strict check
s.owner = to;
let c_from = table::borrow_mut(&mut stats_res.owner_counts, from);
*c_from = *c_from - 1;
let c_to = table::borrow_mut(&mut stats_res.owner_counts, to);
*c_to = *c_to + 1;
}

Support

FAQs

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