-
Notifications
You must be signed in to change notification settings - Fork 353
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
Conversation
Codecov ReportAttention: Patch coverage is
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
Continue to review full report in Codecov by Sentry.
|
There was a problem hiding this 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.
@@ -84,6 +83,36 @@ pub mod ERC721EnumerableComponent { | |||
} | |||
} | |||
|
|||
#[embeddable_as(ERC721EnumerableExtendedImpl)] | |||
impl ERC721EnumerableExtended< |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
- We add the new
all_tokens_of_owner
function directly to the existingIERC721Enumerable
interface and its impl - We resolve the new SRC5 interface ID and name it
IERC721ENUMERABLE_V2_ID
- We keep the current interface ID by naming it
IERC721ENUMERABLE_V1_ID
ERC721EnumerableComponent
will implementall_tokens_of_owner
function and will register both interface IDs in its initializer- New contracts utilizing the component will provide the extended functionality and return
true
for the both interface IDs - Old contracts with
ERC721EnumerableComponent
will returntrue
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
withIERC721ENUMERABLE_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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
}
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this 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 :)
@@ -84,6 +83,36 @@ pub mod ERC721EnumerableComponent { | |||
} | |||
} | |||
|
|||
#[embeddable_as(ERC721EnumerableExtendedImpl)] | |||
impl ERC721EnumerableExtended< |
There was a problem hiding this comment.
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< |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
There was a problem hiding this 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
Fixes #1127