Skip to content

Add ERC721Consecutive extension#1630

Merged
ericnordelo merged 17 commits intomainfrom
feat/add-erc721-consecutive-extension-#1579
Jan 23, 2026
Merged

Add ERC721Consecutive extension#1630
ericnordelo merged 17 commits intomainfrom
feat/add-erc721-consecutive-extension-#1579

Conversation

@ericnordelo
Copy link
Copy Markdown
Member

@ericnordelo ericnordelo commented Jan 12, 2026

Fixes #1579

PR Checklist

  • Tests
  • Documentation
  • Added entry to CHANGELOG.md
  • Tried the feature on a public network

Summary by CodeRabbit

Release Notes

  • New Features

    • BitMap utility for efficient bitwise storage operations
    • ERC721 Consecutive Extension supporting batch token creation at construction time
    • Checkpoint utilities: Added lower_lookup for threshold-based historical lookups
    • ERC721 OwnerOf trait enabling flexible token ownership resolution
  • Documentation

    • Numeric literal usage guidelines added

✏️ Tip: You can customize this high-level summary in your review settings.

@ericnordelo ericnordelo requested a review from immrsd January 12, 2026 17:16
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 12, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This PR implements the ERC-721 Consecutive Extension (ERC-2309) with constructor-only batch minting, adds BitMap and lower_lookup utilities, introduces ERC721OwnerOfTrait abstraction across ERC721 components, and fixes signature handling by removing unnecessary type conversions in vote casting and SRC9 validation paths.

Changes

Cohort / File(s) Summary
Documentation & Changelog
AGENTS.md, CHANGELOG.md, packages/utils/CHANGELOG.md
Added guidelines for numeric literals, documented new public crate openzeppelin_utils, and recorded additions for lower_lookup and BitMap.
Signature Handling Refactoring
packages/account/src/extensions/src9/src9.cairo, packages/governance/src/governor/governor.cairo, packages/governance/src/votes/votes.cairo
Removed .into() conversions when passing Span<felt252> signatures to assert_valid_signature, aligning with function parameter types without runtime conversion overhead.
Import & Visibility Cleanup
packages/governance/src/tests/test_timelock.cairo, packages/governance/src/tests/votes/test_block_number.cairo, packages/token/src/erc721.cairo
Consolidated imports, removed unused IERC721 import, and exposed ERC721OwnerOfDefaultImpl in public exports.
ERC721OwnerOfTrait Abstraction
packages/token/src/erc721/erc721.cairo, packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo, packages/token/src/erc721/extensions/erc721_wrapper.cairo, packages/governance/src/votes/votes.cairo
Introduced ERC721OwnerOfTrait with default implementation and added trait bounds to ERC721 component implementations, enabling extensible ownership resolution (critical for consecutive extension).
ERC721 Consecutive Extension
packages/token/src/erc721/extensions/erc721_consecutive.cairo, packages/token/src/erc721/extensions.cairo
Implemented ERC721ConsecutiveComponent supporting ERC-2309 constructor-only batch minting with sequential ownership tracking, burn state management, and ConsecutiveTransfer event emission (~235 lines).
BitMap Utility
packages/utils/src/structs/bitmap.cairo, packages/utils/src/structs.cairo
Added compact bitset structure using 256-bit bucketing with get, set, unset, and set_to operations; exposed via public module in structs.
Checkpoint Lower Lookup
packages/utils/src/structs/checkpoint.cairo
Added lower_lookup method performing binary search for first checkpoint with key ≥ search key; complements existing upper_lookup.
ERC721 Test Mocks & Fixtures
packages/test_common/src/mocks/erc721.cairo, packages/test_common/src/mocks/votes.cairo, packages/test_common/src/mocks/checkpoint.cairo, packages/test_common/src/mocks/bitmap.cairo, packages/test_common/src/mocks.cairo
Added ERC721ConsecutiveMock contract, IMockBitMap interface, lower_lookup method to IMockTrace, and expanded imports for ERC721OwnerOfDefaultImpl across multiple mocks.
ERC721 Helpers & Assertions
packages/test_common/src/erc721.cairo
Added assert_event_consecutive_transfer and assert_only_event_consecutive_transfer spy helpers for validating ConsecutiveTransfer event emissions.
Comprehensive Test Suites
packages/token/src/tests/erc721/test_erc721_consecutive.cairo, packages/utils/src/tests/test_bitmap.cairo, packages/utils/src/tests/test_checkpoint.cairo, packages/utils/src/tests/test_fuzz_checkpoint.cairo, packages/utils/src/tests.cairo, packages/token/src/tests/erc721.cairo
Added 313+ lines of tests for consecutive minting (ownership, transfers, burns, batches), 212+ lines for bitmap operations (buckets, boundaries, idempotence), and lower_lookup checkpoint behavior.
Build Configuration
packages/token/Scarb.toml
Registered ERC721ConsecutiveMock in test build-external-contracts for unit testing.

Sequence Diagram

sequenceDiagram
    participant Constructor
    participant ERC721Consecutive
    participant SequentialOwnership
    participant SequentialBurn
    participant Event as Event Emitter

    Constructor->>ERC721Consecutive: mint_consecutive(to, batch_size)
    ERC721Consecutive->>ERC721Consecutive: Validate constructor scope
    ERC721Consecutive->>ERC721Consecutive: Compute next_consecutive_id
    ERC721Consecutive->>SequentialOwnership: Store ownership anchor
    ERC721Consecutive->>ERC721Consecutive: Increment balance
    ERC721Consecutive->>Event: Emit ConsecutiveTransfer(from_token_id, to_token_id, from, to)
    Note over ERC721Consecutive: Sequential tokens owned by 'to'
    
    participant User
    User->>ERC721Consecutive: transferFrom(from, to, token_id)
    ERC721Consecutive->>SequentialBurn: Check if token burned
    alt Token not burned
        ERC721Consecutive->>SequentialOwnership: Resolve owner via sequential_owner_of
        ERC721Consecutive->>Event: Emit Transfer event
    else Token is burned
        ERC721Consecutive->>ERC721Consecutive: Panic - invalid token
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Add ERC20WrapperComponent #1617: Modifies signature verification calls in ERC20 to pass raw signatures without .into() conversion, paralleling the same refactoring pattern applied in this PR across multiple components.
  • Add ERC721Wrapper #1625: Implements the ERC721Wrapper component that is enhanced in this PR with the new ERC721OwnerOfTrait bound additions.

Suggested reviewers

  • immrsd
  • bidzyyys

Poem

🐰 Hops through batches, tokens mint in rows,
Ownership flows where sequential grows,
Bitmaps compact, checkpoints lookup low,
ERC-2309 steals the show! ✨🎪

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add ERC721Consecutive extension' clearly and concisely summarizes the main change - implementing a new ERC721 Consecutive extension, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR successfully implements the ERC-721 Consecutive extension as requested in issue #1579, including component architecture, storage, validation logic, batch minting, event emission, test coverage, and integration with ERC721.
Out of Scope Changes check ✅ Passed All changes are within scope of implementing the ERC721Consecutive extension. Updates to signature verification (removing .into() conversions), trait bounds additions, and related infrastructure changes are necessary supporting changes for the extension implementation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add-erc721-consecutive-extension-#1579

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

@ericnordelo ericnordelo requested a review from bidzyyys January 12, 2026 17:16
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 12, 2026

🧪 Cairo Contract Size Benchmark Diff

BYTECODE SIZE (felts) (limit: 81,920 felts)

Contract Old New Δ Note
ERC6909ContentURIMock 7851 +7851 ✅ NEW
ERC6909MetadataMock 9032 +9032 ✅ NEW
ERC6909Mock 5307 +5307 ✅ NEW
ERC6909MockWithHooks 5541 +5541 ✅ NEW
ERC6909TokenSupplyMock 5638 +5638 ✅ NEW
ERC721ConsecutiveMock 12226 +12226 ✅ NEW
ERC721ConsecutiveMultiBatchMock 12150 +12150 ✅ NEW
MockBitMap 2033 +2033 ✅ NEW
MockTrace 4426 5180 🟢 +754

SIERRA CONTRACT CLASS SIZE (bytes) (limit: 4,089,446 bytes)

Contract Old New Δ Note
ERC6909ContentURIMock 173503 +173503 ✅ NEW
ERC6909MetadataMock 199396 +199396 ✅ NEW
ERC6909Mock 108287 +108287 ✅ NEW
ERC6909MockWithHooks 115288 +115288 ✅ NEW
ERC6909TokenSupplyMock 113703 +113703 ✅ NEW
ERC721ConsecutiveMock 265912 +265912 ✅ NEW
ERC721ConsecutiveMultiBatchMock 265702 +265702 ✅ NEW
MockBitMap 37046 +37046 ✅ NEW
MockTrace 87030 100600 🟢 +13570

This comment was generated automatically from benchmark diffs.

Copy link
Copy Markdown
Contributor

@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.

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (4)
packages/utils/src/tests/test_fuzz_checkpoint.cairo (1)

26-46: Fix push_checkpoints: it populates a different contract state than the one being asserted on.
Right now push_checkpoints creates a fresh ContractState, so test_upper_lookup, test_upper_lookup_recent, test_lower_lookup, and test_get_at_position appear to query an uninitialized state.

Proposed fix
@@
 #[test]
 #[fuzzer]
 fn test_upper_lookup(checkpoints: Span<Checkpoint>) {
     let mut mock_trace = CONTRACT_STATE();
-    push_checkpoints(checkpoints);
+    push_checkpoints(ref mut mock_trace, checkpoints);
@@
 fn test_upper_lookup_recent(checkpoints: Span<Checkpoint>) {
     let mut mock_trace = CONTRACT_STATE();
-    push_checkpoints(checkpoints);
+    push_checkpoints(ref mut mock_trace, checkpoints);
@@
 fn test_lower_lookup(checkpoints: Span<Checkpoint>) {
     let mut mock_trace = CONTRACT_STATE();
-    push_checkpoints(checkpoints);
+    push_checkpoints(ref mut mock_trace, checkpoints);
@@
 fn test_get_at_position(checkpoints: Span<Checkpoint>) {
     let mut mock_trace = CONTRACT_STATE();
-    push_checkpoints(checkpoints);
+    push_checkpoints(ref mut mock_trace, checkpoints);
@@
-fn push_checkpoints(checkpoints: Span<Checkpoint>) {
-    let mut mock_trace = CONTRACT_STATE();
+fn push_checkpoints(ref mut mock_trace: MockTrace::ContractState, checkpoints: Span<Checkpoint>) {
     for point in checkpoints {
         mock_trace.push_checkpoint(*point.key, *point.value);
     }
 }

Also applies to: 50-58, 62-72, 127-132

CHANGELOG.md (1)

10-21: Resolve CHANGELOG placeholders: replace (#) with the actual PR number(s).
These TODOs tend to get forgotten and block release notes quality.

Proposed fix
@@
 - `openzeppelin_utils`
-  - Added `lower_lookup` support to checkpoint utilities (#) <!-- TODO: add PR number -->
-  - Added `BitMap` struct and associated helpers to `openzeppelin_utils::structs::bitmap` (#) <!-- TODO: add PR number -->
+  - Added `lower_lookup` support to checkpoint utilities (#1630)
+  - Added `BitMap` struct and associated helpers to `openzeppelin_utils::structs::bitmap` (#1630)
packages/token/src/erc721/extensions/erc721_wrapper.cairo (1)

39-48: Generic trait bound already used in related ERC721 implementations; verify if this aligns with intended API design.

The ERC721OwnerOfTrait bound is not new—it's already required in other IERC721 interface implementations (IERC721, IERC721Metadata, IERC721CamelOnly) and in the Votes component for ERC721. A public default implementation (ERC721OwnerOfDefaultImpl) is available and exported from the component. The test mock demonstrates successful instantiation with this bound, so the pattern is established and functional.

This is a clarification of existing requirements rather than a breaking API change. However, external contracts that manually instantiate these impl blocks without including ERC721OwnerOfDefaultImpl in scope would fail to compile.

packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo (1)

44-53: Public surface change: new ERC721OwnerOfTrait bound on ERC721EnumerableComponent requires downstream updates.

The bound is functionally necessary—the enumerable impl calls _owner_of() internally (line 121). Downstream consumers manually implementing or embedding ERC721EnumerableComponent must now satisfy +ERC721Component::ERC721OwnerOfTrait<TContractState>.

Mitigation: Use the #[with_components(...)] macro (which auto-satisfies bounds) or import the publicly exported ERC721OwnerOfDefaultImpl for a no-op implementation.

🤖 Fix all issues with AI agents
In @packages/test_common/src/mocks/erc721.cairo:
- Around line 363-409: Add the missing import for ERC721OwnerOfDefaultImpl in
the ERC721ConsecutiveMock module: include a `use
openzeppelin_token::erc721::ERC721OwnerOfDefaultImpl;` alongside the other `use`
statements in the ERC721ConsecutiveMock block so the exposed `ERC721Impl` (and
any owner-of behavior) matches the other ERC721 mocks.

In @packages/token/src/erc721/erc721.cairo:
- Around line 25-26: Remove the unused alias import
ERC721ConsecutiveInternalImpl from the top of the file: the file already uses
ERC721ConsecutiveComponent for trait bounds (e.g., in
ConsecutiveExtensionERC721OwnerOfTraitImpl) and accesses the component via
get_dep_component!, so delete the second import line that aliases InternalImpl
(ERC721ConsecutiveComponent::InternalImpl as ERC721ConsecutiveInternalImpl) to
eliminate the dead import without changing any other code or dependencies.

In @packages/token/src/erc721/extensions/erc721_consecutive.cairo:
- Around line 148-152: The docstring for the consecutive mint function is
inaccurate: when batch_size == 0 the function returns next_consecutive_id (not
the “number minted so far” if FIRST_CONSECUTIVE_ID != 0). Either update the
comment to state it returns next_consecutive_id (the next token id to be minted)
or change the return value in the function to return (next_consecutive_id -
FIRST_CONSECUTIVE_ID) to provide the actual count; reference the symbols
FIRST_CONSECUTIVE_ID and next_consecutive_id (and the mint function handling
batch_size == 0) when making the change.
- Around line 89-92: The u256_to_address function currently uses unwrap() on the
final felt-to-ContractAddress conversion which can panic and bypass your
Errors::ADDRESS_OVERFLOW handling; change the final conversion to use
try_into().expect(Errors::ADDRESS_OVERFLOW) (or equivalent error propagation)
instead of unwrap() so the same ADDRESS_OVERFLOW error is returned on overflow;
update the function body in u256_to_address to use the expect call referencing
Errors::ADDRESS_OVERFLOW.
- Around line 219-224: The sequential-burn bitmap update in the
ERC721Consecutive burn path currently only checks token_id <
self.next_consecutive_id() and so can mark individually-minted IDs below
FIRST_CONSECUTIVE_ID as sequentially burned; update the conditional in the
ERC721Consecutive burn logic (the block that calls
self.ERC721Consecutive_sequential_burn.deref().set(token_id)) to also require
token_id >= FIRST_CONSECUTIVE_ID (i.e., add an explicit lower-bound check
alongside the existing to.is_zero(), token_id <
self.next_consecutive_id().into(), and !self.is_sequentially_burned(token_id)
checks) so only tokens in the consecutive-minted range are recorded as
sequentially burned.
- Around line 136-146: The sequential_owner_of function lacks bounds checking
and can return owners for unminted ids; before calling
ERC721Consecutive_sequential_ownership.deref().lower_lookup(token_id_u64)
compute let first = Self::first_consecutive_id(self) and let next =
Self::next_consecutive_id(self) and assert that token_id_u64 >= first &&
token_id_u64 < next, using ERC721Component::Errors::INVALID_TOKEN_ID as the
assertion error; place this guard at the top of sequential_owner_of (before
u256_to_u64 or immediately after) so invalid token queries are rejected rather
than returning a later batch owner.

In @packages/token/src/tests/erc721/test_erc721_consecutive.cairo:
- Around line 79-151: Replace raw integer literals used in assertions comparing
to token.balance_of(...) with explicit .into() conversions to match the return
type; e.g., in test_balance_after_transfers_and_burns (replace
assert_eq!(token.balance_of(OTHER), 2) with 2.into()), in
test_zero_batch_size_balance_is_zero (replace 0 with 0.into()), and likewise
update any other assertions in this file (including the referenced 175-223
region) that compare token.balance_of(...) to raw integers so they use N.into().

In @packages/utils/CHANGELOG.md:
- Around line 10-15: Replace the placeholder "(#) <!-- TODO: add PR number -->"
in the Unreleased "Added" entry that mentions `lower_lookup` with the actual PR
number for the change; locate the line containing "Added `lower_lookup` support
to checkpoint utilities" and update the trailing "(#)" to the correct PR
reference (e.g., "(#1234)") so the changelog entry includes the real PR number.
🧹 Nitpick comments (9)
packages/utils/src/tests/test_fuzz_checkpoint.cairo (1)

48-58: test_lower_lookup is a good addition, but it only tests “exact key” lookups.
Consider also asserting “between keys” behavior (e.g., lower_lookup(key - 1) returns the previous checkpoint’s value) to catch off-by-one errors.

packages/utils/src/tests/test_bitmap.cairo (1)

1-155: Nice coverage: bucket boundaries, idempotence, and mixed operation sequences.
Optional: consider suffixing small literals with _u256 (or annotating index variables) to make the intended API type unambiguous.

Also applies to: 191-212

packages/test_common/src/erc721.cairo (1)

84-114: ConsecutiveTransfer assertion matches expected indexed/non-indexed split; please verify against event ABI.
The keys/data mapping looks consistent with ERC-2309, but it’s worth confirming the exact field ordering/encoding used by the Cairo event definition and ExpectedEvent.

Proposed tweak (clarify keys vs data order)
         let expected = ExpectedEvent::new()
             .key(selector!("ConsecutiveTransfer"))
             .key(from_token_id)
-            .data(to_token_id)
             .key(from_address)
-            .key(to_address);
+            .key(to_address)
+            .data(to_token_id);
packages/utils/src/structs/checkpoint.cairo (1)

34-46: Binary search logic for lower_lookup / _lower_binary_lookup looks correct (standard lower_bound).
Only nit: the doc strings say “greater or equal than” → “greater than or equal to”.

Also applies to: 80-92, 179-199

packages/utils/src/structs/bitmap.cairo (3)

15-47: BitMapImpl read/modify/write is correct; consider “no-op write” optimization.

set()/unset() always write even if the bucket already has the desired bit state; optional to skip the write to reduce storage churn (esp. if callers may set the same bit repeatedly).


49-75: Prefer bit-shifts over Pow for mask creation if Cairo supports it cleanly.

2_u128.pow(...) works, but 1_u128 << shift (or equivalent) is typically clearer and cheaper. If shift isn’t available/ergonomic for u128, current approach is fine.


51-58: Avoid unwrap() in _bucket_and_mask() if you can keep it provably total.

The comment is correct (mod < 256), but an unwrap() still bakes in a panic path. If there’s an idiomatic “narrowing cast” / expect with message / or a way to compute mask from u256 directly, prefer that.

packages/token/src/tests/erc721/test_erc721_consecutive.cairo (1)

32-62: Hardcoded token id 0 is fragile; derive from first_consecutive_id() consistently.
Several tests assume the consecutive range starts at 0 (including event assertions). If first_consecutive_id() ever changes, these tests become misleading.

Suggested tweak: use first_token_id everywhere
 fn test_owner_of_first_middle_last() {
     let batch_size = 100;
     let (token, _) = setup(RECIPIENT, batch_size);

-    let first_token_id = 0;
+    let first_token_id: u256 = CONSECUTIVE_STATE().first_consecutive_id();
     assert_eq!(token.owner_of(first_token_id.into()), RECIPIENT);

-    let middle_token_id = 50;
+    let middle_token_id = first_token_id + 50;
     assert_eq!(token.owner_of(middle_token_id.into()), RECIPIENT);

-    let last_token_id = batch_size - 1;
+    let last_token_id = first_token_id + (batch_size - 1).into();
     assert_eq!(token.owner_of(last_token_id.into()), RECIPIENT);
 }

Also applies to: 107-133, 262-285

packages/token/src/erc721/erc721.cairo (1)

703-719: Consider adding zero-address validation.

The increase_balance function doesn't validate that account is non-zero. While the documentation warns this is unsafe and requires careful pairing with ERC721OwnerOfTrait, allowing balance increases for the zero address could lead to accounting inconsistencies.

♻️ Optional: Add zero-address check
 fn increase_balance(
     ref self: ComponentState<TContractState>, account: ContractAddress, value: u128,
 ) {
+    assert(!account.is_zero(), Errors::INVALID_ACCOUNT);
     let current_balance: u256 = self.ERC721_balances.read(account);
     self.ERC721_balances.write(account, current_balance + value.into());
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6364709 and 4b60705.

📒 Files selected for processing (30)
  • AGENTS.md
  • CHANGELOG.md
  • packages/account/src/extensions/src9/src9.cairo
  • packages/governance/src/governor/governor.cairo
  • packages/governance/src/tests/test_timelock.cairo
  • packages/governance/src/tests/votes/test_block_number.cairo
  • packages/governance/src/votes/votes.cairo
  • packages/test_common/src/erc721.cairo
  • packages/test_common/src/mocks.cairo
  • packages/test_common/src/mocks/bitmap.cairo
  • packages/test_common/src/mocks/checkpoint.cairo
  • packages/test_common/src/mocks/erc721.cairo
  • packages/test_common/src/mocks/votes.cairo
  • packages/token/Scarb.toml
  • packages/token/src/erc721.cairo
  • packages/token/src/erc721/erc721.cairo
  • packages/token/src/erc721/extensions.cairo
  • packages/token/src/erc721/extensions/erc721_consecutive.cairo
  • packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo
  • packages/token/src/erc721/extensions/erc721_wrapper.cairo
  • packages/token/src/tests/erc721.cairo
  • packages/token/src/tests/erc721/test_erc721_consecutive.cairo
  • packages/utils/CHANGELOG.md
  • packages/utils/src/structs.cairo
  • packages/utils/src/structs/bitmap.cairo
  • packages/utils/src/structs/checkpoint.cairo
  • packages/utils/src/tests.cairo
  • packages/utils/src/tests/test_bitmap.cairo
  • packages/utils/src/tests/test_checkpoint.cairo
  • packages/utils/src/tests/test_fuzz_checkpoint.cairo
💤 Files with no reviewable changes (1)
  • packages/governance/src/tests/votes/test_block_number.cairo
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: boostsecurity - boostsecurityio/scanner
  • GitHub Check: comment-benchmark-diff
  • GitHub Check: Lint and test macros
🔇 Additional comments (29)
AGENTS.md (1)

1-5: Nice lightweight contributor guideline; no issues.

packages/governance/src/tests/test_timelock.cairo (1)

24-27: Import cleanup is correct. Event is not referenced anywhere in the file, so the removal from the snforge_std imports is safe.

packages/token/src/erc721/extensions.cairo (1)

4-6: Export wiring confirmed. The module erc721_consecutive resolves correctly, and both re-exported items (DefaultConfig and ERC721ConsecutiveComponent) are properly defined and actively used throughout the codebase. No issues detected.

packages/utils/src/tests.cairo (1)

1-1: Test suite hook-up is correct. The module file test_bitmap.cairo exists at packages/utils/src/tests/test_bitmap.cairo and is properly referenced by the mod test_bitmap; declaration.

packages/governance/src/governor/governor.cairo (1)

625-644: Change is correct: assert_valid_signature parameter signature matches the Span<felt252> passed at all vote-by-sig callsites. No remaining .into() conversions found in governance vote paths.

All three call sites (lines 640 and 680 in governor.cairo, plus votes.cairo:189) correctly pass Span<felt252> directly, matching the function's parameter type in packages/utils/src/execution.cairo.

packages/account/src/extensions/src9/src9.cairo (1)

101-111: The change is correct. The assert_valid_signature function expects signature: Span<felt252> (by value), and passing signature directly matches the function signature. This is consistent across all callsites in the codebase.

packages/token/src/tests/erc721.cairo (1)

1-8: LGTM: test module wired in.
Just ensure packages/token/src/tests/erc721/test_erc721_consecutive.cairo is always present/compilable under the same cfgs as this parent module (i.e., not accidentally requiring the fuzzing feature).

packages/utils/src/structs.cairo (1)

1-5: Good: bitmap module is publicly exposed and BitMap is re-exported.
Double-check this is the intended stable import path for external users (openzeppelin_utils::structs::BitMap vs ...::structs::bitmap::BitMap).

packages/utils/src/tests/test_checkpoint.cairo (1)

74-105: Nice boundary coverage for lower_lookup (empty, below-min, exact-hit, above-max).
These tests align well with the intended “first key ≥ search key, else 0” semantics.

packages/token/src/erc721.cairo (1)

5-5: Public re-export looks fine; double-check for downstream name collisions.
Since this broadens the public surface, it’s worth ensuring no other module already exposes ERC721OwnerOfDefaultImpl under the same path.

packages/test_common/src/mocks/checkpoint.cairo (1)

10-11: Mock lower_lookup wiring is consistent with the other lookup helpers.

Also applies to: 45-48

packages/token/src/tests/erc721/test_erc721_consecutive.cairo (2)

79-297: Nice coverage of ERC-2309 behaviors (events, boundaries, burns/transfers).
The suite hits the important edges: zero batch, out-of-range, first/middle/last, burn + transfer interactions, and ConsecutiveTransfer emission.


20-77: State isolation risk: CONSECUTIVE_STATE() called separately in tests may not share storage with version from setup().

In test functions like test_owner_of_after_burn_panics() (line 102) and test_balance_after_transfers_and_burns() (line 88), a fresh consecutive_state is created via CONSECUTIVE_STATE() and then mark_sequential_burn() is called on it. However, the erc721_state comes from setup(), which created a separate consecutive_state instance that was never returned. If component_state_for_testing() doesn't guarantee shared storage across multiple calls within the same test, then the burn marking will not be visible to owner_of() checks on the token state, potentially causing false test results.

Recommended fix: return ConsecutiveState from setup() and thread it through test functions to ensure both states reference the same underlying storage throughout each test.

packages/test_common/src/mocks/bitmap.cairo (1)

1-36: Clean mock; delegation to BitMapTrait is straightforward.
No issues spotted—this should be handy for isolated bitmap tests.

packages/test_common/src/mocks/erc721.cairo (1)

8-8: Good: importing ERC721OwnerOfDefaultImpl to keep mocks compatible with the new owner-of abstraction.
This matches the direction of the PR (owner_of split-out) and should prevent “missing impl” failures in tests.

Also applies to: 46-46, 210-210, 278-278, 313-313

packages/token/src/erc721/extensions/erc721_consecutive.cairo (2)

69-72: is_constructor_scope() check looks correct for enforcing “constructor-only” batch minting.


231-235: DefaultConfig implementation is clean and keeps the component ergonomic to adopt.

packages/governance/src/votes/votes.cairo (2)

189-189: LGTM!

Removing the unnecessary .into() conversion is correct. The signature parameter is already Span<felt252>, which matches what assert_valid_signature expects.


247-273: LGTM!

The addition of +ERC721Component::ERC721OwnerOfTrait<TContractState> trait bound is necessary to support the new ownership resolution abstraction introduced for the ERC721Consecutive extension. The removal of .into() on Line 272 is correct since balance_of already returns u256.

packages/token/src/erc721/erc721.cairo (6)

10-10: LGTM!

Adding Bounded import is necessary for checking the u64::MAX boundary in the consecutive ownership logic.


112-125: LGTM!

The ERC721OwnerOfTrait abstraction is well-designed with clear documentation about the invariant that implementations must maintain. The default implementation provides a simple storage read, which is appropriate for standard ERC721 behavior.


127-157: LGTM!

The consecutive ownership resolution logic is well-structured:

  1. Storage check first handles transferred tokens correctly
  2. Boundary validation (token_id > Bounded::<u64>::MAX and token_id < first_consecutive_id) properly excludes tokens outside the consecutive range
  3. Burn check via is_sequentially_burned prevents returning owners for burned tokens
  4. The fallback to sequential_owner_of correctly resolves batch-minted token ownership

The logic correctly handles edge cases where token_id equals first_consecutive_id (included in consecutive range) or Bounded::<u64>::MAX (included in consecutive range).


163-171: LGTM!

The +ERC721OwnerOfTrait<TContractState> trait bound is consistently added to the ERC721Impl, enabling the ownership resolution abstraction throughout the component.


768-771: LGTM!

The delegation to OwnerOf::owner_of cleanly enables pluggable ownership resolution while maintaining the existing API surface.


934-939: LGTM!

The ERC721OwnerOfDefaultImpl correctly provides a default implementation by leveraging the trait's default method. This allows contracts not using the consecutive extension to easily satisfy the trait bound.

packages/token/Scarb.toml (1)

66-66: LGTM!

Adding ERC721ConsecutiveMock to the external contracts build list is necessary for testing the new consecutive extension.

packages/test_common/src/mocks.cairo (1)

3-3: LGTM!

Adding the bitmap module declaration exposes the BitMap mock required for testing the consecutive extension's burned token tracking.

packages/test_common/src/mocks/votes.cairo (2)

192-192: LGTM!

The ERC721OwnerOfDefaultImpl import is required to satisfy the new ERC721OwnerOfTrait bound on ERC721 components. This provides standard ownership resolution for the votes mock.


249-249: LGTM!

Same as above - the import provides the default ownership trait implementation for the block number votes mock.

@codecov
Copy link
Copy Markdown

codecov bot commented Jan 13, 2026

Codecov Report

❌ Patch coverage is 97.75281% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.11%. Comparing base (3703fa2) to head (0b9be38).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
packages/governance/src/governor/governor.cairo 50.00% 1 Missing ⚠️
...ken/src/erc721/extensions/erc721_consecutive.cairo 97.67% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1630      +/-   ##
==========================================
+ Coverage   93.98%   94.11%   +0.12%     
==========================================
  Files          92       94       +2     
  Lines        2279     2360      +81     
==========================================
+ Hits         2142     2221      +79     
- Misses        137      139       +2     
Files with missing lines Coverage Δ
packages/account/src/extensions/src9/src9.cairo 94.44% <100.00%> (-0.30%) ⬇️
packages/governance/src/votes/votes.cairo 100.00% <100.00%> (ø)
packages/presets/src/erc721.cairo 91.30% <ø> (ø)
packages/token/src/erc721/erc721.cairo 93.07% <100.00%> (+0.63%) ⬆️
...tensions/erc721_enumerable/erc721_enumerable.cairo 98.00% <ø> (ø)
...s/token/src/erc721/extensions/erc721_wrapper.cairo 86.84% <ø> (ø)
packages/utils/src/structs/bitmap.cairo 100.00% <100.00%> (ø)
packages/utils/src/structs/checkpoint.cairo 94.82% <100.00%> (+0.95%) ⬆️
packages/governance/src/governor/governor.cairo 92.53% <50.00%> (-0.75%) ⬇️
...ken/src/erc721/extensions/erc721_consecutive.cairo 97.67% <97.67%> (ø)

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3703fa2...0b9be38. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Collaborator

@bidzyyys bidzyyys left a comment

Choose a reason for hiding this comment

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

Looks good to me!

@ericnordelo ericnordelo requested a review from immrsd January 22, 2026 11:29
Copy link
Copy Markdown
Collaborator

@immrsd immrsd left a comment

Choose a reason for hiding this comment

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

LGTM

@ericnordelo ericnordelo merged commit 924e9d0 into main Jan 23, 2026
13 checks passed
@ericnordelo ericnordelo deleted the feat/add-erc721-consecutive-extension-#1579 branch January 23, 2026 13:50
@ericnordelo ericnordelo restored the feat/add-erc721-consecutive-extension-#1579 branch January 23, 2026 14:18
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.

[Feature]: Add ERC-721 Consecutive Extension

3 participants