-
Notifications
You must be signed in to change notification settings - Fork 338
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
ERC721 URI Storage #1031
base: main
Are you sure you want to change the base?
ERC721 URI Storage #1031
Conversation
The new tests or all tests? If the latter, are you in the package root ( |
@andrew-fleming , they were running but it was not showing any errors on my local machine. When I opened the pull request three of them were failing and the tests were running. I have committed the changes and all the tests are passing right now . Let me know if more tests are needed. |
// OpenZeppelin Contracts for Cairo v0.14.0 (token/erc721/extensions/erc721_uri_storage.cairo) | ||
|
||
#[starknet::component] | ||
pub mod ERC721URIstorageComponent { |
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.
pub mod ERC721URIstorageComponent { | |
pub mod ERC721URIStorageComponent { |
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.
Do I need to change that everywhere? ERC721URIstorage -> ERC721URIStorage
.
Because I have used the earlier one in naming of files,Impl and in tests 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.
Yes
|
||
#[storage] | ||
struct Storage { | ||
token_uris: LegacyMap<u256, ByteArray>, |
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.
#[event] | ||
#[derive(Drop, PartialEq, starknet::Event)] | ||
pub enum Event { | ||
MetadataUpdate: MetadataUpdate, |
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.
MetadataUpdate: MetadataUpdate, | |
MetadataUpdated: MetadataUpdated, |
For consistency among the codebase we strive to have all (almost) events named in the same way. Following this approach the name of this event should be MetadataUpdated
, although it is indeed name MetadataUpdate
in the Solidity counterpart
fn symbol(self: @ComponentState<TContractState>) -> ByteArray { | ||
self._symbol() | ||
} | ||
/// Returns the Uniform Resource Identifier (URI) for the `token_id` token. |
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.
/// Returns the Uniform Resource Identifier (URI) for the `token_id` token. | |
/// Returns the Uniform Resource Identifier (URI) for the `token_id` token. |
fn _name(self: @ComponentState<TContractState>) -> ByteArray { | ||
let erc721_component = get_dep_component!(self, ERC721); | ||
let name = erc721_component.ERC721_name.read(); | ||
return name; |
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.
return name; | |
name |
fn _symbol(self: @ComponentState<TContractState>) -> ByteArray { | ||
let erc721_component = get_dep_component!(self, ERC721); | ||
let symbol = erc721_component.ERC721_symbol.read(); | ||
return symbol; |
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.
return symbol; | |
symbol |
return ""; | ||
} else { | ||
return format!("{}{}", base_uri, token_id); | ||
} |
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.
- Double if-else statement
- Consider avoiding early returns if possible
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.
return format!("{}{}", base_uri, token_id);
@immrsd, should I replace the second if-else with this?
@immrsd , @andrew-fleming , I wanted to contribute more to the repository and am waiting for any suggestions to add to this PR to get merged, to take up a new issue. Can you please review again? |
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.
Hey, thank you for opening this PR!
I wanted to contribute more to the repository and am waiting for any suggestions to add to this PR to get merged, to take up a new issue. Can you please review again?
We appreciate your eagerness to contribute! We review contributions as soon as we can, but please understand that reviews take time. That said, I finished my initial review. I left a lot of feedback (sorry!). In addition to addressing the review, could you kindly finish the PR checklist:
- add this entry to CHANGELOG
- deploy/interact with this feature on sepolia and provide an address and/or tx hash to document that the feature behaves as expected on a public network?
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 much better! I left some feedback. Would you mind also updating your branch and fixing conflicts?
e605ffe
to
eacbd42
Compare
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! Left a few comments mostly from the cairo bump
507eadb
to
e4e7368
Compare
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.
Great work on the changes! LGTM! Let's wait on a second pair of eyes before merging
Hey @gerceboss, sorry for being this late to the conversation. I reviewed the PR and the implementation, tests, and docs look great. With this said, I think that for this particular case we don't need an extra component, because we can add just an extra embeddable implementation (ERC721MetadataURIStorage) to the existing ERC721 component (besides the storage member, the event, and the internal set_token_uri function). This would make the usage of it less verbose, since users would only need to change an embedded implementation on the final contract, instead of adding the extra component verbosity on it. What do you think @andrew-fleming? |
@ericnordelo I lean toward keeping the component light and not introduce non-core impls directly. The additions are small, but it forces all contracts to add extra storage for a feature that many contracts likely won't use. On the contrary, if it seems worth it to include this in the component itself, why don't we make the URIStorage |
Note that the extra storage is completely transparent to the final contract, and it doesn't require any overhead gas-wise. We also have the two-step ownable setup as an extra embeddable implementation to avoid adding an extra component, so this would be consistent, and even smaller and easier to use than that.
We can make it the default implementation, but I'm leaning toward keeping consistency with our Solidity counterpart on this, for the sake of consistency but also because even when it may be negligible gas-wise, developers often want the option to avoid an extra check on storage, that could be useful for a use case that we currently don't see. |
@ericnordelo yeah, those are fair points. A separate impl in the core component sounds good to me. One last thing to note is that the Solidity implementation also supports erc4906. If we include support for that or a SN equivalent, I think we'll need to have a separate initializer for URI storage to declare support. We can address that later though |
@ericnordelo @andrew-fleming ,so , how do we need to change the implementation as your suggestion is to not have it as an extension? And about erc4906, I am planning to make a PR but am waiting to complete this one first. |
We can move the embeddable URI storage impl from the extension into the erc721 component itself. Be aware, we're now using SN Foundry for testing so the tests will need to be refactored accordingly and we're also in the process of migrating to multiple packages (#1065) |
Will check that tomorrow 👍 |
/// | ||
/// - `token_id` exists. | ||
fn token_uri(self: @ComponentState<TContractState>, token_id: u256) -> ByteArray { | ||
let mut erc721_component = get_dep_component!(self, ERC721); |
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.
Does it have to be mutable?
// If there is no base_uri, return the token_uri | ||
if base_uri.len() == 0 { | ||
return token_uri; | ||
} | ||
|
||
// If both are set, concatenate the base_uri and token_uri | ||
if token_uri.len() > 0 { | ||
return format!("{}{}", base_uri, token_uri); | ||
} | ||
|
||
// Implementation from ERC721Metadata::token_uri | ||
return format!("{}{}", base_uri, token_id); |
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 there is no base_uri, return the token_uri | |
if base_uri.len() == 0 { | |
return token_uri; | |
} | |
// If both are set, concatenate the base_uri and token_uri | |
if token_uri.len() > 0 { | |
return format!("{}{}", base_uri, token_uri); | |
} | |
// Implementation from ERC721Metadata::token_uri | |
return format!("{}{}", base_uri, token_id); | |
if base_uri.len() == 0 { | |
// If there is no base_uri, return the token_uri | |
token_uri | |
} else if token_uri.len() == 0 { | |
// Implementation from ERC721Metadata::token_uri | |
format!("{}{}", base_uri, token_id) | |
} else { | |
// If both are set, concatenate the base_uri and token_uri | |
format!("{}{}", base_uri, token_uri) | |
} |
ref self: ComponentState<TContractState>, token_id: u256, token_uri: ByteArray | ||
) { | ||
self.ERC721URIStorage_token_uris.write(token_id, token_uri); | ||
self.emit(MetadataUpdated { token_id: token_id }); |
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.
self.emit(MetadataUpdated { token_id: token_id }); | |
self.emit(MetadataUpdated { token_id }); |
0fc0b3b
to
5f2a7de
Compare
@andrew-fleming @ericnordelo , wanted to confirm the steps for the change:
the second option is to add a new interface like ERC721Metadata interface, can you confirm what to do? |
Do we need another implementation just for
In the same Storage struct, yes
Not sure what you mean by second option. It's the same interface so we don't need to define a new interface. The idea is to have an additional implementation of the same IERC721Metadata interface within the ERC721Component module like this:
|
@andrew-fleming, can you also mention how do I change the testing ? |
I think the only thing to change in these tests are the event assertions. Instead of using Here's an implementation for reference: https://github.com/OpenZeppelin/cairo-contracts/blob/main/packages/security/src/tests/test_pausable.cairo#L125-L154 |
Resolves #1019
Added extension's component implementation, mock contract, tests and updated documentation
Purpose: Adding ERC721URIstorage extension to OZ.
PR Checklist
Transaction hash on Starknet sepolia:
0x044e53a70cb6cd021d2e2344a0b1921b833874cc25c676f997f8aa819302e664
Test contract deployed:
0x00f199172801f6685601944d749557d8efa121bb6f439026a9e37799c6b1fbae