Skip to content

refactor(registration)!: key flags by token_id, drop entry_id reverse#108

Merged
starknetdev merged 2 commits intomainfrom
refactor/registration-flags-by-token
Apr 26, 2026
Merged

refactor(registration)!: key flags by token_id, drop entry_id reverse#108
starknetdev merged 2 commits intomainfrom
refactor/registration-flags-by-token

Conversation

@starknetdev
Copy link
Copy Markdown
Member

@starknetdev starknetdev commented Apr 26, 2026

Summary

Second pass on the registration component. PR #107 added a token → entry_id reverse index so consumers could resolve a token's slot in O(1). This PR observes that the only reason consumers need entry_id after the lookup is because `Registration_flags` is keyed by `(context_id, entry_id)`. Re-keying the flags map directly by `token_id` collapses the two-hop `token → entry_id → flags` chain into one hop and makes the entry_id reverse redundant.

Changes

Storage

  • ❌ `Registration_token_entry_id: Map<felt252, u32>` — deleted
  • 🔁 `Registration_flags: Map<(u64, u32), u8>` → `Map<felt252, u8>`
  • ✅ `Registration_token_ids`, `Registration_token_context`, `Registration_entry_counts` — unchanged

Public `IRegistration` trait (BREAKING)

  • `is_entry_banned(context_id, entry_id) -> bool` → `is_token_banned(token_id) -> bool`
  • `get_entry`, `entry_exists`, `get_entry_count` unchanged

Internal trait

  • DROP `_get_entry_id_for_token`, `mark_entry_submitted(c,e)`, `ban_entry(c,e)`
  • ADD `mark_token_submitted(token_id)`, `ban_token(token_id)`, `_is_token_submitted(token_id)`, `_is_token_banned(token_id)`
  • KEEP `_get_token_context(token_id)`

The new `_is_token_submitted` / `_is_token_banned` helpers let bokendo's `_is_token_completed` shrink from 3 storage hops to 1.

Why

Net per-consumer wins:

  • −1 SSTORE per registration (entry_id reverse map writes gone from `set_entry`)
  • −1 SLOAD per submit/ban/refund (token → flags is now direct)
  • Less component bytecode embedded into every consumer that embeds `RegistrationImpl` — material for budokan, which is currently ~71 felts over the 81920-felt CASM deploy limit even after PR feat(registration): add token reverse-index for O(1) lookups #107's first-pass migration.

Indexing / data considerations

Verified by grepping consumer indexers and clients:

  • Indexer schemas are token-keyed already: budokan PK = `(tournament_id, game_token_id)`, bokendo unique = `(player_address, quest_id)`. `entry_number` is informational, not a key — so structural compatibility is preserved.
  • No client code calls `is_entry_banned` or `get_entry` directly — only generated ABIs reference them; safe to rename.
  • One downstream consequence: contracts currently emit `entry_number` on register/submit/ban events, and indexers upsert it on every event. After this redesign, contracts can no longer cheaply derive `entry_id` from `token_id` at submit/ban time. Consumers should:
    • Pass `entry_number: 0` at submit/ban event sites (still emit it correctly at register time, where `entry_id` is fresh from `increment_entry_count`).
    • Drop `entryNumber` from the indexer's `onConflictDoUpdate` SET clause so submit/ban events don't overwrite the value persisted at register time.

This affects the budokan and bokendo migrations (PRs to follow). No on-chain data migration is needed — both contracts will be redeployed fresh.

Test plan

  • `snforge test test_registration` — 24/24 pass (added `test_token_flags_are_independent_per_token` to lock down the new key isolation)
  • Full metagame test suite green (per local run)
  • `scarb fmt --check --workspace` clean
  • `scarb build` clean

Release

  • Cut v1.2.0 after merge — this is a breaking trait change.

Follow-ups

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Refactored registration system APIs to transition from entry-based to token-based operations
    • Optimized registration storage architecture by consolidating multiple state fields into a single efficient packed-state model
    • Updated test infrastructure to align with new token-centric API design

Re-key Registration_flags from Map<(u64, u32), u8> to Map<felt252, u8>.
Each token belongs to exactly one (context_id, entry_id) slot within a
component instance, so flags can live directly under the token. This
collapses the two-step `token -> entry_id -> flags` lookup that PR #107
introduced into a single `token -> flags` lookup.

Storage:
  - DROP Registration_token_entry_id (no longer needed)
  - REKEY Registration_flags to Map<felt252, u8>
  - Registration_token_context kept (drives has_context)
  - Registration_token_ids kept (forward enumeration)

Public IRegistration trait (BREAKING):
  - Renames is_entry_banned(context_id, entry_id) -> is_token_banned(token_id)
  - Other public methods unchanged

Internal trait:
  - DROP _get_entry_id_for_token, mark_entry_submitted, ban_entry
  - ADD mark_token_submitted, ban_token, _is_token_submitted, _is_token_banned
  - _get_token_context preserved
  - The new _is_token_submitted lets bokendo's _is_token_completed shrink
    from 3 storage hops to 1.

set_entry simplifies: only one reverse map (token_context) to write,
and the stale-clear-on-overwrite logic only zeros that one map.
get_entry now performs a forward-map lookup followed by token-keyed
flag read (2 SLOADs at enumeration time, acceptable for a rare path).

Net effect for consumers: 1 fewer SSTORE per registration, 1 fewer SLOAD
per submit/ban/refund hot path, less component bytecode embedded into
each consumer.

This is a breaking interface change — bump to v1.2.0 on tag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

📝 Walkthrough

Walkthrough

The registration system's API is refactored from entry-based operations (using context_id + entry_id pairs) to token-based operations (using token_id). Storage consolidates from per-entry flags and reverse-index maps into a single packed token state field, eliminating separate reverse-lookup methods.

Changes

Cohort / File(s) Summary
Interface Definition
packages/interfaces/src/registration.cairo
Trait method updated: is_entry_banned(context_id, entry_id) replaced with is_token_banned(token_id).
Storage Abstraction & Data Structures
packages/metagame/src/registration/store.cairo, packages/metagame/src/registration/structs.cairo
Store trait refactored from separate flag and reverse-index getters/setters to unified get_token_state_raw/set_token_state_raw. New TokenState struct with packed layout and public unpacking helpers for extracting context_id, has_submitted, and is_banned.
Core Implementation
packages/metagame/src/registration/registration_component.cairo, packages/metagame/src/registration/registration_store.cairo
Storage model and public APIs switch from per-(context_id, entry_id) operations to per-token_id operations. Trait methods changed: removed is_entry_banned/mark_entry_submitted/ban_entry, added get_token_context/is_token_submitted/is_token_banned/mark_token_submitted/ban_token. Helper methods updated to use packed token state unpacking/packing.
Testing
packages/metagame/src/registration/tests/mocks/registration_mock.cairo, packages/metagame/src/registration/tests/test_registration_component.cairo
Mock contract and test suite updated to use token-based APIs. Reverse-lookup get_entry_id_for_token replaced with boolean is_token_submitted. Test assertions migrated from token→(context, entry_id) lookups to token-scoped flag verification.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 From entries bound by context chains,
We hopped to tokens, freed from chains!
One packed state, no flags to keep,
Simpler storage, logic clean and deep.
Token by token, the system takes a leap!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main refactoring: keying registration flags by token_id and removing the entry_id reverse index, which is the core structural change across all modified files.
Description check ✅ Passed The description is comprehensive and well-structured, covering all template sections: summary, changes, rationale, indexing considerations, test plan, release notes, and follow-ups. All critical sections are complete.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/registration-flags-by-token

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 and usage tips.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 26, 2026

Claude finished @starknetdev's task —— View job


Claude Review - General Engineering Review

run=24964205766 attempt=1 sha=dce354296029d59d935d8571eb4e755cde3be1e1 scope=general

Todo List

  • Run scoped git diff to identify changes outside packages/
  • Review any non-package changes for CI/CD, documentation, and tooling issues
  • Post review findings

Review Results

No issues found.

This PR contains only changes within the packages/ directory (Cairo contract code), which are outside the scope of this general engineering review. All modified files are:

  • packages/interfaces/src/registration.cairo
  • packages/metagame/src/registration/registration_component.cairo
  • packages/metagame/src/registration/registration_store.cairo
  • packages/metagame/src/registration/store.cairo
  • packages/metagame/src/registration/tests/mocks/registration_mock.cairo
  • packages/metagame/src/registration/tests/test_registration_component.cairo

No changes were made to CI/CD configuration, documentation, tooling, or scripts outside the packages directory.

Summary: 0 CRITICAL, 0 HIGH, 0 MEDIUM, 0 LOW, 0 INFO


@github-actions
Copy link
Copy Markdown

Codex Review - General Engineering Review

Review process failed to complete.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 26, 2026

Claude finished @starknetdev's task —— View job


Claude Review - Cairo/Starknet Contract Review

run=24964205766 attempt=1 sha=dce354296029d59d935d8571eb4e755cde3be1e1 scope=packages

Review Progress

  • Read and analyze storage layout changes
  • Review interface and API changes
  • Examine implementation logic
  • Check test coverage and new test cases
  • Verify security implications
  • Assess gas optimization claims

Security Analysis

[LOW] packages/metagame/src/registration/registration_store.cairo:44-46 - Token displacement cleanup only clears context mapping
Impact: When a slot is overwritten with a different token, the old token's flags remain in storage keyed by its token_id, potentially causing confusion if the same token is registered elsewhere
Fix: Add self.set_flags(prev_token, 0) to clear the old token's flags when displacement occurs

Gas Optimization Verification

✅ Claims validated:

  • −1 SSTORE per registration: Confirmed - removed Registration_token_entry_id write in set_entry
  • −1 SLOAD per flag access: Confirmed - is_token_banned/mark_token_submitted/ban_token now direct token_id → flags lookup instead of token_id → entry_id → flags
  • Reduced component bytecode: Confirmed - eliminated storage field and associated getter/setter methods

API Breaking Changes Assessment

✅ Breaking changes properly handled:

  • is_entry_banned(context_id, entry_id) -> boolis_token_banned(token_id) -> bool
  • Internal trait methods correctly migrated from entry-based to token-based operations
  • All test cases updated to match new API signatures

Test Coverage Analysis

✅ Comprehensive test coverage:

  • Added test_token_flags_are_independent_per_token - critical test ensuring token-scoped flag isolation
  • All existing functionality retested with new API
  • Edge cases covered: token displacement, flag combinations, reverse lookups
  • Coverage: 24/24 tests pass as claimed

Implementation Logic Review

✅ Storage layout refactoring correctly implemented:

  • Registration_flags properly changed from Map<(u64, u32), u8> to Map<felt252, u8>
  • Removed Registration_token_entry_id mapping cleanly
  • get_entry correctly reconstructs entry by reading flags via game_token_id
  • Flag operations (mark_token_submitted, ban_token) correctly use token_id directly

✅ Reverse index logic preserved:

  • Registration_token_context (token_id → context_id) maintained for metagame interface compatibility
  • Proper cleanup in set_entry when tokens are displaced

Code Quality

✅ Following Cairo idioms:

  • Proper use of storage traits and component patterns
  • Consistent error handling and validation
  • Clear separation between storage operations and business logic

No issues found.

Summary: 0 CRITICAL, 0 HIGH, 0 MEDIUM, 1 LOW, 0 INFO


@github-actions
Copy link
Copy Markdown

Codex Review - Cairo/Starknet Contract Review

Review process failed to complete.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the registration system to key flags (submission and ban status) by token_id for O(1) access. Feedback suggests clearing flags upon token displacement to avoid stale data, optimizing flag reads for empty slots, and adding validation to prevent modification of the null token ID.

Comment on lines 44 to 46
if prev_token != 0 && prev_token != *registration.game_token_id {
self.set_token_context(prev_token, 0);
self.set_token_entry_id(prev_token, 0);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When a token is displaced from a slot, its context is cleared, but its flags (submission status and ban status) remain in storage keyed by the token_id. If a consumer relies on the "1-hop" check mentioned in the PR description (checking is_token_banned without verifying context), they might see stale data for unregistered tokens. Clearing the flags here ensures the 1-hop check remains safe and provides a gas refund for the storage cleanup.

        if prev_token != 0 && prev_token != *registration.game_token_id {
            self.set_token_context(prev_token, 0);
            self.set_flags(prev_token, 0);
        }

fn get_entry(self: @T, context_id: u64, entry_id: u32) -> Registration {
let game_token_id = self.get_token_id(context_id, entry_id);
let flags = self.get_flags(context_id, entry_id);
let flags = self.get_flags(game_token_id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When fetching an entry for an empty slot (game_token_id == 0), calling get_flags(0) results in an unnecessary storage read (SLOAD) that will always return 0. Since token_id 0 is reserved for empty slots and cannot be registered, we can optimize this by skipping the storage read.

        let flags = if game_token_id == 0 { 0 } else { self.get_flags(game_token_id) };

Comment on lines +72 to 75
fn mark_token_submitted(ref self: T, token_id: felt252) {
let flags = self.get_flags(token_id);
self.set_flags(token_id, RegistrationOperationsImpl::set_submitted(flags));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It is important to ensure that token_id is non-zero before marking it as submitted. If a consumer accidentally passes 0 (e.g., from an empty slot lookup), it would corrupt the flags for the "empty" state, causing all empty slots to appear as submitted in get_entry.

    fn mark_token_submitted(ref self: T, token_id: felt252) {
        RegistrationValidationImpl::assert_valid_token_id(token_id);
        let flags = self.get_flags(token_id);
        self.set_flags(token_id, RegistrationOperationsImpl::set_submitted(flags));
    }

Comment on lines +77 to 80
fn ban_token(ref self: T, token_id: felt252) {
let flags = self.get_flags(token_id);
self.set_flags(token_id, RegistrationOperationsImpl::set_banned(flags));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to mark_token_submitted, we should validate that the token_id is non-zero to prevent banning the null token ID, which would affect the default state of all empty registration slots.

    fn ban_token(ref self: T, token_id: felt252) {
        RegistrationValidationImpl::assert_valid_token_id(token_id);
        let flags = self.get_flags(token_id);
        self.set_flags(token_id, RegistrationOperationsImpl::set_banned(flags));
    }

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 26, 2026

Codecov Report

❌ Patch coverage is 94.11765% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/metagame/src/registration/structs.cairo 87.50% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Combine context_id (u64) and the two flag bits into a single packed
felt252 stored under one Map<felt252, felt252>, replacing the prior
pair of Registration_flags + Registration_token_context maps.

Bit layout (low → high):
  bits  0..63: context_id
  bit   64:    has_submitted
  bit   65:    is_banned

Net effect:
  - set_entry: 3 SSTOREs → 2 SSTOREs (token_id + token_state).
  - Overwrite-clear: zeroing the packed slot atomically clears both
    context and flags for the displaced token.
  - Combined queries (e.g. bokendo's _is_token_completed) collapse to
    a single SLOAD via per-field unpack helpers.

Per-field unpack helpers `unpack_token_context_id`,
`unpack_token_has_submitted`, `unpack_token_is_banned` mirror the
pattern used by entry_fee/structs.cairo — callers reading a single
field skip the full struct rebuild.

Store trait surface shrinks: dropped get/set_flags and
get/set_token_context, replaced by get/set_token_state_raw -> felt252.
The bridge in registration_store.cairo absorbs the pack/unpack.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude Bot commented Apr 26, 2026

Claude finished @starknetdev's task —— View job


Claude Review - General Engineering Review

run=24964682974 attempt=1 sha=26fdc613f93412e982eeb1e5bd2435a583c85784 scope=general

Todo List

  • Run scoped git diff to identify changes outside packages/
  • Review any non-package changes for CI/CD, documentation, and tooling issues
  • Post review findings

Review Results

No issues found.

This PR contains only changes within the packages/ directory (Cairo contract code), which are outside the scope of this general engineering review. All modified files are:

  • packages/interfaces/src/registration.cairo
  • packages/metagame/src/registration/registration_component.cairo
  • packages/metagame/src/registration/registration_store.cairo
  • packages/metagame/src/registration/store.cairo
  • packages/metagame/src/registration/tests/mocks/registration_mock.cairo
  • packages/metagame/src/registration/tests/test_registration_component.cairo

No changes were made to CI/CD configuration, documentation, tooling, or scripts outside the packages directory.

Summary: 0 CRITICAL, 0 HIGH, 0 MEDIUM, 0 LOW, 0 INFO


@github-actions
Copy link
Copy Markdown

Codex Review - General Engineering Review

Review process failed to complete.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 26, 2026

Claude finished @starknetdev's task —— View job


Claude Review - Cairo/Starknet Contract Review

run=24964682974 attempt=1 sha=26fdc613f93412e982eeb1e5bd2435a583c85784 scope=packages

Todo List

  • Run git diff to analyze exact changes in packages/
  • Read and analyze each changed file for security implications
  • Verify gas optimization claims (-1 SSTORE, -1 SLOAD)
  • Review API breaking changes and migration strategy
  • Analyze test coverage and new test cases
  • Check Cairo idiom compliance
  • Post comprehensive review findings

Security Analysis

✅ No critical security vulnerabilities found

The refactor correctly implements the token-keyed flag design:

  • Token displacement in set_entry properly clears old token's packed state (line 55)
  • Access control patterns preserved - no new attack vectors introduced
  • StorePacking implementation uses correct bit masking and casting operations
  • No arithmetic overflow/underflow risks in the bit manipulation code

Gas Optimization Verification

✅ Claims validated:

  • −1 SSTORE per registration: Confirmed by removing Registration_token_entry_id writes and Registration_flags writes in set_entry
  • −1 SLOAD per flag access: Confirmed - new methods like is_token_banned, mark_token_submitted, ban_token access flags directly via token_id instead of the previous 2-hop token_id → entry_id → flags lookup
  • Reduced component bytecode: Confirmed by eliminating multiple storage fields and their associated getter/setter methods

API Breaking Changes Assessment

✅ Breaking changes properly handled:

  • Public interface change: is_entry_banned(context_id, entry_id)is_token_banned(token_id) correctly reflects the new token-centric design
  • Internal trait methods correctly migrated from entry-based to token-based operations
  • All test cases updated to match new signatures
  • Consumer migration strategy clearly documented in PR description

Test Coverage Analysis

✅ Comprehensive test coverage with key addition:

  • Critical new test: test_token_flags_are_independent_per_token (lines 428-445) validates the core assumption that flags are now scoped per token, not per (context, entry_id)
  • Existing functionality fully retested with new API signatures
  • Edge cases covered: token displacement, flag combinations, reverse lookups
  • All 24 tests pass as claimed

Cairo Idiom Compliance

✅ Follows Cairo best practices:

  • Proper use of StorePacking trait for efficient packed storage
  • Consistent error handling with assert_valid_token_id validation
  • Clean separation between storage operations and business logic
  • Appropriate use of component patterns and trait implementations

Storage Layout Analysis

✅ Storage refactoring correctly implemented:

  • Eliminated redundant mappings: Registration_token_entry_id and Registration_flags removed
  • New single packed mapping: Registration_token_state: Map<felt252, felt252> with well-defined bit layout
  • Bit packing properly implemented: context_id (bits 0-63), has_submitted (bit 64), is_banned (bit 65)
  • Helper functions for partial unpacking avoid unnecessary full struct reconstruction

Code Quality Assessment

✅ Implementation quality is high:

  • Clear documentation of bit layout in structs.cairo
  • Efficient per-field unpack helpers (unpack_token_context_id, etc.) for performance
  • Proper validation in flag modification methods
  • Clean migration from the previous design with no leftover artifacts

No issues found.

Summary: 0 CRITICAL, 0 HIGH, 0 MEDIUM, 0 LOW, 0 INFO


@github-actions
Copy link
Copy Markdown

Codex Review - Cairo/Starknet Contract Review

Review process failed to complete.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/metagame/src/registration/registration_component.cairo (1)

135-165: ⚠️ Potential issue | 🟡 Minor

Verify internal callers of mark_token_submitted / ban_token always guard with registration check.

These internal helpers do unconditional RMW on Registration_token_state[token_id]. If invoked on a token that was never registered (context_id == 0), they'll persist { context_id: 0, has_submitted/is_banned: true }. From that point on, the public is_token_banned would incorrectly return true for an unregistered token.

Since mark_token_submitted and ban_token are internal-only (part of RegistrationInternalTrait), verify that all embedding contracts call them only on tokens with a valid registration context (context_id > 0). Either add an assertion assert!(state.context_id != 0, "token not registered") to both functions, or have is_token_banned return false when context_id == 0.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/metagame/src/registration/registration_component.cairo` around lines
135 - 165, The internal helpers mark_token_submitted and ban_token perform
unconditional RMW on Registration_token_state[token_id]; add a guard to both
functions to prevent persisting state for unregistered tokens by asserting the
token has a valid context_id before mutating: fetch the token's context via
RegistrationStoreTrait::get_token_context(self, token_id) or existing state
access and assert context_id != 0 (e.g., "token not registered") at the start of
mark_token_submitted and ban_token in the ComponentState<TContractState>
implementation; this ensures callers cannot accidentally mark or ban an
unregistered token (alternatively, you may instead change
RegistrationStoreTrait::is_token_banned to return false when context_id == 0,
but prefer adding the assert in mark_token_submitted and ban_token to keep the
internal-trait semantics strict).
🧹 Nitpick comments (4)
packages/metagame/src/registration/registration_store.cairo (1)

85-95: Optional: skip the unpack/pack round-trip on flag-only RMWs.

mark_token_submitted / ban_token only need to OR a single bit. The unpack→mutate→pack form is readable but performs two extra try_into round-trips per call. Since this is on the hot submit/ban path (a key motivation for the PR per the objectives), a raw-OR variant could shave a few steps:

     fn mark_token_submitted(ref self: T, token_id: felt252) {
-        let mut state = TokenStateStorePacking::unpack(self.get_token_state_raw(token_id));
-        state.has_submitted = true;
-        self.set_token_state_raw(token_id, TokenStateStorePacking::pack(state));
+        let packed: u128 = self.get_token_state_raw(token_id).try_into().unwrap();
+        self.set_token_state_raw(token_id, (packed | 0x10000000000000000_u128).into());
     }

     fn ban_token(ref self: T, token_id: felt252) {
-        let mut state = TokenStateStorePacking::unpack(self.get_token_state_raw(token_id));
-        state.is_banned = true;
-        self.set_token_state_raw(token_id, TokenStateStorePacking::pack(state));
+        let packed: u128 = self.get_token_state_raw(token_id).try_into().unwrap();
+        self.set_token_state_raw(token_id, (packed | 0x20000000000000000_u128).into());
     }

If you go this route, expose HAS_SUBMITTED_BIT / IS_BANNED_BIT from structs.cairo rather than duplicating the literals. Given the chill review preference, feel free to defer — current form is plenty readable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/metagame/src/registration/registration_store.cairo` around lines 85
- 95, mark_token_submitted and ban_token perform a full unpack/pack RMW but only
need to set a single flag; replace the unpack→mutate→pack sequence in functions
mark_token_submitted and ban_token with a raw bitwise-OR update on the stored
packed value (read current raw via get_token_state_raw, OR with the appropriate
flag bit, and write back via set_token_state_raw) to avoid try_into round-trips.
Import and use the HAS_SUBMITTED_BIT and IS_BANNED_BIT constants from
structs.cairo (do not duplicate literals) and ensure the OR uses the same packed
layout as TokenStateStorePacking so semantics stay identical.
packages/metagame/src/registration/structs.cairo (1)

76-83: Document the implicit packed ≤ u128 invariant on unpack.

value.try_into().unwrap() on the felt252 will panic if anyone ever writes a state value with bits above 127 into Registration_token_state. Today the only writer is TokenStateStorePacking::pack, which is bounded to u128, so this is safe. Worth a one-line comment so future contributors who want to use bits 66..127 (reserved per the layout) know they must keep the packed value within u128, otherwise existing reads will start reverting on every load.

     fn unpack(value: felt252) -> TokenState {
+        // Invariant: any writer to this slot must keep the packed value within u128.
+        // Reserved bits 66..127 are available; bit 128+ would break this unwrap.
         let packed: u128 = value.try_into().unwrap();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/metagame/src/registration/structs.cairo` around lines 76 - 83, The
unpack function assumes the incoming felt252 fits into u128 and currently calls
value.try_into().unwrap(), which will panic if high bits are set; add a one-line
comment in or immediately above the unpack function documenting the implicit
invariant "packed ≤ u128" and note that TokenStateStorePacking::pack is the
current writer that enforces this bound so future contributors must preserve
that constraint (or widen unpack) if they use bits above 127; reference the
unpack function and TokenStateStorePacking::pack in the comment for clarity.
packages/metagame/src/registration/tests/test_registration_component.cairo (1)

294-298: Refresh stale "reverse mapping" wording.

The comment still references the removed reverse-index map. The assertion is correct because set_entry now zeroes the displaced token's full packed state (see registration_store.cairo line 51–56), so get_token_context returns 0. Suggest aligning the wording with the new mechanism:

-    // Previous token's reverse mapping must be cleared so it no longer claims the slot.
+    // Previous token's packed state must be zeroed so it no longer claims this slot.
     assert!(mock.get_token_context(0x7777) == 0, "old token context should be cleared");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/metagame/src/registration/tests/test_registration_component.cairo`
around lines 294 - 298, Update the test comment to reflect that the displaced
token's packed state is zeroed by set_entry (so get_token_context(0x7777)
returns 0) rather than referring to the removed "reverse-index" map; mention
set_entry and get_token_context in the comment to make the mechanism clear
(e.g., "Previous token's packed state is cleared by set_entry, so
get_token_context(...) returns 0").
packages/metagame/src/registration/registration_component.cairo (1)

17-22: Update the "reverse index" wording — the reverse map no longer exists.

The convention is correct (context_id == 0 means "not registered" via _get_token_context), but the words "reverse index" referred to the removed Registration_token_entry_id / Registration_token_context maps. With this PR the lookup is the low-64 bits of the packed token state in Registration_token_state, so the comment is slightly misleading to a future reader.

 /// Conventions:
-///   - context_id is expected to be >= 1. `_get_token_context` treats 0 as
-///     "not registered", so a caller using context_id == 0 cannot
-///     distinguish a real context-zero registration from an unknown
-///     token via the reverse index. entry_id is always >= 1 by
-///     construction (see `increment_entry_count`).
+///   - context_id is expected to be >= 1. `_get_token_context` reads the
+///     low 64 bits of the packed token state and treats 0 as
+///     "not registered", so a caller using context_id == 0 cannot
+///     distinguish a real context-zero registration from an unknown
+///     token. entry_id is always >= 1 by construction
+///     (see `increment_entry_count`).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/metagame/src/registration/registration_component.cairo` around lines
17 - 22, The comment incorrectly refers to a "reverse index" that no longer
exists; update the wording to state that `_get_token_context` treats `context_id
== 0` as "not registered" and that the lookup now reads the low-64 bits of the
packed token state stored in `Registration_token_state` rather than a separate
reverse map. Modify the comment around `context_id`, `_get_token_context`,
`entry_id`, and `increment_entry_count` to explain that `entry_id >= 1` and
registration presence is determined by the low-64 bits of
`Registration_token_state` (packed token state), removing any mention of
`Registration_token_entry_id`/`Registration_token_context` or a "reverse index."
Ensure the new phrasing is concise and accurate for future readers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/metagame/src/registration/registration_component.cairo`:
- Around line 135-165: The internal helpers mark_token_submitted and ban_token
perform unconditional RMW on Registration_token_state[token_id]; add a guard to
both functions to prevent persisting state for unregistered tokens by asserting
the token has a valid context_id before mutating: fetch the token's context via
RegistrationStoreTrait::get_token_context(self, token_id) or existing state
access and assert context_id != 0 (e.g., "token not registered") at the start of
mark_token_submitted and ban_token in the ComponentState<TContractState>
implementation; this ensures callers cannot accidentally mark or ban an
unregistered token (alternatively, you may instead change
RegistrationStoreTrait::is_token_banned to return false when context_id == 0,
but prefer adding the assert in mark_token_submitted and ban_token to keep the
internal-trait semantics strict).

---

Nitpick comments:
In `@packages/metagame/src/registration/registration_component.cairo`:
- Around line 17-22: The comment incorrectly refers to a "reverse index" that no
longer exists; update the wording to state that `_get_token_context` treats
`context_id == 0` as "not registered" and that the lookup now reads the low-64
bits of the packed token state stored in `Registration_token_state` rather than
a separate reverse map. Modify the comment around `context_id`,
`_get_token_context`, `entry_id`, and `increment_entry_count` to explain that
`entry_id >= 1` and registration presence is determined by the low-64 bits of
`Registration_token_state` (packed token state), removing any mention of
`Registration_token_entry_id`/`Registration_token_context` or a "reverse index."
Ensure the new phrasing is concise and accurate for future readers.

In `@packages/metagame/src/registration/registration_store.cairo`:
- Around line 85-95: mark_token_submitted and ban_token perform a full
unpack/pack RMW but only need to set a single flag; replace the
unpack→mutate→pack sequence in functions mark_token_submitted and ban_token with
a raw bitwise-OR update on the stored packed value (read current raw via
get_token_state_raw, OR with the appropriate flag bit, and write back via
set_token_state_raw) to avoid try_into round-trips. Import and use the
HAS_SUBMITTED_BIT and IS_BANNED_BIT constants from structs.cairo (do not
duplicate literals) and ensure the OR uses the same packed layout as
TokenStateStorePacking so semantics stay identical.

In `@packages/metagame/src/registration/structs.cairo`:
- Around line 76-83: The unpack function assumes the incoming felt252 fits into
u128 and currently calls value.try_into().unwrap(), which will panic if high
bits are set; add a one-line comment in or immediately above the unpack function
documenting the implicit invariant "packed ≤ u128" and note that
TokenStateStorePacking::pack is the current writer that enforces this bound so
future contributors must preserve that constraint (or widen unpack) if they use
bits above 127; reference the unpack function and TokenStateStorePacking::pack
in the comment for clarity.

In `@packages/metagame/src/registration/tests/test_registration_component.cairo`:
- Around line 294-298: Update the test comment to reflect that the displaced
token's packed state is zeroed by set_entry (so get_token_context(0x7777)
returns 0) rather than referring to the removed "reverse-index" map; mention
set_entry and get_token_context in the comment to make the mechanism clear
(e.g., "Previous token's packed state is cleared by set_entry, so
get_token_context(...) returns 0").

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 67fcc77a-4c63-47c3-be72-55dc264e95b5

📥 Commits

Reviewing files that changed from the base of the PR and between 180e6b3 and 26fdc61.

📒 Files selected for processing (7)
  • packages/interfaces/src/registration.cairo
  • packages/metagame/src/registration/registration_component.cairo
  • packages/metagame/src/registration/registration_store.cairo
  • packages/metagame/src/registration/store.cairo
  • packages/metagame/src/registration/structs.cairo
  • packages/metagame/src/registration/tests/mocks/registration_mock.cairo
  • packages/metagame/src/registration/tests/test_registration_component.cairo

@starknetdev starknetdev merged commit 82b9ce5 into main Apr 26, 2026
24 of 27 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.

1 participant