Skip to content

Fix burned NFT soulbound flag when ObjectCore is retained - #46

Open
sausagee wants to merge 4 commits into
mainfrom
cursor/fix-burned-nft-soulbound-796a
Open

Fix burned NFT soulbound flag when ObjectCore is retained#46
sausagee wants to merge 4 commits into
mainfrom
cursor/fix-burned-nft-soulbound-796a

Conversation

@sausagee

@sausagee sausagee commented Sep 6, 2026

Copy link
Copy Markdown

Bug

TokenOwnershipV2::get_burned_nft_v2_from_write_resource decides is_soulbound_v2 with:

object_metadatas.get(&token_data_id).map(|obj| obj.untransferable.as_ref()).is_some()

.map() turns Option<&ObjectAggregatedData> into Option<Option<&Untransferable>>. Then .is_some() is true whenever the object is in the map, even when untransferable is None.

On the production burn path, Loop 1 always inserts ObjectCore into object_metadatas. So every burned V2 NFT that still has ObjectCore written is stored as is_soulbound_v2 = true, including freely transferable tokens (untransferable = None, allow_ungated_transfer = true).

The live ownership path in the same file already uses the correct check:

object_data.untransferable.as_ref().is_some()

The test_token_v2_burn_event_v1 golden encoded this: is_soulbound_v2: true next to non_transferrable_by_owner: false.

Not covered by open PRs #16#45 (#37 is burned-NFT owner lookup on DB error; #38#41 are ObjectCore-missing skips).

Root cause

Option nesting: Some(None).is_some() is true. The burn path needed and_then, not map.

Fix

Use .and_then(|obj| obj.untransferable.as_ref()).is_some() so a present object with untransferable = None falls through to !allow_ungated_transfer. Update the burn-event-v1 golden to is_soulbound_v2: false.

Tests run (rustc 1.85.0)

  • cargo test -p processor --lib burned_3 passed
  • cargo test -p processor --lib — 27 passed; 18 failed only on missing Docker/GCS (processor_status_saver / parquet buffer), unrelated
  • cargo clippy -p processor --lib --tests -- -D warningsclean (stable 1.85)
  • cargo +nightly fmt -- --check on the changed file — clean
  • Integration: test_token_v2_burn_event_v1 failed on the stale golden (true vs false); golden updated. Other token_v2 tests including test_token_v2_burn_event_v2 passed.
  • Nightly CI lint (cargo +nightly xclippy) fails compiling allocative (Infallible vs !). Same failure on Fix fee-payer signer omitted from signatures and account_transactions #43Fix transfer-row non_transferrable_by_owner copying is_soulbound #47; not introduced here.
  • Aikido SAST: not run (plugin requires sign-in)

Out of scope (checked, not filed)

Open in Web Open in Cursor 

cursoragent and others added 2 commits September 6, 2026 12:05
.map().is_some() treated any object_metadatas hit as soulbound,
including transferable tokens with untransferable=None (Some(None)).

Co-authored-by: Young Yang Liauw <sausagee@users.noreply.github.com>
Co-authored-by: Young Yang Liauw <sausagee@users.noreply.github.com>
The fixture already has non_transferrable_by_owner=false. is_soulbound_v2
was true only because of the map().is_some() Option-nesting bug.

Co-authored-by: Young Yang Liauw <sausagee@users.noreply.github.com>
@sausagee
sausagee changed the base branch from main to fix/lint-stable-xclippy September 7, 2026 22:19
@sausagee
sausagee changed the base branch from fix/lint-stable-xclippy to main September 7, 2026 22:30
Stack the CI unblocker files from PR #70 (stable cargo xclippy, bookworm Dockerfiles with make) onto this bugfix branch so CI can go green while #70 awaits review. Merge target remains main; this does not merge #70.
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