Conversation
token::royalty picked the collection to fall back to by re-deriving create_collection_address(creator, collection_name). collection::set_name moves the name out from under that derivation, so after any rename the lookup no longer resolves to the token's own collection: - if no collection of that creator holds the new name, the #[view] aborts with EOBJECT_DOES_NOT_EXIST for every token in the collection, breaking marketplaces that call it to compute payouts - if the creator already holds another collection under that name, the derivation lands on that one and the token pays out its royalty instead, defeating a royalty published without a MutatorRef and therefore meant to be immutable Read the Object<Collection> recorded on the token at mint time instead. It is written once at creation and is unaffected by renames. Folding it into the existing borrow also preserves the ETOKEN_DOES_NOT_EXIST guard ordering. Regression tests cover the rename fallback, the name-collision misdirection, token-level royalty precedence, and the absent-royalty case. The first three fail against the old lookup. Co-Authored-By: Claude
seanyoung
requested review from
0xIcarus,
Primata,
ganymedio and
musitdev
as code owners
August 20, 2026 15:09
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
token::royaltyfalls back to the collection's royalty when a token carries none of its own. It picked that collection by re-derivingcollection::create_collection_address(creator, collection_name). Collection names are mutable viacollection::set_name, which moves the name out from under that derivation, so after any rename the lookup no longer resolves to the token's own collection. Two consequences, both reachable:The view aborts. If no collection of that creator holds the new name,
object::address_to_objectaborts withEOBJECT_DOES_NOT_EXISTfor every token in the collection.royaltyis a#[view], and marketplaces call it to compute payouts — seecompute_royaltyinaptos-move/move-examples/marketplace/sources/listing.move, which callstokenv2::royalty(listing.object)directly. Sales of any renamed collection break, unrecoverably.Royalty is misdirected. If the creator already holds a second collection under that name, the derivation lands on that collection and the token pays out its royalty. This defeats immutability: a royalty published without a
royalty::MutatorRefcannot be edited, but renaming repoints which royalty is read at all.The fix reads the
Object<Collection>recorded on the token at mint time (Token.collection) rather than re-deriving it. That field is written once at creation and is unaffected by renames. Folding the read into the existingborrowalso preserves the pre-existingETOKEN_DOES_NOT_EXISTguard ordering, so a non-token object still aborts before any royalty is read.Note on the threat model
A related patch upstream (aptos-labs#20385) describes this as a third-party "shadow collection" attack, where an outside attacker registers a same-named collection at the derived address. That is not possible.
create_collection_address(creator, name)isobject::create_object_address(creator, name), andCollection.creatoris alwayssigner::address_ofof the account that published the named object — so the derived address always sits in the legitimate creator's own named-object space, which no other account can publish into. The reachable failure modes are the two above; the second requires only the creator's own two collections, no attacker account.Scope
create_common(token.move) andaptos_token::collection_objectalso derive collections by name, but there the caller supplies the name — they are lookup-by-name APIs, andcollection::set_name's doc comment already states that contract. Changing them would alter public API semantics rather than fix a bug, so they are left alone.How Has This Been Tested?
Four regression tests added to
aptos-token-objects/sources/token.move. Run with:test_collection_royalty_fallback_survives_rename393218(EOBJECT_DOES_NOT_EXIST)test_collection_royalty_not_repointed_by_name_collision2— token reported 99% to@0xbadtest_royalty_absent_survives_rename393218test_token_royalty_takes_precedence_over_collectionThe first three fail against the old lookup; the fourth is a guard against over-correcting. The collision test additionally asserts that the name-derived address really does resolve to the other collection and really does hold its royalty, so it pins the mechanism rather than just the symptom.
Full
aptos-token-objectssuite after the fix: 87 passed, 0 failed.Key Areas to Review
ETOKEN_DOES_NOT_EXIST-before-royalty::getordering matters to any caller.let collection = borrow(&token).collection;keeps the old behaviour, where the bareborrow(&token);ran first.royalty()currently aborts for them and will start returning the original collection's royalty. That is the intended fix, but it does change observed values for any collection that was renamed onto a sibling's name.Type of Change
Which Components or Systems Does This Change Impact?
Checklist
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.