Skip to content

Fix minor code review issues in NFTStorefrontV2#113

Merged
joshuahannan merged 9 commits intomainfrom
josh/report-fixes
Apr 6, 2026
Merged

Fix minor code review issues in NFTStorefrontV2#113
joshuahannan merged 9 commits intomainfrom
josh/report-fixes

Conversation

@joshuahannan
Copy link
Copy Markdown
Contributor

@joshuahannan joshuahannan commented Mar 24, 2026

Summary

Addresses issues identified in a security and code quality review of NFTStorefrontV2.cdc.

  • Bug fix (Issue 2): borrowNFT() — replace borrow()! force-unwrap with if-let so a revoked provider capability returns nil instead of panicking, honoring the documented return contract
  • Bug fix (Issue 4): Listing.init — replace silent nft! force-unwrap of borrowNFT result with ?? panic(...) for a descriptive error when the NFT ID is absent from the collection
  • Security (Issue 6): purchase() — assert commissionReceiver.check() before the allowlist loop so a revoked capability fails with a clear message rather than a confusing borrow() panic
  • Correctness (Issue 9): getDuplicateListingIDs — replace manual index loop with firstIndex(of:); mark getExistingListingIDs as view in interface, implementation, and MaliciousStorefrontV2
  • Style (Issue 10): createListing — eliminate allowedCommissionReceivers!.append() force-unwrap anti-pattern by building a local array directly
  • Style (Issue 11): createListing — replace self.owner?.address! optional-chain-then-force-unwrap with self.owner!.address
  • Style (Issue 12): cleanupExpiredListings — simplify index + UInt64(1) to index + 1
  • Docs (Issue 13): Add missing doc comment to setCustomID
  • Docs (Issue 14): Add missing doc comments to burnCallback and ResourceDestroyed

Test plan

  • make test passes (all 17 Cadence tests + Go tests green)
  • make ci passes

🤖 Generated with Claude Code

joshuahannan and others added 3 commits March 24, 2026 11:09
- Issue 2: borrowNFT() — replace force-unwrap on provider borrow() with
  if-let so a revoked capability returns nil instead of panicking
- Issue 4: Listing.init — replace nft! force-unwrap of borrowNFT result
  with ?? panic(...) for a descriptive error when the NFT is absent
- Issue 6: purchase() — assert commissionReceiver.check() before the
  allowlist loop so a revoked capability fails with a clear message
- Issue 9: getDuplicateListingIDs / getExistingListingIDs — replace
  manual index loop with firstIndex(of:); mark getExistingListingIDs
  view in interface and implementation; update MaliciousStorefrontV2
  to conform
- Issue 10: createListing — remove force-unwrap anti-pattern on
  allowedCommissionReceivers, build addresses array directly
- Issue 11: createListing — replace self.owner?.address! with
  self.owner!.address
- Issue 12: cleanupExpiredListings — simplify index + UInt64(1) to
  index + 1
- Issue 13: setCustomID — add missing doc comment
- Issue 14: burnCallback / ResourceDestroyed — add missing doc comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AGENTS.md is a symlink to CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@joshuahannan joshuahannan changed the title Fix code review issues 2, 4, 6, 9, 10, 11, 12, 13, 14 in NFTStorefrontV2 Fix minor code review issues in NFTStorefrontV2 Mar 24, 2026
Regenerate assets.go to embed the updated NFTStorefrontV2.cdc and
MaliciousStorefrontV2.cdc after the report fix commits.

Update CLAUDE.md to clarify that make ci (not make test) is the
required pre-commit gate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tional difference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
joshuahannan and others added 4 commits April 6, 2026 14:51
…meGhosted()

hasListingBecomeGhosted() returns true when the NFT is still present and false
when absent — the opposite of what its name implies. Existing callers in the wild
already compensate with !, so fixing the semantics in place would silently break
them. Instead, add isGhostListing() with correct semantics and deprecate the old
function without changing its behaviour.

Changes:
- Add isGhostListing() to ListingPublic interface and Listing resource
- Update cleanupGhostListings to use isGhostListing() directly
- Add deprecation notices to hasListingBecomeGhosted() in interface, impl, and scripts
- Add is_ghost_listing.cdc and read_all_unique_ghost_listings_v2.cdc scripts
- Add testIsGhostListing covering both new scripts
- Update documentation to clearly mark hasListingBecomeGhosted() as deprecated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
testIsGhostListing emits one additional ListingCompleted event (on ghost
listing cleanup), shifting the cumulative event count seen by testRemoveItem
from 5 to 6.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After removing the ghost listing from self.listings, the function fetched
duplicate listing IDs and cleaned each one up — but never called
removeDuplicateListing for the ghost listing itself. Every other removal
path (removeListing, cleanupPurchasedListings, cleanup) correctly removes
the primary listing from listedNFTs before processing duplicates.

The fix calls removeDuplicateListing for the ghost listing immediately after
removing it from self.listings, and before getDuplicateListingIDs is called.
This ensures getDuplicateListingIDs sees the correct state and the listedNFTs
entry is fully cleared.

Also adds:
- scripts/get_existing_listing_ids.cdc to expose getExistingListingIDs for tests
- testCleanupGhostListingsRemovesListedNFTsEntry to regression-test the fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous commit called removeDuplicateListing on the primary listing
before getDuplicateListingIDs, but getDuplicateListingIDs uses
contains(listingID) as a guard — once the primary is removed from
listedNFTs, it returns [] and duplicates are never cleaned up.

Correct order: fetch duplicates first (while the primary is still in
listedNFTs), then call removeDuplicateListing for the primary, then
clean up each duplicate.

Also bumps testRemoveItem's accumulated ListingCompleted event count
from 6 to 8: testCleanupGhostListingsRemovesListedNFTsEntry emits two
additional events (one for the primary ghost, one for its duplicate).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@joshuahannan joshuahannan merged commit 3b7a039 into main Apr 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants