Skip to content

Conversation

@ericnordelo
Copy link
Member

@ericnordelo ericnordelo commented Dec 30, 2025

Fixes #1581

PR Checklist

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ERC721 Wrapper functionality enabling users to wrap underlying ERC721 tokens and unwrap them, including deposit operations for wrapping and withdrawal operations for unwrapping
  • Tests

    • Added comprehensive test suite covering wrapper initialization, token deposits, withdrawals, safe token transfers, recovery operations, and various error handling scenarios

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

@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

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 an ERC721 Wrapper extension enabling wrapping and unwrapping of existing ERC721 tokens. It introduces the IERC721Wrapper interface, a core ERC721WrapperComponent with deposit/withdraw functionality, test mock contracts, and comprehensive test coverage for deposit, withdrawal, safe transfer, and recovery flows.

Changes

Cohort / File(s) Summary
Interface Definitions
packages/interfaces/src/token/erc721.cairo
Adds IERC721ReceiverMut<TState> trait with mutable receiver callback and IERC721Wrapper<TState> trait exposing underlying(), deposit_for(), and withdraw_to() functions.
Core Component Implementation
packages/token/src/erc721/extensions/erc721_wrapper.cairo
Implements ERC721WrapperComponent with storage for underlying token address, error constants (INVALID_UNDERLYING_ADDRESS, UNSUPPORTED_TOKEN, INCORRECT_OWNER), and three key implementations: ERC721Wrapper (deposit/withdraw logic), ERC721WrapperReceiver (safe transfer callback), and InternalImpl (initialization and recovery).
Module Exports
packages/token/src/erc721/extensions.cairo
Exports new erc721_wrapper module and re-exports ERC721WrapperComponent for public API exposure.
Test Mocks
packages/test_common/src/mocks/erc721.cairo
Adds IERC721Mintable trait, IERC721WrapperRecoverer trait, ERC721MintableMock contract (for minting test tokens), and ERC721WrapperMock contract (wrapper instance for testing).
Build Configuration
packages/token/Scarb.toml
Registers ERC721MintableMock and ERC721WrapperMock as external contracts for test target compilation.
Test Suite
packages/token/src/tests/erc721.cairo
packages/token/src/tests/erc721/test_erc721_wrapper.cairo
Adds test module import and comprehensive test suite covering initialization, deposit, withdrawal, safe transfer callback, unauthorized access, and token recovery scenarios with event verification.
Documentation
CHANGELOG.md
Documents addition of IERC721Wrapper interface and ERC721WrapperComponent in Unreleased section.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Wrapper as ERC721Wrapper<br/>Contract
    participant Underlying as Underlying<br/>ERC721
    participant Storage

    rect rgba(200, 220, 255, 0.3)
        Note over User,Storage: deposit_for(receiver, token_ids)
        User->>Underlying: approve(wrapper_address, token_id)
        User->>Wrapper: deposit_for(receiver, [token_id])
        Wrapper->>Underlying: transferFrom(user, wrapper, token_id)
        Underlying->>Storage: Update owner to wrapper
        Underlying-->>Wrapper: Transfer complete
        Wrapper->>Wrapper: mint(receiver, token_id)
        Storage->>Storage: Store wrapped token ownership
        Wrapper-->>User: Return true
    end
Loading
sequenceDiagram
    participant User
    participant Wrapper as ERC721Wrapper<br/>Contract
    participant Underlying as Underlying<br/>ERC721
    participant Storage

    rect rgba(220, 200, 255, 0.3)
        Note over User,Storage: withdraw_to(receiver, token_ids)
        User->>Wrapper: withdraw_to(receiver, [token_id])
        Wrapper->>Wrapper: burn(token_id)
        Storage->>Storage: Remove wrapped token
        Wrapper->>Underlying: transferFrom(wrapper, receiver, token_id)
        Underlying->>Storage: Update owner to receiver
        Underlying-->>Wrapper: Transfer complete
        Wrapper-->>User: Return true
    end
Loading
sequenceDiagram
    participant Sender
    participant Underlying as Underlying<br/>ERC721
    participant Wrapper as ERC721Wrapper<br/>Contract
    participant Storage

    rect rgba(255, 220, 200, 0.3)
        Note over Sender,Storage: on_erc721_received (safe transfer)<br/>from underlying token
        Sender->>Underlying: safeTransferFrom(from, wrapper, token_id)
        Underlying->>Wrapper: on_erc721_received(operator, from,<br/>token_id, data)
        Wrapper->>Wrapper: Verify caller is underlying
        Wrapper->>Wrapper: mint(from, token_id)
        Storage->>Storage: Store wrapped token ownership
        Wrapper-->>Underlying: Return IERC721_RECEIVER_ID
        Underlying-->>Sender: Transfer complete
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Wrapping tokens, what a delight!
ERC721 now fits just right.
Deposit, withdraw, safe transfers too,
A wrapper component, shiny and new!
Hop along with wrapped tokens so bright! 🌟

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR implements the ERC721 Wrapper extension following the Solidity reference, includes comprehensive tests, interface definitions, and component implementation as required by issue #1581.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the ERC721Wrapper component: interfaces, implementation, mocks, tests, and configuration updates. No unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title 'Add ERC721Wrapper' directly and clearly describes the main change: introducing a new ERC721Wrapper component to the codebase.

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

@ericnordelo ericnordelo changed the title Add ERC721Wrapper Add ERC721Wrapper v2 Dec 30, 2025
@ericnordelo ericnordelo mentioned this pull request Dec 30, 2025
4 tasks
@github-actions
Copy link
Contributor

github-actions bot commented Dec 30, 2025

🧪 Cairo Contract Size Benchmark Diff

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

Contract Old New Δ Note
DualCaseAccessControlDefaultAdminRulesMock 8246 8303 🟢 +57
ERC20BlockNumberVotesMock 13221 13449 🟢 +228
ERC20CustomDecimalsMock 5950 +5950 ✅ NEW
ERC20TimestampVotesMock 13221 13449 🟢 +228
ERC20WrapperMock 7132 +7132 ✅ NEW
ERC2981AccessControlDefaultAdminRulesMock 6976 7006 🟢 +30
ERC4626AssetsFeesMock 17934 18471 🟢 +537
ERC4626SharesFeesMock 17576 18245 🟢 +669
ERC721MintableMock 7145 +7145 ✅ NEW
ERC721WrapperMock 9589 +9589 ✅ NEW

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

Contract Old New Δ Note
DualCaseAccessControlDefaultAdminRulesMock 169089 170171 🟢 +1082
ERC20BlockNumberVotesMock 291841 297660 🟢 +5819
ERC20CustomDecimalsMock 122640 +122640 ✅ NEW
ERC20TimestampVotesMock 291805 297629 🟢 +5824
ERC20WrapperMock 162648 +162648 ✅ NEW
ERC2981AccessControlDefaultAdminRulesMock 142584 143301 🟢 +717
ERC4626AssetsFeesMock 383904 398372 🟢 +14468
ERC4626SharesFeesMock 373484 393994 🟢 +20510
ERC721MintableMock 159737 +159737 ✅ NEW
ERC721WrapperMock 217883 +217883 ✅ NEW
GovernorMock 483230 483224 🔴 −6
GovernorQuorumFractionMock 584052 584053 🟢 +1
GovernorTimelockedMock 583868 583852 🔴 −16

This comment was generated automatically from benchmark diffs.

Copy link

@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: 2

🧹 Nitpick comments (2)
packages/token/src/tests/erc721/test_erc721_wrapper.cairo (2)

237-246: Consider adding expected panic message for clarity.

The test uses #[should_panic] without specifying the expected message. Adding expected: 'Wrapper: unsupported token' would make the test more precise and serve as documentation.

🔎 Proposed fix
 #[test]
-#[should_panic]
+#[should_panic(expected: 'Wrapper: unsupported token')]
 fn on_erc721_received_reverts_for_unsupported_token() {

99-111: Consider adding expected panic messages for initializer tests.

Similar to other tests in this file, adding expected panic messages would improve test precision and documentation.

🔎 Proposed fix
 #[test]
-#[should_panic]
+#[should_panic(expected: 'Wrapper: invalid underlying')]
 fn initializer_reverts_on_zero_underlying() {
     let mut state = COMPONENT_STATE();
     state.initializer(ZERO);
 }

 #[test]
-#[should_panic]
+#[should_panic(expected: 'Wrapper: invalid underlying')]
 fn initializer_reverts_on_self_underlying() {
     let mut state = COMPONENT_STATE();
     state.initializer(test_address());
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8de5342 and dbe45de.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • packages/interfaces/src/token/erc721.cairo
  • packages/test_common/src/mocks/erc721.cairo
  • packages/token/Scarb.toml
  • packages/token/src/erc721/extensions.cairo
  • packages/token/src/erc721/extensions/erc721_wrapper.cairo
  • packages/token/src/tests/erc721.cairo
  • packages/token/src/tests/erc721/test_erc721_wrapper.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: comment-benchmark-diff
  • GitHub Check: Lint and test Cairo
  • GitHub Check: Lint and test macros
🔇 Additional comments (18)
CHANGELOG.md (1)

14-15: Verify the PR reference number.

The changelog entries reference PR #1624, but this PR appears to be #1625. Please verify the correct PR number should be used here.

packages/token/Scarb.toml (1)

62-63: LGTM!

The new mock contracts are correctly added to the test build configuration, aligning with the mock definitions for the ERC721 wrapper tests.

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

4-4: LGTM!

The new test module is correctly added to the test suite.

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

3-5: LGTM!

The new wrapper module and component are correctly exposed following the existing pattern for ERC721EnumerableComponent.

packages/interfaces/src/token/erc721.cairo (2)

131-142: LGTM!

The IERC721ReceiverMut interface correctly uses ref self to allow state mutations during token reception, which is necessary for the wrapper to mint wrapped tokens upon receiving underlying tokens.


198-203: LGTM!

The IERC721Wrapper interface follows the Solidity reference implementation with clear methods for querying the underlying token and performing deposit/withdraw operations.

packages/token/src/tests/erc721/test_erc721_wrapper.cairo (4)

84-97: LGTM!

The initializer tests properly verify that the underlying address is stored, and that the wrapper registers support for IERC721_RECEIVER_ID and ISRC5_ID interfaces.


117-164: LGTM!

The deposit_for tests comprehensively verify single and multiple token deposits, including ownership transfers, event emissions, and balance updates.


170-213: LGTM!

The withdraw_to tests properly verify successful withdrawals and unauthorized caller rejection.


252-282: LGTM!

The recover tests properly verify the recovery mechanism for tokens transferred directly (without using safe_transfer_from).

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

263-271: LGTM!

The test interfaces IERC721Mintable and IERC721WrapperRecoverer are appropriately scoped for testing purposes.


273-304: LGTM!

The ERC721MintableMock provides a clean, minimal implementation for the underlying ERC721 token needed in wrapper tests.


328-339: LGTM!

The storage and event declarations correctly integrate the ERC721WrapperComponent substorage and events.

packages/token/src/erc721/extensions/erc721_wrapper.cairo (5)

155-169: Consider edge case in recover function.

If the wrapped token with token_id has already been minted (e.g., via deposit_for or a previous recover call), the safe_mint will panic. This is likely the intended behavior since it prevents double-wrapping, but consider adding a comment clarifying this or checking existence first for a more descriptive error.


67-73: Acknowledge reentrancy safety design.

The comment explains why the unsafe transfer_from is acceptable here. The reasoning is sound: the underlying token is trusted by design, and no external untrusted contracts are called.


90-98: LGTM!

The withdraw_to flow correctly burns the wrapped token first (with auth check via update), then safely transfers the underlying token to the receiver.


145-153: LGTM!

The initializer correctly validates the underlying address (non-zero and not self) and registers the IERC721_RECEIVER_ID interface for safe transfer support.


5-10: LGTM!

Good documentation explaining the wrapper's purpose and use cases, particularly the governance token example.

@codecov
Copy link

codecov bot commented Dec 30, 2025

Codecov Report

❌ Patch coverage is 87.80488% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.02%. Comparing base (ca66bf7) to head (64e886d).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...s/token/src/erc721/extensions/erc721_wrapper.cairo 87.50% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1625      +/-   ##
==========================================
+ Coverage   92.89%   93.02%   +0.12%     
==========================================
  Files          86       87       +1     
  Lines        2323     2364      +41     
==========================================
+ Hits         2158     2199      +41     
  Misses        165      165              
Files with missing lines Coverage Δ
packages/interfaces/src/token/erc721.cairo 100.00% <100.00%> (ø)
...s/token/src/erc721/extensions/erc721_wrapper.cairo 87.50% <87.50%> (ø)

... and 1 file with indirect coverage changes


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 ca66bf7...64e886d. Read the comment docs.

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

@bidzyyys bidzyyys requested review from bidzyyys and immrsd December 30, 2025 15:20
Copy link
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.

LGTM!

@bidzyyys bidzyyys changed the title Add ERC721Wrapper v2 Add ERC721Wrapper Dec 30, 2025
Copy link
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! We should also support ERC721Wrapper in macros (and ERC20Wrapper as well)

@ericnordelo ericnordelo merged commit 6364709 into main Jan 6, 2026
12 checks passed
@ericnordelo ericnordelo deleted the feat/add-erc721-wrapper-v2-#1581 branch January 6, 2026 12:44
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 Wrapper Extension

5 participants