Skip to content

refactor: remove all_tokens enumeration from EnumerableComponent#91

Merged
starknetdev merged 1 commit intomainfrom
refactor/remove-all-tokens-enumeration
Apr 6, 2026
Merged

refactor: remove all_tokens enumeration from EnumerableComponent#91
starknetdev merged 1 commit intomainfrom
refactor/remove-all-tokens-enumeration

Conversation

@starknetdev
Copy link
Copy Markdown
Member

@starknetdev starknetdev commented Apr 6, 2026

Summary

  • Remove global token enumeration (total_supply, token_by_index, Enumerable_all_tokens, Enumerable_all_tokens_len) from EnumerableComponent
  • Replace OZ IERC721Enumerable interface with custom IEnumerableOwner (only token_of_owner_by_index)
  • New SRC5 interface ID: IENUMERABLE_OWNER_ID
  • Delete dead packages/interfaces/src/token/enumerable.cairo (was comment-only, not declared as a module)

Motivation

Saves 2 storage writes per mint (Enumerable_all_tokens map write + Enumerable_all_tokens_len increment). On-chain owner-based enumeration is more valuable than global enumeration, which can be served by an indexer/API.

Breaking changes

  • total_supply() and token_by_index() no longer exist on contracts using EnumerableComponent
  • SRC5 interface ID changes from IERC721_ENUMERABLE_ID to IENUMERABLE_OWNER_ID
  • Consumers using IERC721EnumerableDispatcher for token_of_owner_by_index should switch to IEnumerableOwnerDispatcher

Test plan

  • All 20 enumerable tests pass (snforge test enumerable)
  • Downstream denshokan viewer/SDK updated in separate PRs

🤖 Generated with Claude Code

Summary by CodeRabbit

  • API Changes
    • Removed global token enumeration: total_supply and global token-by-index queries are no longer available.
    • Updated to owner-based enumeration only: users can still query tokens owned by a specific address.

Remove global token enumeration (total_supply, token_by_index) to save
2 storage writes per mint. Only owner-based enumeration remains via
IEnumerableOwner (token_of_owner_by_index, all_tokens_of_owner).

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

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

This PR modifies the ERC721 enumerable extension to replace the standard IERC721Enumerable interface with a custom IEnumerableOwner interface, removing global enumeration functionality (total_supply, token_by_index) while retaining owner-based token enumeration support.

Changes

Cohort / File(s) Summary
Enumerable Extension Implementation
packages/embeddable_game_standard/src/token/extensions/enumerable/enumerable.cairo
Updated component interface implementation from oz_interface::IERC721Enumerable to IEnumerableOwner, removed global enumeration storage fields (Enumerable_all_tokens_len, Enumerable_all_tokens), deleted helper _add_token_to_all_tokens_enumeration, and adjusted before_update hook to skip global token enumeration while retaining owner-based enumeration logic.
Enumerable Interface Definition
packages/embeddable_game_standard/src/token/extensions/enumerable/interface.cairo
Removed re-exports of OpenZeppelin's IERC721Enumerable and related dispatcher types. Added new IEnumerableOwner<TState> interface with IENUMERABLE_OWNER_ID constant and single method token_of_owner_by_index.
Test Updates
packages/embeddable_game_standard/src/token/tests/test_enumerable.cairo, packages/interfaces/src/token/enumerable.cairo
Updated test suite to verify IENUMERABLE_OWNER_ID support. Removed test cases for global enumeration (test_total_supply, test_token_by_index, test__add_token_to_all_tokens_enumeration) and removed corresponding helper assertions. Deleted placeholder interface re-export file.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 Whiskers twitch with delight,
Global tokens hop away to the night,
Only owner's lists remain so bright,
Simpler paths, cleaner sight!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: remove all_tokens enumeration from EnumerableComponent' is clear, concise, and directly reflects the main change—removing global token enumeration while keeping owner-based enumeration.
Description check ✅ Passed The description covers the required template sections: Summary (what changed and why), Breaking changes clearly listed, and Test plan documented. Scope and Change Type sections are implicitly addressed through the content.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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/remove-all-tokens-enumeration

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 6, 2026

Claude finished @starknetdev's task —— View job


Claude Review - General Engineering Review

Todo List

  • Check git diff to identify files outside packages/ in scope for review
  • Review any CI/CD configuration changes
  • Review documentation updates
  • Review tooling/script changes
  • Provide formatted findings

run=24041056419 attempt=1 sha=53627afe700503ce24f28ba8c7dfcc8a38bcbed7 scope=general

Review Results

No issues found.

This PR contains changes only within the packages/** directory structure. The git diff excluding packages shows no modifications to:

  • CI/CD workflow configurations
  • Documentation files
  • Build scripts or tooling
  • Root-level configuration files

All changes are limited to Cairo contract code within the packages directory, which falls outside the scope of this general engineering review.

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


@claude
Copy link
Copy Markdown

claude bot commented Apr 6, 2026

Claude finished @starknetdev's task —— View job


Claude Review - Cairo/Starknet Contract Review

run=24041056419 attempt=1 sha=53627afe700503ce24f28ba8c7dfcc8a38bcbed7 scope=packages

Todo List

  • Get PR diff to understand exact changes
  • Review enumerable.cairo implementation changes
  • Review interface.cairo changes
  • Review test coverage changes
  • Check for security issues
  • Verify Cairo idioms compliance
  • Assess gas optimization claims
  • Provide final review summary

Review Findings

[MEDIUM] packages/embeddable_game_standard/src/token/extensions/enumerable/enumerable.cairo:114-116 - Potential underflow in removal logic
Impact: The subtraction (erc721_component.balance_of(from) - 1) could underflow if balance_of(from) returns 0, leading to a panic.
Fix: Add explicit check: assert(erc721_component.balance_of(from) > 0, 'insufficient balance') before the subtraction.

[LOW] packages/embeddable_game_standard/src/token/extensions/enumerable/enumerable.cairo:76 - Conversion could panic
Impact: token_id.try_into().unwrap() will panic if token_id exceeds felt252 range (extremely unlikely but theoretically possible with u256).
Fix: Consider using proper error handling or documenting the assumption that token IDs fit in felt252.

[LOW] packages/embeddable_game_standard/src/token/extensions/enumerable/enumerable.cairo:50 - Conversion could panic
Impact: Similar index.try_into().unwrap() could panic for very large indices.
Fix: Use proper error handling with a descriptive error message.

[INFO] packages/embeddable_game_standard/src/token/tests/test_enumerable.cairo:175 - Test coverage reduced
Impact: Removal of total_supply and token_by_index tests reduces coverage of storage invariants that these functions would have validated.
Fix: Consider adding internal tests that verify the storage state directly using load/store to ensure enumeration mappings remain consistent.

Positive Observations

Security: Burn protection is correctly maintained with assert(!to.is_zero(), Errors::BURN_NOT_SUPPORTED)
Gas Optimization: Confirmed savings of 2 storage writes per mint (removal of Enumerable_all_tokens map write + Enumerable_all_tokens_len increment)
Cairo Idioms: Proper use of get_dep_component! macros and storage patterns
Interface Design: Clean separation of concerns - only owner-based enumeration retained
SRC5 Integration: Correct interface registration with the new ID
Hook Logic: The balance_of(to) usage in enumeration is correct - before_update hook runs before balance updates

Interface ID Verification

The new interface ID follows established patterns:

/// EFS: token_of_owner_by_index(ContractAddress,(u128,u128))->(u128,u128)
pub const IENUMERABLE_OWNER_ID: felt252 = 0x312c74a3a4f7aaf9aa3e80ddea171f958139ef0c3dbea524e0763682b7d57dd;

The EFS signature correctly represents token_of_owner_by_index(owner: ContractAddress, index: u256) -> u256 where u256 expands to (u128,u128).

Test Coverage Assessment

The test removals are appropriate given the functionality removal. Tests properly cover:

  • Owner enumeration scenarios (mint, transfer first/last token)
  • Boundary conditions and error cases
  • Hook behavior validation
  • Storage invariant verification through helper functions

Missing: Direct storage verification for edge cases around empty balances.

Summary

1 MEDIUM potential underflow requiring attention
2 LOW conversion safety improvements
1 INFO test coverage consideration

The refactor successfully achieves its gas optimization goals while maintaining security properties. The main concern is the potential underflow in the removal enumeration logic that should be addressed for robustness.


@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

Codex Review - General Engineering Review

No issues found.
Summary: 0 CRITICAL, 0 HIGH, 0 MEDIUM, 0 LOW, 0 INFO

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 EnumerableComponent to optimize gas usage by removing global token enumeration and focusing on owner-based enumeration. It introduces a new IEnumerableOwner interface and updates the component's storage and logic accordingly. Feedback identifies that the IENUMERABLE_OWNER_ID does not follow the SRC5 standard for interface identification, which requires the ID to be the XOR of the function selectors.

Comment on lines +4 to +5
pub const IENUMERABLE_OWNER_ID: felt252 =
0x312c74a3a4f7aaf9aa3e80ddea171f958139ef0c3dbea524e0763682b7d57dd;
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

The interface ID 0x312c74a3a4f7aaf9aa3e80ddea171f958139ef0c3dbea524e0763682b7d57dd does not follow the SRC5 (and ERC165) standard, which defines the interface ID as the XOR of all function selectors in the trait. For a single-function trait like IEnumerableOwner, the ID should be exactly the selector of token_of_owner_by_index. The current value seems to be the hash of the trait name IEnumerableOwner, which will break standard-compliant interface discovery.

pub const IENUMERABLE_OWNER_ID: felt252 =
    0x02e30a6aa3d393d8d117333e03280b639194efc4480276dad4d23d53f2f9131;

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

Codex Review - Cairo/Starknet Contract Review

No issues found.
Summary: 0 CRITICAL, 0 HIGH, 0 MEDIUM, 0 LOW, 0 INFO

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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.

🧹 Nitpick comments (3)
packages/embeddable_game_standard/src/token/tests/test_enumerable.cairo (2)

61-69: Assert that the legacy enumerable ID is no longer advertised.

This only proves IENUMERABLE_OWNER_ID is present. Add a negative assertion for the old IERC721Enumerable interface ID too, otherwise a regression could still report support for removed selectors via supports_interface.

As per coding guidelines "Use SRC5 interface discovery (supports_interface) for cross-contract capability detection".

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

In `@packages/embeddable_game_standard/src/token/tests/test_enumerable.cairo`
around lines 61 - 69, The test_initializer currently only asserts the new
IENUMERABLE_OWNER_ID and ISRC5_ID via mock_state.supports_interface; add a
negative assertion to ensure the legacy IERC721 enumerable interface ID is not
advertised (e.g.,
assert!(!mock_state.supports_interface(IERC721_ENUMERABLE_ID))); update the test
function test_initializer in the same file (using COMPONENT_STATE(),
CONTRACT_STATE(), and supports_interface) to include this negative check so
regressions cannot reintroduce the old IERC721Enumerable selectors.

174-192: Add coverage for the self-transfer no-op path.

Line 78 in packages/embeddable_game_standard/src/token/extensions/enumerable/enumerable.cairo now skips both remove/add when previous_owner == to, but none of these tests exercise that branch. A regression there would silently reorder or duplicate owner slots.

🧪 Suggested regression test
+#[test]
+fn test_before_update_when_transfer_to_same_owner() {
+    let (mut state, _) = setup();
+
+    state.before_update(OWNER(), TOKEN_1);
+
+    assert_owned_tokens_list_after_update(
+        OWNER(), array![TOKEN_1, TOKEN_2, TOKEN_3].span(),
+    );
+}

As per coding guidelines "Achieve and maintain 90% line coverage for tests; edge cases must be fuzzed".

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

In `@packages/embeddable_game_standard/src/token/tests/test_enumerable.cairo`
around lines 174 - 192, Add a test that covers the self-transfer no-op branch by
calling state.before_update with to equal to the token's current owner so
neither remove nor add runs; in test_enumerable.cairo create a test (e.g.,
test_before_update_self_transfer_noop) that sets up state, captures the owned
lists, calls state.before_update(OWNER(), TOKEN_2) where OWNER() is the current
owner, and then asserts the owner's and recipient's owned token lists remain
exactly unchanged, verifying the branch guarded by the previous_owner == to
check in enumerable.cairo is exercised.
packages/embeddable_game_standard/src/token/extensions/enumerable/interface.cairo (1)

7-10: Document the owner-only ABI contract.

IEnumerableOwner now replaces the standard enumerable interface, but token_of_owner_by_index has no docstring describing the owner-only scope, parameter constraints, or return semantics. Add function-level docs here so integrators do not assume full IERC721Enumerable behavior.

As per coding guidelines "Every function must include clear explanation of what it does and why, parameter descriptions with types and constraints, return value documentation, and example usage when appropriate".

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

In
`@packages/embeddable_game_standard/src/token/extensions/enumerable/interface.cairo`
around lines 7 - 10, Add a detailed function-level docstring for the
IEnumerableOwner trait's token_of_owner_by_index method: state that
IEnumerableOwner is an owner-only ABI (not full IERC721Enumerable), describe
token_of_owner_by_index(self: `@TState`, owner: ContractAddress, index: u256) ->
u256 semantics (index is zero-based and must be < owner balance, index and
returned token id are u256, owner must be a valid ContractAddress/non-zero),
specify failure behavior (e.g., revert or error if index out of bounds or owner
has no tokens), clarify that the return value is the token ID owned by owner at
that index, and add a short example of usage showing how to iterate owner tokens
by incrementing index until out-of-bounds error or balance limit; attach this
docstring directly above the token_of_owner_by_index declaration in the
IEnumerableOwner trait.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@packages/embeddable_game_standard/src/token/extensions/enumerable/interface.cairo`:
- Around line 7-10: Add a detailed function-level docstring for the
IEnumerableOwner trait's token_of_owner_by_index method: state that
IEnumerableOwner is an owner-only ABI (not full IERC721Enumerable), describe
token_of_owner_by_index(self: `@TState`, owner: ContractAddress, index: u256) ->
u256 semantics (index is zero-based and must be < owner balance, index and
returned token id are u256, owner must be a valid ContractAddress/non-zero),
specify failure behavior (e.g., revert or error if index out of bounds or owner
has no tokens), clarify that the return value is the token ID owned by owner at
that index, and add a short example of usage showing how to iterate owner tokens
by incrementing index until out-of-bounds error or balance limit; attach this
docstring directly above the token_of_owner_by_index declaration in the
IEnumerableOwner trait.

In `@packages/embeddable_game_standard/src/token/tests/test_enumerable.cairo`:
- Around line 61-69: The test_initializer currently only asserts the new
IENUMERABLE_OWNER_ID and ISRC5_ID via mock_state.supports_interface; add a
negative assertion to ensure the legacy IERC721 enumerable interface ID is not
advertised (e.g.,
assert!(!mock_state.supports_interface(IERC721_ENUMERABLE_ID))); update the test
function test_initializer in the same file (using COMPONENT_STATE(),
CONTRACT_STATE(), and supports_interface) to include this negative check so
regressions cannot reintroduce the old IERC721Enumerable selectors.
- Around line 174-192: Add a test that covers the self-transfer no-op branch by
calling state.before_update with to equal to the token's current owner so
neither remove nor add runs; in test_enumerable.cairo create a test (e.g.,
test_before_update_self_transfer_noop) that sets up state, captures the owned
lists, calls state.before_update(OWNER(), TOKEN_2) where OWNER() is the current
owner, and then asserts the owner's and recipient's owned token lists remain
exactly unchanged, verifying the branch guarded by the previous_owner == to
check in enumerable.cairo is exercised.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6690c5f-7bfe-4e0d-bcf9-5cbbf4789990

📥 Commits

Reviewing files that changed from the base of the PR and between 5457caf and 53627af.

📒 Files selected for processing (4)
  • packages/embeddable_game_standard/src/token/extensions/enumerable/enumerable.cairo
  • packages/embeddable_game_standard/src/token/extensions/enumerable/interface.cairo
  • packages/embeddable_game_standard/src/token/tests/test_enumerable.cairo
  • packages/interfaces/src/token/enumerable.cairo
💤 Files with no reviewable changes (1)
  • packages/interfaces/src/token/enumerable.cairo

@starknetdev starknetdev merged commit 4e056a9 into main Apr 6, 2026
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