From fdc28c16f4259de06337d2b4f55445006ac4e3db Mon Sep 17 00:00:00 2001 From: RM2 Date: Sat, 29 Jun 2024 12:39:37 -0300 Subject: [PATCH 1/2] changed token_uri() mutability to view --- .../components/token/erc721/erc721_metadata.cairo | 14 +++++++------- .../erc721/enumerable_mintable_burnable.cairo | 4 ++-- token/src/presets/erc721/mintable_burnable.cairo | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/token/src/components/token/erc721/erc721_metadata.cairo b/token/src/components/token/erc721/erc721_metadata.cairo index fd3dcd4d..f36ddb00 100644 --- a/token/src/components/token/erc721/erc721_metadata.cairo +++ b/token/src/components/token/erc721/erc721_metadata.cairo @@ -22,16 +22,16 @@ struct ERC721MetaModel { trait IERC721Metadata { fn name(self: @TState) -> ByteArray; fn symbol(self: @TState) -> ByteArray; - fn token_uri(ref self: TState, token_id: u256) -> ByteArray; + fn token_uri(self: @TState, token_id: u256) -> ByteArray; } #[starknet::interface] trait IERC721MetadataCamel { - fn tokenURI(ref self: TState, tokenId: u256) -> ByteArray; + fn tokenURI(self: @TState, tokenId: u256) -> ByteArray; } /// -/// ERC20Metadata Component +/// ERC721Metadata Component /// #[starknet::component] mod erc721_metadata_component { @@ -68,7 +68,7 @@ mod erc721_metadata_component { fn symbol(self: @ComponentState) -> ByteArray { self.get_meta().symbol } - fn token_uri(ref self: ComponentState, token_id: u256) -> ByteArray { + fn token_uri(self: @ComponentState, token_id: u256) -> ByteArray { self.get_uri(token_id) } } @@ -81,7 +81,7 @@ mod erc721_metadata_component { impl ERC721Owner: erc721_owner_comp::HasComponent, +Drop, > of IERC721MetadataCamel> { - fn tokenURI(ref self: ComponentState, tokenId: u256) -> ByteArray { + fn tokenURI(self: @ComponentState, tokenId: u256) -> ByteArray { self.get_uri(tokenId) } } @@ -99,8 +99,8 @@ mod erc721_metadata_component { get!(self.get_contract().world(), get_contract_address(), (ERC721MetaModel)) } - fn get_uri(ref self: ComponentState, token_id: u256) -> ByteArray { - let mut erc721_owner = get_dep_component_mut!(ref self, ERC721Owner); + fn get_uri(self: @ComponentState, token_id: u256) -> ByteArray { + let mut erc721_owner = get_dep_component!(self, ERC721Owner); assert(erc721_owner.exists(token_id), Errors::INVALID_TOKEN_ID); let base_uri = self.get_meta().base_uri; if base_uri.len() == 0 { diff --git a/token/src/presets/erc721/enumerable_mintable_burnable.cairo b/token/src/presets/erc721/enumerable_mintable_burnable.cairo index 1dd05213..8502475d 100644 --- a/token/src/presets/erc721/enumerable_mintable_burnable.cairo +++ b/token/src/presets/erc721/enumerable_mintable_burnable.cairo @@ -6,7 +6,7 @@ trait IERC721EnumMintBurnPreset { // IERC721 fn name(self: @TState) -> ByteArray; fn symbol(self: @TState) -> ByteArray; - fn token_uri(ref self: TState, token_id: u256) -> ByteArray; + fn token_uri(self: @TState, token_id: u256) -> ByteArray; fn owner_of(self: @TState, account: ContractAddress) -> bool; fn get_approved(self: @TState, token_id: u256) -> ContractAddress; fn approve(ref self: TState, to: ContractAddress, token_id: u256); @@ -15,7 +15,7 @@ trait IERC721EnumMintBurnPreset { fn token_of_owner_by_index(self: @TState, owner: ContractAddress, index: u256) -> u256; // IERC721CamelOnly - fn tokenURI(ref self: TState, token_id: u256) -> ByteArray; + fn tokenURI(self: @TState, token_id: u256) -> ByteArray; // IWorldProvider fn world(self: @TState,) -> IWorldDispatcher; diff --git a/token/src/presets/erc721/mintable_burnable.cairo b/token/src/presets/erc721/mintable_burnable.cairo index 6c7ada8b..11217dad 100644 --- a/token/src/presets/erc721/mintable_burnable.cairo +++ b/token/src/presets/erc721/mintable_burnable.cairo @@ -6,7 +6,7 @@ trait IERC721MintableBurnablePreset { // IERC721 fn name(self: @TState) -> ByteArray; fn symbol(self: @TState) -> ByteArray; - fn token_uri(ref self: TState, token_id: u256) -> ByteArray; + fn token_uri(self: @TState, token_id: u256) -> ByteArray; fn owner_of(self: @TState, account: ContractAddress) -> bool; fn balance_of(self: @TState, account: ContractAddress) -> u256; fn get_approved(self: @TState, token_id: u256) -> ContractAddress; @@ -14,7 +14,7 @@ trait IERC721MintableBurnablePreset { fn approve(ref self: TState, to: ContractAddress, token_id: u256); // IERC721CamelOnly - fn tokenURI(ref self: TState, token_id: u256) -> ByteArray; + fn tokenURI(self: @TState, token_id: u256) -> ByteArray; fn balanceOf(self: @TState, account: ContractAddress) -> u256; fn transferFrom(ref self: TState, from: ContractAddress, to: ContractAddress, token_id: u256); From 286d785d6c62a897ae0eabc625da63769d838867 Mon Sep 17 00:00:00 2001 From: RM2 Date: Sat, 29 Jun 2024 17:11:26 -0300 Subject: [PATCH 2/2] added metadata hooks --- token/src/components/tests.cairo | 3 + .../erc721/erc721_metadata_hooks_mock.cairo | 88 +++++++++++++++++++ .../mocks/erc721/erc721_metadata_mock.cairo | 1 + .../erc721_mintable_burnable_mock.cairo | 1 + .../erc721/test_erc721_metadata_hooks.cairo | 41 +++++++++ .../token/erc721/erc721_metadata.cairo | 19 +++- .../token/erc721/erc721_metadata_hooks.cairo | 20 +++++ token/src/lib.cairo | 1 + .../erc721/enumerable_mintable_burnable.cairo | 1 + .../presets/erc721/mintable_burnable.cairo | 1 + 10 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 token/src/components/tests/mocks/erc721/erc721_metadata_hooks_mock.cairo create mode 100644 token/src/components/tests/token/erc721/test_erc721_metadata_hooks.cairo create mode 100644 token/src/components/token/erc721/erc721_metadata_hooks.cairo diff --git a/token/src/components/tests.cairo b/token/src/components/tests.cairo index 2663c376..408cd1fd 100644 --- a/token/src/components/tests.cairo +++ b/token/src/components/tests.cairo @@ -15,6 +15,7 @@ mod mocks { mod erc721_balance_mock; mod erc721_enumerable_mock; mod erc721_metadata_mock; + mod erc721_metadata_hooks_mock; mod erc721_mintable_burnable_mock; mod erc721_receiver_mock; } @@ -55,6 +56,8 @@ mod token { #[cfg(test)] mod test_erc721_metadata; #[cfg(test)] + mod test_erc721_metadata_hooks; + #[cfg(test)] mod test_erc721_mintable_burnable; } } diff --git a/token/src/components/tests/mocks/erc721/erc721_metadata_hooks_mock.cairo b/token/src/components/tests/mocks/erc721/erc721_metadata_hooks_mock.cairo new file mode 100644 index 00000000..3870eaf6 --- /dev/null +++ b/token/src/components/tests/mocks/erc721/erc721_metadata_hooks_mock.cairo @@ -0,0 +1,88 @@ +use dojo::world::IWorldDispatcher; + +#[starknet::interface] +// trait IERC721EnumMintBurnPreset { +trait IERC721MetadataHooksMock { + // IWorldProvider + fn world(self: @TContractState,) -> IWorldDispatcher; +} + +#[dojo::contract] +mod erc721_metadata_hooks_mock { + + use starknet::{get_contract_address}; + use token::components::token::erc721::erc721_approval::erc721_approval_component; + use token::components::token::erc721::erc721_balance::erc721_balance_component; + use token::components::token::erc721::erc721_metadata::erc721_metadata_component; + use token::components::token::erc721::erc721_mintable::erc721_mintable_component; + use token::components::token::erc721::erc721_owner::erc721_owner_component; + + component!( + path: erc721_approval_component, storage: erc721_approval, event: ERC721ApprovalEvent + ); + component!(path: erc721_balance_component, storage: erc721_balance, event: ERC721BalanceEvent); + component!( + path: erc721_metadata_component, storage: erc721_metadata, event: ERC721MetadataEvent + ); + component!( + path: erc721_mintable_component, storage: erc721_mintable, event: ERC721MintableEvent + ); + component!(path: erc721_owner_component, storage: erc721_owner, event: ERC721OwnerEvent); + + #[abi(embed_v0)] + impl ERC721MetadataImpl = + erc721_metadata_component::ERC721MetadataImpl; + + impl ERC721ApprovalInternalImpl = erc721_approval_component::InternalImpl; + impl ERC721BalanceInternalImpl = erc721_balance_component::InternalImpl; + impl ERC721MetadataInternalImpl = erc721_metadata_component::InternalImpl; + impl ERC721MintableInternalImpl = erc721_mintable_component::InternalImpl; + impl ERC721OwnerInternalImpl = erc721_owner_component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc721_approval: erc721_approval_component::Storage, + #[substorage(v0)] + erc721_balance: erc721_balance_component::Storage, + #[substorage(v0)] + erc721_metadata: erc721_metadata_component::Storage, + #[substorage(v0)] + erc721_mintable: erc721_mintable_component::Storage, + #[substorage(v0)] + erc721_owner: erc721_owner_component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + ERC721ApprovalEvent: erc721_approval_component::Event, + ERC721BalanceEvent: erc721_balance_component::Event, + ERC721MetadataEvent: erc721_metadata_component::Event, + ERC721MintableEvent: erc721_mintable_component::Event, + ERC721OwnerEvent: erc721_owner_component::Event + } + + + // + // Metadata Hooks + // + use super::{IERC721MetadataHooksMockDispatcher, IERC721MetadataHooksMockDispatcherTrait}; + impl ERC721MetadataHooksImpl of erc721_metadata_component::ERC721MetadataHooksTrait { + fn custom_uri( + self: @erc721_metadata_component::ComponentState, + base_uri: @ByteArray, + token_id: u256, + ) -> ByteArray { + // + // example on how to access the world + // (does not work for testing, throws 'CONTRACT_NOT_DEPLOYED') + // + // let contract_address = get_contract_address(); + // let selfie = IERC721MetadataHooksMockDispatcher{ contract_address }; + // let _world = selfie.world(); + + format!("CUSTOM{}{}", base_uri, token_id) + } + } +} diff --git a/token/src/components/tests/mocks/erc721/erc721_metadata_mock.cairo b/token/src/components/tests/mocks/erc721/erc721_metadata_mock.cairo index 11f47d6e..2eec40ea 100644 --- a/token/src/components/tests/mocks/erc721/erc721_metadata_mock.cairo +++ b/token/src/components/tests/mocks/erc721/erc721_metadata_mock.cairo @@ -3,6 +3,7 @@ mod erc721_metadata_mock { use token::components::token::erc721::erc721_approval::erc721_approval_component; use token::components::token::erc721::erc721_balance::erc721_balance_component; use token::components::token::erc721::erc721_metadata::erc721_metadata_component; + use token::components::token::erc721::erc721_metadata_hooks::ERC721MetadataHooksEmptyImpl; use token::components::token::erc721::erc721_mintable::erc721_mintable_component; use token::components::token::erc721::erc721_owner::erc721_owner_component; diff --git a/token/src/components/tests/mocks/erc721/erc721_mintable_burnable_mock.cairo b/token/src/components/tests/mocks/erc721/erc721_mintable_burnable_mock.cairo index 199d9ca3..61a69732 100644 --- a/token/src/components/tests/mocks/erc721/erc721_mintable_burnable_mock.cairo +++ b/token/src/components/tests/mocks/erc721/erc721_mintable_burnable_mock.cairo @@ -3,6 +3,7 @@ mod erc721_mintable_burnable_mock { use token::components::token::erc721::erc721_approval::erc721_approval_component; use token::components::token::erc721::erc721_balance::erc721_balance_component; use token::components::token::erc721::erc721_metadata::erc721_metadata_component; + use token::components::token::erc721::erc721_metadata_hooks::ERC721MetadataHooksEmptyImpl; use token::components::token::erc721::erc721_mintable::erc721_mintable_component; use token::components::token::erc721::erc721_burnable::erc721_burnable_component; use token::components::token::erc721::erc721_owner::erc721_owner_component; diff --git a/token/src/components/tests/token/erc721/test_erc721_metadata_hooks.cairo b/token/src/components/tests/token/erc721/test_erc721_metadata_hooks.cairo new file mode 100644 index 00000000..e72b9340 --- /dev/null +++ b/token/src/components/tests/token/erc721/test_erc721_metadata_hooks.cairo @@ -0,0 +1,41 @@ +use integer::BoundedInt; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; +use dojo::test_utils::spawn_test_world; +use token::tests::constants::{OWNER}; + +use token::components::token::erc721::erc721_metadata::{erc_721_meta_model, ERC721MetaModel,}; +use token::components::token::erc721::erc721_metadata::erc721_metadata_component::{ + ERC721MetadataImpl, ERC721MetadataCamelImpl, InternalImpl +}; +use token::components::token::erc721::erc721_mintable::erc721_mintable_component::InternalImpl as ERC721MintableInternalImpl; + +use token::components::tests::mocks::erc721::erc721_metadata_hooks_mock::erc721_metadata_hooks_mock; +use starknet::storage::{StorageMemberAccessTrait}; + + +fn STATE() -> (IWorldDispatcher, erc721_metadata_hooks_mock::ContractState) { + let world = spawn_test_world(array![erc_721_meta_model::TEST_CLASS_HASH,]); + + let mut state = erc721_metadata_hooks_mock::contract_state_for_testing(); + state.world_dispatcher.write(world); + + (world, state) +} + +#[test] +fn test_erc721_metadata_hooks_initialize() { + let (_world, mut state) = STATE(); + + let NAME: ByteArray = "NAME"; + let SYMBOL: ByteArray = "SYMBOL"; + let URI: ByteArray = "URI"; + + state.erc721_metadata.initialize(NAME, SYMBOL, URI); + + assert(state.erc721_metadata.name() == "NAME", 'Should be NAME'); + assert(state.erc721_metadata.symbol() == "SYMBOL", 'Should be SYMBOL'); + + state.erc721_mintable.mint(OWNER(), 1); + assert(state.erc721_metadata.token_uri(1) == "CUSTOMURI1", 'Should be CUSTOMURI1'); + assert(state.erc721_metadata.tokenURI(1) == "CUSTOMURI1", 'Should be CUSTOMURI1'); +} diff --git a/token/src/components/token/erc721/erc721_metadata.cairo b/token/src/components/token/erc721/erc721_metadata.cairo index f36ddb00..d298b53c 100644 --- a/token/src/components/token/erc721/erc721_metadata.cairo +++ b/token/src/components/token/erc721/erc721_metadata.cairo @@ -54,12 +54,24 @@ mod erc721_metadata_component { const INVALID_TOKEN_ID: felt252 = 'ERC721: invalid token ID'; } + /// + /// Hooks + /// + trait ERC721MetadataHooksTrait { + fn custom_uri( + self: @ComponentState, + base_uri: @ByteArray, + token_id: u256, + ) -> ByteArray; + } + #[embeddable_as(ERC721MetadataImpl)] impl ERC721Metadata< TContractState, +HasComponent, +IWorldProvider, impl ERC721Owner: erc721_owner_comp::HasComponent, + +ERC721MetadataHooksTrait, +Drop, > of IERC721Metadata> { fn name(self: @ComponentState) -> ByteArray { @@ -79,6 +91,7 @@ mod erc721_metadata_component { +HasComponent, +IWorldProvider, impl ERC721Owner: erc721_owner_comp::HasComponent, + +ERC721MetadataHooksTrait, +Drop, > of IERC721MetadataCamel> { fn tokenURI(self: @ComponentState, tokenId: u256) -> ByteArray { @@ -93,6 +106,7 @@ mod erc721_metadata_component { +HasComponent, +IWorldProvider, impl ERC721Owner: erc721_owner_comp::HasComponent, + impl Hooks: ERC721MetadataHooksTrait, +Drop, > of InternalTrait { fn get_meta(self: @ComponentState) -> ERC721MetaModel { @@ -103,7 +117,10 @@ mod erc721_metadata_component { let mut erc721_owner = get_dep_component!(self, ERC721Owner); assert(erc721_owner.exists(token_id), Errors::INVALID_TOKEN_ID); let base_uri = self.get_meta().base_uri; - if base_uri.len() == 0 { + let custom_uri = Hooks::custom_uri(self, @base_uri, token_id); + if custom_uri.len() > 0 { + return custom_uri; + } else if base_uri.len() == 0 { return ""; } else { return format!("{}{}", base_uri, token_id); diff --git a/token/src/components/token/erc721/erc721_metadata_hooks.cairo b/token/src/components/token/erc721/erc721_metadata_hooks.cairo new file mode 100644 index 00000000..0fbe5074 --- /dev/null +++ b/token/src/components/token/erc721/erc721_metadata_hooks.cairo @@ -0,0 +1,20 @@ + +/// +/// An empty implementation of the ERC721MetadataHooksTrait +/// +/// When the hook is not required, import together with the component: +/// use token::components::token::erc721::erc721_metadata::erc721_metadata_component; +/// use token::components::token::erc721::erc721_metadata_hooks::ERC721MetadataHooksEmptyImpl; +/// +/// Or implement your own (example on erc721_metadata_hooks_mock.cairo) +/// + +use token::components::token::erc721::erc721_metadata::erc721_metadata_component; + +impl ERC721MetadataHooksEmptyImpl of erc721_metadata_component::ERC721MetadataHooksTrait { + fn custom_uri( + self: @erc721_metadata_component::ComponentState, + base_uri: @ByteArray, + token_id: u256, + ) -> ByteArray { "" } +} diff --git a/token/src/lib.cairo b/token/src/lib.cairo index 6d54fb8b..05ab5c32 100644 --- a/token/src/lib.cairo +++ b/token/src/lib.cairo @@ -23,6 +23,7 @@ mod components { mod erc721_burnable; mod erc721_enumerable; mod erc721_metadata; + mod erc721_metadata_hooks; mod erc721_mintable; mod erc721_owner; mod erc721_receiver; diff --git a/token/src/presets/erc721/enumerable_mintable_burnable.cairo b/token/src/presets/erc721/enumerable_mintable_burnable.cairo index 8502475d..f78f909e 100644 --- a/token/src/presets/erc721/enumerable_mintable_burnable.cairo +++ b/token/src/presets/erc721/enumerable_mintable_burnable.cairo @@ -82,6 +82,7 @@ mod ERC721EnumMintBurn { use token::components::token::erc721::erc721_burnable::erc721_burnable_component; use token::components::token::erc721::erc721_enumerable::erc721_enumerable_component; use token::components::token::erc721::erc721_metadata::erc721_metadata_component; + use token::components::token::erc721::erc721_metadata_hooks::ERC721MetadataHooksEmptyImpl; use token::components::token::erc721::erc721_mintable::erc721_mintable_component; use token::components::token::erc721::erc721_owner::erc721_owner_component; diff --git a/token/src/presets/erc721/mintable_burnable.cairo b/token/src/presets/erc721/mintable_burnable.cairo index 11217dad..615e1004 100644 --- a/token/src/presets/erc721/mintable_burnable.cairo +++ b/token/src/presets/erc721/mintable_burnable.cairo @@ -61,6 +61,7 @@ mod ERC721MintableBurnable { use token::components::token::erc721::erc721_balance::erc721_balance_component; use token::components::token::erc721::erc721_burnable::erc721_burnable_component; use token::components::token::erc721::erc721_metadata::erc721_metadata_component; + use token::components::token::erc721::erc721_metadata_hooks::ERC721MetadataHooksEmptyImpl; use token::components::token::erc721::erc721_mintable::erc721_mintable_component; use token::components::token::erc721::erc721_owner::erc721_owner_component;