Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC721Enumerable helper function #1196

Merged
merged 17 commits into from
Nov 6, 2024
Merged

Conversation

immrsd
Copy link
Collaborator

@immrsd immrsd commented Oct 29, 2024

Fixes #1127

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

Copy link

codecov bot commented Oct 29, 2024

Codecov Report

Attention: Patch coverage is 80.00000% with 1 line in your changes missing coverage. Please review.

Project coverage is 91.56%. Comparing base (8f4e8a1) to head (ef8ffd6).
Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...tensions/erc721_enumerable/erc721_enumerable.cairo 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1196      +/-   ##
==========================================
- Coverage   91.61%   91.56%   -0.05%     
==========================================
  Files          47       47              
  Lines        1228     1233       +5     
==========================================
+ Hits         1125     1129       +4     
- Misses        103      104       +1     
Files with missing lines Coverage Δ
...tensions/erc721_enumerable/erc721_enumerable.cairo 95.91% <80.00%> (-1.81%) ⬇️

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 8f4e8a1...ef8ffd6. Read the comment docs.

@immrsd immrsd marked this pull request as ready for review October 29, 2024 15:34
Copy link
Member

@ericnordelo ericnordelo left a comment

Choose a reason for hiding this comment

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

Looking good @immrsd, letf some comments.

docs/modules/ROOT/pages/api/erc721.adoc Outdated Show resolved Hide resolved
packages/test_common/src/mocks/erc721_enumerable.cairo Outdated Show resolved Hide resolved
packages/test_common/src/mocks/erc721_enumerable.cairo Outdated Show resolved Hide resolved
@@ -84,6 +83,36 @@ pub mod ERC721EnumerableComponent {
}
}

#[embeddable_as(ERC721EnumerableExtendedImpl)]
impl ERC721EnumerableExtended<
Copy link
Member

Choose a reason for hiding this comment

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

This implementation won't be used much without the not extended version functions. What do you think if we add the not extended functions to this as well, and so users would need to embed just the extended implementation instead of both? Similar to what we have in Ownable with the two-step implementation. If we do this we need to update the doc-site as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It may work. It would also lead to some concern though:

  • We'll have 2 almost similar interfaces and the same situation with their implementations
  • It won't be clear which one to use and why did we introduce 2 slightly different versions in the first place
  • Should we add an interface ID for IERC721ENUMERABLE_EXTENDED_ID?
  • If we do, what interface ID should be registered in the initializer of the component: the standard or the extended one?
    To summarize, I believe it would complicate things for our users and there's no strong reason to do so.

At the same time, I agree with you and the current implementation doesn't look perfect to me: users have to know about the extended trait version and they have to add 2 enumerable impls to get the full functionality.

I've got an idea how to improve things:

  1. We add the new all_tokens_of_owner function directly to the existing IERC721Enumerable interface and its impl
  2. We resolve the new SRC5 interface ID and name it IERC721ENUMERABLE_V2_ID
  3. We keep the current interface ID by naming it IERC721ENUMERABLE_V1_ID
  4. ERC721EnumerableComponent will implement all_tokens_of_owner function and will register both interface IDs in its initializer
  5. New contracts utilizing the component will provide the extended functionality and return true for the both interface IDs
  6. Old contracts with ERC721EnumerableComponent will return true only for the original interface ID

As a result:

  • It will have no effect on the already deployed contracts
  • If a caller is interested in the general Enumerable functionality, he will call supports_interface with IERC721ENUMERABLE_V1_ID
  • If he's looking for the extended functionality, he will check for IERC721ENUMERABLE_V2_ID

What do you think? I know it may sound complex, but it's actually very concise approach that will lead to having a single interface, a single implementation and the extended functionality built-in by default.

Copy link
Member

Choose a reason for hiding this comment

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

Regarding the concerns:

  • We'll have 2 almost similar interfaces and the same situation with their implementations

We can reference functions of one in the other one so we don't need to duplicate the code.

  • It won't be clear which one to use and why did we introduce 2 slightly different versions in the first place

I think the Extended suffix leaves implicit that the implementation is extending the other one, and with correct comments and docs it should be clear why.

  • Should we add an interface ID for IERC721ENUMERABLE_EXTENDED_ID?

I don't think we have to, since we add interfaces for standards to make them easier to track, but in this case we are providing a helper on top of the standard, not a different one.

  • If we do, what interface ID should be registered in the initializer of the component: the standard or the extended one?

We can register the same interface we are registering now, since the Extended implementation will provide every method defined in the interface, so it is implemented. It doesn't matter that the contract has an extra external method for this matter.

Regarding the suggestion, the only part I don't like much is adding a new interface ID, since as mentioned before I think is enough with the current one even if we are adding a new external function. If we do this we would have an interface whose ID won't match the SRC5 standard. For that, I'm still leaning toward the first approach:

  • IERC721Enumerable and IERC721EnumerableExtended embeddable implementations with the second one being a superset of the first one (as suggested by the name).
  • Registering only the IERC721Enumerable interface ID even when the extended version is used, since that implies that the IERC721Enumerable is implemented as it is contained.
  • This will make things easier for users as they will only need to embed one implementation, and with appropriate comments and docs it should be easy to get the difference.

Copy link
Collaborator

Choose a reason for hiding this comment

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

  • IERC721Enumerable and IERC721EnumerableExtended embeddable implementations with the second one being a superset of the first one (as suggested by the name).
  • Registering only the IERC721Enumerable interface ID even when the extended version is used, since that implies that the IERC721Enumerable is implemented as it is contained.
  • This will make things easier for users as they will only need to embed one implementation, and with appropriate comments and docs it should be easy to get the difference.

Between the proposed approaches, I lean toward what's quoted above. To @immrsd's points though:

  • We'll have 2 almost similar interfaces and the same situation with their implementations
  • It won't be clear which one to use and why did we introduce 2 slightly different versions in the first place

it seems a bit clunky. We're just adding a non-standardized helper view fn. What do you guys think about making this even simpler (in some respects) and just have all_tokens_of_owner as an internal fn that the using contract may manually expose? Here's the difference between implementions:

    #[abi(embed_v0)]
    impl ERC721EnumerableExtendedImpl =
        ERC721EnumerableComponent::ERC721EnumerableExtendedImpl<ContractState>;

vs

    #[abi(embed_v0)]
    fn all_tokens_of_owner(self: @ContractState, owner: ContractAddress) -> Span<u256> {
        self.erc721_enumerable.all_tokens_of_owner(owner)
    }

Copy link
Collaborator

Choose a reason for hiding this comment

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

If we don't like the suggestion, again, I lean toward the superset impl

Copy link
Member

Choose a reason for hiding this comment

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

I personally like what @andrew-fleming is suggesting the most, but I don't have a strong opinion against adding the extra implementation, as long as we don't start abusing the pattern.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I also like what @andrew-fleming has suggested, this way we don't have to introduce a new interface and interface ID. I refactored the code accordingly

@immrsd immrsd self-assigned this Oct 30, 2024
Copy link
Collaborator

@andrew-fleming andrew-fleming left a comment

Choose a reason for hiding this comment

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

Looking good so far! I think you guys raised a lot of good points in the big thread. I left my two cents :)

docs/modules/ROOT/pages/api/erc721.adoc Outdated Show resolved Hide resolved
@@ -84,6 +83,36 @@ pub mod ERC721EnumerableComponent {
}
}

#[embeddable_as(ERC721EnumerableExtendedImpl)]
impl ERC721EnumerableExtended<
Copy link
Collaborator

Choose a reason for hiding this comment

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

  • IERC721Enumerable and IERC721EnumerableExtended embeddable implementations with the second one being a superset of the first one (as suggested by the name).
  • Registering only the IERC721Enumerable interface ID even when the extended version is used, since that implies that the IERC721Enumerable is implemented as it is contained.
  • This will make things easier for users as they will only need to embed one implementation, and with appropriate comments and docs it should be easy to get the difference.

Between the proposed approaches, I lean toward what's quoted above. To @immrsd's points though:

  • We'll have 2 almost similar interfaces and the same situation with their implementations
  • It won't be clear which one to use and why did we introduce 2 slightly different versions in the first place

it seems a bit clunky. We're just adding a non-standardized helper view fn. What do you guys think about making this even simpler (in some respects) and just have all_tokens_of_owner as an internal fn that the using contract may manually expose? Here's the difference between implementions:

    #[abi(embed_v0)]
    impl ERC721EnumerableExtendedImpl =
        ERC721EnumerableComponent::ERC721EnumerableExtendedImpl<ContractState>;

vs

    #[abi(embed_v0)]
    fn all_tokens_of_owner(self: @ContractState, owner: ContractAddress) -> Span<u256> {
        self.erc721_enumerable.all_tokens_of_owner(owner)
    }

@@ -84,6 +83,36 @@ pub mod ERC721EnumerableComponent {
}
}

#[embeddable_as(ERC721EnumerableExtendedImpl)]
impl ERC721EnumerableExtended<
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we don't like the suggestion, again, I lean toward the superset impl

Copy link
Member

@ericnordelo ericnordelo left a comment

Choose a reason for hiding this comment

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

LGTM!

Copy link
Collaborator

@andrew-fleming andrew-fleming left a comment

Choose a reason for hiding this comment

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

LGTM! 🤞 for coverage not to weirdly fail

@immrsd immrsd merged commit 02c9ce6 into OpenZeppelin:main Nov 6, 2024
7 of 8 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.

ERC721 Enumerable Helper
3 participants