Skip to content

fix: dispatch royalty_info existence check through ContractType - #846

Open
yigitcangokmen wants to merge 1 commit into
OpenZeppelin:mainfrom
yigitcangokmen:fix/royalty-info-dispatches-through-contracttype-#845
Open

fix: dispatch royalty_info existence check through ContractType#846
yigitcangokmen wants to merge 1 commit into
OpenZeppelin:mainfrom
yigitcangokmen:fix/royalty-info-dispatches-through-contracttype-#845

Conversation

@yigitcangokmen

@yigitcangokmen yigitcangokmen commented Aug 20, 2026

Copy link
Copy Markdown

Fixes #845

Problem

NonFungibleRoyalties::royalty_info's default body called Base::royalty_info, which establishes existence with Base::owner_of. Under ContractType = Consecutive ownership is stored sparsely: only the last token of a batch has a materialised Owner entry, and the rest are resolved by walking back through the bucket. Every non-boundary token was therefore rejected with #200 NonExistentToken.

The library documents the correct pattern in non_fungible/overrides.rs, and every sibling default body follows it. royalty_info was the exception, which looks like it was missed when the default bodies landed in #604.

Change

The existence check now goes through Self::ContractType::owner_of, matching how every other default body in this library dispatches.

The lookup itself moves into Base::royalty_info_unchecked, so Base::royalty_info keeps its current behaviour exactly: it still calls Base::owner_of and then the same lookup.

This adds public API to a published crate, which is worth flagging up front. The _unchecked suffix follows credit_identity_unchecked in rwa/compliance/modules/max_balance/storage.rs, and the new function carries a # Security Warning section as that precedent does. If you would rather not grow the surface, the alternative is to put royalty_info on ContractOverrides with the Base behaviour as the default and override it in Consecutive. Say the word and I will redo it that way.

Evidence

test_royalty_info_resolves_every_token_under_consecutive batch-mints ten tokens and asserts that each one resolves both an owner and royalty information.

Reverting only the two source hunks and keeping the test gives:

test ...resolves_every_token_under_consecutive ... FAILED
HostError: Error(Contract, #200)
  data: ["contract call failed", royalty_info, [0, 1000000]]

The test also asserts owner_of answers for all ten. That is the control: the tokens demonstrably exist and Consecutive resolves them, so the failure is in the dispatch rather than the token.

Checks

  • cargo +nightly fmt --all -- --check clean
  • cargo +stable clippy --release --locked --all-targets -- -D warnings exit 0
  • cargo test -p stellar-tokens passes: 714 tests, 713 before plus this one. nft-royalties-example passes

Summary by CodeRabbit

  • Bug Fixes

    • Royalty information now works correctly for tokens issued through consecutive token batches.
    • Royalty lookups validate token ownership before returning royalty details.
  • Tests

    • Added regression coverage for royalty resolution across all tokens in a consecutive batch.

The NonFungibleRoyalties default body called Base::royalty_info directly,
which establishes existence with Base::owner_of. Under Consecutive only the
last token of a batch has a materialised Owner entry, so every other token
was rejected with OpenZeppelin#200 NonExistentToken even though Consecutive::owner_of
resolves it correctly.

The check now goes through Self::ContractType::owner_of, matching how every
other default body in this library dispatches. Base::royalty_info_unchecked
carries the lookup so Base::royalty_info keeps its current behaviour.

Note this adds public API to a published crate. The _unchecked suffix follows
credit_identity_unchecked in rwa/compliance/modules/max_balance, and the new
function carries a Security Warning section as that precedent does.

Adds a regression test asserting all 10 tokens of a batch resolve both an
owner and royalty information. With the source reverted to main and the test
kept, it fails on token 0 with OpenZeppelin#200 while the other 9 royalty tests pass.
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e6582d07-8ff1-4d51-a68d-a7532d791bc6

📥 Commits

Reviewing files that changed from the base of the PR and between fbfde38 and 0873c9c.

📒 Files selected for processing (3)
  • packages/tokens/src/non_fungible/extensions/royalties/mod.rs
  • packages/tokens/src/non_fungible/extensions/royalties/storage.rs
  • packages/tokens/src/non_fungible/extensions/royalties/test.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

The royalties extension now validates token ownership through ContractType::owner_of and separates this check from royalty calculation. Tests add a consecutive-token contract and verify royalty information for every token in a batch.

Changes

Royalty dispatch correction

Layer / File(s) Summary
Ownership dispatch and unchecked royalty lookup
packages/tokens/src/non_fungible/extensions/royalties/mod.rs, packages/tokens/src/non_fungible/extensions/royalties/storage.rs
royalty_info validates ownership through the contract type and then calls royalty_info_unchecked.
Consecutive royalty regression coverage
packages/tokens/src/non_fungible/extensions/royalties/test.rs
The tests add ConsecutiveRoyaltiesContract and verify 5% royalty results for ten consecutive tokens.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 0873c

The PR corrects royalty lookup dispatch for consecutive token ownership and adds focused regression coverage; no actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant ContractCaller
  participant NonFungibleRoyalties
  participant ContractType
  participant Base
  ContractCaller->>NonFungibleRoyalties: royalty_info(token_id, sale_price)
  NonFungibleRoyalties->>ContractType: owner_of(token_id)
  ContractType-->>NonFungibleRoyalties: owner
  NonFungibleRoyalties->>Base: royalty_info_unchecked(token_id, sale_price)
  Base-->>ContractCaller: royalty recipient and amount
Loading

Suggested reviewers: ozgunozerk

Poem

I’m a rabbit with tokens in a row,
Ten hops where royalties flow.
Ownership checks now find each one,
Even when the batch is spun.
Five percent returns with cheer—
Every token answers clear! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary fix: dispatching the royalty_info existence check through ContractType.
Description check ✅ Passed The description includes the issue reference, problem, change, API impact, regression evidence, and validation results.
Linked Issues check ✅ Passed The changes address issue #845 by using ContractType::owner_of, preserving Base behavior, adding the unchecked lookup, and testing consecutive tokens.
Out of Scope Changes check ✅ Passed The source, API, and test changes directly support issue #845 and contain no unrelated code changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

royalty_info hardcodes Base::, so it reverts for 9 of every 10 tokens under ContractType = Consecutive

1 participant