# Stale mapping after record closure

Close/delete paths that drop a record but leave id or index mappings pointing at it

_Area: Smart contract access control and control flow. Seen in 4 disclosed reports; latest 2025-09-01. Updated 2026-10-23._

## How it works
A record is closed, deleted, or overwritten, but a secondary mapping keyed by its id or index is not cleared. The id can later be reused, so the stale entry now points at a new record. The assumption that breaks: that an id uniquely identifies one record for its whole life. Once ids are reusable and the back-pointer is not validated, an old handle (NFT, index slot, reverse-index entry) reaches new state.

## Where it shows up
- Close/remove/delete functions that clear the primary record but not a companion mapping (id→record, owner→id, tokenId→id).
- Ids or indices that are reused after deletion (slot reuse, counter reset, recycled keys).
- Transfer or lookup paths that trust the caller-supplied id without checking the record's own back-pointer (e.g. record.tokenId == tokenId).
- Array-backed structures where pop decrements size but does not zero the vacated slot.
- Reverse indices updated only on insert, guarded by a "key absent" check rather than "value changed".

## How to review
Enumerate every mutation path (insert, overwrite, owner change, delete, close) and confirm each keeps all companion mappings in sync. The invariant: for every live record, every mapping that references its id resolves to that record, and no mapping references a closed or deleted id. Check that transfer/lookup validates the record's back-pointer against the supplied id, not just that the id exists. For heaps/arrays, confirm the vacated slot is zeroed and bounds checks precede any child reads.

## Variants seen
- NFT-to-record mapping survives closure; reused record id reachable via old NFT because transfer never checks short.tokenId == tokenId [E1](https://bugbountylibrary.com/r/c4%3A2024-03-dittoeth%3AM-04.md).
- Heap pop leaves stale slot; maxHeapify reads out-of-bounds child before the size guard, re-extracting a removed id [E2](https://bugbountylibrary.com/r/c4%3A2023-12-revolutionprotocol%3AM-08.md).
- Reverse owner index never reconciled on owner change or removal; stale entries accumulate and enable ghost entries [E3](https://bugbountylibrary.com/r/cantina%3Asolaxy%3A3.1.1.md).

## Impact and severity
Led to unauthorized transfer of a newly created record via an old handle [E1](https://bugbountylibrary.com/r/c4%3A2024-03-dittoeth%3AM-04.md), duplicate extraction of an already-removed element [E2](https://bugbountylibrary.com/r/c4%3A2023-12-revolutionprotocol%3AM-08.md), and unbounded index growth exhausting memory/CPU [E3](https://bugbountylibrary.com/r/cantina%3Asolaxy%3A3.1.1.md). Severity rises when ids are reusable and the stale handle carries prior approvals or trust.

## Why fixes fail
Clearing only the primary record, or adding an existence check instead of a back-pointer match, leaves the stale mapping reachable; the fix must reconcile every companion mapping on each mutation path.

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2024-03-dittoeth%3AM-04.md
- E2: https://bugbountylibrary.com/r/c4%3A2023-12-revolutionprotocol%3AM-08.md
- E3: https://bugbountylibrary.com/r/cantina%3Asolaxy%3A3.1.1.md
