From e5c5e5ff6e3d78f371136e618cbf245a78588ecb Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Mon, 15 Apr 2024 15:02:20 +0200 Subject: [PATCH 01/73] feat: working in logic --- src/token/erc721/erc721.cairo | 204 ++++++++++++++++++++++------------ 1 file changed, 135 insertions(+), 69 deletions(-) diff --git a/src/token/erc721/erc721.cairo b/src/token/erc721/erc721.cairo index 2e5d05bf1..9a0e4df66 100644 --- a/src/token/erc721/erc721.cairo +++ b/src/token/erc721/erc721.cairo @@ -130,10 +130,10 @@ mod ERC721Component { token_id: u256, data: Span ) { + ERC721::transfer_from(ref self, from, to, token_id); assert( - self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED + _check_on_erc721_received(from, to, token_id, data), Errors::SAFE_TRANSFER_FAILED ); - self._safe_transfer(from, to, token_id, data); } /// Transfers ownership of `token_id` from `from` to `to`. @@ -152,10 +152,13 @@ mod ERC721Component { to: ContractAddress, token_id: u256 ) { - assert( - self._is_approved_or_owner(get_caller_address(), token_id), Errors::UNAUTHORIZED - ); - self._transfer(from, to, token_id); + assert(!to.is_zero(), Errors::INVALID_RECEIVER); + + // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + let previous_owner = self._update(to, token_id, get_caller_address()); + + assert(from == previous_owner, Errors::WRONG_SENDER); } /// Change or reaffirm the approved address for an NFT. @@ -163,19 +166,11 @@ mod ERC721Component { /// Requirements: /// /// - The caller is either an approved operator or the `token_id` owner. - /// - `to` cannot be the token owner. /// - `token_id` exists. /// /// Emits an `Approval` event. fn approve(ref self: ComponentState, to: ContractAddress, token_id: u256) { - let owner = self._owner_of(token_id); - - let caller = get_caller_address(); - assert( - owner == caller || ERC721::is_approved_for_all(@self, owner, caller), - Errors::UNAUTHORIZED - ); - self._approve(to, token_id); + self._approve(to, token_id, get_caller_address()); } /// Enable or disable approval for `operator` to manage all of the @@ -198,7 +193,7 @@ mod ERC721Component { /// /// - `token_id` exists. fn get_approved(self: @ComponentState, token_id: u256) -> ContractAddress { - assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + self._require_owned(token_id); self.ERC721_token_approvals.read(token_id) } @@ -234,7 +229,7 @@ mod ERC721Component { /// /// - `token_id` exists. fn token_uri(self: @ComponentState, token_id: u256) -> ByteArray { - assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); + self._require_owned(token_id); let base_uri = self._base_uri(); if base_uri.len() == 0 { return ""; @@ -343,49 +338,57 @@ mod ERC721Component { /// /// - `token_id` exists. fn _owner_of(self: @ComponentState, token_id: u256) -> ContractAddress { - let owner = self.ERC721_owners.read(token_id); - match owner.is_zero() { - bool::False(()) => owner, - bool::True(()) => panic_with_felt252(Errors::INVALID_TOKEN_ID) - } + self._require_owned(token_id) } - /// Returns whether `token_id` exists. - fn _exists(self: @ComponentState, token_id: u256) -> bool { - !self.ERC721_owners.read(token_id).is_zero() + fn _require_owned( + self: @ComponentState, token_id: u256 + ) -> ContractAddress { + let owner = self._owner_of(token_id); + assert(!owner.is_zero(), Errors::INVALID_TOKEN_ID); + owner } - /// Returns whether `spender` is allowed to manage `token_id`. + /// Approve `to` to operate on `token_id` /// - /// Requirements: + /// The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is + /// either the owner of the token, or approved to operate on all tokens held by this owner. /// - /// - `token_id` exists. - fn _is_approved_or_owner( - self: @ComponentState, spender: ContractAddress, token_id: u256 - ) -> bool { - let owner = self._owner_of(token_id); - let is_approved_for_all = ERC721::is_approved_for_all(self, owner, spender); - owner == spender - || is_approved_for_all - || spender == ERC721::get_approved(self, token_id) + /// May emit an `Approval` event. + fn _approve(ref self: ComponentState, to: ContractAddress, token_id: u256, auth: ContractAddress) { + self._approve_with_optional_event(to, token_id, auth, true); } - /// Changes or reaffirms the approved address for an NFT. - /// - /// Internal function without access restriction. + /// Variant of `_approve` with an optional flag to enable or disable the `Approval` event. The event is not + /// emitted in the context of transfers. /// /// Requirements: /// - /// - `token_id` exists. - /// - `to` is not the current token owner. + /// - if `auth` is non-zero, it must be either the owner of the token or approved to + /// operate on all of its tokens. /// - /// Emits an `Approval` event. - fn _approve(ref self: ComponentState, to: ContractAddress, token_id: u256) { - let owner = self._owner_of(token_id); - assert(owner != to, Errors::APPROVAL_TO_OWNER); + /// May emit an `Approval` event. + fn _approve_with_optional_event( + ref self: ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress, + emit_event: bool + ) { + if emit_event || !auth.is_zero() { + let owner = self._require_owned(token_id); + + if !auth.is_zero() && owner != auth { + let is_approved_for_all = ERC721::is_approved_for_all(@self, owner, auth); + assert(is_approved_for_all, Errors::UNAUTHORIZED); + } + + if emit_event { + self.emit(Approval { owner, approved: to, token_id }); + } + } self.ERC721_token_approvals.write(token_id, to); - self.emit(Approval { owner, approved: to, token_id }); } /// Enables or disables approval for `operator` to manage @@ -410,20 +413,20 @@ mod ERC721Component { /// Mints `token_id` and transfers it to `to`. /// Internal function without access restriction. /// + /// WARNING: Usage of this method is discouraged, use `_safe_mint` whenever possible + /// /// Requirements: /// /// - `to` is not the zero address. - /// - `token_id` does not exist. + /// - `token_id` must not exist. /// /// Emits a `Transfer` event. fn _mint(ref self: ComponentState, to: ContractAddress, token_id: u256) { assert(!to.is_zero(), Errors::INVALID_RECEIVER); - assert(!self._exists(token_id), Errors::ALREADY_MINTED); - self.ERC721_balances.write(to, self.ERC721_balances.read(to) + 1); - self.ERC721_owners.write(token_id, to); + let previous_owner = self._update(to, token_id, Zeroable::zero()); - self.emit(Transfer { from: Zeroable::zero(), to, token_id }); + assert(previous_owner.is_zero(), Errors::ALREADY_MINTED); } /// Transfers `token_id` from `from` to `to`. @@ -444,17 +447,12 @@ mod ERC721Component { token_id: u256 ) { assert(!to.is_zero(), Errors::INVALID_RECEIVER); - let owner = self._owner_of(token_id); - assert(from == owner, Errors::WRONG_SENDER); - // Implicit clear approvals, no need to emit an event - self.ERC721_token_approvals.write(token_id, Zeroable::zero()); + let previous_owner = self._update(to, token_id, Zeroable::zero()); - self.ERC721_balances.write(from, self.ERC721_balances.read(from) - 1); - self.ERC721_balances.write(to, self.ERC721_balances.read(to) + 1); - self.ERC721_owners.write(token_id, to); + assert(!previous_owner.is_zero(), Errors::INVALID_TOKEN_ID); + assert(from == previous_owner, Errors::WRONG_SENDER); - self.emit(Transfer { from, to, token_id }); } /// Destroys `token_id`. The approval is cleared when the token is burned. @@ -468,15 +466,8 @@ mod ERC721Component { /// /// Emits a `Transfer` event. fn _burn(ref self: ComponentState, token_id: u256) { - let owner = self._owner_of(token_id); - - // Implicit clear approvals, no need to emit an event - self.ERC721_token_approvals.write(token_id, Zeroable::zero()); - - self.ERC721_balances.write(owner, self.ERC721_balances.read(owner) - 1); - self.ERC721_owners.write(token_id, Zeroable::zero()); - - self.emit(Transfer { from: owner, to: Zeroable::zero(), token_id }); + let previous_owner = self._update(Zeroable::zero(), token_id, Zeroable::zero()); + assert(!previous_owner.is_zero(), Errors::INVALID_TOKEN_ID); } /// Mints `token_id` if `to` is either an account or `IERC721Receiver`. @@ -539,6 +530,82 @@ mod ERC721Component { fn _base_uri(self: @ComponentState) -> ByteArray { self.ERC721_base_uri.read() } + + /// Returns whether `spender` is allowed to manage `owner`'s tokens, or `token_id` in + /// particular (ignoring whether it is owned by `owner`). + /// + /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this + /// assumption. + fn _is_authorized( + self: @ComponentState, + owner: ContractAddress, + spender: ContractAddress, + token_id: u256 + ) -> bool { + let is_approved_for_all = ERC721::is_approved_for_all(self, owner, spender); + + !spender.is_zero() + && (owner == spender + || is_approved_for_all + || spender == ERC721::get_approved(self, token_id)) + } + + /// Checks if `spender` can operate on `token_id`, assuming the provided `owner` is the actual owner. + /// + /// Requirements: + /// + /// - `owner` cannot be the zero address. + /// - `spender` cannot be the zero address. + /// - `spender` must be the owner of `token_id` or be approved to operate on it. + /// + /// WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this + /// assumption. + fn _check_authorized( + self: @ComponentState, + owner: ContractAddress, + spender: ContractAddress, + token_id: u256 + ) { + // Non-existent token + assert(!owner.is_zero(), Errors::INVALID_TOKEN_ID); + assert(self._is_authorized(owner, spender, token_id), Errors::UNAUTHORIZED); + } + + /// Transfers `token_id` from its current owner to `to`, or alternatively mints (or burns) if the current owner + /// (or `to`) is the zero address. Returns the owner of the `token_id` before the update. + /// + /// The `auth` argument is optional. If the value passed is non 0, then this function will check that + /// `auth` is either the owner of the token, or approved to operate on the token (by the owner). + /// + /// Emits a `Transfer` event. + fn _update( + ref self: ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) -> ContractAddress { + let from = self._owner_of(token_id); + + // Perform (optional) operator check + if !auth.is_zero() { + self._check_authorized(from, auth, token_id); + } + if !from.is_zero() { + let zero_address = Zeroable::zero(); + self._approve_with_optional_event(zero_address, token_id, zero_address, false); + + self.ERC721_balances.write(from, self.ERC721_balances.read(from) - 1); + } + if !to.is_zero() { + self.ERC721_balances.write(to, self.ERC721_balances.read(to) + 1); + } + + self.ERC721_owners.write(token_id, to); + + self.emit(Transfer { from, to, token_id }); + + from + } } /// Checks if `to` either is an account contract or has registered support @@ -584,7 +651,6 @@ mod ERC721Component { ERC721::safe_transfer_from(ref self, from, to, token_id, data); } - fn transfer_from( ref self: ComponentState, from: ContractAddress, From 110d8f7d3896390d304ced344a52ce3e8fcf0129 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Tue, 23 Apr 2024 13:17:34 +0200 Subject: [PATCH 02/73] feat: add empty impl --- src/presets/erc721.cairo | 3 +- src/token/erc721.cairo | 3 ++ src/token/erc721/erc721.cairo | 66 ++++++++++++++++++++++++++++++++--- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/presets/erc721.cairo b/src/presets/erc721.cairo index 64aec5dd5..fb33b30dc 100644 --- a/src/presets/erc721.cairo +++ b/src/presets/erc721.cairo @@ -8,7 +8,7 @@ #[starknet::contract] mod ERC721 { use openzeppelin::introspection::src5::SRC5Component; - use openzeppelin::token::erc721::ERC721Component; + use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; use starknet::ContractAddress; component!(path: ERC721Component, storage: erc721, event: ERC721Event); @@ -63,7 +63,6 @@ mod ERC721 { break; } let id = *token_ids.pop_front().unwrap(); - self.erc721._mint(recipient, id); } } diff --git a/src/token/erc721.cairo b/src/token/erc721.cairo index 823b4c561..56f9eb8a2 100644 --- a/src/token/erc721.cairo +++ b/src/token/erc721.cairo @@ -5,4 +5,7 @@ mod erc721_receiver; mod interface; use erc721::ERC721Component; +use erc721::ERC721HooksEmptyImpl; use erc721_receiver::ERC721ReceiverComponent; +use interface::ERC721ABIDispatcher; +use interface::ERC721ABIDispatcherTrait; diff --git a/src/token/erc721/erc721.cairo b/src/token/erc721/erc721.cairo index 9a0e4df66..071d74aa1 100644 --- a/src/token/erc721/erc721.cairo +++ b/src/token/erc721/erc721.cairo @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT // OpenZeppelin Contracts for Cairo v0.11.0 (token/erc721/erc721.cairo) +use starknet::ContractAddress; + /// # ERC721 Component /// /// The ERC721 component provides implementations for both the IERC721 interface @@ -75,7 +77,6 @@ mod ERC721Component { const INVALID_TOKEN_ID: felt252 = 'ERC721: invalid token ID'; const INVALID_ACCOUNT: felt252 = 'ERC721: invalid account'; const UNAUTHORIZED: felt252 = 'ERC721: unauthorized caller'; - const APPROVAL_TO_OWNER: felt252 = 'ERC721: approval to owner'; const SELF_APPROVAL: felt252 = 'ERC721: self approval'; const INVALID_RECEIVER: felt252 = 'ERC721: invalid receiver'; const ALREADY_MINTED: felt252 = 'ERC721: token already minted'; @@ -84,6 +85,26 @@ mod ERC721Component { const SAFE_TRANSFER_FAILED: felt252 = 'ERC721: safe transfer failed'; } + // + // Hooks + // + + trait ERC721HooksTrait { + fn before_update( + ref self: ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ); + + fn after_update( + ref self: ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ); + } + // // External // @@ -93,6 +114,7 @@ mod ERC721Component { TContractState, +HasComponent, +SRC5Component::HasComponent, + +ERC721HooksTrait, +Drop > of interface::IERC721> { /// Returns the number of NFTs owned by `account`. @@ -154,7 +176,7 @@ mod ERC721Component { ) { assert(!to.is_zero(), Errors::INVALID_RECEIVER); - // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // Setting an "auth" arguments enables the `_is_authorized` check which verifies that the token exists // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. let previous_owner = self._update(to, token_id, get_caller_address()); @@ -210,6 +232,7 @@ mod ERC721Component { TContractState, +HasComponent, +SRC5Component::HasComponent, + +ERC721HooksTrait, +Drop > of interface::IERC721Metadata> { /// Returns the NFT name. @@ -245,6 +268,7 @@ mod ERC721Component { TContractState, +HasComponent, +SRC5Component::HasComponent, + +ERC721HooksTrait, +Drop > of interface::IERC721CamelOnly> { fn balanceOf(self: @ComponentState, account: ContractAddress) -> u256 { @@ -297,6 +321,7 @@ mod ERC721Component { TContractState, +HasComponent, +SRC5Component::HasComponent, + +ERC721HooksTrait, +Drop > of interface::IERC721MetadataCamelOnly> { fn tokenURI(self: @ComponentState, tokenId: u256) -> ByteArray { @@ -313,6 +338,7 @@ mod ERC721Component { TContractState, +HasComponent, impl SRC5: SRC5Component::HasComponent, + impl Hooks: ERC721HooksTrait, +Drop > of InternalTrait { /// Initializes the contract by setting the token name, symbol, and base URI. @@ -341,6 +367,11 @@ mod ERC721Component { self._require_owned(token_id) } + /// Returns the owner address of `token_id`. + /// + /// Requirements: + /// + /// - `token_id` exists. fn _require_owned( self: @ComponentState, token_id: u256 ) -> ContractAddress { @@ -355,7 +386,12 @@ mod ERC721Component { /// either the owner of the token, or approved to operate on all tokens held by this owner. /// /// May emit an `Approval` event. - fn _approve(ref self: ComponentState, to: ContractAddress, token_id: u256, auth: ContractAddress) { + fn _approve( + ref self: ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { self._approve_with_optional_event(to, token_id, auth, true); } @@ -452,7 +488,6 @@ mod ERC721Component { assert(!previous_owner.is_zero(), Errors::INVALID_TOKEN_ID); assert(from == previous_owner, Errors::WRONG_SENDER); - } /// Destroys `token_id`. The approval is cleared when the token is burned. @@ -584,6 +619,8 @@ mod ERC721Component { token_id: u256, auth: ContractAddress ) -> ContractAddress { + Hooks::before_update(ref self, to, token_id, auth); + let from = self._owner_of(token_id); // Perform (optional) operator check @@ -601,9 +638,10 @@ mod ERC721Component { } self.ERC721_owners.write(token_id, to); - self.emit(Transfer { from, to, token_id }); + Hooks::after_update(ref self, to, token_id, auth); + from } } @@ -630,6 +668,7 @@ mod ERC721Component { TContractState, +HasComponent, impl SRC5: SRC5Component::HasComponent, + +ERC721HooksTrait, +Drop > of interface::ERC721ABI> { // IERC721 @@ -751,3 +790,20 @@ mod ERC721Component { } } } + +/// An empty implementation of the ERC721 hooks to be used in basic ERC721 preset contracts. +impl ERC721HooksEmptyImpl of ERC721Component::ERC721HooksTrait { + fn before_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) {} + + fn after_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) {} +} From d71ee92b62fc3ffb21179271ddef84df21dde4d6 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Tue, 23 Apr 2024 14:35:23 +0200 Subject: [PATCH 03/73] feat: finish docs --- docs/modules/ROOT/pages/api/erc721.adoc | 185 ++++++++++++++++++------ src/tests/mocks/erc721_mocks.cairo | 6 +- src/tests/presets/test_erc721.cairo | 8 - src/tests/token/test_erc721.cairo | 32 ++-- src/token/erc721/erc721.cairo | 15 +- 5 files changed, 158 insertions(+), 88 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index afe5e4b63..323a4b909 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -192,6 +192,17 @@ ERC721 component implementing <> and < u256++` [.item-kind]#external# -See <>. +See <>. [.contract-item] [[ERC721Component-ownerOf]] ==== `[.contract-item-name]#++ownerOf++#++(self: @ContractState, tokenId: u256) -> ContractAddress++` [.item-kind]#external# -See <>. +See <>. [.contract-item] [[ERC721Component-safeTransferFrom]] ==== `[.contract-item-name]#++safeTransferFrom++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256, data: Span)++` [.item-kind]#external# -See <>. +See <>. [.contract-item] [[ERC721Component-transferFrom]] ==== `[.contract-item-name]#++transferFrom++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, tokenId: u256)++` [.item-kind]#external# -See <>. +See <>. [.contract-item] [[ERC721Component-setApprovalForAll]] ==== `[.contract-item-name]#++setApprovalForAll++#++(ref self: ContractState, operator: ContractAddress, approved: bool)++` [.item-kind]#external# -See <>. +See <>. [.contract-item] [[ERC721Component-getApproved]] ==== `[.contract-item-name]#++getApproved++#++(self: @ContractState, tokenId: u256) -> ContractAddress++` [.item-kind]#external# -See <>. +See <>. [.contract-item] [[ERC721Component-isApprovedForAll]] ==== `[.contract-item-name]#++isApprovedForAll++#++(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool++` [.item-kind]#external# -See <>. +See <>. [.contract-item] [[ERC721Component-tokenURI]] ==== `[.contract-item-name]#++tokenURI++#++(self: @ContractState, tokenId: u256) -> ByteArray++` [.item-kind]#external# -See <>. +See <>. ==== Internal functions @@ -437,7 +474,12 @@ This should be used inside the contract's constructor. ==== `[.contract-item-name]#++_owner_of++#++(self: @ContractState, token_id: felt252) -> ContractAddress++` [.item-kind]#internal# Internal function that returns the owner address of `token_id`. -This function will panic if the token does not exist. + +[.contract-item] +[[ERC721Component-_require_owned]] +==== `[.contract-item-name]#++_require_owned++#++(self: @ContractState, token_id: felt252) -> ContractAddress++` [.item-kind]#internal# + +Version of xref:#ERC721Component-_owner_of[_owner_of] that panics if owner is the zero address. [.contract-item] [[ERC721Component-_exists]] @@ -448,61 +490,66 @@ Internal function that returns whether `token_id` exists. Tokens start existing when they are minted (<>), and stop existing when they are burned (<>). [.contract-item] -[[ERC721Component-_is_approved_or_owner]] -==== `[.contract-item-name]#++_is_approved_or_owner++#++(ref self: ContractState, spender: ContractAddress, token_id: u256) -> bool++` [.item-kind]#internal# +[[ERC721Component-_approve]] +==== `[.contract-item-name]#++_approve++#++(ref self: ContractState, to: ContractAddress, token_id: u256, auth: ContractAddress)++` [.item-kind]#internal# -Internal function that returns whether `spender` is allowed to manage `token_id`. +Approve `to` to operate on `token_id` -Requirements: +The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is +either the owner of the token, or approved to operate on all tokens held by this owner. -- `token_id` exists. +Emits an <> event. [.contract-item] -[[ERC721Component-_approve]] -==== `[.contract-item-name]#++_approve++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# +[[ERC721Component-_approve_with_optional_event]] +==== `[.contract-item-name]#++_approve_with_optional_event++#++(ref self: ContractState, to: ContractAddress, token_id: u256, auth: ContractAddress, emit_event: bool)++` [.item-kind]#internal# -Internal function that changes or reaffirms the approved address for an NFT. - -Emits an <> event. +Variant of xref:#ERC721Component-_approve[_approve] with an optional flag to enable or disable the `Approval` event. +The event is not emitted in the context of transfers. Requirements: -- `token_id` exists. -- `to` is not the current token owner. +- if `auth` is non-zero, it must be either the owner of the token or approved to +operate on all of its tokens. + +May emit an <> event. [.contract-item] [[ERC721Component-_set_approval_for_all]] ==== `[.contract-item-name]#++_set_approval_for_all++#++(ref self: ContractState, owner: ContractAddress, operator: ContractAddress, approved: bool)++` [.item-kind]#internal# -Internal function that enables or disables approval for `operator` to manage all of the -`owner` assets. - -Emits an <> event. +Enables or disables approval for `operator` to manage +all of the `owner` assets. Requirements: - `operator` cannot be the caller. +Emits an <> event. + [.contract-item] [[ERC721Component-_mint]] ==== `[.contract-item-name]#++_mint++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# -Internal function that mints `token_id` and transfers it to `to`. +Mints `token_id` and transfers it to `to`. +Internal function without access restriction. -Emits an <> event. +WARNING: Usage of this method is discouraged, use `_safe_mint` whenever possible Requirements: - `to` is not the zero address. -- `token_id` does not already exist. +- `token_id` must not exist. + +Emits a <> event. [.contract-item] [[ERC721Component-_transfer]] ==== `[.contract-item-name]#++_transfer++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# -Internal function that transfers `token_id` from `from` to `to`. +Transfers `token_id` from `from` to `to`. -Emits an <> event. +Internal function without access restriction. Requirements: @@ -510,52 +557,54 @@ Requirements: - `from` is the token owner. - `token_id` exists. +Emits a <> event. + [.contract-item] [[ERC721Component-_burn]] ==== `[.contract-item-name]#++_burn++#++(ref self: ContractState, token_id: u256)++` [.item-kind]#internal# -Internal function that destroys `token_id`. -The approval is cleared when the token is burned. -This internal function does not check if the sender is authorized to operate on the token. +Destroys `token_id`. The approval is cleared when the token is burned. -Emits an <> event. +This internal function does not check if the caller is authorized +to operate on the token. Requirements: -`token_id` exists. +- `token_id` exists. + +Emits a <> event. [.contract-item] [[ERC721Component-_safe_mint]] ==== `[.contract-item-name]#++_safe_mint++#++(ref self: ContractState, to: ContractAddress, token_id: u256, data: Span)++` [.item-kind]#internal# -Internal function that mints `token_id` and transfers it to `to`. -If `to` is not an account contract, `to` must support <>; otherwise, the transaction will fail. +Mints `token_id` if `to` is either an account or `IERC721Receiver`. -Emits an <> event. +`data` is additional data, it has no specified format and it is sent in call to `to`. Requirements: -- `token_id` does not already exist. -- `to` is either an account contract or supports the <> interface. +- `token_id` does not exist. +- `to` is either an account contract or supports the `IERC721Receiver` interface. + +Emits a <> event. [.contract-item] [[ERC721Component-_safe_transfer]] ==== `[.contract-item-name]#++_safe_transfer++#++(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256, data: Span)++` [.item-kind]#internal# -Internal function that transfers `token_id` token from `from` to `to`, checking first that contract recipients are aware of the ERC721 protocol to prevent tokens from being forever locked. +Transfers ownership of `token_id` from `from` if `to` is either an account or `IERC721Receiver`. `data` is additional data, it has no specified format and it is sent in call to `to`. -This internal function does not include permissions but can be useful for instances like implementing alternative mechanisms to perform signature-based token transfers. - -Emits an <> event. - Requirements: - `to` cannot be the zero address. - `from` must be the token owner. - `token_id` exists. -- `to` either is an account contract or supports the <> interface. +- `to` is either an account contract or supports the `IERC721Receiver` interface. + +Emits a <> event. [.contract-item] [[ERC721Component-_set_base_uri]] @@ -569,6 +618,46 @@ Internal function that sets the `base_uri`. Base URI for computing <>. +If set, the resulting URI for each token will be the concatenation of the base URI and the token ID. +Returns an empty `ByteArray` if not set. + +[.contract-item] +[[ERC721Component-_is_authorized]] +==== `[.contract-item-name]#++_is_authorized++#++(self: @ContractState, owner: ContractAddress, spender: ContractAddress, token_id: u256) -> bool++` [.item-kind]#internal# + +Returns whether `spender` is allowed to manage ``owner``'s tokens, or `token_id` in +particular (ignoring whether it is owned by `owner`). + +WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this +assumption. + +[.contract-item] +[[ERC721Component-_check_authorized]] +==== `[.contract-item-name]#++_check_authorized++#++(self: @ContractState, owner: ContractAddress, spender: ContractAddress, token_id: u256) -> bool++` [.item-kind]#internal# + +Checks if `spender` can operate on `token_id`, assuming the provided `owner` is the actual owner. + +Requirements: + +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. +- `spender` must be the owner of `token_id` or be approved to operate on it. + +WARNING: This function assumes that `owner` is the actual owner of `token_id` and does not verify this +assumption. + +[.contract-item] +[[ERC721Component-_update]] +==== `[.contract-item-name]#++_update++#++(ref self: ContractState, to: ContractAddress, token_id: u256, auth: ContractAddress)++` [.item-kind]#internal# + +Transfers `token_id` from its current owner to `to`, or alternatively mints (or burns) if the current owner +(or `to`) is the zero address. Returns the owner of the `token_id` before the update. + +The `auth` argument is optional. If the value passed is non 0, then this function will check that +`auth` is either the owner of the token, or approved to operate on the token (by the owner). + +Emits a <> event. + ==== Events [.contract-item] diff --git a/src/tests/mocks/erc721_mocks.cairo b/src/tests/mocks/erc721_mocks.cairo index 6a183bdcc..a1862a768 100644 --- a/src/tests/mocks/erc721_mocks.cairo +++ b/src/tests/mocks/erc721_mocks.cairo @@ -1,7 +1,7 @@ #[starknet::contract] mod DualCaseERC721Mock { use openzeppelin::introspection::src5::SRC5Component; - use openzeppelin::token::erc721::ERC721Component; + use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; use starknet::ContractAddress; component!(path: ERC721Component, storage: erc721, event: ERC721Event); @@ -57,7 +57,7 @@ mod DualCaseERC721Mock { #[starknet::contract] mod SnakeERC721Mock { use openzeppelin::introspection::src5::SRC5Component; - use openzeppelin::token::erc721::ERC721Component; + use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; use starknet::ContractAddress; component!(path: ERC721Component, storage: erc721, event: ERC721Event); @@ -109,7 +109,7 @@ mod SnakeERC721Mock { mod CamelERC721Mock { use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::token::erc721::ERC721Component::{ERC721Impl, ERC721MetadataImpl}; - use openzeppelin::token::erc721::ERC721Component; + use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; use starknet::ContractAddress; component!(path: ERC721Component, storage: erc721, event: ERC721Event); diff --git a/src/tests/presets/test_erc721.cairo b/src/tests/presets/test_erc721.cairo index 2edef2fcf..4bc5788c4 100644 --- a/src/tests/presets/test_erc721.cairo +++ b/src/tests/presets/test_erc721.cairo @@ -269,14 +269,6 @@ fn test_approve_from_unauthorized() { dispatcher.approve(SPENDER(), TOKEN_1); } -#[test] -#[should_panic(expected: ('ERC721: approval to owner', 'ENTRYPOINT_FAILED'))] -fn test_approve_to_owner() { - let dispatcher = setup_dispatcher(); - - dispatcher.approve(OWNER(), TOKEN_1); -} - #[test] #[should_panic(expected: ('ERC721: invalid token ID', 'ENTRYPOINT_FAILED'))] fn test_approve_nonexistent() { diff --git a/src/tests/token/test_erc721.cairo b/src/tests/token/test_erc721.cairo index 4b3590f53..e42111440 100644 --- a/src/tests/token/test_erc721.cairo +++ b/src/tests/token/test_erc721.cairo @@ -156,7 +156,7 @@ fn test_get_approved() { let token_id = TOKEN_ID; assert_eq!(state.get_approved(token_id), ZERO()); - state._approve(spender, token_id); + state._approve(spender, token_id, ZERO()); assert_eq!(state.get_approved(token_id), spender); } @@ -236,15 +236,6 @@ fn test_approve_from_unauthorized() { state.approve(SPENDER(), TOKEN_ID); } -#[test] -#[should_panic(expected: ('ERC721: approval to owner',))] -fn test_approve_to_owner() { - let mut state = setup(); - - testing::set_caller_address(OWNER()); - state.approve(OWNER(), TOKEN_ID); -} - #[test] #[should_panic(expected: ('ERC721: invalid token ID',))] fn test_approve_nonexistent() { @@ -255,25 +246,18 @@ fn test_approve_nonexistent() { #[test] fn test__approve() { let mut state = setup(); - state._approve(SPENDER(), TOKEN_ID); + state._approve(SPENDER(), TOKEN_ID, ZERO()); assert_only_event_approval(ZERO(), OWNER(), SPENDER(), TOKEN_ID); let approved = state.get_approved(TOKEN_ID); assert_eq!(approved, SPENDER()); } -#[test] -#[should_panic(expected: ('ERC721: approval to owner',))] -fn test__approve_to_owner() { - let mut state = setup(); - state._approve(OWNER(), TOKEN_ID); -} - #[test] #[should_panic(expected: ('ERC721: invalid token ID',))] fn test__approve_nonexistent() { let mut state = COMPONENT_STATE(); - state._approve(SPENDER(), TOKEN_ID); + state._approve(SPENDER(), TOKEN_ID, ZERO()); } // @@ -362,7 +346,7 @@ fn test_transfer_from_owner() { let owner = OWNER(); let recipient = RECIPIENT(); // set approval to check reset - state._approve(OTHER(), token_id); + state._approve(OTHER(), token_id, ZERO()); utils::drop_event(ZERO()); assert_state_before_transfer(owner, recipient, token_id); @@ -384,7 +368,7 @@ fn test_transferFrom_owner() { let owner = OWNER(); let recipient = RECIPIENT(); // set approval to check reset - state._approve(OTHER(), token_id); + state._approve(OTHER(), token_id, ZERO()); utils::drop_event(ZERO()); assert_state_before_transfer(owner, recipient, token_id); @@ -403,6 +387,7 @@ fn test_transferFrom_owner() { #[should_panic(expected: ('ERC721: invalid token ID',))] fn test_transfer_from_nonexistent() { let mut state = COMPONENT_STATE(); + testing::set_caller_address(OWNER()); state.transfer_from(ZERO(), RECIPIENT(), TOKEN_ID); } @@ -410,6 +395,7 @@ fn test_transfer_from_nonexistent() { #[should_panic(expected: ('ERC721: invalid token ID',))] fn test_transferFrom_nonexistent() { let mut state = COMPONENT_STATE(); + testing::set_caller_address(OWNER()); state.transferFrom(ZERO(), RECIPIENT(), TOKEN_ID); } @@ -762,6 +748,7 @@ fn test_safeTransferFrom_to_non_receiver() { #[should_panic(expected: ('ERC721: invalid token ID',))] fn test_safe_transfer_from_nonexistent() { let mut state = COMPONENT_STATE(); + testing::set_caller_address(OWNER()); state.safe_transfer_from(ZERO(), RECIPIENT(), TOKEN_ID, DATA(true)); } @@ -769,6 +756,7 @@ fn test_safe_transfer_from_nonexistent() { #[should_panic(expected: ('ERC721: invalid token ID',))] fn test_safeTransferFrom_nonexistent() { let mut state = COMPONENT_STATE(); + testing::set_caller_address(OWNER()); state.safeTransferFrom(ZERO(), RECIPIENT(), TOKEN_ID, DATA(true)); } @@ -1229,7 +1217,7 @@ fn test__safe_mint_already_exist() { fn test__burn() { let mut state = setup(); - state._approve(OTHER(), TOKEN_ID); + state._approve(OTHER(), TOKEN_ID, ZERO()); utils::drop_event(ZERO()); assert_eq!(state.owner_of(TOKEN_ID), OWNER()); diff --git a/src/token/erc721/erc721.cairo b/src/token/erc721/erc721.cairo index a376dba3c..4c642501e 100644 --- a/src/token/erc721/erc721.cairo +++ b/src/token/erc721/erc721.cairo @@ -129,7 +129,7 @@ mod ERC721Component { /// /// - `token_id` exists. fn owner_of(self: @ComponentState, token_id: u256) -> ContractAddress { - self._owner_of(token_id) + self._require_owned(token_id) } /// Transfers ownership of `token_id` from `from` if `to` is either an account or `IERC721Receiver`. @@ -359,12 +359,8 @@ mod ERC721Component { } /// Returns the owner address of `token_id`. - /// - /// Requirements: - /// - /// - `token_id` exists. fn _owner_of(self: @ComponentState, token_id: u256) -> ContractAddress { - self._require_owned(token_id) + self.ERC721_owners.read(token_id) } /// Returns the owner address of `token_id`. @@ -380,12 +376,17 @@ mod ERC721Component { owner } + /// Returns whether `token_id` exists. + fn _exists(self: @ComponentState, token_id: u256) -> bool { + !self._owner_of(token_id).is_zero() + } + /// Approve `to` to operate on `token_id` /// /// The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is /// either the owner of the token, or approved to operate on all tokens held by this owner. /// - /// May emit an `Approval` event. + /// Emits an `Approval` event. fn _approve( ref self: ComponentState, to: ContractAddress, From d61e4498bbe757a605f64ccf0a112cd5e68dce47 Mon Sep 17 00:00:00 2001 From: Eric Nordelo Date: Tue, 23 Apr 2024 14:37:43 +0200 Subject: [PATCH 04/73] feat: update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85c6e7cb6..2c31036a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- before_update and after_update hooks to ERC721Component (#978) + ## 0.12.0 (2024-04-21) ### Added From 8e894ea37fc921e5cd29669988fcc8a60dcc0992 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 27 Apr 2024 21:34:29 -0400 Subject: [PATCH 05/73] draft erc721 enum --- src/token/erc721.cairo | 1 + src/token/erc721/extensions.cairo | 3 + .../erc721/extensions/erc721_enumerable.cairo | 4 + .../erc721_enumerable/erc721_enumerable.cairo | 168 ++++++++++++++++++ .../erc721_enumerable/interface.cairo | 34 ++++ 5 files changed, 210 insertions(+) create mode 100644 src/token/erc721/extensions.cairo create mode 100644 src/token/erc721/extensions/erc721_enumerable.cairo create mode 100644 src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo create mode 100644 src/token/erc721/extensions/erc721_enumerable/interface.cairo diff --git a/src/token/erc721.cairo b/src/token/erc721.cairo index 56f9eb8a2..e899020c8 100644 --- a/src/token/erc721.cairo +++ b/src/token/erc721.cairo @@ -2,6 +2,7 @@ mod dual721; mod dual721_receiver; mod erc721; mod erc721_receiver; +mod extensions; mod interface; use erc721::ERC721Component; diff --git a/src/token/erc721/extensions.cairo b/src/token/erc721/extensions.cairo new file mode 100644 index 000000000..97b362633 --- /dev/null +++ b/src/token/erc721/extensions.cairo @@ -0,0 +1,3 @@ +mod erc721_enumerable; + +use erc721_enumerable::erc721_enumerable::ERC721EnumerableComponent; diff --git a/src/token/erc721/extensions/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable.cairo new file mode 100644 index 000000000..e08f66c24 --- /dev/null +++ b/src/token/erc721/extensions/erc721_enumerable.cairo @@ -0,0 +1,4 @@ +mod erc721_enumerable; +mod interface; + +use erc721_enumerable::ERC721EnumerableComponent; diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo new file mode 100644 index 000000000..5fb9d4504 --- /dev/null +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.12.0 (token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo) + +/// # ERC721Enumerable Component +/// +/// +#[starknet::component] +mod ERC721EnumerableComponent { + use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; + use openzeppelin::introspection::src5::SRC5Component::SRC5; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::ERC721Component::ERC721HooksTrait; + use openzeppelin::token::erc721::ERC721Component::ERC721Impl; + use openzeppelin::token::erc721::ERC721Component::InternalImpl as ERC721InternalImpl; + use openzeppelin::token::erc721::ERC721Component; + use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::IERC721Enumerable; + use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; + use starknet::ContractAddress; + + #[storage] + struct Storage { + ERC721Enumerable_owned_tokens: LegacyMap<(ContractAddress, u256), u256>, + ERC721Enumerable_owned_tokens_index: LegacyMap, + ERC721Enumerable_all_tokens_len: u256, + ERC721Enumerable_all_tokens: LegacyMap, + ERC721Enumerable_all_tokens_index: LegacyMap + } + + #[event] + #[derive(Drop, PartialEq, starknet::Event)] + enum Event {} + + mod Errors { + const OUT_OF_BOUNDS_INDEX: felt252 = 'ERC721Enum: out of bounds index'; + } + + #[embeddable_as(ERC721EnumerableImpl)] + impl ERC721Enumerable< + TContractState, + +HasComponent, + impl ERC721: ERC721Component::HasComponent, + +ERC721Component::ERC721HooksTrait, + +SRC5Component::HasComponent, + +Drop + > of IERC721Enumerable> { + /// + fn total_supply(self: @ComponentState) -> u256 { + self.ERC721Enumerable_all_tokens_len.read() + } + + /// + fn token_of_owner_by_index( + self: @ComponentState, address: ContractAddress, index: u256 + ) -> u256 { + let erc721_component = get_dep_component!(self, ERC721); + assert(index >= erc721_component.balance_of(address), Errors::OUT_OF_BOUNDS_INDEX); + self.ERC721Enumerable_owned_tokens.read((address, index)) + } + + /// + fn token_by_index(self: @ComponentState, index: u256) -> u256 { + assert(index >= self.total_supply(), Errors::OUT_OF_BOUNDS_INDEX); + self.ERC721Enumerable_all_tokens.read(index) + } + } + + // + // Internal + // + + #[generate_trait] + impl InternalImpl< + TContractState, + +HasComponent, + impl ERC721: ERC721Component::HasComponent, + +ERC721Component::ERC721HooksTrait, + impl SRC5: SRC5Component::HasComponent, + +Drop + > of InternalTrait { + /// + fn initializer(ref self: ComponentState) { + let mut src5_component = get_dep_component_mut!(ref self, SRC5); + src5_component.register_interface(interface::IERC721ENUMERABLE_ID); + } + + /// + fn _update( + ref self: ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) -> ContractAddress { + let mut erc721_component = get_dep_component_mut!(ref self, ERC721); + let previous_owner = erc721_component._update(to, token_id, auth); + let zero_address = Zeroable::zero(); + + if previous_owner == zero_address { + self._add_token_to_all_tokens_enumeration(token_id); + } else if previous_owner != to { + self._remove_token_from_owner_enumeration(previous_owner, token_id); + } + + if to == zero_address { + self._remove_token_from_all_tokens_enumeration(token_id); + } else if previous_owner != to { + self._add_token_to_owner_enumeration(to, token_id); + } + + previous_owner + } + + /// + fn _add_token_to_owner_enumeration( + ref self: ComponentState, to: ContractAddress, token_id: u256 + ) { + let mut erc721_component = get_dep_component_mut!(ref self, ERC721); + let len = erc721_component.balance_of(to) - 1; + self.ERC721Enumerable_owned_tokens.write((to, len), token_id); + self.ERC721Enumerable_owned_tokens_index.write(token_id, len); + } + + /// + fn _add_token_to_all_tokens_enumeration( + ref self: ComponentState, token_id: u256 + ) { + let supply = self.total_supply(); + self.ERC721Enumerable_all_tokens_index.write(token_id, supply); + self.ERC721Enumerable_all_tokens.write(supply, token_id); + } + + /// + fn _remove_token_from_owner_enumeration( + ref self: ComponentState, from: ContractAddress, token_id: u256 + ) { + let erc721_component = get_dep_component!(@self, ERC721); + let last_token_index = erc721_component.balance_of(from) - 1; + let token_index = self.ERC721Enumerable_owned_tokens_index.read(token_id); + + if token_index == last_token_index { + self.ERC721Enumerable_owned_tokens_index.write(token_id, 0); + self.ERC721Enumerable_owned_tokens.write((from, last_token_index), 0); + } + + let last_token_id = self.ERC721Enumerable_owned_tokens.read((from, last_token_index)); + self.ERC721Enumerable_owned_tokens.write((from, token_index), last_token_id); + self.ERC721Enumerable_owned_tokens_index.write(last_token_id, token_index); + } + + /// + fn _remove_token_from_all_tokens_enumeration( + ref self: ComponentState, token_id: u256 + ) { + let supply = self.total_supply(); + let last_token_index = supply - 1; + let this_token_index = self.ERC721Enumerable_all_tokens_index.read(token_id); + let last_token_id = self.ERC721Enumerable_all_tokens.read(last_token_index); + + self.ERC721Enumerable_all_tokens.write(last_token_index, 0); + self.ERC721Enumerable_all_tokens_index.write(token_id, 0); + self.ERC721Enumerable_all_tokens_len.write(last_token_index); + + if last_token_index == this_token_index { + self.ERC721Enumerable_all_tokens_index.write(last_token_id, this_token_index); + self.ERC721Enumerable_all_tokens.write(this_token_index, last_token_id); + } + } + } +} diff --git a/src/token/erc721/extensions/erc721_enumerable/interface.cairo b/src/token/erc721/extensions/erc721_enumerable/interface.cairo new file mode 100644 index 000000000..79468325a --- /dev/null +++ b/src/token/erc721/extensions/erc721_enumerable/interface.cairo @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.12.0 (token/erc721/extensions/erc721_enumerable/interface.cairo) + +use starknet::ContractAddress; + +const IERC721ENUMERABLE_ID: felt252 = + 0x16bc0f502eeaf65ce0b3acb5eea656e2f26979ce6750e8502a82f377e538c87; + +#[starknet::interface] +trait IERC721Enumerable { + fn total_supply(self: @TState) -> u256; + fn token_of_owner_by_index(self: @TState, address: ContractAddress, index: u256) -> u256; + fn token_by_index(self: @TState, index: u256) -> u256; +} + +#[starknet::interface] +trait IERC721EnumerableCamel { + fn totalSupply(self: @TState) -> u256; + fn tokenOfOwnerByIndex(self: @TState, address: ContractAddress, index: u256) -> u256; + fn tokenByIndex(self: @TState, index: u256) -> u256; +} + +#[starknet::interface] +trait ERC721Enumerable_ABI { + // IERC721Enumerable + fn total_supply(self: @TState) -> u256; + fn token_of_owner_by_index(self: @TState, address: ContractAddress, index: u256) -> u256; + fn token_by_index(self: @TState, index: u256) -> u256; + + // IERC721EnumerableCamel + fn totalSupply(self: @TState) -> u256; + fn tokenOfOwnerByIndex(self: @TState, address: ContractAddress, index: u256) -> u256; + fn tokenByIndex(self: @TState, index: u256) -> u256; +} From 295530b79a27808cda94fc081ddec3a72c74bf97 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 28 Apr 2024 19:35:06 -0400 Subject: [PATCH 06/73] fix comp, start testing --- src/tests/mocks.cairo | 1 + src/tests/mocks/erc721_enumerable_mocks.cairo | 90 ++++++++++++++++++ src/tests/token.cairo | 1 + src/tests/token/test_erc721_enumerable.cairo | 94 +++++++++++++++++++ .../erc721_enumerable/erc721_enumerable.cairo | 43 +++++++-- .../erc721_enumerable/interface.cairo | 2 +- 6 files changed, 221 insertions(+), 10 deletions(-) create mode 100644 src/tests/mocks/erc721_enumerable_mocks.cairo create mode 100644 src/tests/token/test_erc721_enumerable.cairo diff --git a/src/tests/mocks.cairo b/src/tests/mocks.cairo index 143f1c6e3..f1c4736b8 100644 --- a/src/tests/mocks.cairo +++ b/src/tests/mocks.cairo @@ -5,6 +5,7 @@ mod erc1155_receiver_mocks; mod erc20_mocks; mod erc20_votes_mocks; mod erc721_mocks; +mod erc721_enumerable_mocks; mod erc721_receiver_mocks; mod eth_account_mocks; mod initializable_mocks; diff --git a/src/tests/mocks/erc721_enumerable_mocks.cairo b/src/tests/mocks/erc721_enumerable_mocks.cairo new file mode 100644 index 000000000..6d38bcfc5 --- /dev/null +++ b/src/tests/mocks/erc721_enumerable_mocks.cairo @@ -0,0 +1,90 @@ +#[starknet::contract] +mod DualCaseERC721EnumerableMock { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::ERC721Component; + use openzeppelin::token::erc721::ERC721Component::ERC721HooksTrait; + use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent; + + use starknet::ContractAddress; + + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC721 + #[abi(embed_v0)] + impl ERC721MixinImpl = ERC721Component::ERC721Impl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + // ERC721Enumerable + #[abi(embed_v0)] + impl ERC721EnumerableImpl = ERC721EnumerableComponent::ERC721EnumerableImpl; + #[abi(embed_v0)] + impl ERC721EnumerableCamelImpl = ERC721EnumerableComponent::ERC721EnumerableCamelImpl; + impl ERC721EnumerableInternalImpl = ERC721EnumerableComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + erc721_enumerable: ERC721EnumerableComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + ERC721EnumerableEvent: ERC721EnumerableComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + impl ERC721EnumerableHooksImpl< + TContractState, + impl ERC721Enumerable: ERC721EnumerableComponent::HasComponent, + impl HasComponent: ERC721Component::HasComponent, + +SRC5Component::HasComponent, + +Drop + > of ERC721Component::ERC721HooksTrait { + fn before_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { + let mut erc721_enumerable_component = get_dep_component_mut!(ref self, ERC721Enumerable); + erc721_enumerable_component._before_update(to, token_id); + } + + fn after_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { + } + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + base_uri: ByteArray, + recipient: ContractAddress, + token_id: u256 + ) { + self.erc721.initializer(name, symbol, base_uri); + self.erc721_enumerable.initializer(); + self.erc721._mint(recipient, token_id); + } +} diff --git a/src/tests/token.cairo b/src/tests/token.cairo index d298942c3..1f7e2d817 100644 --- a/src/tests/token.cairo +++ b/src/tests/token.cairo @@ -8,4 +8,5 @@ mod test_erc1155_receiver; mod test_erc20; mod test_erc20_votes; mod test_erc721; +mod test_erc721_enumerable; mod test_erc721_receiver; diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo new file mode 100644 index 000000000..4769e3d87 --- /dev/null +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -0,0 +1,94 @@ +use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; +use openzeppelin::introspection::src5; +use openzeppelin::introspection; +use openzeppelin::tests::mocks::erc721_enumerable_mocks::DualCaseERC721EnumerableMock; +use openzeppelin::tests::utils::constants::{ + DATA, ZERO, OWNER, RECIPIENT, SPENDER, OPERATOR, OTHER, NAME, SYMBOL, TOKEN_ID, PUBKEY, + BASE_URI, BASE_URI_2 +}; +use openzeppelin::token::erc721::ERC721Component::{ERC721Impl, InternalImpl as ERC721InternalImpl}; +use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent; +use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent::{ERC721EnumerableImpl, ERC721EnumerableCamelImpl, InternalImpl}; +use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; +use openzeppelin::utils::serde::SerializedAppend; +use starknet::ContractAddress; +use starknet::contract_address_const; +use starknet::storage::StorageMapMemberAccessTrait; +use starknet::testing; + +// Token IDs +const TOKEN_1: u256 = 1; +const TOKEN_2: u256 = 2; +const TOKEN_3: u256 = 3; +const NONEXISTENT: u256 = 9898; + +const TOKENS_LEN: u256 = 3; + +// +// Setup +// + +type ComponentState = ERC721EnumerableComponent::ComponentState; + +fn CONTRACT_STATE() -> DualCaseERC721EnumerableMock::ContractState { + DualCaseERC721EnumerableMock::contract_state_for_testing() +} + +fn COMPONENT_STATE() -> ComponentState { + ERC721EnumerableComponent::component_state_for_testing() +} + +fn setup() -> ComponentState { + let mut state = COMPONENT_STATE(); + let mut mock_state = CONTRACT_STATE(); + state.initializer(); + + let mut tokens = array![TOKEN_1, TOKEN_2, TOKEN_3]; + loop { + if tokens.len() == 0 { + break; + }; + let token = tokens.pop_front().unwrap(); + mock_state.erc721._mint(OWNER(), token); + }; + + state +} + +// +// Initializers +// + +#[test] +fn test_initialize() { + let mut state = COMPONENT_STATE(); + let mock_state = CONTRACT_STATE(); + + state.initializer(); + + let supports_ierc721_enum = mock_state.supports_interface(interface::IERC721ENUMERABLE_ID); + assert!(supports_ierc721_enum); + + let supports_isrc5 = mock_state.supports_interface(introspection::interface::ISRC5_ID); + assert!(supports_isrc5); +} + +// +// total_supply +// + +#[test] +fn test_total_supply() { + let mut state = setup(); + + let supply = state.total_supply(); + assert_eq!(supply, TOKENS_LEN); +} + +#[test] +fn test_totalSupply() { + let mut state = setup(); + + let supply = state.totalSupply(); + assert_eq!(supply, TOKENS_LEN); +} diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 5fb9d4504..051237fd1 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -13,7 +13,7 @@ mod ERC721EnumerableComponent { use openzeppelin::token::erc721::ERC721Component::ERC721Impl; use openzeppelin::token::erc721::ERC721Component::InternalImpl as ERC721InternalImpl; use openzeppelin::token::erc721::ERC721Component; - use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::IERC721Enumerable; + use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::{IERC721Enumerable, IERC721EnumerableCamel}; use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; use starknet::ContractAddress; @@ -64,6 +64,33 @@ mod ERC721EnumerableComponent { } } + #[embeddable_as(ERC721EnumerableCamelImpl)] + impl ERC721EnumerableCamel< + TContractState, + +HasComponent, + impl ERC721: ERC721Component::HasComponent, + +ERC721Component::ERC721HooksTrait, + +SRC5Component::HasComponent, + +Drop + > of IERC721EnumerableCamel> { + /// + fn totalSupply(self: @ComponentState) -> u256 { + self.total_supply() + } + + /// + fn tokenOfOwnerByIndex( + self: @ComponentState, address: ContractAddress, index: u256 + ) -> u256 { + self.token_of_owner_by_index(address, index) + } + + /// + fn tokenByIndex(self: @ComponentState, index: u256) -> u256 { + self.token_by_index(index) + } + } + // // Internal // @@ -84,14 +111,13 @@ mod ERC721EnumerableComponent { } /// - fn _update( + fn _before_update( ref self: ComponentState, to: ContractAddress, token_id: u256, - auth: ContractAddress - ) -> ContractAddress { - let mut erc721_component = get_dep_component_mut!(ref self, ERC721); - let previous_owner = erc721_component._update(to, token_id, auth); + ) { + let erc721_component = get_dep_component!(@self, ERC721); + let previous_owner = erc721_component._owner_of(token_id); let zero_address = Zeroable::zero(); if previous_owner == zero_address { @@ -105,8 +131,6 @@ mod ERC721EnumerableComponent { } else if previous_owner != to { self._add_token_to_owner_enumeration(to, token_id); } - - previous_owner } /// @@ -114,7 +138,7 @@ mod ERC721EnumerableComponent { ref self: ComponentState, to: ContractAddress, token_id: u256 ) { let mut erc721_component = get_dep_component_mut!(ref self, ERC721); - let len = erc721_component.balance_of(to) - 1; + let len = erc721_component.balance_of(to); self.ERC721Enumerable_owned_tokens.write((to, len), token_id); self.ERC721Enumerable_owned_tokens_index.write(token_id, len); } @@ -126,6 +150,7 @@ mod ERC721EnumerableComponent { let supply = self.total_supply(); self.ERC721Enumerable_all_tokens_index.write(token_id, supply); self.ERC721Enumerable_all_tokens.write(supply, token_id); + self.ERC721Enumerable_all_tokens_len.write(supply + 1); } /// diff --git a/src/token/erc721/extensions/erc721_enumerable/interface.cairo b/src/token/erc721/extensions/erc721_enumerable/interface.cairo index 79468325a..3ee442dbf 100644 --- a/src/token/erc721/extensions/erc721_enumerable/interface.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/interface.cairo @@ -21,7 +21,7 @@ trait IERC721EnumerableCamel { } #[starknet::interface] -trait ERC721Enumerable_ABI { +trait ERC721EnumerableABI { // IERC721Enumerable fn total_supply(self: @TState) -> u256; fn token_of_owner_by_index(self: @TState, address: ContractAddress, index: u256) -> u256; From d5838d339460d5998dc1f36d2db7df14c60a05cc Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 28 Apr 2024 19:35:26 -0400 Subject: [PATCH 07/73] fix fmt --- src/tests/mocks.cairo | 2 +- src/tests/mocks/erc721_enumerable_mocks.cairo | 19 ++++++++++++------- src/tests/token/test_erc721_enumerable.cairo | 7 +++++-- .../erc721_enumerable/erc721_enumerable.cairo | 8 ++++---- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/tests/mocks.cairo b/src/tests/mocks.cairo index f1c4736b8..8d240ca3a 100644 --- a/src/tests/mocks.cairo +++ b/src/tests/mocks.cairo @@ -4,8 +4,8 @@ mod erc1155_mocks; mod erc1155_receiver_mocks; mod erc20_mocks; mod erc20_votes_mocks; -mod erc721_mocks; mod erc721_enumerable_mocks; +mod erc721_mocks; mod erc721_receiver_mocks; mod eth_account_mocks; mod initializable_mocks; diff --git a/src/tests/mocks/erc721_enumerable_mocks.cairo b/src/tests/mocks/erc721_enumerable_mocks.cairo index 6d38bcfc5..eb52aeff0 100644 --- a/src/tests/mocks/erc721_enumerable_mocks.cairo +++ b/src/tests/mocks/erc721_enumerable_mocks.cairo @@ -1,14 +1,16 @@ #[starknet::contract] mod DualCaseERC721EnumerableMock { use openzeppelin::introspection::src5::SRC5Component; - use openzeppelin::token::erc721::ERC721Component; use openzeppelin::token::erc721::ERC721Component::ERC721HooksTrait; + use openzeppelin::token::erc721::ERC721Component; use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent; use starknet::ContractAddress; component!(path: ERC721Component, storage: erc721, event: ERC721Event); - component!(path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent); + component!( + path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent + ); component!(path: SRC5Component, storage: src5, event: SRC5Event); // ERC721 @@ -18,9 +20,11 @@ mod DualCaseERC721EnumerableMock { // ERC721Enumerable #[abi(embed_v0)] - impl ERC721EnumerableImpl = ERC721EnumerableComponent::ERC721EnumerableImpl; + impl ERC721EnumerableImpl = + ERC721EnumerableComponent::ERC721EnumerableImpl; #[abi(embed_v0)] - impl ERC721EnumerableCamelImpl = ERC721EnumerableComponent::ERC721EnumerableCamelImpl; + impl ERC721EnumerableCamelImpl = + ERC721EnumerableComponent::ERC721EnumerableCamelImpl; impl ERC721EnumerableInternalImpl = ERC721EnumerableComponent::InternalImpl; // SRC5 @@ -61,7 +65,9 @@ mod DualCaseERC721EnumerableMock { token_id: u256, auth: ContractAddress ) { - let mut erc721_enumerable_component = get_dep_component_mut!(ref self, ERC721Enumerable); + let mut erc721_enumerable_component = get_dep_component_mut!( + ref self, ERC721Enumerable + ); erc721_enumerable_component._before_update(to, token_id); } @@ -70,8 +76,7 @@ mod DualCaseERC721EnumerableMock { to: ContractAddress, token_id: u256, auth: ContractAddress - ) { - } + ) {} } #[constructor] diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index 4769e3d87..dad221426 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -7,8 +7,10 @@ use openzeppelin::tests::utils::constants::{ BASE_URI, BASE_URI_2 }; use openzeppelin::token::erc721::ERC721Component::{ERC721Impl, InternalImpl as ERC721InternalImpl}; +use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent::{ + ERC721EnumerableImpl, ERC721EnumerableCamelImpl, InternalImpl +}; use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent; -use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent::{ERC721EnumerableImpl, ERC721EnumerableCamelImpl, InternalImpl}; use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; use openzeppelin::utils::serde::SerializedAppend; use starknet::ContractAddress; @@ -28,7 +30,8 @@ const TOKENS_LEN: u256 = 3; // Setup // -type ComponentState = ERC721EnumerableComponent::ComponentState; +type ComponentState = + ERC721EnumerableComponent::ComponentState; fn CONTRACT_STATE() -> DualCaseERC721EnumerableMock::ContractState { DualCaseERC721EnumerableMock::contract_state_for_testing() diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 051237fd1..4f08efa68 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -13,7 +13,9 @@ mod ERC721EnumerableComponent { use openzeppelin::token::erc721::ERC721Component::ERC721Impl; use openzeppelin::token::erc721::ERC721Component::InternalImpl as ERC721InternalImpl; use openzeppelin::token::erc721::ERC721Component; - use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::{IERC721Enumerable, IERC721EnumerableCamel}; + use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::{ + IERC721Enumerable, IERC721EnumerableCamel + }; use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; use starknet::ContractAddress; @@ -112,9 +114,7 @@ mod ERC721EnumerableComponent { /// fn _before_update( - ref self: ComponentState, - to: ContractAddress, - token_id: u256, + ref self: ComponentState, to: ContractAddress, token_id: u256, ) { let erc721_component = get_dep_component!(@self, ERC721); let previous_owner = erc721_component._owner_of(token_id); From b225343cfaf22bd37898fdb32441a5f5f3fd11e7 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 30 Apr 2024 10:24:34 -0400 Subject: [PATCH 08/73] fix fmt --- .../erc721_enumerable/erc721_enumerable.cairo | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 4f08efa68..6bef23265 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -55,13 +55,13 @@ mod ERC721EnumerableComponent { self: @ComponentState, address: ContractAddress, index: u256 ) -> u256 { let erc721_component = get_dep_component!(self, ERC721); - assert(index >= erc721_component.balance_of(address), Errors::OUT_OF_BOUNDS_INDEX); + assert(index < erc721_component.balance_of(address), Errors::OUT_OF_BOUNDS_INDEX); self.ERC721Enumerable_owned_tokens.read((address, index)) } /// fn token_by_index(self: @ComponentState, index: u256) -> u256 { - assert(index >= self.total_supply(), Errors::OUT_OF_BOUNDS_INDEX); + assert(index < self.total_supply(), Errors::OUT_OF_BOUNDS_INDEX); self.ERC721Enumerable_all_tokens.read(index) } } @@ -113,9 +113,7 @@ mod ERC721EnumerableComponent { } /// - fn _before_update( - ref self: ComponentState, to: ContractAddress, token_id: u256, - ) { + fn _update(ref self: ComponentState, to: ContractAddress, token_id: u256,) { let erc721_component = get_dep_component!(@self, ERC721); let previous_owner = erc721_component._owner_of(token_id); let zero_address = Zeroable::zero(); @@ -139,7 +137,9 @@ mod ERC721EnumerableComponent { ) { let mut erc721_component = get_dep_component_mut!(ref self, ERC721); let len = erc721_component.balance_of(to); + // address => index => id self.ERC721Enumerable_owned_tokens.write((to, len), token_id); + // id => index self.ERC721Enumerable_owned_tokens_index.write(token_id, len); } From 649c7c65c0d91e22a87e2de6ecee7648326cad81 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 30 Apr 2024 10:25:07 -0400 Subject: [PATCH 09/73] change fn name --- src/tests/mocks/erc721_enumerable_mocks.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/mocks/erc721_enumerable_mocks.cairo b/src/tests/mocks/erc721_enumerable_mocks.cairo index eb52aeff0..612082b73 100644 --- a/src/tests/mocks/erc721_enumerable_mocks.cairo +++ b/src/tests/mocks/erc721_enumerable_mocks.cairo @@ -68,7 +68,7 @@ mod DualCaseERC721EnumerableMock { let mut erc721_enumerable_component = get_dep_component_mut!( ref self, ERC721Enumerable ); - erc721_enumerable_component._before_update(to, token_id); + erc721_enumerable_component._update(to, token_id); } fn after_update( From cff482a2ac9fd7e23e6c796b4a9a956e014e181b Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 30 Apr 2024 10:25:58 -0400 Subject: [PATCH 10/73] add more tests for interface fns --- src/tests/token/test_erc721_enumerable.cairo | 95 ++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index dad221426..460b6467a 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -26,6 +26,10 @@ const NONEXISTENT: u256 = 9898; const TOKENS_LEN: u256 = 3; +fn TOKENS_LIST() -> Array { + array![TOKEN_1, TOKEN_2, TOKEN_3] +} + // // Setup // @@ -95,3 +99,94 @@ fn test_totalSupply() { let supply = state.totalSupply(); assert_eq!(supply, TOKENS_LEN); } + +// +// token_of_owner_by_index +// + +#[test] +fn test_token_of_owner_by_index_when_index_is_lt_owned_tokens() { + let mut state = setup(); + + let mut i = 0; + loop { + if i == TOKENS_LIST().len() { + break; + }; + let token = state.token_of_owner_by_index(OWNER(), i.into()); + assert_eq!(token, *TOKENS_LIST().at(i)); + i = i + 1; + }; +} + +#[test] +#[should_panic(expected: ('ERC721Enum: out of bounds index',))] +fn test_token_of_owner_by_index_when_index_equals_owned_tokens() { + let mut state = setup(); + + state.token_of_owner_by_index(OWNER(), TOKENS_LEN); +} + +#[test] +#[should_panic(expected: ('ERC721Enum: out of bounds index',))] +fn test_token_of_owner_by_index_when_index_exceeds_owned_tokens() { + let mut state = setup(); + + state.token_of_owner_by_index(OWNER(), TOKENS_LEN + 1); +} + +#[test] +#[should_panic(expected: ('ERC721Enum: out of bounds index',))] +fn test_token_of_owner_by_index_when_target_has_no_tokens() { + let mut state = setup(); + + state.token_of_owner_by_index(OTHER(), 0); +} + +#[test] +fn test_token_of_owner_by_index_when_all_tokens_transferred() { + let mut state = setup(); + let mut contract_state = CONTRACT_STATE(); + testing::set_caller_address(OWNER()); + + contract_state.transfer_from(OWNER(), RECIPIENT(), TOKEN_1); + contract_state.transfer_from(OWNER(), RECIPIENT(), TOKEN_2); + contract_state.transfer_from(OWNER(), RECIPIENT(), TOKEN_3); + + let mut token = state.token_of_owner_by_index(RECIPIENT(), 0); + assert_eq!(token, TOKEN_1); + + token = state.token_of_owner_by_index(RECIPIENT(), 1); + assert_eq!(token, TOKEN_2); + + token = state.token_of_owner_by_index(RECIPIENT(), 2); + assert_eq!(token, TOKEN_3); +} + +// +// token_by_index +// + +#[test] +fn test_token_by_index() { + let mut state = setup(); + + let mut index = 0; + loop { + if index == TOKENS_LIST().len() { + break; + }; + + let token = state.token_by_index(index.into()); + assert_eq!(token, *TOKENS_LIST().at(index)); + index = index + 1; + }; +} + +#[test] +#[should_panic(expected: ('ERC721Enum: out of bounds index',))] +fn test_token_by_index_equal_to_supply() { + let mut state = setup(); + + state.token_by_index(TOKENS_LEN); +} From f76da3ea3bf9e9733723caaaf168866f00cf7193 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 2 May 2024 23:57:46 -0400 Subject: [PATCH 11/73] add in-code comments --- .../erc721_enumerable/erc721_enumerable.cairo | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 6bef23265..f11129442 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -137,9 +137,7 @@ mod ERC721EnumerableComponent { ) { let mut erc721_component = get_dep_component_mut!(ref self, ERC721); let len = erc721_component.balance_of(to); - // address => index => id self.ERC721Enumerable_owned_tokens.write((to, len), token_id); - // id => index self.ERC721Enumerable_owned_tokens_index.write(token_id, len); } @@ -159,35 +157,41 @@ mod ERC721EnumerableComponent { ) { let erc721_component = get_dep_component!(@self, ERC721); let last_token_index = erc721_component.balance_of(from) - 1; - let token_index = self.ERC721Enumerable_owned_tokens_index.read(token_id); - - if token_index == last_token_index { - self.ERC721Enumerable_owned_tokens_index.write(token_id, 0); - self.ERC721Enumerable_owned_tokens.write((from, last_token_index), 0); + let this_token_index = self.ERC721Enumerable_owned_tokens_index.read(token_id); + + // When `token_id` is the last token, the swap operation is unnecessary + if this_token_index != last_token_index { + let last_token_id = self.ERC721Enumerable_owned_tokens.read((from, last_token_index)); + // Set `token_id` index to point to last token id + self.ERC721Enumerable_owned_tokens.write((from, this_token_index), last_token_id); + // Set the last token id index to point to `token_id`'s index position + self.ERC721Enumerable_owned_tokens_index.write(last_token_id, this_token_index); } - let last_token_id = self.ERC721Enumerable_owned_tokens.read((from, last_token_index)); - self.ERC721Enumerable_owned_tokens.write((from, token_index), last_token_id); - self.ERC721Enumerable_owned_tokens_index.write(last_token_id, token_index); + // Remove `token_id` from last token index position + self.ERC721Enumerable_owned_tokens.write((from, last_token_index), 0); } /// fn _remove_token_from_all_tokens_enumeration( ref self: ComponentState, token_id: u256 ) { - let supply = self.total_supply(); - let last_token_index = supply - 1; + let last_token_index = self.total_supply() - 1; let this_token_index = self.ERC721Enumerable_all_tokens_index.read(token_id); let last_token_id = self.ERC721Enumerable_all_tokens.read(last_token_index); + // Set last token index to zero self.ERC721Enumerable_all_tokens.write(last_token_index, 0); + // Set `token_id` index to 0 self.ERC721Enumerable_all_tokens_index.write(token_id, 0); + // Remove one from total supply self.ERC721Enumerable_all_tokens_len.write(last_token_index); - if last_token_index == this_token_index { - self.ERC721Enumerable_all_tokens_index.write(last_token_id, this_token_index); - self.ERC721Enumerable_all_tokens.write(this_token_index, last_token_id); - } + // When the token to delete is the last token, the swap operation is unnecessary. However, + // since this occurs rarely (when the last minted token is burnt), we still do the swap + // which avoids the additional expense of including an `if` statement + self.ERC721Enumerable_all_tokens_index.write(last_token_id, this_token_index); + self.ERC721Enumerable_all_tokens.write(this_token_index, last_token_id); } } } From 96f9d09b8c5b2a51f3efd20c21c6c09b6ca1320f Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 2 May 2024 23:58:17 -0400 Subject: [PATCH 12/73] add more tests --- src/tests/token/test_erc721_enumerable.cairo | 162 +++++++++++++------ 1 file changed, 109 insertions(+), 53 deletions(-) diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index 460b6467a..e003a9029 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -2,34 +2,24 @@ use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; use openzeppelin::introspection::src5; use openzeppelin::introspection; use openzeppelin::tests::mocks::erc721_enumerable_mocks::DualCaseERC721EnumerableMock; -use openzeppelin::tests::utils::constants::{ - DATA, ZERO, OWNER, RECIPIENT, SPENDER, OPERATOR, OTHER, NAME, SYMBOL, TOKEN_ID, PUBKEY, - BASE_URI, BASE_URI_2 -}; +use openzeppelin::tests::utils::constants::{OWNER, RECIPIENT, OTHER}; use openzeppelin::token::erc721::ERC721Component::{ERC721Impl, InternalImpl as ERC721InternalImpl}; use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent::{ ERC721EnumerableImpl, ERC721EnumerableCamelImpl, InternalImpl }; use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent; use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; -use openzeppelin::utils::serde::SerializedAppend; use starknet::ContractAddress; -use starknet::contract_address_const; -use starknet::storage::StorageMapMemberAccessTrait; +use starknet::storage::{StorageMapMemberAccessTrait, StorageMemberAccessTrait}; use starknet::testing; // Token IDs -const TOKEN_1: u256 = 1; -const TOKEN_2: u256 = 2; -const TOKEN_3: u256 = 3; -const NONEXISTENT: u256 = 9898; +const TOKEN_1: u256 = 111; +const TOKEN_2: u256 = 222; +const TOKEN_3: u256 = 333; const TOKENS_LEN: u256 = 3; -fn TOKENS_LIST() -> Array { - array![TOKEN_1, TOKEN_2, TOKEN_3] -} - // // Setup // @@ -63,11 +53,11 @@ fn setup() -> ComponentState { } // -// Initializers +// Initializer // #[test] -fn test_initialize() { +fn test_initializer() { let mut state = COMPONENT_STATE(); let mock_state = CONTRACT_STATE(); @@ -94,7 +84,7 @@ fn test_total_supply() { #[test] fn test_totalSupply() { - let mut state = setup(); + let state = setup(); let supply = state.totalSupply(); assert_eq!(supply, TOKENS_LEN); @@ -105,24 +95,17 @@ fn test_totalSupply() { // #[test] -fn test_token_of_owner_by_index_when_index_is_lt_owned_tokens() { - let mut state = setup(); +fn test_token_of_owner_by_index() { + let state = setup(); + let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; - let mut i = 0; - loop { - if i == TOKENS_LIST().len() { - break; - }; - let token = state.token_of_owner_by_index(OWNER(), i.into()); - assert_eq!(token, *TOKENS_LIST().at(i)); - i = i + 1; - }; + assert_token_of_owner_by_index(state, OWNER(), tokens_list); } #[test] #[should_panic(expected: ('ERC721Enum: out of bounds index',))] fn test_token_of_owner_by_index_when_index_equals_owned_tokens() { - let mut state = setup(); + let state = setup(); state.token_of_owner_by_index(OWNER(), TOKENS_LEN); } @@ -130,7 +113,7 @@ fn test_token_of_owner_by_index_when_index_equals_owned_tokens() { #[test] #[should_panic(expected: ('ERC721Enum: out of bounds index',))] fn test_token_of_owner_by_index_when_index_exceeds_owned_tokens() { - let mut state = setup(); + let state = setup(); state.token_of_owner_by_index(OWNER(), TOKENS_LEN + 1); } @@ -138,29 +121,24 @@ fn test_token_of_owner_by_index_when_index_exceeds_owned_tokens() { #[test] #[should_panic(expected: ('ERC721Enum: out of bounds index',))] fn test_token_of_owner_by_index_when_target_has_no_tokens() { - let mut state = setup(); + let state = setup(); state.token_of_owner_by_index(OTHER(), 0); } #[test] fn test_token_of_owner_by_index_when_all_tokens_transferred() { - let mut state = setup(); + let state = setup(); let mut contract_state = CONTRACT_STATE(); + let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; + testing::set_caller_address(OWNER()); contract_state.transfer_from(OWNER(), RECIPIENT(), TOKEN_1); contract_state.transfer_from(OWNER(), RECIPIENT(), TOKEN_2); contract_state.transfer_from(OWNER(), RECIPIENT(), TOKEN_3); - let mut token = state.token_of_owner_by_index(RECIPIENT(), 0); - assert_eq!(token, TOKEN_1); - - token = state.token_of_owner_by_index(RECIPIENT(), 1); - assert_eq!(token, TOKEN_2); - - token = state.token_of_owner_by_index(RECIPIENT(), 2); - assert_eq!(token, TOKEN_3); + assert_token_of_owner_by_index(state, RECIPIENT(), tokens_list); } // @@ -169,24 +147,102 @@ fn test_token_of_owner_by_index_when_all_tokens_transferred() { #[test] fn test_token_by_index() { - let mut state = setup(); - - let mut index = 0; - loop { - if index == TOKENS_LIST().len() { - break; - }; + let state = setup(); + let token_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; - let token = state.token_by_index(index.into()); - assert_eq!(token, *TOKENS_LIST().at(index)); - index = index + 1; - }; + assert_token_by_index(state, token_list); } #[test] #[should_panic(expected: ('ERC721Enum: out of bounds index',))] fn test_token_by_index_equal_to_supply() { - let mut state = setup(); + let state = setup(); state.token_by_index(TOKENS_LEN); } + +#[test] +#[should_panic(expected: ('ERC721Enum: out of bounds index',))] +fn test_token_by_index_greater_than_supply() { + let state = setup(); + + state.token_by_index(TOKENS_LEN + 1); +} + +#[test] +fn test_token_by_index_burn_last_token() { + let state = setup(); + let mut contract_state = CONTRACT_STATE(); + let last_token = TOKEN_3; + + contract_state.erc721._burn(last_token); + + let expected_list = array![TOKEN_1, TOKEN_2]; + assert_token_by_index(state, expected_list); +} + +#[test] +fn test_token_by_index_burn_first_token() { + let state = setup(); + let mut contract_state = CONTRACT_STATE(); + let first_token = TOKEN_1; + + contract_state.erc721._burn(first_token); + + // Burnt tokens are replaced by the last token + // to prevent indexing gaps + let expected_list = array![TOKEN_3, TOKEN_2]; + assert_token_by_index(state, expected_list); +} + +#[test] +fn test_token_by_index_burn_and_mint_all() { + let state = setup(); + let mut contract_state = CONTRACT_STATE(); + + contract_state.erc721._burn(TOKEN_2); + contract_state.erc721._burn(TOKEN_3); + contract_state.erc721._burn(TOKEN_1); + + let supply = state.total_supply(); + assert_eq!(supply, 0); + + contract_state.erc721._mint(OWNER(), TOKEN_1); + contract_state.erc721._mint(OWNER(), TOKEN_2); + contract_state.erc721._mint(OWNER(), TOKEN_3); + + let expected_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; + assert_token_by_index(state, expected_list); +} + +// +// Helpers +// + +fn assert_token_of_owner_by_index( + state: ComponentState, owner: ContractAddress, expected_token_list: Array +) { + let mut i = 0; + loop { + if i == expected_token_list.len() { + break; + }; + let token = state.token_of_owner_by_index(owner, i.into()); + assert_eq!(token, *expected_token_list.at(i)); + i = i + 1; + }; +} + +fn assert_token_by_index( + state: ComponentState, expected_token_list: Array +) { + let mut i = 0; + loop { + if i == expected_token_list.len() { + break; + }; + let token = state.token_by_index(i.into()); + assert_eq!(token, *expected_token_list.at(i)); + i = i + 1; + }; +} From d9455aedbcb212ebb6982cea5187ff1a87484300 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 2 May 2024 23:59:18 -0400 Subject: [PATCH 13/73] fix formatting --- src/tests/token/test_erc721_enumerable.cairo | 4 +--- .../extensions/erc721_enumerable/erc721_enumerable.cairo | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index e003a9029..7a9daf9e7 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -233,9 +233,7 @@ fn assert_token_of_owner_by_index( }; } -fn assert_token_by_index( - state: ComponentState, expected_token_list: Array -) { +fn assert_token_by_index(state: ComponentState, expected_token_list: Array) { let mut i = 0; loop { if i == expected_token_list.len() { diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index f11129442..7e6c757ae 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -161,7 +161,9 @@ mod ERC721EnumerableComponent { // When `token_id` is the last token, the swap operation is unnecessary if this_token_index != last_token_index { - let last_token_id = self.ERC721Enumerable_owned_tokens.read((from, last_token_index)); + let last_token_id = self + .ERC721Enumerable_owned_tokens + .read((from, last_token_index)); // Set `token_id` index to point to last token id self.ERC721Enumerable_owned_tokens.write((from, this_token_index), last_token_id); // Set the last token id index to point to `token_id`'s index position From 73fbbc9ae489bdaeea5bc2f8ae314f10efdc5471 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 3 May 2024 00:08:32 -0400 Subject: [PATCH 14/73] remove unused imports, clean up code --- src/tests/token/test_erc721_enumerable.cairo | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index 7a9daf9e7..51a290bdb 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -1,5 +1,4 @@ use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; -use openzeppelin::introspection::src5; use openzeppelin::introspection; use openzeppelin::tests::mocks::erc721_enumerable_mocks::DualCaseERC721EnumerableMock; use openzeppelin::tests::utils::constants::{OWNER, RECIPIENT, OTHER}; @@ -10,8 +9,6 @@ use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721Enumerable use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent; use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; use starknet::ContractAddress; -use starknet::storage::{StorageMapMemberAccessTrait, StorageMemberAccessTrait}; -use starknet::testing; // Token IDs const TOKEN_1: u256 = 111; @@ -132,11 +129,9 @@ fn test_token_of_owner_by_index_when_all_tokens_transferred() { let mut contract_state = CONTRACT_STATE(); let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; - testing::set_caller_address(OWNER()); - - contract_state.transfer_from(OWNER(), RECIPIENT(), TOKEN_1); - contract_state.transfer_from(OWNER(), RECIPIENT(), TOKEN_2); - contract_state.transfer_from(OWNER(), RECIPIENT(), TOKEN_3); + contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_1); + contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_2); + contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_3); assert_token_of_owner_by_index(state, RECIPIENT(), tokens_list); } From 5a128ca49b6e40559ed673eda1010eb4423d74db Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 4 May 2024 03:12:36 -0400 Subject: [PATCH 15/73] remove space --- docs/modules/ROOT/pages/api/erc20.adoc | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/modules/ROOT/pages/api/erc20.adoc b/docs/modules/ROOT/pages/api/erc20.adoc index 9a0cef17c..392562d15 100644 --- a/docs/modules/ROOT/pages/api/erc20.adoc +++ b/docs/modules/ROOT/pages/api/erc20.adoc @@ -477,7 +477,6 @@ WARNING: To track voting units, this extension requires that the xref:#ERC20VotesComponent-transfer_voting_units[transfer_voting_units] function is called after every transfer, mint, or burn operation. For this, the xref:ERC20Component-ERC20HooksTrait[ERC20HooksTrait] must be used. - This extension keeps a history (checkpoints) of each account’s vote power. Vote power can be delegated either by calling the xref:#ERC20VotesComponent-delegate[delegate] function directly, or by providing a signature to be used with xref:#ERC20VotesComponent-delegate_by_sig[delegate_by_sig]. Voting power can be queried through the public accessors From 5987c8fd019195f89d31aada027c40aff6bf753e Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 4 May 2024 03:12:58 -0400 Subject: [PATCH 16/73] start erc721enum section --- docs/modules/ROOT/pages/api/erc721.adoc | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 323a4b909..20c77334d 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -775,6 +775,53 @@ See < Date: Sun, 5 May 2024 06:20:35 -0400 Subject: [PATCH 17/73] fix fn order --- src/tests/token/test_erc721_enumerable.cairo | 98 +++++++++---------- .../erc721_enumerable/interface.cairo | 8 +- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index 51a290bdb..6acbbd4f3 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -87,55 +87,6 @@ fn test_totalSupply() { assert_eq!(supply, TOKENS_LEN); } -// -// token_of_owner_by_index -// - -#[test] -fn test_token_of_owner_by_index() { - let state = setup(); - let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; - - assert_token_of_owner_by_index(state, OWNER(), tokens_list); -} - -#[test] -#[should_panic(expected: ('ERC721Enum: out of bounds index',))] -fn test_token_of_owner_by_index_when_index_equals_owned_tokens() { - let state = setup(); - - state.token_of_owner_by_index(OWNER(), TOKENS_LEN); -} - -#[test] -#[should_panic(expected: ('ERC721Enum: out of bounds index',))] -fn test_token_of_owner_by_index_when_index_exceeds_owned_tokens() { - let state = setup(); - - state.token_of_owner_by_index(OWNER(), TOKENS_LEN + 1); -} - -#[test] -#[should_panic(expected: ('ERC721Enum: out of bounds index',))] -fn test_token_of_owner_by_index_when_target_has_no_tokens() { - let state = setup(); - - state.token_of_owner_by_index(OTHER(), 0); -} - -#[test] -fn test_token_of_owner_by_index_when_all_tokens_transferred() { - let state = setup(); - let mut contract_state = CONTRACT_STATE(); - let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; - - contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_1); - contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_2); - contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_3); - - assert_token_of_owner_by_index(state, RECIPIENT(), tokens_list); -} - // // token_by_index // @@ -210,6 +161,55 @@ fn test_token_by_index_burn_and_mint_all() { assert_token_by_index(state, expected_list); } +// +// token_of_owner_by_index +// + +#[test] +fn test_token_of_owner_by_index() { + let state = setup(); + let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; + + assert_token_of_owner_by_index(state, OWNER(), tokens_list); +} + +#[test] +#[should_panic(expected: ('ERC721Enum: out of bounds index',))] +fn test_token_of_owner_by_index_when_index_equals_owned_tokens() { + let state = setup(); + + state.token_of_owner_by_index(OWNER(), TOKENS_LEN); +} + +#[test] +#[should_panic(expected: ('ERC721Enum: out of bounds index',))] +fn test_token_of_owner_by_index_when_index_exceeds_owned_tokens() { + let state = setup(); + + state.token_of_owner_by_index(OWNER(), TOKENS_LEN + 1); +} + +#[test] +#[should_panic(expected: ('ERC721Enum: out of bounds index',))] +fn test_token_of_owner_by_index_when_target_has_no_tokens() { + let state = setup(); + + state.token_of_owner_by_index(OTHER(), 0); +} + +#[test] +fn test_token_of_owner_by_index_when_all_tokens_transferred() { + let state = setup(); + let mut contract_state = CONTRACT_STATE(); + let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; + + contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_1); + contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_2); + contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_3); + + assert_token_of_owner_by_index(state, RECIPIENT(), tokens_list); +} + // // Helpers // diff --git a/src/token/erc721/extensions/erc721_enumerable/interface.cairo b/src/token/erc721/extensions/erc721_enumerable/interface.cairo index 3ee442dbf..95363a502 100644 --- a/src/token/erc721/extensions/erc721_enumerable/interface.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/interface.cairo @@ -9,26 +9,26 @@ const IERC721ENUMERABLE_ID: felt252 = #[starknet::interface] trait IERC721Enumerable { fn total_supply(self: @TState) -> u256; - fn token_of_owner_by_index(self: @TState, address: ContractAddress, index: u256) -> u256; fn token_by_index(self: @TState, index: u256) -> u256; + fn token_of_owner_by_index(self: @TState, owner: ContractAddress, index: u256) -> u256; } #[starknet::interface] trait IERC721EnumerableCamel { fn totalSupply(self: @TState) -> u256; - fn tokenOfOwnerByIndex(self: @TState, address: ContractAddress, index: u256) -> u256; fn tokenByIndex(self: @TState, index: u256) -> u256; + fn tokenOfOwnerByIndex(self: @TState, owner: ContractAddress, index: u256) -> u256; } #[starknet::interface] trait ERC721EnumerableABI { // IERC721Enumerable fn total_supply(self: @TState) -> u256; - fn token_of_owner_by_index(self: @TState, address: ContractAddress, index: u256) -> u256; fn token_by_index(self: @TState, index: u256) -> u256; + fn token_of_owner_by_index(self: @TState, owner: ContractAddress, index: u256) -> u256; // IERC721EnumerableCamel fn totalSupply(self: @TState) -> u256; - fn tokenOfOwnerByIndex(self: @TState, address: ContractAddress, index: u256) -> u256; fn tokenByIndex(self: @TState, index: u256) -> u256; + fn tokenOfOwnerByIndex(self: @TState, owner: ContractAddress, index: u256) -> u256; } From fc27ae78041bc6359cde63ee6d6ee54b7c3de403 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 5 May 2024 06:20:57 -0400 Subject: [PATCH 18/73] add in-code comments, fix fn order --- .../erc721_enumerable/erc721_enumerable.cairo | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 7e6c757ae..419db1c2e 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -45,27 +45,40 @@ mod ERC721EnumerableComponent { +SRC5Component::HasComponent, +Drop > of IERC721Enumerable> { - /// + /// Returns the total amount of tokens stored by the contract. fn total_supply(self: @ComponentState) -> u256 { self.ERC721Enumerable_all_tokens_len.read() } + /// Returns a token id at a given `index` of all the tokens stored by the contract. + /// Use along with `total_supply` to enumerate all tokens. /// - fn token_of_owner_by_index( - self: @ComponentState, address: ContractAddress, index: u256 - ) -> u256 { - let erc721_component = get_dep_component!(self, ERC721); - assert(index < erc721_component.balance_of(address), Errors::OUT_OF_BOUNDS_INDEX); - self.ERC721Enumerable_owned_tokens.read((address, index)) - } - + /// Requirements: /// + /// - `index` is less than the total token supply. fn token_by_index(self: @ComponentState, index: u256) -> u256 { + assert(index < self.total_supply(), Errors::OUT_OF_BOUNDS_INDEX); assert(index < self.total_supply(), Errors::OUT_OF_BOUNDS_INDEX); self.ERC721Enumerable_all_tokens.read(index) } + + /// Returns the token id owned by `owner` at a given `index` of its token list. + /// Use along with `ERC721::balance_of` to enumerate all of `owner`'s tokens. + /// + /// Requirements: + /// + /// - `index` is less than `owner`'s token balance. + /// - `owner` is not the zero address. + fn token_of_owner_by_index( + self: @ComponentState, owner: ContractAddress, index: u256 + ) -> u256 { + let erc721_component = get_dep_component!(self, ERC721); + assert(index < erc721_component.balance_of(owner), Errors::OUT_OF_BOUNDS_INDEX); + self.ERC721Enumerable_owned_tokens.read((owner, index)) + } } + /// Adds camelCase support for `IERC721Enumerable`. #[embeddable_as(ERC721EnumerableCamelImpl)] impl ERC721EnumerableCamel< TContractState, @@ -75,19 +88,16 @@ mod ERC721EnumerableComponent { +SRC5Component::HasComponent, +Drop > of IERC721EnumerableCamel> { - /// fn totalSupply(self: @ComponentState) -> u256 { self.total_supply() } - /// fn tokenOfOwnerByIndex( - self: @ComponentState, address: ContractAddress, index: u256 + self: @ComponentState, owner: ContractAddress, index: u256 ) -> u256 { - self.token_of_owner_by_index(address, index) + self.token_of_owner_by_index(owner, index) } - /// fn tokenByIndex(self: @ComponentState, index: u256) -> u256 { self.token_by_index(index) } @@ -106,14 +116,21 @@ mod ERC721EnumerableComponent { impl SRC5: SRC5Component::HasComponent, +Drop > of InternalTrait { - /// + /// Initializes the contract by declaring support for the IERC721Enumerable + /// interface id. This should only be used inside the contract's constructor. fn initializer(ref self: ComponentState) { let mut src5_component = get_dep_component_mut!(ref self, SRC5); src5_component.register_interface(interface::IERC721ENUMERABLE_ID); } + /// Updates the ownership and token-tracking data structures. /// - fn _update(ref self: ComponentState, to: ContractAddress, token_id: u256,) { + /// When a token is minted (or burned), `token_id` is added to (or removed from) + /// the token-tracking structures. + /// + /// When a token is transferred, the ownership-tracking data structures reflect + /// the change in ownership of `token_id`. + fn _update(ref self: ComponentState, to: ContractAddress, token_id: u256) { let erc721_component = get_dep_component!(@self, ERC721); let previous_owner = erc721_component._owner_of(token_id); let zero_address = Zeroable::zero(); @@ -131,7 +148,7 @@ mod ERC721EnumerableComponent { } } - /// + /// Adds token to this extension's ownership-tracking data structures. fn _add_token_to_owner_enumeration( ref self: ComponentState, to: ContractAddress, token_id: u256 ) { @@ -141,7 +158,7 @@ mod ERC721EnumerableComponent { self.ERC721Enumerable_owned_tokens_index.write(token_id, len); } - /// + /// Adds token to this extension's token-tracking data structures. fn _add_token_to_all_tokens_enumeration( ref self: ComponentState, token_id: u256 ) { @@ -151,7 +168,8 @@ mod ERC721EnumerableComponent { self.ERC721Enumerable_all_tokens_len.write(supply + 1); } - /// + /// Removes a token from this extension's ownership-tracking data structures. + /// This has 0(1) time complexity but alters the order of the owned-tokens list. fn _remove_token_from_owner_enumeration( ref self: ComponentState, from: ContractAddress, token_id: u256 ) { @@ -159,12 +177,14 @@ mod ERC721EnumerableComponent { let last_token_index = erc721_component.balance_of(from) - 1; let this_token_index = self.ERC721Enumerable_owned_tokens_index.read(token_id); + // To prevent a gap in the token indexing of `from`, we store the last token + // in the index of the token to delete and then remove the last slot (swap and pop). // When `token_id` is the last token, the swap operation is unnecessary if this_token_index != last_token_index { let last_token_id = self .ERC721Enumerable_owned_tokens .read((from, last_token_index)); - // Set `token_id` index to point to last token id + // Set `token_id` index to point to the last token id self.ERC721Enumerable_owned_tokens.write((from, this_token_index), last_token_id); // Set the last token id index to point to `token_id`'s index position self.ERC721Enumerable_owned_tokens_index.write(last_token_id, this_token_index); @@ -174,7 +194,9 @@ mod ERC721EnumerableComponent { self.ERC721Enumerable_owned_tokens.write((from, last_token_index), 0); } - /// + /// Removes `token_id` from this extension's token-tracking data structures. + /// This has 0(1) time complexity, but alters the order of the list by swapping + /// `token_id` and index thereof with the last token id and index thereof. fn _remove_token_from_all_tokens_enumeration( ref self: ComponentState, token_id: u256 ) { From 1266c553663da820b33ccfa6b8f738e87ac12a72 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 5 May 2024 06:21:32 -0400 Subject: [PATCH 19/73] add interface docs and start component docs --- docs/modules/ROOT/pages/api/erc721.adoc | 94 ++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 20c77334d..df6a38baa 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -179,6 +179,48 @@ Returns the NFT ticker symbol. Returns the Uniform Resource Identifier (URI) for the `token_id` token. If the URI is not set for `token_id`, the return value will be an empty `ByteArray`. +[.contract] +[[IERC721Enumerable]] +=== `++IERC721Enumerable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.12.0/src/token/erc721/extensions/erc721_enumerable/interface.cairo[{github-icon},role=heading-link] + +Interface for the optional enumerable functions in {eip721}. + +[.contract-index] +.{inner-src5} +-- +0x16bc0f502eeaf65ce0b3acb5eea656e2f26979ce6750e8502a82f377e538c87 +-- + +[.contract-index] +.Functions +-- +* xref:#IERC721Enumerable-total_supply[`++total_supply()++`] +* xref:#IERC721Enumerable-token_by_index[`++token_by_index(index)++`] +* xref:#IERC721Enumerable-token_of_owner_by_index[`++token_of_owner_by_index(owner, index)++`] +-- + +==== Functions + +[.contract-item] +[[IERC721Enumerable-total_supply]] +==== `[.contract-item-name]#++total_supply++#++() -> u256++` [.item-kind]#external# + +Returns the total amount of tokens stored by the contract. + +[.contract-item] +[[IERC721Metadata-token_by_index]] +==== `[.contract-item-name]#++token_by_index++#++(index) -> u256++` [.item-kind]#external# + +Returns a token id at a given `index` of all the tokens stored by the contract. +Use along with xref:#IERC721Enumerable-total_supply[IERC721Enumerable::total_supply] to enumerate all tokens. + +[.contract-item] +[[IERC721Metadata-token_of_owner_by_index]] +==== `[.contract-item-name]#++token_of_owner_by_index++#++(owner, index) -> u256++` [.item-kind]#external# + +Returns the token id owned by `owner` at a given `index` of its token list. +Use along with xref:#IERC721-balance_of[IERC721::balance_of] to enumerate all of ``owner``'s tokens. + [.contract] [[ERC721Component]] === `++ERC721Component++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.12.0/src/token/erc721/erc721.cairo#L7[{github-icon},role=heading-link] @@ -800,14 +842,14 @@ This extension allows contracts to publish their entire list of NFTs and make th [.sub-index#ERC721EnumerableComponent-Embeddable-Impls-ERC721EnumerableImpl] .ERC721EnumerableImpl * xref:#ERC721EnumerableComponent-total_supply[`++total_supply(self)++`] -* xref:#ERC721EnumerableComponent-token_of_owner_by_index[`++token_of_owner_by_index(self, address, index)++`] * xref:#ERC721EnumerableComponent-token_by_index[`++token_by_index(self, index)++`] +* xref:#ERC721EnumerableComponent-token_of_owner_by_index[`++token_of_owner_by_index(self, address, index)++`] [.sub-index#ERC721EnumerableComponent-Embeddable-Impls-ERC721EnumerableCamelImpl] .ERC721EnumerableCamelImpl * xref:#ERC721EnumerableComponent-total_Spply[`++totalSupply(self)++`] -* xref:#ERC721EnumerableComponent-tokenOfOwnerByIndex[`++tokenOfOwnerByIndex(self, address, index)++`] * xref:#ERC721EnumerableComponent-tokenByIndex[`++tokenByIndex(self, index)++`] +* xref:#ERC721EnumerableComponent-tokenOfOwnerByIndex[`++tokenOfOwnerByIndex(self, owner, index)++`] -- [.contract-index] @@ -822,6 +864,54 @@ This extension allows contracts to publish their entire list of NFTs and make th * xref:#ERC721EnumerableComponent-_remove_token_from_all_tokens_enumeration[`++_remove_token_from_all_tokens_enumeration(self, token_id)++`] -- +[#ERC721EnumerableComponent-Embeddable-functions] +==== Embeddable functions + +[.contract-item] +[[ERC721EnumerableComponent-total_supply]] +==== `[.contract-item-name]#++total_supply++#++(self: @ContractState) → u256++` [.item-kind]#external# + +Returns the current amount of votes that `account` has. + +[.contract-item] +[[ERC721EnumerableComponent-token_by_index]] +==== `[.contract-item-name]#++token_by_index++#++(self: @ContractState, index: u256) → u256++` [.item-kind]#external# + +See xref:#IERC721Enumerable-token_by_index[IERC721Enumerable::token_by_index]. + +Requirements: + +- `index` is less than the total token supply. + +[.contract-item] +[[ERC721EnumerableComponent-token_of_owner_by_index]] +==== `[.contract-item-name]#++token_of_owner_by_index++#++(self: @ContractState, owner: ContractAddress, index: u256) → u256++` [.item-kind]#external# + +See xref:#IERC721Enumerable-token_of_owner_by_index[IERC721Enumerable::token_of_owner_by_index]. + +Requirements: + +- `index` is less than ``owner``'s token balance. +- `owner` is not the zero address. + +[.contract-item] +[[ERC721EnumerableComponent-totalSupply]] +==== `[.contract-item-name]#++totalSupply++#++(self: @ContractState) → u256++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721EnumerableComponent-tokenByIndex]] +==== `[.contract-item-name]#++tokenByIndex++#++(self: @ContractState, index: u256) → u256++` [.item-kind]#external# + +See <>. + +[.contract-item] +[[ERC721EnumerableComponent-tokenOfOwnerByIndex]] +==== `[.contract-item-name]#++tokenOfOwnerByIndex++#++(self: @ContractState, owner: ContractAddress, index: u256) → u256++` [.item-kind]#external# + +See <>. + == Presets [.contract] From 2409cd3a000229d6a10a9eb0a40b51b36bce1ad5 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 6 May 2024 11:06:43 -0400 Subject: [PATCH 20/73] document privateimpl in enum extension --- docs/modules/ROOT/pages/api/erc721.adoc | 49 +++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index df6a38baa..81381e9c8 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -858,6 +858,8 @@ This extension allows contracts to publish their entire list of NFTs and make th .InternalImpl * xref:#ERC721EnumerableComponent-initializer[`++initializer(self)++`] * xref:#ERC721EnumerableComponent-_update[`++_update(self, to, token_id)++`] + +.PrivateImpl * xref:#ERC721EnumerableComponent-_add_token_to_owner_enumeration[`++_add_token_to_owner_enumeration(self, to, token_id)++`] * xref:#ERC721EnumerableComponent-_add_token_to_all_tokens_enumeration[`++_add_token_to_all_tokens_enumeration(self, token_id)++`] * xref:#ERC721EnumerableComponent-_remove_token_from_owner_enumeration[`++_remove_token_from_owner_enumeration(self, from, token_id)++`] @@ -912,6 +914,53 @@ See <>. +[#ERC721EnumerableComponent-Internal-functions] +==== Internal functions + +[.contract-item] +[[ERC721EnumerableComponent-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState)++` [.item-kind]#internal# + +Registers the `IERC721Enumerable` interface ID as supported through introspection. + +[.contract-item] +[[ERC721EnumerableComponent-_update]] +==== `[.contract-item-name]#++_update++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# + +Updates the ownership and token-tracking data structures. + +When a token is minted (or burned), `token_id` is added to (or removed from) the token-tracking structures. + +When a token is transferred, the ownership-tracking data structures reflect the change in ownership of `token_id`. + +[.contract-item] +[[ERC721EnumerableComponent-_add_token_to_owner_enumeration]] +==== `[.contract-item-name]#++_add_token_to_owner_enumeration++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#private# + +Adds token to this extension's ownership-tracking data structures. + +[.contract-item] +[[ERC721EnumerableComponent-_add_token_to_all_tokens_enumeration]] +==== `[.contract-item-name]#++_add_token_to_all_tokens_enumeration++#++(ref self: ContractState, token_id: u256)++` [.item-kind]#private# + +Adds token to this extension's token-tracking data structures. + +[.contract-item] +[[ERC721EnumerableComponent-_remove_token_from_owner_enumeration]] +==== `[.contract-item-name]#++_remove_token_from_owner_enumeration++#++(ref self: ContractState, from: ContractAddress, token_id: u256)++` [.item-kind]#private# + +Removes a token from this extension's ownership-tracking data structures. + +This has 0(1) time complexity but alters the indexed order of owned tokens by swapping `token_id` and the index thereof with the last token id and the index thereof e.g. removing `1` from `[1, 2, 3, 4]` results in `[4, 2, 3]`. + +[.contract-item] +[[ERC721EnumerableComponent-_remove_token_from_all_tokens_enumeration]] +==== `[.contract-item-name]#++_remove_token_from_all_tokens_enumeration++#++(ref self: ContractState, token_id: u256)++` [.item-kind]#private# + +Removes `token_id` from this extension's token-tracking data structures. + +This has 0(1) time complexity but alters the indexed order by swapping `token_id` and the index thereof with the last token id and the index thereof e.g. removing `1` from `[1, 2, 3, 4]` results in `[4, 2, 3]`. + == Presets [.contract] From 6f3524fc63ae95f62092b155089600c6b89100fa Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 6 May 2024 11:07:35 -0400 Subject: [PATCH 21/73] add privateimpl in erc721enum --- .../erc721_enumerable/erc721_enumerable.cairo | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 419db1c2e..fe83ef44e 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -116,8 +116,8 @@ mod ERC721EnumerableComponent { impl SRC5: SRC5Component::HasComponent, +Drop > of InternalTrait { - /// Initializes the contract by declaring support for the IERC721Enumerable - /// interface id. This should only be used inside the contract's constructor. + /// Initializes the contract by declaring support for the `IERC721Enumerable` + /// interface id. fn initializer(ref self: ComponentState) { let mut src5_component = get_dep_component_mut!(ref self, SRC5); src5_component.register_interface(interface::IERC721ENUMERABLE_ID); @@ -136,18 +136,30 @@ mod ERC721EnumerableComponent { let zero_address = Zeroable::zero(); if previous_owner == zero_address { - self._add_token_to_all_tokens_enumeration(token_id); + PrivateImpl::_add_token_to_all_tokens_enumeration(ref self, token_id); } else if previous_owner != to { - self._remove_token_from_owner_enumeration(previous_owner, token_id); + PrivateImpl::_remove_token_from_owner_enumeration( + ref self, previous_owner, token_id + ); } if to == zero_address { - self._remove_token_from_all_tokens_enumeration(token_id); + PrivateImpl::_remove_token_from_all_tokens_enumeration(ref self, token_id); } else if previous_owner != to { - self._add_token_to_owner_enumeration(to, token_id); + PrivateImpl::_add_token_to_owner_enumeration(ref self, to, token_id); } } + } + #[generate_trait] + impl PrivateImpl< + TContractState, + +HasComponent, + impl ERC721: ERC721Component::HasComponent, + +ERC721Component::ERC721HooksTrait, + +SRC5Component::HasComponent, + +Drop + > of PrivateTrait { /// Adds token to this extension's ownership-tracking data structures. fn _add_token_to_owner_enumeration( ref self: ComponentState, to: ContractAddress, token_id: u256 @@ -169,7 +181,10 @@ mod ERC721EnumerableComponent { } /// Removes a token from this extension's ownership-tracking data structures. - /// This has 0(1) time complexity but alters the order of the owned-tokens list. + /// + /// This has 0(1) time complexity but alters the indexed order of owned-tokens by + /// swapping `token_id` and the index thereof with the last token id and the index + /// thereof. fn _remove_token_from_owner_enumeration( ref self: ComponentState, from: ContractAddress, token_id: u256 ) { @@ -195,8 +210,9 @@ mod ERC721EnumerableComponent { } /// Removes `token_id` from this extension's token-tracking data structures. - /// This has 0(1) time complexity, but alters the order of the list by swapping - /// `token_id` and index thereof with the last token id and index thereof. + /// + /// This has 0(1) time complexity but alters the indexed order by swapping + /// `token_id` and the index thereof with the last token id and the index thereof. fn _remove_token_from_all_tokens_enumeration( ref self: ComponentState, token_id: u256 ) { From 8d02fb4b2d02e03d1247a82fb84690965f0bd6be Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 6 May 2024 11:08:51 -0400 Subject: [PATCH 22/73] add tests checking order of owner index --- src/tests/token/test_erc721_enumerable.cairo | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index 6acbbd4f3..b4aef0e18 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -197,6 +197,32 @@ fn test_token_of_owner_by_index_when_target_has_no_tokens() { state.token_of_owner_by_index(OTHER(), 0); } +#[test] +fn test_token_of_owner_by_index_remove_last_token() { + let state = setup(); + let mut contract_state = CONTRACT_STATE(); + let last_token = TOKEN_3; + + contract_state.erc721._transfer(OWNER(), RECIPIENT(), last_token); + + let expected_list = array![TOKEN_1, TOKEN_2]; + assert_token_of_owner_by_index(state, OWNER(), expected_list); +} + +#[test] +fn test_token_of_owner_by_index_remove_first_token() { + let state = setup(); + let mut contract_state = CONTRACT_STATE(); + let first_token = TOKEN_1; + + contract_state.erc721._transfer(OWNER(), RECIPIENT(), first_token); + + // Removed tokens are replaced by the last token + // to prevent indexing gaps + let expected_list = array![TOKEN_3, TOKEN_2]; + assert_token_of_owner_by_index(state, OWNER(), expected_list); +} + #[test] fn test_token_of_owner_by_index_when_all_tokens_transferred() { let state = setup(); From 72c65c145d61ac79e7fad630fea11c346ac978a1 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 6 May 2024 13:55:40 -0400 Subject: [PATCH 23/73] fix supply tests, add camel tests --- src/tests/token/test_erc721_enumerable.cairo | 84 +++++++++++++++----- 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index b4aef0e18..ebca8df34 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -1,7 +1,7 @@ use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; use openzeppelin::introspection; use openzeppelin::tests::mocks::erc721_enumerable_mocks::DualCaseERC721EnumerableMock; -use openzeppelin::tests::utils::constants::{OWNER, RECIPIENT, OTHER}; +use openzeppelin::tests::utils::constants::{OWNER, RECIPIENT, OTHER, ZERO}; use openzeppelin::token::erc721::ERC721Component::{ERC721Impl, InternalImpl as ERC721InternalImpl}; use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent::{ ERC721EnumerableImpl, ERC721EnumerableCamelImpl, InternalImpl @@ -73,22 +73,50 @@ fn test_initializer() { #[test] fn test_total_supply() { - let mut state = setup(); + let mut state = COMPONENT_STATE(); + let mut contract_state = CONTRACT_STATE(); + let token = TOKEN_1; - let supply = state.total_supply(); - assert_eq!(supply, TOKENS_LEN); + let no_supply = state.total_supply(); + assert_eq!(no_supply, 0); + + // Mint + contract_state.erc721._mint(OWNER(), token); + + let new_supply = state.total_supply(); + assert_eq!(new_supply, 1); + + // Burn + contract_state.erc721._burn(token); + + let no_supply = state.total_supply(); + assert_eq!(no_supply, 0); } #[test] fn test_totalSupply() { - let state = setup(); + let mut state = COMPONENT_STATE(); + let mut contract_state = CONTRACT_STATE(); + let token = TOKEN_1; + + let no_supply = state.totalSupply(); + assert_eq!(no_supply, 0); + + // Mint + contract_state.erc721._mint(OWNER(), token); - let supply = state.totalSupply(); - assert_eq!(supply, TOKENS_LEN); + let new_supply = state.totalSupply(); + assert_eq!(new_supply, 1); + + // Burn + contract_state.erc721._burn(token); + + let no_supply = state.totalSupply(); + assert_eq!(no_supply, 0); } // -// token_by_index +// token_by_index & tokenByIndex // #[test] @@ -96,7 +124,7 @@ fn test_token_by_index() { let state = setup(); let token_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; - assert_token_by_index(state, token_list); + assert_dual_token_by_index(state, token_list); } #[test] @@ -124,7 +152,7 @@ fn test_token_by_index_burn_last_token() { contract_state.erc721._burn(last_token); let expected_list = array![TOKEN_1, TOKEN_2]; - assert_token_by_index(state, expected_list); + assert_dual_token_by_index(state, expected_list); } #[test] @@ -138,7 +166,7 @@ fn test_token_by_index_burn_first_token() { // Burnt tokens are replaced by the last token // to prevent indexing gaps let expected_list = array![TOKEN_3, TOKEN_2]; - assert_token_by_index(state, expected_list); + assert_dual_token_by_index(state, expected_list); } #[test] @@ -158,11 +186,11 @@ fn test_token_by_index_burn_and_mint_all() { contract_state.erc721._mint(OWNER(), TOKEN_3); let expected_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; - assert_token_by_index(state, expected_list); + assert_dual_token_by_index(state, expected_list); } // -// token_of_owner_by_index +// token_of_owner_by_index & tokenOfOwnerByIndex // #[test] @@ -170,7 +198,7 @@ fn test_token_of_owner_by_index() { let state = setup(); let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; - assert_token_of_owner_by_index(state, OWNER(), tokens_list); + assert_dual_token_of_owner_by_index(state, OWNER(), tokens_list); } #[test] @@ -197,6 +225,14 @@ fn test_token_of_owner_by_index_when_target_has_no_tokens() { state.token_of_owner_by_index(OTHER(), 0); } +#[test] +#[should_panic(expected: ('ERC721: invalid account',))] +fn test_token_of_owner_by_index_when_owner_is_zero() { + let state = setup(); + + state.token_of_owner_by_index(ZERO(), 0); +} + #[test] fn test_token_of_owner_by_index_remove_last_token() { let state = setup(); @@ -206,7 +242,7 @@ fn test_token_of_owner_by_index_remove_last_token() { contract_state.erc721._transfer(OWNER(), RECIPIENT(), last_token); let expected_list = array![TOKEN_1, TOKEN_2]; - assert_token_of_owner_by_index(state, OWNER(), expected_list); + assert_dual_token_of_owner_by_index(state, OWNER(), expected_list); } #[test] @@ -220,7 +256,7 @@ fn test_token_of_owner_by_index_remove_first_token() { // Removed tokens are replaced by the last token // to prevent indexing gaps let expected_list = array![TOKEN_3, TOKEN_2]; - assert_token_of_owner_by_index(state, OWNER(), expected_list); + assert_dual_token_of_owner_by_index(state, OWNER(), expected_list); } #[test] @@ -233,14 +269,14 @@ fn test_token_of_owner_by_index_when_all_tokens_transferred() { contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_2); contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_3); - assert_token_of_owner_by_index(state, RECIPIENT(), tokens_list); + assert_dual_token_of_owner_by_index(state, RECIPIENT(), tokens_list); } // // Helpers // -fn assert_token_of_owner_by_index( +fn assert_dual_token_of_owner_by_index( state: ComponentState, owner: ContractAddress, expected_token_list: Array ) { let mut i = 0; @@ -248,20 +284,30 @@ fn assert_token_of_owner_by_index( if i == expected_token_list.len() { break; }; + // snake_case let token = state.token_of_owner_by_index(owner, i.into()); assert_eq!(token, *expected_token_list.at(i)); + + // camelCase + let token = state.tokenOfOwnerByIndex(owner, i.into()); + assert_eq!(token, *expected_token_list.at(i)); i = i + 1; }; } -fn assert_token_by_index(state: ComponentState, expected_token_list: Array) { +fn assert_dual_token_by_index(state: ComponentState, expected_token_list: Array) { let mut i = 0; loop { if i == expected_token_list.len() { break; }; + // snake_case let token = state.token_by_index(i.into()); assert_eq!(token, *expected_token_list.at(i)); + + // camelCase + let token = state.tokenByIndex(i.into()); + assert_eq!(token, *expected_token_list.at(i)); i = i + 1; }; } From 7ef4de40feeea4e6c1f9de8236c40dd08803fcbb Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 6 May 2024 19:43:09 -0400 Subject: [PATCH 24/73] add ierc721enumerable selectors --- src/utils/selectors.cairo | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/utils/selectors.cairo b/src/utils/selectors.cairo index 68657e99a..3128e8d11 100644 --- a/src/utils/selectors.cairo +++ b/src/utils/selectors.cairo @@ -50,6 +50,15 @@ const transferFrom: felt252 = selector!("transferFrom"); const safe_transfer_from: felt252 = selector!("safe_transfer_from"); const safeTransferFrom: felt252 = selector!("safeTransferFrom"); +// +// ERC721Enumerable +// + +const token_by_index: felt252 = selector!("token_by_index"); +const tokenByIndex: felt252 = selector!("tokenByIndex"); +const token_of_owner_by_index: felt252 = selector!("token_of_owner_by_index"); +const tokenOfOwnerByIndex: felt252 = selector!("tokenOfOwnerByIndex"); + // // ERC721Receiver // From 984000e912d31fcce6f2bf5f711fd5219d46091d Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 6 May 2024 19:44:37 -0400 Subject: [PATCH 25/73] add dualcase erc721enumerable --- src/tests/mocks/erc721_enumerable_mocks.cairo | 249 ++++++++++++++++++ src/token/erc721.cairo | 1 + src/token/erc721/dual721_enumerable.cairo | 206 +++++++++++++++ 3 files changed, 456 insertions(+) create mode 100644 src/token/erc721/dual721_enumerable.cairo diff --git a/src/tests/mocks/erc721_enumerable_mocks.cairo b/src/tests/mocks/erc721_enumerable_mocks.cairo index 612082b73..2123cfbd0 100644 --- a/src/tests/mocks/erc721_enumerable_mocks.cairo +++ b/src/tests/mocks/erc721_enumerable_mocks.cairo @@ -93,3 +93,252 @@ mod DualCaseERC721EnumerableMock { self.erc721._mint(recipient, token_id); } } + +#[starknet::contract] +mod SnakeERC721EnumerableMock { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::ERC721Component::ERC721HooksTrait; + use openzeppelin::token::erc721::ERC721Component; + use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent; + + use starknet::ContractAddress; + + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!( + path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent + ); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC721 + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + // ERC721Enumerable + #[abi(embed_v0)] + impl ERC721EnumerableImpl = + ERC721EnumerableComponent::ERC721EnumerableImpl; + impl ERC721EnumerableInternalImpl = ERC721EnumerableComponent::InternalImpl; + + // SRC5 + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + erc721_enumerable: ERC721EnumerableComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + ERC721EnumerableEvent: ERC721EnumerableComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + impl ERC721EnumerableHooksImpl< + TContractState, + impl ERC721Enumerable: ERC721EnumerableComponent::HasComponent, + impl HasComponent: ERC721Component::HasComponent, + +SRC5Component::HasComponent, + +Drop + > of ERC721Component::ERC721HooksTrait { + fn before_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { + let mut erc721_enumerable_component = get_dep_component_mut!( + ref self, ERC721Enumerable + ); + erc721_enumerable_component._update(to, token_id); + } + + fn after_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) {} + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + base_uri: ByteArray, + recipient: ContractAddress, + token_id: u256 + ) { + self.erc721.initializer(name, symbol, base_uri); + self.erc721_enumerable.initializer(); + self.erc721._mint(recipient, token_id); + } +} + +#[starknet::contract] +mod CamelERC721EnumerableMock { + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::ERC721Component::ERC721HooksTrait; + use openzeppelin::token::erc721::ERC721Component; + use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent; + + use starknet::ContractAddress; + + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!( + path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent + ); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC721 + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + // ERC721Enumerable + #[abi(embed_v0)] + impl ERC721EnumerableCamelImpl = + ERC721EnumerableComponent::ERC721EnumerableCamelImpl; + impl ERC721EnumerableInternalImpl = ERC721EnumerableComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + erc721_enumerable: ERC721EnumerableComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + ERC721EnumerableEvent: ERC721EnumerableComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + impl ERC721EnumerableHooksImpl< + TContractState, + impl ERC721Enumerable: ERC721EnumerableComponent::HasComponent, + impl HasComponent: ERC721Component::HasComponent, + +SRC5Component::HasComponent, + +Drop + > of ERC721Component::ERC721HooksTrait { + fn before_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { + let mut erc721_enumerable_component = get_dep_component_mut!( + ref self, ERC721Enumerable + ); + erc721_enumerable_component._update(to, token_id); + } + + fn after_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) {} + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + base_uri: ByteArray, + recipient: ContractAddress, + token_id: u256 + ) { + self.erc721.initializer(name, symbol, base_uri); + self.erc721_enumerable.initializer(); + self.erc721._mint(recipient, token_id); + } +} + +/// Although these modules are designed to panic, functions +/// still need a valid return value. We chose: +/// +/// 3 for felt252 +/// zero for ContractAddress +/// u256 { 3, 3 } for u256 +#[starknet::contract] +mod SnakeERC721EnumerablePanicMock { + use starknet::ContractAddress; + use zeroable::Zeroable; + + #[storage] + struct Storage {} + + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn total_supply(self: @ContractState) -> u256 { + panic!("Some error"); + u256 { low: 3, high: 3 } + } + + #[external(v0)] + fn token_by_index(self: @ContractState, index: u256) -> u256 { + panic!("Some error"); + u256 { low: 3, high: 3 } + } + + #[external(v0)] + fn token_of_owner_by_index(self: @ContractState, owner: ContractAddress, index: u256) -> u256 { + panic!("Some error"); + u256 { low: 3, high: 3 } + } + } +} + +#[starknet::contract] +mod CamelERC721EnumerablePanicMock { + use starknet::ContractAddress; + use zeroable::Zeroable; + + #[storage] + struct Storage {} + + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn totalSupply(self: @ContractState) -> u256 { + panic!("Some error"); + u256 { low: 3, high: 3 } + } + + #[external(v0)] + fn tokenByIndex(self: @ContractState, index: u256) -> u256 { + panic!("Some error"); + u256 { low: 3, high: 3 } + } + + #[external(v0)] + fn tokenOfOwnerByIndex(self: @ContractState, owner: ContractAddress, index: u256) -> u256 { + panic!("Some error"); + u256 { low: 3, high: 3 } + } + } +} diff --git a/src/token/erc721.cairo b/src/token/erc721.cairo index e899020c8..cae55c127 100644 --- a/src/token/erc721.cairo +++ b/src/token/erc721.cairo @@ -1,4 +1,5 @@ mod dual721; +mod dual721_enumerable; mod dual721_receiver; mod erc721; mod erc721_receiver; diff --git a/src/token/erc721/dual721_enumerable.cairo b/src/token/erc721/dual721_enumerable.cairo new file mode 100644 index 000000000..fdda6d01b --- /dev/null +++ b/src/token/erc721/dual721_enumerable.cairo @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.12.0 (token/erc721/dual721.cairo) + +use openzeppelin::utils::UnwrapAndCast; +use openzeppelin::utils::selectors; +use openzeppelin::utils::serde::SerializedAppend; +use openzeppelin::utils::try_selector_with_fallback; +use starknet::ContractAddress; +use starknet::SyscallResultTrait; +use starknet::call_contract_syscall; + +#[derive(Copy, Drop)] +struct DualCaseERC721Enumerable { + contract_address: ContractAddress +} + +trait DualCaseERC721EnumerableTrait { + fn name(self: @DualCaseERC721Enumerable) -> ByteArray; + fn symbol(self: @DualCaseERC721Enumerable) -> ByteArray; + fn token_uri(self: @DualCaseERC721Enumerable, token_id: u256) -> ByteArray; + fn balance_of(self: @DualCaseERC721Enumerable, account: ContractAddress) -> u256; + fn owner_of(self: @DualCaseERC721Enumerable, token_id: u256) -> ContractAddress; + fn get_approved(self: @DualCaseERC721Enumerable, token_id: u256) -> ContractAddress; + fn approve(self: @DualCaseERC721Enumerable, to: ContractAddress, token_id: u256); + fn set_approval_for_all(self: @DualCaseERC721Enumerable, operator: ContractAddress, approved: bool); + fn transfer_from( + self: @DualCaseERC721Enumerable, from: ContractAddress, to: ContractAddress, token_id: u256 + ); + fn is_approved_for_all( + self: @DualCaseERC721Enumerable, owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn safe_transfer_from( + self: @DualCaseERC721Enumerable, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ); + fn supports_interface(self: @DualCaseERC721Enumerable, interface_id: felt252) -> bool; + fn total_supply(self: @DualCaseERC721Enumerable) -> u256; + fn token_by_index(self: @DualCaseERC721Enumerable, index: u256) -> u256; + fn token_of_owner_by_index(self: @DualCaseERC721Enumerable, owner: ContractAddress, index: u256) -> u256; +} + +impl DualCaseERC72EnumerableImpl of DualCaseERC721EnumerableTrait { + fn name(self: @DualCaseERC721Enumerable) -> ByteArray { + call_contract_syscall(*self.contract_address, selectors::name, array![].span()) + .unwrap_and_cast() + } + + fn symbol(self: @DualCaseERC721Enumerable) -> ByteArray { + call_contract_syscall(*self.contract_address, selectors::symbol, array![].span()) + .unwrap_and_cast() + } + + fn token_uri(self: @DualCaseERC721Enumerable, token_id: u256) -> ByteArray { + let mut args = array![]; + args.append_serde(token_id); + + try_selector_with_fallback( + *self.contract_address, selectors::token_uri, selectors::tokenURI, args.span() + ) + .unwrap_and_cast() + } + + fn balance_of(self: @DualCaseERC721Enumerable, account: ContractAddress) -> u256 { + let mut args = array![]; + args.append_serde(account); + + try_selector_with_fallback( + *self.contract_address, selectors::balance_of, selectors::balanceOf, args.span() + ) + .unwrap_and_cast() + } + + fn owner_of(self: @DualCaseERC721Enumerable, token_id: u256) -> ContractAddress { + let mut args = array![]; + args.append_serde(token_id); + + try_selector_with_fallback( + *self.contract_address, selectors::owner_of, selectors::ownerOf, args.span() + ) + .unwrap_and_cast() + } + + fn get_approved(self: @DualCaseERC721Enumerable, token_id: u256) -> ContractAddress { + let mut args = array![]; + args.append_serde(token_id); + + try_selector_with_fallback( + *self.contract_address, selectors::get_approved, selectors::getApproved, args.span() + ) + .unwrap_and_cast() + } + + fn is_approved_for_all( + self: @DualCaseERC721Enumerable, owner: ContractAddress, operator: ContractAddress + ) -> bool { + let mut args = array![]; + args.append_serde(owner); + args.append_serde(operator); + + try_selector_with_fallback( + *self.contract_address, + selectors::is_approved_for_all, + selectors::isApprovedForAll, + args.span() + ) + .unwrap_and_cast() + } + + fn approve(self: @DualCaseERC721Enumerable, to: ContractAddress, token_id: u256) { + let mut args = array![]; + args.append_serde(to); + args.append_serde(token_id); + call_contract_syscall(*self.contract_address, selectors::approve, args.span()) + .unwrap_syscall(); + } + + fn set_approval_for_all(self: @DualCaseERC721Enumerable, operator: ContractAddress, approved: bool) { + let mut args = array![]; + args.append_serde(operator); + args.append_serde(approved); + + try_selector_with_fallback( + *self.contract_address, + selectors::set_approval_for_all, + selectors::setApprovalForAll, + args.span() + ) + .unwrap_syscall(); + } + + fn transfer_from( + self: @DualCaseERC721Enumerable, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + let mut args = array![]; + args.append_serde(from); + args.append_serde(to); + args.append_serde(token_id); + + try_selector_with_fallback( + *self.contract_address, selectors::transfer_from, selectors::transferFrom, args.span() + ) + .unwrap_syscall(); + } + + fn safe_transfer_from( + self: @DualCaseERC721Enumerable, + from: ContractAddress, + to: ContractAddress, + token_id: u256, + mut data: Span + ) { + let mut args = array![]; + args.append_serde(from); + args.append_serde(to); + args.append_serde(token_id); + args.append_serde(data); + + try_selector_with_fallback( + *self.contract_address, + selectors::safe_transfer_from, + selectors::safeTransferFrom, + args.span() + ) + .unwrap_syscall(); + } + + fn supports_interface(self: @DualCaseERC721Enumerable, interface_id: felt252) -> bool { + let mut args = array![]; + args.append_serde(interface_id); + + call_contract_syscall(*self.contract_address, selectors::supports_interface, args.span()) + .unwrap_and_cast() + } + + fn total_supply(self: @DualCaseERC721Enumerable) -> u256 { + let mut args = array![]; + try_selector_with_fallback( + *self.contract_address, selectors::total_supply, selectors::totalSupply, args.span() + ) + .unwrap_and_cast() + } + + fn token_by_index(self: @DualCaseERC721Enumerable, index: u256) -> u256 { + let mut args = array![]; + args.append_serde(index); + + try_selector_with_fallback( + *self.contract_address, selectors::token_by_index, selectors::tokenByIndex, args.span() + ) + .unwrap_and_cast() + } + + fn token_of_owner_by_index(self: @DualCaseERC721Enumerable, owner: ContractAddress, index: u256) -> u256 { + let mut args = array![]; + args.append_serde(owner); + args.append_serde(index); + + try_selector_with_fallback( + *self.contract_address, selectors::token_of_owner_by_index, selectors::tokenOfOwnerByIndex, args.span() + ) + .unwrap_and_cast() + } +} From 4e02b3c9c1790bdcdcf257db1d8ef576839d5cea Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 6 May 2024 19:44:54 -0400 Subject: [PATCH 26/73] add dualcase erc721enum tests --- src/tests/token.cairo | 1 + src/tests/token/test_dual721_enumerable.cairo | 161 ++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 src/tests/token/test_dual721_enumerable.cairo diff --git a/src/tests/token.cairo b/src/tests/token.cairo index 1f7e2d817..67b2d8142 100644 --- a/src/tests/token.cairo +++ b/src/tests/token.cairo @@ -2,6 +2,7 @@ mod test_dual1155; mod test_dual1155_receiver; mod test_dual20; mod test_dual721; +mod test_dual721_enumerable; mod test_dual721_receiver; mod test_erc1155; mod test_erc1155_receiver; diff --git a/src/tests/token/test_dual721_enumerable.cairo b/src/tests/token/test_dual721_enumerable.cairo new file mode 100644 index 000000000..2b4bb711e --- /dev/null +++ b/src/tests/token/test_dual721_enumerable.cairo @@ -0,0 +1,161 @@ +use openzeppelin::tests::mocks::erc721_enumerable_mocks::{CamelERC721EnumerableMock, SnakeERC721EnumerableMock}; +use openzeppelin::tests::mocks::erc721_enumerable_mocks::{CamelERC721EnumerablePanicMock, SnakeERC721EnumerablePanicMock}; +use openzeppelin::tests::mocks::non_implementing_mock::NonImplementingMock; +use openzeppelin::tests::utils::constants::{OWNER, NAME, SYMBOL, BASE_URI, TOKEN_ID}; +use openzeppelin::tests::utils; +use openzeppelin::token::erc721::dual721_enumerable::{DualCaseERC721Enumerable, DualCaseERC721EnumerableTrait}; +use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::{IERC721EnumerableDispatcher, IERC721EnumerableCamelDispatcher}; +use openzeppelin::utils::serde::SerializedAppend; +use starknet::ContractAddress; + +// +// Setup +// + +fn setup_snake() -> (DualCaseERC721Enumerable, IERC721EnumerableDispatcher) { + let mut calldata = array![]; + calldata.append_serde(NAME()); + calldata.append_serde(SYMBOL()); + calldata.append_serde(BASE_URI()); + calldata.append_serde(OWNER()); + calldata.append_serde(TOKEN_ID); + let target = utils::deploy(SnakeERC721EnumerableMock::TEST_CLASS_HASH, calldata); + (DualCaseERC721Enumerable { contract_address: target }, IERC721EnumerableDispatcher { contract_address: target }) +} + +fn setup_camel() -> (DualCaseERC721Enumerable, IERC721EnumerableCamelDispatcher) { + let mut calldata = array![]; + calldata.append_serde(NAME()); + calldata.append_serde(SYMBOL()); + calldata.append_serde(BASE_URI()); + calldata.append_serde(OWNER()); + calldata.append_serde(TOKEN_ID); + let target = utils::deploy(CamelERC721EnumerableMock::TEST_CLASS_HASH, calldata); + ( + DualCaseERC721Enumerable { contract_address: target }, + IERC721EnumerableCamelDispatcher { contract_address: target } + ) +} + +fn setup_non_erc721_enumerable() -> DualCaseERC721Enumerable { + let calldata = array![]; + let target = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, calldata); + DualCaseERC721Enumerable { contract_address: target } +} + +fn setup_erc721_enumerable_panic() -> (DualCaseERC721Enumerable, DualCaseERC721Enumerable) { + let snake_target = utils::deploy(SnakeERC721EnumerablePanicMock::TEST_CLASS_HASH, array![]); + let camel_target = utils::deploy(CamelERC721EnumerablePanicMock::TEST_CLASS_HASH, array![]); + ( + DualCaseERC721Enumerable { contract_address: snake_target }, + DualCaseERC721Enumerable { contract_address: camel_target } + ) +} + +// +// snake_case target +// + +#[test] +fn test_dual_total_supply() { + let (dispatcher, _) = setup_snake(); + assert_eq!(dispatcher.total_supply(), 1); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_total_supply() { + let dispatcher = setup_non_erc721_enumerable(); + dispatcher.total_supply(); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_total_supply_exists_and_panics() { + let (dispatcher, _) = setup_erc721_enumerable_panic(); + dispatcher.total_supply(); +} + +#[test] +fn test_dual_token_by_index() { + let (dispatcher, _) = setup_snake(); + assert_eq!(dispatcher.token_by_index(0), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_token_by_index() { + let dispatcher = setup_non_erc721_enumerable(); + dispatcher.token_by_index(0); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_token_by_index_exists_and_panics() { + let (dispatcher, _) = setup_erc721_enumerable_panic(); + dispatcher.token_by_index(0); +} + +#[test] +fn test_dual_token_of_owner_by_index() { + let (dispatcher, _) = setup_snake(); + assert_eq!(dispatcher.token_of_owner_by_index(OWNER(), 0), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] +fn test_dual_no_token_of_owner_by_index() { + let dispatcher = setup_non_erc721_enumerable(); + dispatcher.token_of_owner_by_index(OWNER(), 0); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_token_of_owner_by_index_exists_and_panics() { + let (dispatcher, _) = setup_erc721_enumerable_panic(); + dispatcher.token_of_owner_by_index(OWNER(), 0); +} + + +// +// camelCase target +// + +#[test] +fn test_dual_totalSupply() { + let (dispatcher, _) = setup_camel(); + assert_eq!(dispatcher.total_supply(), 1); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_totalSupply_exists_and_panics() { + let (_, dispatcher) = setup_erc721_enumerable_panic(); + dispatcher.total_supply(); +} + +#[test] +fn test_dual_tokenByIndex() { + let (dispatcher, _) = setup_camel(); + assert_eq!(dispatcher.token_by_index(0), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_tokenByIndex_exists_and_panics() { + let (_, dispatcher) = setup_erc721_enumerable_panic(); + dispatcher.token_by_index(0); +} + +#[test] +fn test_dual_tokenOfOwnerByIndex() { + let (dispatcher, _) = setup_camel(); + assert_eq!(dispatcher.token_of_owner_by_index(OWNER(), 0), TOKEN_ID); +} + +#[test] +#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] +fn test_dual_tokenOfOwnerByIndex_exists_and_panics() { + let (_, dispatcher) = setup_erc721_enumerable_panic(); + dispatcher.token_of_owner_by_index(OWNER(), 0); +} From a26167758e1962166cacbf2471517506ee605c66 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 6 May 2024 19:46:01 -0400 Subject: [PATCH 27/73] fix formatting --- src/tests/mocks/erc721_enumerable_mocks.cairo | 4 +++- src/tests/token/test_dual721_enumerable.cairo | 22 ++++++++++++++----- src/token/erc721/dual721_enumerable.cairo | 21 +++++++++++++----- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/tests/mocks/erc721_enumerable_mocks.cairo b/src/tests/mocks/erc721_enumerable_mocks.cairo index 2123cfbd0..677570c13 100644 --- a/src/tests/mocks/erc721_enumerable_mocks.cairo +++ b/src/tests/mocks/erc721_enumerable_mocks.cairo @@ -305,7 +305,9 @@ mod SnakeERC721EnumerablePanicMock { } #[external(v0)] - fn token_of_owner_by_index(self: @ContractState, owner: ContractAddress, index: u256) -> u256 { + fn token_of_owner_by_index( + self: @ContractState, owner: ContractAddress, index: u256 + ) -> u256 { panic!("Some error"); u256 { low: 3, high: 3 } } diff --git a/src/tests/token/test_dual721_enumerable.cairo b/src/tests/token/test_dual721_enumerable.cairo index 2b4bb711e..ade616769 100644 --- a/src/tests/token/test_dual721_enumerable.cairo +++ b/src/tests/token/test_dual721_enumerable.cairo @@ -1,10 +1,18 @@ -use openzeppelin::tests::mocks::erc721_enumerable_mocks::{CamelERC721EnumerableMock, SnakeERC721EnumerableMock}; -use openzeppelin::tests::mocks::erc721_enumerable_mocks::{CamelERC721EnumerablePanicMock, SnakeERC721EnumerablePanicMock}; +use openzeppelin::tests::mocks::erc721_enumerable_mocks::{ + CamelERC721EnumerableMock, SnakeERC721EnumerableMock +}; +use openzeppelin::tests::mocks::erc721_enumerable_mocks::{ + CamelERC721EnumerablePanicMock, SnakeERC721EnumerablePanicMock +}; use openzeppelin::tests::mocks::non_implementing_mock::NonImplementingMock; use openzeppelin::tests::utils::constants::{OWNER, NAME, SYMBOL, BASE_URI, TOKEN_ID}; use openzeppelin::tests::utils; -use openzeppelin::token::erc721::dual721_enumerable::{DualCaseERC721Enumerable, DualCaseERC721EnumerableTrait}; -use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::{IERC721EnumerableDispatcher, IERC721EnumerableCamelDispatcher}; +use openzeppelin::token::erc721::dual721_enumerable::{ + DualCaseERC721Enumerable, DualCaseERC721EnumerableTrait +}; +use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::{ + IERC721EnumerableDispatcher, IERC721EnumerableCamelDispatcher +}; use openzeppelin::utils::serde::SerializedAppend; use starknet::ContractAddress; @@ -20,7 +28,10 @@ fn setup_snake() -> (DualCaseERC721Enumerable, IERC721EnumerableDispatcher) { calldata.append_serde(OWNER()); calldata.append_serde(TOKEN_ID); let target = utils::deploy(SnakeERC721EnumerableMock::TEST_CLASS_HASH, calldata); - (DualCaseERC721Enumerable { contract_address: target }, IERC721EnumerableDispatcher { contract_address: target }) + ( + DualCaseERC721Enumerable { contract_address: target }, + IERC721EnumerableDispatcher { contract_address: target } + ) } fn setup_camel() -> (DualCaseERC721Enumerable, IERC721EnumerableCamelDispatcher) { @@ -116,7 +127,6 @@ fn test_dual_token_of_owner_by_index_exists_and_panics() { dispatcher.token_of_owner_by_index(OWNER(), 0); } - // // camelCase target // diff --git a/src/token/erc721/dual721_enumerable.cairo b/src/token/erc721/dual721_enumerable.cairo index fdda6d01b..57ad2d6bd 100644 --- a/src/token/erc721/dual721_enumerable.cairo +++ b/src/token/erc721/dual721_enumerable.cairo @@ -22,7 +22,9 @@ trait DualCaseERC721EnumerableTrait { fn owner_of(self: @DualCaseERC721Enumerable, token_id: u256) -> ContractAddress; fn get_approved(self: @DualCaseERC721Enumerable, token_id: u256) -> ContractAddress; fn approve(self: @DualCaseERC721Enumerable, to: ContractAddress, token_id: u256); - fn set_approval_for_all(self: @DualCaseERC721Enumerable, operator: ContractAddress, approved: bool); + fn set_approval_for_all( + self: @DualCaseERC721Enumerable, operator: ContractAddress, approved: bool + ); fn transfer_from( self: @DualCaseERC721Enumerable, from: ContractAddress, to: ContractAddress, token_id: u256 ); @@ -39,7 +41,9 @@ trait DualCaseERC721EnumerableTrait { fn supports_interface(self: @DualCaseERC721Enumerable, interface_id: felt252) -> bool; fn total_supply(self: @DualCaseERC721Enumerable) -> u256; fn token_by_index(self: @DualCaseERC721Enumerable, index: u256) -> u256; - fn token_of_owner_by_index(self: @DualCaseERC721Enumerable, owner: ContractAddress, index: u256) -> u256; + fn token_of_owner_by_index( + self: @DualCaseERC721Enumerable, owner: ContractAddress, index: u256 + ) -> u256; } impl DualCaseERC72EnumerableImpl of DualCaseERC721EnumerableTrait { @@ -117,7 +121,9 @@ impl DualCaseERC72EnumerableImpl of DualCaseERC721EnumerableTrait { .unwrap_syscall(); } - fn set_approval_for_all(self: @DualCaseERC721Enumerable, operator: ContractAddress, approved: bool) { + fn set_approval_for_all( + self: @DualCaseERC721Enumerable, operator: ContractAddress, approved: bool + ) { let mut args = array![]; args.append_serde(operator); args.append_serde(approved); @@ -193,13 +199,18 @@ impl DualCaseERC72EnumerableImpl of DualCaseERC721EnumerableTrait { .unwrap_and_cast() } - fn token_of_owner_by_index(self: @DualCaseERC721Enumerable, owner: ContractAddress, index: u256) -> u256 { + fn token_of_owner_by_index( + self: @DualCaseERC721Enumerable, owner: ContractAddress, index: u256 + ) -> u256 { let mut args = array![]; args.append_serde(owner); args.append_serde(index); try_selector_with_fallback( - *self.contract_address, selectors::token_of_owner_by_index, selectors::tokenOfOwnerByIndex, args.span() + *self.contract_address, + selectors::token_of_owner_by_index, + selectors::tokenOfOwnerByIndex, + args.span() ) .unwrap_and_cast() } From 2c0474ecdce9ece1b1056cb771ad3815cb8fa62b Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 6 May 2024 19:55:04 -0400 Subject: [PATCH 28/73] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c31036a6..cc86518b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - before_update and after_update hooks to ERC721Component (#978) +- ERC721Enumerable component ## 0.12.0 (2024-04-21) From 7f849dccfe429a9b482f821a108df914ef27c5af Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 6 May 2024 20:00:43 -0400 Subject: [PATCH 29/73] update changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc86518b2..44f0c6e4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - before_update and after_update hooks to ERC721Component (#978) -- ERC721Enumerable component +- ERC721Enumerable component (#983) ## 0.12.0 (2024-04-21) From c3a029e62518c1447071b3b6ebed0401ff35ef4e Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 7 May 2024 11:03:20 -0400 Subject: [PATCH 30/73] change _update to before_update --- docs/modules/ROOT/pages/api/erc721.adoc | 12 +++++++----- src/tests/mocks/erc721_enumerable_mocks.cairo | 12 +++--------- .../erc721_enumerable/erc721_enumerable.cairo | 4 +++- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 81381e9c8..83744e26d 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -831,8 +831,8 @@ Extension of ERC721 as defined in the EIP that adds enumerability of all the tok NOTE: Implementing xref:#ERC721Component[ERC721Component] is a requirement for this component to be implemented. -WARNING: To properly track token ids, this extension requires that the xref:#ERC721EnumerableComponent-_update[ERC721EnumerableComponent::_update] function is called after every transfer, mint, or burn operation. -This must be included in the xref:ERC721Component-before_update[ERC721HooksTrait::before_update] hook. +WARNING: To properly track token ids, this extension requires that the xref:#ERC721EnumerableComponent-before_update[ERC721EnumerableComponent::before_update] function is called after every transfer, mint, or burn operation. +For this, the xref:ERC721Component-before_update[ERC721HooksTrait::before_update] hook must be used. This extension allows contracts to publish their entire list of NFTs and make them discoverable. @@ -857,7 +857,7 @@ This extension allows contracts to publish their entire list of NFTs and make th -- .InternalImpl * xref:#ERC721EnumerableComponent-initializer[`++initializer(self)++`] -* xref:#ERC721EnumerableComponent-_update[`++_update(self, to, token_id)++`] +* xref:#ERC721EnumerableComponent-before_update[`++before_update(self, to, token_id)++`] .PrivateImpl * xref:#ERC721EnumerableComponent-_add_token_to_owner_enumeration[`++_add_token_to_owner_enumeration(self, to, token_id)++`] @@ -924,8 +924,8 @@ See <, to: ContractAddress, token_id: u256) { + /// + /// This must be added to the implementing contract's `ERC721HooksTrait::before_update` hook. + fn before_update(ref self: ComponentState, to: ContractAddress, token_id: u256) { let erc721_component = get_dep_component!(@self, ERC721); let previous_owner = erc721_component._owner_of(token_id); let zero_address = Zeroable::zero(); From 72b75cdef2d49ff7c45222bc68d803da010250a3 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 7 May 2024 11:04:15 -0400 Subject: [PATCH 31/73] fix formatting --- .../extensions/erc721_enumerable/erc721_enumerable.cairo | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 0bc2d1c9b..10599cd9a 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -132,7 +132,9 @@ mod ERC721EnumerableComponent { /// the change in ownership of `token_id`. /// /// This must be added to the implementing contract's `ERC721HooksTrait::before_update` hook. - fn before_update(ref self: ComponentState, to: ContractAddress, token_id: u256) { + fn before_update( + ref self: ComponentState, to: ContractAddress, token_id: u256 + ) { let erc721_component = get_dep_component!(@self, ERC721); let previous_owner = erc721_component._owner_of(token_id); let zero_address = Zeroable::zero(); From 3170a7bbb8817aa5ee28dd2350aa0e9ba2dc291e Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 7 May 2024 11:32:00 -0400 Subject: [PATCH 32/73] remove unused imports --- .../erc721/extensions/erc721_enumerable/erc721_enumerable.cairo | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 10599cd9a..07cb05d67 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -7,9 +7,7 @@ #[starknet::component] mod ERC721EnumerableComponent { use openzeppelin::introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait; - use openzeppelin::introspection::src5::SRC5Component::SRC5; use openzeppelin::introspection::src5::SRC5Component; - use openzeppelin::token::erc721::ERC721Component::ERC721HooksTrait; use openzeppelin::token::erc721::ERC721Component::ERC721Impl; use openzeppelin::token::erc721::ERC721Component::InternalImpl as ERC721InternalImpl; use openzeppelin::token::erc721::ERC721Component; From f904280839e3f9025656cc6eb3e09f0fc27757ba Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 11 May 2024 16:55:28 -0400 Subject: [PATCH 33/73] remove excess error, fix _remove_token_from_owner_enumeration --- .../extensions/erc721_enumerable/erc721_enumerable.cairo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 07cb05d67..c87cf65fe 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -55,7 +55,6 @@ mod ERC721EnumerableComponent { /// /// - `index` is less than the total token supply. fn token_by_index(self: @ComponentState, index: u256) -> u256 { - assert(index < self.total_supply(), Errors::OUT_OF_BOUNDS_INDEX); assert(index < self.total_supply(), Errors::OUT_OF_BOUNDS_INDEX); self.ERC721Enumerable_all_tokens.read(index) } @@ -81,7 +80,7 @@ mod ERC721EnumerableComponent { impl ERC721EnumerableCamel< TContractState, +HasComponent, - impl ERC721: ERC721Component::HasComponent, + +ERC721Component::HasComponent, +ERC721Component::ERC721HooksTrait, +SRC5Component::HasComponent, +Drop @@ -207,8 +206,9 @@ mod ERC721EnumerableComponent { self.ERC721Enumerable_owned_tokens_index.write(last_token_id, this_token_index); } - // Remove `token_id` from last token index position + // Set the last token index and `token_id` to zero self.ERC721Enumerable_owned_tokens.write((from, last_token_index), 0); + self.ERC721Enumerable_owned_tokens_index.write(token_id, 0); } /// Removes `token_id` from this extension's token-tracking data structures. From 33ee6b7bb9388e13003578b2299f40d7a72d5300 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 11 May 2024 16:55:43 -0400 Subject: [PATCH 34/73] add internal tests --- src/tests/token/test_erc721_enumerable.cairo | 407 +++++++++++++++++-- 1 file changed, 364 insertions(+), 43 deletions(-) diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index ebca8df34..ca7f49306 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -1,3 +1,4 @@ +use openzeppelin::token::erc721::extensions::erc721_enumerable::erc721_enumerable::ERC721EnumerableComponent::PrivateTrait; use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; use openzeppelin::introspection; use openzeppelin::tests::mocks::erc721_enumerable_mocks::DualCaseERC721EnumerableMock; @@ -9,11 +10,12 @@ use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721Enumerable use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent; use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; use starknet::ContractAddress; +use starknet::storage::{StorageMemberAccessTrait, StorageMapMemberAccessTrait}; // Token IDs -const TOKEN_1: u256 = 111; -const TOKEN_2: u256 = 222; -const TOKEN_3: u256 = 333; +const TOKEN_1: u256 = 'TOKEN_1'; +const TOKEN_2: u256 = 'TOKEN_2'; +const TOKEN_3: u256 = 'TOKEN_3'; const TOKENS_LEN: u256 = 3; @@ -32,21 +34,25 @@ fn COMPONENT_STATE() -> ComponentState { ERC721EnumerableComponent::component_state_for_testing() } -fn setup() -> ComponentState { +fn setup() -> (ComponentState, Span) { let mut state = COMPONENT_STATE(); let mut mock_state = CONTRACT_STATE(); state.initializer(); - let mut tokens = array![TOKEN_1, TOKEN_2, TOKEN_3]; + let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3].span(); + let mut i = 0; + loop { - if tokens.len() == 0 { + if i == tokens_list.len() { break; }; - let token = tokens.pop_front().unwrap(); + + let token = *tokens_list.at(i); mock_state.erc721._mint(OWNER(), token); + i = i + 1; }; - state + (state, tokens_list) } // @@ -121,43 +127,44 @@ fn test_totalSupply() { #[test] fn test_token_by_index() { - let state = setup(); - let token_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; + let (_, token_list) = setup(); - assert_dual_token_by_index(state, token_list); + assert_dual_token_by_index(token_list); } #[test] #[should_panic(expected: ('ERC721Enum: out of bounds index',))] fn test_token_by_index_equal_to_supply() { - let state = setup(); + let (state, token_list) = setup(); + let supply = token_list.len().into(); - state.token_by_index(TOKENS_LEN); + state.token_by_index(supply); } #[test] #[should_panic(expected: ('ERC721Enum: out of bounds index',))] fn test_token_by_index_greater_than_supply() { - let state = setup(); + let (state, token_list) = setup(); + let supply_plus_one = token_list.len().into() + 1; - state.token_by_index(TOKENS_LEN + 1); + state.token_by_index(supply_plus_one); } #[test] fn test_token_by_index_burn_last_token() { - let state = setup(); + let (_, _) = setup(); let mut contract_state = CONTRACT_STATE(); let last_token = TOKEN_3; contract_state.erc721._burn(last_token); let expected_list = array![TOKEN_1, TOKEN_2]; - assert_dual_token_by_index(state, expected_list); + assert_dual_token_by_index(expected_list.span()); } #[test] fn test_token_by_index_burn_first_token() { - let state = setup(); + let (_, _) = setup(); let mut contract_state = CONTRACT_STATE(); let first_token = TOKEN_1; @@ -166,12 +173,12 @@ fn test_token_by_index_burn_first_token() { // Burnt tokens are replaced by the last token // to prevent indexing gaps let expected_list = array![TOKEN_3, TOKEN_2]; - assert_dual_token_by_index(state, expected_list); + assert_dual_token_by_index(expected_list.span()); } #[test] fn test_token_by_index_burn_and_mint_all() { - let state = setup(); + let (state, _) = setup(); let mut contract_state = CONTRACT_STATE(); contract_state.erc721._burn(TOKEN_2); @@ -186,7 +193,7 @@ fn test_token_by_index_burn_and_mint_all() { contract_state.erc721._mint(OWNER(), TOKEN_3); let expected_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; - assert_dual_token_by_index(state, expected_list); + assert_dual_token_by_index(expected_list.span()); } // @@ -195,32 +202,33 @@ fn test_token_by_index_burn_and_mint_all() { #[test] fn test_token_of_owner_by_index() { - let state = setup(); - let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; + let (_, tokens_list) = setup(); - assert_dual_token_of_owner_by_index(state, OWNER(), tokens_list); + assert_dual_token_of_owner_by_index(OWNER(), tokens_list); } #[test] #[should_panic(expected: ('ERC721Enum: out of bounds index',))] fn test_token_of_owner_by_index_when_index_equals_owned_tokens() { - let state = setup(); + let (state, tokens_list) = setup(); + let owned_token_len = tokens_list.len().into(); - state.token_of_owner_by_index(OWNER(), TOKENS_LEN); + state.token_of_owner_by_index(OWNER(), owned_token_len); } #[test] #[should_panic(expected: ('ERC721Enum: out of bounds index',))] fn test_token_of_owner_by_index_when_index_exceeds_owned_tokens() { - let state = setup(); + let (state, tokens_list) = setup(); + let owned_tokens_len_plus_one = tokens_list.len().into() + 1; - state.token_of_owner_by_index(OWNER(), TOKENS_LEN + 1); + state.token_of_owner_by_index(OWNER(), owned_tokens_len_plus_one); } #[test] #[should_panic(expected: ('ERC721Enum: out of bounds index',))] fn test_token_of_owner_by_index_when_target_has_no_tokens() { - let state = setup(); + let (state, _) = setup(); state.token_of_owner_by_index(OTHER(), 0); } @@ -228,57 +236,306 @@ fn test_token_of_owner_by_index_when_target_has_no_tokens() { #[test] #[should_panic(expected: ('ERC721: invalid account',))] fn test_token_of_owner_by_index_when_owner_is_zero() { - let state = setup(); + let (state, _) = setup(); state.token_of_owner_by_index(ZERO(), 0); } #[test] fn test_token_of_owner_by_index_remove_last_token() { - let state = setup(); + let (_, tokens_list) = setup(); let mut contract_state = CONTRACT_STATE(); - let last_token = TOKEN_3; + let last_token = *tokens_list.at(tokens_list.len() - 1); contract_state.erc721._transfer(OWNER(), RECIPIENT(), last_token); let expected_list = array![TOKEN_1, TOKEN_2]; - assert_dual_token_of_owner_by_index(state, OWNER(), expected_list); + assert_dual_token_of_owner_by_index(OWNER(), expected_list.span()); } #[test] fn test_token_of_owner_by_index_remove_first_token() { - let state = setup(); + let (_, tokens_list) = setup(); let mut contract_state = CONTRACT_STATE(); - let first_token = TOKEN_1; + let first_token = *tokens_list.at(0); contract_state.erc721._transfer(OWNER(), RECIPIENT(), first_token); // Removed tokens are replaced by the last token // to prevent indexing gaps let expected_list = array![TOKEN_3, TOKEN_2]; - assert_dual_token_of_owner_by_index(state, OWNER(), expected_list); + assert_dual_token_of_owner_by_index(OWNER(), expected_list.span()); } #[test] fn test_token_of_owner_by_index_when_all_tokens_transferred() { - let state = setup(); + let (_, tokens_list) = setup(); let mut contract_state = CONTRACT_STATE(); - let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_1); contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_2); contract_state.erc721._transfer(OWNER(), RECIPIENT(), TOKEN_3); - assert_dual_token_of_owner_by_index(state, RECIPIENT(), tokens_list); + assert_dual_token_of_owner_by_index(RECIPIENT(), tokens_list); +} + +// +// _update +// + +#[test] +fn test__update_when_mint() { + let (mut state, _) = setup(); + let initial_supply = state.total_supply(); + let new_token = 'TOKEN_4'; + + state.before_update(OWNER(), new_token); + + // Check new supply + let new_supply = state.total_supply(); + assert_eq!(initial_supply + 1, new_supply); + + // Check owner's tokens + let exp_owner_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3, new_token]; + assert_after_update_owned_tokens_list(OWNER(), exp_owner_tokens.span()); + + // Check total tokens list + let exp_total_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3, new_token]; + assert_dual_token_by_index(exp_total_tokens.span()); +} + +#[test] +fn test__update_when_last_token_burned() { + let (mut state, tokens_list) = setup(); + let initial_supply = state.total_supply(); + let last_token_to_burn = *tokens_list.at(initial_supply.try_into().unwrap() - 1); + + state.before_update(ZERO(), last_token_to_burn); + + // Check new supply + let new_supply = state.total_supply(); + assert_eq!(initial_supply - 1, new_supply); + + // Check owner's tokens + let exp_owner_tokens = array![TOKEN_1, TOKEN_2]; + assert_after_update_owned_tokens_list(OWNER(), exp_owner_tokens.span()); + + // Check total tokens + let exp_total_tokens = array![TOKEN_1, TOKEN_2]; + assert_after_update_all_tokens_list(exp_total_tokens.span()); +} + +#[test] +fn test__update_when_first_token_burned() { + let (mut state, tokens_list) = setup(); + let initial_supply = state.total_supply(); + let first_token_to_burn = *tokens_list.at(0); + + state.before_update(ZERO(), first_token_to_burn); + + // Check new supply + let new_supply = state.total_supply(); + assert_eq!(initial_supply - 1, new_supply); + + // Removed tokens are replaced by the last token + // to prevent indexing gaps + // + // Check owner's tokens + let exp_owner_tokens = array![TOKEN_3, TOKEN_2]; + assert_after_update_owned_tokens_list(OWNER(), exp_owner_tokens.span()); + + // Check total tokens + let exp_total_tokens = array![TOKEN_3, TOKEN_2]; + assert_after_update_all_tokens_list(exp_total_tokens.span()); +} + +#[test] +fn test__update_when_transfer_last_token() { + let (mut state, tokens_list) = setup(); + let initial_supply = state.total_supply(); + let transfer_token = *tokens_list.at(initial_supply.try_into().unwrap() - 1); + + state.before_update(RECIPIENT(), transfer_token); + + // Check supply doesn't change + let new_supply = state.total_supply(); + assert_eq!(initial_supply, new_supply); + + // Check owner's tokens + let exp_owner_tokens = array![TOKEN_1, TOKEN_2]; + assert_after_update_owned_tokens_list(OWNER(), exp_owner_tokens.span()); + + // Check recipient's tokens + let exp_recipient_tokens = array![transfer_token]; + assert_after_update_owned_tokens_list(RECIPIENT(), exp_recipient_tokens.span()); + + // Check total tokens + let exp_total_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3]; + assert_after_update_all_tokens_list(exp_total_tokens.span()); +} + +#[test] +fn test__update_when_transfer_first_token() { + let (mut state, tokens_list) = setup(); + let initial_supply = state.total_supply(); + let transfer_token = *tokens_list.at(0); + + state.before_update(RECIPIENT(), transfer_token); + + // Check supply doesn't change + let new_supply = state.total_supply(); + assert_eq!(initial_supply, new_supply); + + // Removed tokens are replaced by the last token + // to prevent indexing gaps + // + // Check owner's tokens + let exp_owner_tokens = array![TOKEN_3, TOKEN_2]; + assert_after_update_owned_tokens_list(OWNER(), exp_owner_tokens.span()); + + // Check recipient's tokens + let exp_recipient_tokens = array![transfer_token]; + assert_after_update_owned_tokens_list(RECIPIENT(), exp_recipient_tokens.span()); + + // Check all tokens + let exp_total_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3]; + assert_after_update_all_tokens_list(exp_total_tokens.span()); +} + +// +// _add_token_to_owner_enumeration +// + +#[test] +fn test__add_token_to_owner_enumeration() { + let (mut state, tokens_list) = setup(); + let new_token_id = 'TOKEN_4'; + let new_token_index = tokens_list.len().into(); + + assert_owner_tokens_index_to_id(OWNER(), new_token_index, 0); + assert_owner_tokens_id_to_index(new_token_id, 0); + + state._add_token_to_owner_enumeration(OWNER(), new_token_id); + + assert_owner_tokens_index_to_id(OWNER(), new_token_index, new_token_id); + assert_owner_tokens_id_to_index(new_token_id, new_token_index); +} + +// +// _add_token_to_all_tokens_enumeration +// + +#[test] +fn test__add_token_to_all_tokens_enumeration() { + let (mut state, _) = setup(); + let initial_supply = state.total_supply(); + let new_token_id = 'TOKEN_4'; + let new_token_index = initial_supply; + + assert_all_tokens_index_to_id(new_token_index, 0); + assert_all_tokens_id_to_index(new_token_id, 0); + + state._add_token_to_all_tokens_enumeration(new_token_id); + + assert_all_tokens_index_to_id(new_token_index, new_token_id); + assert_all_tokens_id_to_index(new_token_id, new_token_index); + + // Check supply + let new_supply = state.total_supply(); + assert_eq!(initial_supply + 1, new_supply); +} + +// +// _remove_token_from_owner_enumeration +// + +#[test] +fn test__remove_token_from_owner_enumeration_with_last_token() { + let (mut state, tokens_list) = setup(); + let last_token_index = state.total_supply() - 1; + let last_token_id = *tokens_list.at(last_token_index.try_into().unwrap()); + + assert_owner_tokens_index_to_id(OWNER(), last_token_index, last_token_id); + assert_owner_tokens_id_to_index(last_token_id, last_token_index); + + state._remove_token_from_owner_enumeration(OWNER(), last_token_id); + + assert_owner_tokens_index_to_id(OWNER(), last_token_index, 0); + assert_owner_tokens_id_to_index(last_token_id, 0); +} + +#[test] +fn test__remove_token_from_owner_enumeration_with_first_token() { + let (mut state, tokens_list) = setup(); + let first_token_index = 0; + let first_token_id = *tokens_list.at(0); + let last_token_index = tokens_list.len() - 1; + let last_token_id = *tokens_list.at(last_token_index); + + assert_owner_tokens_index_to_id(OWNER(), first_token_index, first_token_id); + assert_owner_tokens_id_to_index(first_token_id, first_token_index); + + state._remove_token_from_owner_enumeration(OWNER(), first_token_id); + + // Note that the original last indexed token id is now the first because of the + // swap-and-pop operation. + assert_owner_tokens_index_to_id(OWNER(), first_token_index, last_token_id); + assert_owner_tokens_id_to_index(first_token_id, 0); +} + +// +// _remove_token_from_all_tokens_enumeration +// + +#[test] +fn test__remove_token_from_all_tokens_enumeration_with_last_token() { + let (mut state, tokens_list) = setup(); + let initial_supply = state.total_supply(); + let last_token_index = state.total_supply() - 1; + let last_token_id = *tokens_list.at(last_token_index.try_into().unwrap()); + + assert_all_tokens_index_to_id(last_token_index, last_token_id); + assert_all_tokens_id_to_index(last_token_id, last_token_index); + + state._remove_token_from_all_tokens_enumeration(last_token_id); + + assert_all_tokens_index_to_id(last_token_index, last_token_id); + assert_all_tokens_id_to_index(last_token_id, last_token_index); + + // Check supply + let new_supply = state.total_supply(); + assert_eq!(initial_supply - 1, new_supply); +} + +#[test] +fn test__remove_token_from_all_tokens_enumeration_with_first_token() { + let (mut state, tokens_list) = setup(); + let initial_supply = state.total_supply(); + let first_token_index = 0; + let first_token_id = *tokens_list.at(0); + let last_token_index = tokens_list.len() - 1; + let last_token_id = *tokens_list.at(last_token_index); + + assert_all_tokens_index_to_id(first_token_index, first_token_id); + assert_all_tokens_id_to_index(first_token_id, first_token_index); + + state._remove_token_from_all_tokens_enumeration(first_token_id); + + assert_all_tokens_index_to_id(first_token_index, last_token_id); + assert_all_tokens_id_to_index(first_token_id, 0); + + // Check supply + let new_supply = state.total_supply(); + assert_eq!(initial_supply - 1, new_supply); } // // Helpers // -fn assert_dual_token_of_owner_by_index( - state: ComponentState, owner: ContractAddress, expected_token_list: Array -) { +fn assert_dual_token_of_owner_by_index(owner: ContractAddress, expected_token_list: Span) { + let mut state = COMPONENT_STATE(); + let mut i = 0; loop { if i == expected_token_list.len() { @@ -291,11 +548,14 @@ fn assert_dual_token_of_owner_by_index( // camelCase let token = state.tokenOfOwnerByIndex(owner, i.into()); assert_eq!(token, *expected_token_list.at(i)); + i = i + 1; }; } -fn assert_dual_token_by_index(state: ComponentState, expected_token_list: Array) { +fn assert_dual_token_by_index(expected_token_list: Span) { + let mut state = COMPONENT_STATE(); + let mut i = 0; loop { if i == expected_token_list.len() { @@ -308,6 +568,67 @@ fn assert_dual_token_by_index(state: ComponentState, expected_token_list: Array< // camelCase let token = state.tokenByIndex(i.into()); assert_eq!(token, *expected_token_list.at(i)); + + i = i + 1; + }; +} + +fn assert_after_update_all_tokens_list(expected_list: Span) { + let state = COMPONENT_STATE(); + + let mut i = 0; + loop { + if i == expected_list.len() { + break; + }; + // Check total tokens list + let token = state.ERC721Enumerable_all_tokens.read(i.into()); + assert_eq!(token, *expected_list.at(i)); + i = i + 1; }; } + +fn assert_after_update_owned_tokens_list(owner: ContractAddress, expected_list: Span) { + let state = COMPONENT_STATE(); + + let mut i = 0; + loop { + if i == expected_list.len() { + break; + }; + // Check owned tokens list + let token = state.ERC721Enumerable_owned_tokens.read((owner, i.into())); + assert_eq!(token, *expected_list.at(i)); + + i = i + 1; + }; +} + +fn assert_all_tokens_index_to_id(index: u256, exp_token_id: u256) { + let state = COMPONENT_STATE(); + + let index_to_id = state.ERC721Enumerable_all_tokens.read(index); + assert_eq!(index_to_id, exp_token_id); +} + +fn assert_all_tokens_id_to_index(token_id: u256, exp_index: u256) { + let state = COMPONENT_STATE(); + + let id_to_index = state.ERC721Enumerable_all_tokens_index.read(token_id); + assert_eq!(id_to_index, exp_index); +} + +fn assert_owner_tokens_index_to_id(owner: ContractAddress, index: u256, exp_token_id: u256) { + let state = COMPONENT_STATE(); + + let index_to_id = state.ERC721Enumerable_owned_tokens.read((owner, index)); + assert_eq!(index_to_id, exp_token_id); +} + +fn assert_owner_tokens_id_to_index(token_id: u256, exp_index: u256) { + let state = COMPONENT_STATE(); + + let id_to_index = state.ERC721Enumerable_owned_tokens_index.read(token_id); + assert_eq!(id_to_index, exp_index); +} From 66d747179e5a4f64a8267dee051a09b65c1d5150 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 11 May 2024 16:57:33 -0400 Subject: [PATCH 35/73] fix formatting --- src/tests/token/test_erc721_enumerable.cairo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index ca7f49306..a5eba5bb8 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -1,4 +1,3 @@ -use openzeppelin::token::erc721::extensions::erc721_enumerable::erc721_enumerable::ERC721EnumerableComponent::PrivateTrait; use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; use openzeppelin::introspection; use openzeppelin::tests::mocks::erc721_enumerable_mocks::DualCaseERC721EnumerableMock; @@ -8,6 +7,7 @@ use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721Enumerable ERC721EnumerableImpl, ERC721EnumerableCamelImpl, InternalImpl }; use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent; +use openzeppelin::token::erc721::extensions::erc721_enumerable::erc721_enumerable::ERC721EnumerableComponent::PrivateTrait; use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; use starknet::ContractAddress; use starknet::storage::{StorageMemberAccessTrait, StorageMapMemberAccessTrait}; @@ -578,7 +578,7 @@ fn assert_after_update_all_tokens_list(expected_list: Span) { let mut i = 0; loop { - if i == expected_list.len() { + if i == expected_list.len() { break; }; // Check total tokens list @@ -594,7 +594,7 @@ fn assert_after_update_owned_tokens_list(owner: ContractAddress, expected_list: let mut i = 0; loop { - if i == expected_list.len() { + if i == expected_list.len() { break; }; // Check owned tokens list From 2a1f876553324b80269d53590d29b77d8f78c3f1 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 14 May 2024 12:39:50 -0400 Subject: [PATCH 36/73] fix before_update description --- docs/modules/ROOT/pages/api/erc721.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 6ee65d71d..4dbf712c0 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -944,7 +944,7 @@ Updates the ownership and token-tracking data structures. When a token is minted (or burned), `token_id` is added to (or removed from) the token-tracking structures. -When a token is transferred, the ownership-tracking data structures reflect the change in ownership of `token_id`. +When a token is transferred, minted, or burned, the ownership-tracking data structures reflect the change in ownership of `token_id`. This must be added to the implementing contract's xref:ERC721Component-before_update[ERC721HooksTrait::before_update] hook. From c5fdc2978c47c9310f9b68196771a47a4ccdfe67 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 14 May 2024 12:43:38 -0400 Subject: [PATCH 37/73] revert comment change --- src/token/erc721/erc721.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token/erc721/erc721.cairo b/src/token/erc721/erc721.cairo index 32683f3a7..1029f84bd 100644 --- a/src/token/erc721/erc721.cairo +++ b/src/token/erc721/erc721.cairo @@ -461,7 +461,7 @@ mod ERC721Component { /// Requirements: /// /// - `to` is not the zero address. - /// - `token_id` must not exist. + /// - `token_id` does not exist. /// /// Emits a `Transfer` event. fn _mint(ref self: ComponentState, to: ContractAddress, token_id: u256) { From 8e815ed2fd3641d81bdce7bbc495c6dbbdd06d64 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 14 May 2024 12:46:49 -0400 Subject: [PATCH 38/73] match comment with api description --- .../erc721/extensions/erc721_enumerable/erc721_enumerable.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index c87cf65fe..82e12a83d 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -125,7 +125,7 @@ mod ERC721EnumerableComponent { /// When a token is minted (or burned), `token_id` is added to (or removed from) /// the token-tracking structures. /// - /// When a token is transferred, the ownership-tracking data structures reflect + /// When a token is transferred, minted, or burned, the ownership-tracking data structures reflect /// the change in ownership of `token_id`. /// /// This must be added to the implementing contract's `ERC721HooksTrait::before_update` hook. From 48b30c3a9342b82c6c0a0164c2bcf23d8d3a0375 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Wed, 15 May 2024 11:33:49 -0400 Subject: [PATCH 39/73] Apply suggestions from code review Co-authored-by: Eric Nordelo --- src/tests/mocks/erc721_enumerable_mocks.cairo | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tests/mocks/erc721_enumerable_mocks.cairo b/src/tests/mocks/erc721_enumerable_mocks.cairo index cadd220bb..f39c15a9d 100644 --- a/src/tests/mocks/erc721_enumerable_mocks.cairo +++ b/src/tests/mocks/erc721_enumerable_mocks.cairo @@ -4,7 +4,6 @@ mod DualCaseERC721EnumerableMock { use openzeppelin::token::erc721::ERC721Component::ERC721HooksTrait; use openzeppelin::token::erc721::ERC721Component; use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent; - use starknet::ContractAddress; component!(path: ERC721Component, storage: erc721, event: ERC721Event); From 7b3735272c1abcd672fff908a66e89dbbf6d457b Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 21 May 2024 10:30:00 -0400 Subject: [PATCH 40/73] remove enum selectors --- src/utils/selectors.cairo | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/utils/selectors.cairo b/src/utils/selectors.cairo index 3128e8d11..68657e99a 100644 --- a/src/utils/selectors.cairo +++ b/src/utils/selectors.cairo @@ -50,15 +50,6 @@ const transferFrom: felt252 = selector!("transferFrom"); const safe_transfer_from: felt252 = selector!("safe_transfer_from"); const safeTransferFrom: felt252 = selector!("safeTransferFrom"); -// -// ERC721Enumerable -// - -const token_by_index: felt252 = selector!("token_by_index"); -const tokenByIndex: felt252 = selector!("tokenByIndex"); -const token_of_owner_by_index: felt252 = selector!("token_of_owner_by_index"); -const tokenOfOwnerByIndex: felt252 = selector!("tokenOfOwnerByIndex"); - // // ERC721Receiver // From ffe01ad9949e4e10ed21df69041bbeb673c404ba Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 21 May 2024 10:30:35 -0400 Subject: [PATCH 41/73] remove dual_case and camel impls from erc721_enum --- src/token/erc721.cairo | 1 - src/token/erc721/dual721_enumerable.cairo | 217 ------------------ .../erc721_enumerable/erc721_enumerable.cairo | 29 +-- .../erc721_enumerable/interface.cairo | 20 -- 4 files changed, 1 insertion(+), 266 deletions(-) delete mode 100644 src/token/erc721/dual721_enumerable.cairo diff --git a/src/token/erc721.cairo b/src/token/erc721.cairo index cae55c127..e899020c8 100644 --- a/src/token/erc721.cairo +++ b/src/token/erc721.cairo @@ -1,5 +1,4 @@ mod dual721; -mod dual721_enumerable; mod dual721_receiver; mod erc721; mod erc721_receiver; diff --git a/src/token/erc721/dual721_enumerable.cairo b/src/token/erc721/dual721_enumerable.cairo deleted file mode 100644 index 57ad2d6bd..000000000 --- a/src/token/erc721/dual721_enumerable.cairo +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.12.0 (token/erc721/dual721.cairo) - -use openzeppelin::utils::UnwrapAndCast; -use openzeppelin::utils::selectors; -use openzeppelin::utils::serde::SerializedAppend; -use openzeppelin::utils::try_selector_with_fallback; -use starknet::ContractAddress; -use starknet::SyscallResultTrait; -use starknet::call_contract_syscall; - -#[derive(Copy, Drop)] -struct DualCaseERC721Enumerable { - contract_address: ContractAddress -} - -trait DualCaseERC721EnumerableTrait { - fn name(self: @DualCaseERC721Enumerable) -> ByteArray; - fn symbol(self: @DualCaseERC721Enumerable) -> ByteArray; - fn token_uri(self: @DualCaseERC721Enumerable, token_id: u256) -> ByteArray; - fn balance_of(self: @DualCaseERC721Enumerable, account: ContractAddress) -> u256; - fn owner_of(self: @DualCaseERC721Enumerable, token_id: u256) -> ContractAddress; - fn get_approved(self: @DualCaseERC721Enumerable, token_id: u256) -> ContractAddress; - fn approve(self: @DualCaseERC721Enumerable, to: ContractAddress, token_id: u256); - fn set_approval_for_all( - self: @DualCaseERC721Enumerable, operator: ContractAddress, approved: bool - ); - fn transfer_from( - self: @DualCaseERC721Enumerable, from: ContractAddress, to: ContractAddress, token_id: u256 - ); - fn is_approved_for_all( - self: @DualCaseERC721Enumerable, owner: ContractAddress, operator: ContractAddress - ) -> bool; - fn safe_transfer_from( - self: @DualCaseERC721Enumerable, - from: ContractAddress, - to: ContractAddress, - token_id: u256, - data: Span - ); - fn supports_interface(self: @DualCaseERC721Enumerable, interface_id: felt252) -> bool; - fn total_supply(self: @DualCaseERC721Enumerable) -> u256; - fn token_by_index(self: @DualCaseERC721Enumerable, index: u256) -> u256; - fn token_of_owner_by_index( - self: @DualCaseERC721Enumerable, owner: ContractAddress, index: u256 - ) -> u256; -} - -impl DualCaseERC72EnumerableImpl of DualCaseERC721EnumerableTrait { - fn name(self: @DualCaseERC721Enumerable) -> ByteArray { - call_contract_syscall(*self.contract_address, selectors::name, array![].span()) - .unwrap_and_cast() - } - - fn symbol(self: @DualCaseERC721Enumerable) -> ByteArray { - call_contract_syscall(*self.contract_address, selectors::symbol, array![].span()) - .unwrap_and_cast() - } - - fn token_uri(self: @DualCaseERC721Enumerable, token_id: u256) -> ByteArray { - let mut args = array![]; - args.append_serde(token_id); - - try_selector_with_fallback( - *self.contract_address, selectors::token_uri, selectors::tokenURI, args.span() - ) - .unwrap_and_cast() - } - - fn balance_of(self: @DualCaseERC721Enumerable, account: ContractAddress) -> u256 { - let mut args = array![]; - args.append_serde(account); - - try_selector_with_fallback( - *self.contract_address, selectors::balance_of, selectors::balanceOf, args.span() - ) - .unwrap_and_cast() - } - - fn owner_of(self: @DualCaseERC721Enumerable, token_id: u256) -> ContractAddress { - let mut args = array![]; - args.append_serde(token_id); - - try_selector_with_fallback( - *self.contract_address, selectors::owner_of, selectors::ownerOf, args.span() - ) - .unwrap_and_cast() - } - - fn get_approved(self: @DualCaseERC721Enumerable, token_id: u256) -> ContractAddress { - let mut args = array![]; - args.append_serde(token_id); - - try_selector_with_fallback( - *self.contract_address, selectors::get_approved, selectors::getApproved, args.span() - ) - .unwrap_and_cast() - } - - fn is_approved_for_all( - self: @DualCaseERC721Enumerable, owner: ContractAddress, operator: ContractAddress - ) -> bool { - let mut args = array![]; - args.append_serde(owner); - args.append_serde(operator); - - try_selector_with_fallback( - *self.contract_address, - selectors::is_approved_for_all, - selectors::isApprovedForAll, - args.span() - ) - .unwrap_and_cast() - } - - fn approve(self: @DualCaseERC721Enumerable, to: ContractAddress, token_id: u256) { - let mut args = array![]; - args.append_serde(to); - args.append_serde(token_id); - call_contract_syscall(*self.contract_address, selectors::approve, args.span()) - .unwrap_syscall(); - } - - fn set_approval_for_all( - self: @DualCaseERC721Enumerable, operator: ContractAddress, approved: bool - ) { - let mut args = array![]; - args.append_serde(operator); - args.append_serde(approved); - - try_selector_with_fallback( - *self.contract_address, - selectors::set_approval_for_all, - selectors::setApprovalForAll, - args.span() - ) - .unwrap_syscall(); - } - - fn transfer_from( - self: @DualCaseERC721Enumerable, from: ContractAddress, to: ContractAddress, token_id: u256 - ) { - let mut args = array![]; - args.append_serde(from); - args.append_serde(to); - args.append_serde(token_id); - - try_selector_with_fallback( - *self.contract_address, selectors::transfer_from, selectors::transferFrom, args.span() - ) - .unwrap_syscall(); - } - - fn safe_transfer_from( - self: @DualCaseERC721Enumerable, - from: ContractAddress, - to: ContractAddress, - token_id: u256, - mut data: Span - ) { - let mut args = array![]; - args.append_serde(from); - args.append_serde(to); - args.append_serde(token_id); - args.append_serde(data); - - try_selector_with_fallback( - *self.contract_address, - selectors::safe_transfer_from, - selectors::safeTransferFrom, - args.span() - ) - .unwrap_syscall(); - } - - fn supports_interface(self: @DualCaseERC721Enumerable, interface_id: felt252) -> bool { - let mut args = array![]; - args.append_serde(interface_id); - - call_contract_syscall(*self.contract_address, selectors::supports_interface, args.span()) - .unwrap_and_cast() - } - - fn total_supply(self: @DualCaseERC721Enumerable) -> u256 { - let mut args = array![]; - try_selector_with_fallback( - *self.contract_address, selectors::total_supply, selectors::totalSupply, args.span() - ) - .unwrap_and_cast() - } - - fn token_by_index(self: @DualCaseERC721Enumerable, index: u256) -> u256 { - let mut args = array![]; - args.append_serde(index); - - try_selector_with_fallback( - *self.contract_address, selectors::token_by_index, selectors::tokenByIndex, args.span() - ) - .unwrap_and_cast() - } - - fn token_of_owner_by_index( - self: @DualCaseERC721Enumerable, owner: ContractAddress, index: u256 - ) -> u256 { - let mut args = array![]; - args.append_serde(owner); - args.append_serde(index); - - try_selector_with_fallback( - *self.contract_address, - selectors::token_of_owner_by_index, - selectors::tokenOfOwnerByIndex, - args.span() - ) - .unwrap_and_cast() - } -} diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 82e12a83d..53443b69d 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -11,9 +11,7 @@ mod ERC721EnumerableComponent { use openzeppelin::token::erc721::ERC721Component::ERC721Impl; use openzeppelin::token::erc721::ERC721Component::InternalImpl as ERC721InternalImpl; use openzeppelin::token::erc721::ERC721Component; - use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::{ - IERC721Enumerable, IERC721EnumerableCamel - }; + use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::IERC721Enumerable; use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; use starknet::ContractAddress; @@ -75,31 +73,6 @@ mod ERC721EnumerableComponent { } } - /// Adds camelCase support for `IERC721Enumerable`. - #[embeddable_as(ERC721EnumerableCamelImpl)] - impl ERC721EnumerableCamel< - TContractState, - +HasComponent, - +ERC721Component::HasComponent, - +ERC721Component::ERC721HooksTrait, - +SRC5Component::HasComponent, - +Drop - > of IERC721EnumerableCamel> { - fn totalSupply(self: @ComponentState) -> u256 { - self.total_supply() - } - - fn tokenOfOwnerByIndex( - self: @ComponentState, owner: ContractAddress, index: u256 - ) -> u256 { - self.token_of_owner_by_index(owner, index) - } - - fn tokenByIndex(self: @ComponentState, index: u256) -> u256 { - self.token_by_index(index) - } - } - // // Internal // diff --git a/src/token/erc721/extensions/erc721_enumerable/interface.cairo b/src/token/erc721/extensions/erc721_enumerable/interface.cairo index 95363a502..42dd33584 100644 --- a/src/token/erc721/extensions/erc721_enumerable/interface.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/interface.cairo @@ -12,23 +12,3 @@ trait IERC721Enumerable { fn token_by_index(self: @TState, index: u256) -> u256; fn token_of_owner_by_index(self: @TState, owner: ContractAddress, index: u256) -> u256; } - -#[starknet::interface] -trait IERC721EnumerableCamel { - fn totalSupply(self: @TState) -> u256; - fn tokenByIndex(self: @TState, index: u256) -> u256; - fn tokenOfOwnerByIndex(self: @TState, owner: ContractAddress, index: u256) -> u256; -} - -#[starknet::interface] -trait ERC721EnumerableABI { - // IERC721Enumerable - fn total_supply(self: @TState) -> u256; - fn token_by_index(self: @TState, index: u256) -> u256; - fn token_of_owner_by_index(self: @TState, owner: ContractAddress, index: u256) -> u256; - - // IERC721EnumerableCamel - fn totalSupply(self: @TState) -> u256; - fn tokenByIndex(self: @TState, index: u256) -> u256; - fn tokenOfOwnerByIndex(self: @TState, owner: ContractAddress, index: u256) -> u256; -} From 359c7115eacaa717ecba37f7cccf2041bfcdeb59 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 21 May 2024 10:32:49 -0400 Subject: [PATCH 42/73] remove unused mocks, fix mock name --- src/tests/mocks/erc721_enumerable_mocks.cairo | 160 +----------------- 1 file changed, 1 insertion(+), 159 deletions(-) diff --git a/src/tests/mocks/erc721_enumerable_mocks.cairo b/src/tests/mocks/erc721_enumerable_mocks.cairo index cadd220bb..e48d537cf 100644 --- a/src/tests/mocks/erc721_enumerable_mocks.cairo +++ b/src/tests/mocks/erc721_enumerable_mocks.cairo @@ -1,5 +1,5 @@ #[starknet::contract] -mod DualCaseERC721EnumerableMock { +mod ERC721EnumerableMock { use openzeppelin::introspection::src5::SRC5Component; use openzeppelin::token::erc721::ERC721Component::ERC721HooksTrait; use openzeppelin::token::erc721::ERC721Component; @@ -22,9 +22,6 @@ mod DualCaseERC721EnumerableMock { #[abi(embed_v0)] impl ERC721EnumerableImpl = ERC721EnumerableComponent::ERC721EnumerableImpl; - #[abi(embed_v0)] - impl ERC721EnumerableCamelImpl = - ERC721EnumerableComponent::ERC721EnumerableCamelImpl; impl ERC721EnumerableInternalImpl = ERC721EnumerableComponent::InternalImpl; // SRC5 @@ -183,158 +180,3 @@ mod SnakeERC721EnumerableMock { self.erc721._mint(recipient, token_id); } } - -#[starknet::contract] -mod CamelERC721EnumerableMock { - use openzeppelin::introspection::src5::SRC5Component; - use openzeppelin::token::erc721::ERC721Component::ERC721HooksTrait; - use openzeppelin::token::erc721::ERC721Component; - use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent; - - use starknet::ContractAddress; - - component!(path: ERC721Component, storage: erc721, event: ERC721Event); - component!( - path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent - ); - component!(path: SRC5Component, storage: src5, event: SRC5Event); - - // ERC721 - impl ERC721InternalImpl = ERC721Component::InternalImpl; - - // ERC721Enumerable - #[abi(embed_v0)] - impl ERC721EnumerableCamelImpl = - ERC721EnumerableComponent::ERC721EnumerableCamelImpl; - impl ERC721EnumerableInternalImpl = ERC721EnumerableComponent::InternalImpl; - - // SRC5 - #[abi(embed_v0)] - impl SRC5Impl = SRC5Component::SRC5Impl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc721: ERC721Component::Storage, - #[substorage(v0)] - erc721_enumerable: ERC721EnumerableComponent::Storage, - #[substorage(v0)] - src5: SRC5Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC721Event: ERC721Component::Event, - #[flat] - ERC721EnumerableEvent: ERC721EnumerableComponent::Event, - #[flat] - SRC5Event: SRC5Component::Event - } - - impl ERC721EnumerableHooksImpl< - TContractState, - impl ERC721Enumerable: ERC721EnumerableComponent::HasComponent, - impl HasComponent: ERC721Component::HasComponent, - +SRC5Component::HasComponent, - +Drop - > of ERC721Component::ERC721HooksTrait { - fn before_update( - ref self: ERC721Component::ComponentState, - to: ContractAddress, - token_id: u256, - auth: ContractAddress - ) { - let mut erc721_enumerable_component = get_dep_component_mut!( - ref self, ERC721Enumerable - ); - erc721_enumerable_component.before_update(to, token_id); - } - - fn after_update( - ref self: ERC721Component::ComponentState, - to: ContractAddress, - token_id: u256, - auth: ContractAddress - ) {} - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - base_uri: ByteArray, - recipient: ContractAddress, - token_id: u256 - ) { - self.erc721.initializer(name, symbol, base_uri); - self.erc721_enumerable.initializer(); - self.erc721._mint(recipient, token_id); - } -} - -#[starknet::contract] -mod SnakeERC721EnumerablePanicMock { - use starknet::ContractAddress; - use zeroable::Zeroable; - - #[storage] - struct Storage {} - - #[abi(per_item)] - #[generate_trait] - impl ExternalImpl of ExternalTrait { - #[external(v0)] - fn total_supply(self: @ContractState) -> u256 { - panic!("Some error"); - u256 { low: 3, high: 3 } - } - - #[external(v0)] - fn token_by_index(self: @ContractState, index: u256) -> u256 { - panic!("Some error"); - u256 { low: 3, high: 3 } - } - - #[external(v0)] - fn token_of_owner_by_index( - self: @ContractState, owner: ContractAddress, index: u256 - ) -> u256 { - panic!("Some error"); - u256 { low: 3, high: 3 } - } - } -} - -#[starknet::contract] -mod CamelERC721EnumerablePanicMock { - use starknet::ContractAddress; - use zeroable::Zeroable; - - #[storage] - struct Storage {} - - #[abi(per_item)] - #[generate_trait] - impl ExternalImpl of ExternalTrait { - #[external(v0)] - fn totalSupply(self: @ContractState) -> u256 { - panic!("Some error"); - u256 { low: 3, high: 3 } - } - - #[external(v0)] - fn tokenByIndex(self: @ContractState, index: u256) -> u256 { - panic!("Some error"); - u256 { low: 3, high: 3 } - } - - #[external(v0)] - fn tokenOfOwnerByIndex(self: @ContractState, owner: ContractAddress, index: u256) -> u256 { - panic!("Some error"); - u256 { low: 3, high: 3 } - } - } -} From 01b86871ee47c7b332c4fab08f5138a756e70e6a Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 21 May 2024 10:33:12 -0400 Subject: [PATCH 43/73] remove dual and camel tests for erc721_enum --- src/tests/token.cairo | 1 - src/tests/token/test_dual721_enumerable.cairo | 171 ------------------ src/tests/token/test_erc721_enumerable.cairo | 46 +---- 3 files changed, 7 insertions(+), 211 deletions(-) delete mode 100644 src/tests/token/test_dual721_enumerable.cairo diff --git a/src/tests/token.cairo b/src/tests/token.cairo index 67b2d8142..1f7e2d817 100644 --- a/src/tests/token.cairo +++ b/src/tests/token.cairo @@ -2,7 +2,6 @@ mod test_dual1155; mod test_dual1155_receiver; mod test_dual20; mod test_dual721; -mod test_dual721_enumerable; mod test_dual721_receiver; mod test_erc1155; mod test_erc1155_receiver; diff --git a/src/tests/token/test_dual721_enumerable.cairo b/src/tests/token/test_dual721_enumerable.cairo deleted file mode 100644 index ade616769..000000000 --- a/src/tests/token/test_dual721_enumerable.cairo +++ /dev/null @@ -1,171 +0,0 @@ -use openzeppelin::tests::mocks::erc721_enumerable_mocks::{ - CamelERC721EnumerableMock, SnakeERC721EnumerableMock -}; -use openzeppelin::tests::mocks::erc721_enumerable_mocks::{ - CamelERC721EnumerablePanicMock, SnakeERC721EnumerablePanicMock -}; -use openzeppelin::tests::mocks::non_implementing_mock::NonImplementingMock; -use openzeppelin::tests::utils::constants::{OWNER, NAME, SYMBOL, BASE_URI, TOKEN_ID}; -use openzeppelin::tests::utils; -use openzeppelin::token::erc721::dual721_enumerable::{ - DualCaseERC721Enumerable, DualCaseERC721EnumerableTrait -}; -use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::{ - IERC721EnumerableDispatcher, IERC721EnumerableCamelDispatcher -}; -use openzeppelin::utils::serde::SerializedAppend; -use starknet::ContractAddress; - -// -// Setup -// - -fn setup_snake() -> (DualCaseERC721Enumerable, IERC721EnumerableDispatcher) { - let mut calldata = array![]; - calldata.append_serde(NAME()); - calldata.append_serde(SYMBOL()); - calldata.append_serde(BASE_URI()); - calldata.append_serde(OWNER()); - calldata.append_serde(TOKEN_ID); - let target = utils::deploy(SnakeERC721EnumerableMock::TEST_CLASS_HASH, calldata); - ( - DualCaseERC721Enumerable { contract_address: target }, - IERC721EnumerableDispatcher { contract_address: target } - ) -} - -fn setup_camel() -> (DualCaseERC721Enumerable, IERC721EnumerableCamelDispatcher) { - let mut calldata = array![]; - calldata.append_serde(NAME()); - calldata.append_serde(SYMBOL()); - calldata.append_serde(BASE_URI()); - calldata.append_serde(OWNER()); - calldata.append_serde(TOKEN_ID); - let target = utils::deploy(CamelERC721EnumerableMock::TEST_CLASS_HASH, calldata); - ( - DualCaseERC721Enumerable { contract_address: target }, - IERC721EnumerableCamelDispatcher { contract_address: target } - ) -} - -fn setup_non_erc721_enumerable() -> DualCaseERC721Enumerable { - let calldata = array![]; - let target = utils::deploy(NonImplementingMock::TEST_CLASS_HASH, calldata); - DualCaseERC721Enumerable { contract_address: target } -} - -fn setup_erc721_enumerable_panic() -> (DualCaseERC721Enumerable, DualCaseERC721Enumerable) { - let snake_target = utils::deploy(SnakeERC721EnumerablePanicMock::TEST_CLASS_HASH, array![]); - let camel_target = utils::deploy(CamelERC721EnumerablePanicMock::TEST_CLASS_HASH, array![]); - ( - DualCaseERC721Enumerable { contract_address: snake_target }, - DualCaseERC721Enumerable { contract_address: camel_target } - ) -} - -// -// snake_case target -// - -#[test] -fn test_dual_total_supply() { - let (dispatcher, _) = setup_snake(); - assert_eq!(dispatcher.total_supply(), 1); -} - -#[test] -#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] -fn test_dual_no_total_supply() { - let dispatcher = setup_non_erc721_enumerable(); - dispatcher.total_supply(); -} - -#[test] -#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] -fn test_dual_total_supply_exists_and_panics() { - let (dispatcher, _) = setup_erc721_enumerable_panic(); - dispatcher.total_supply(); -} - -#[test] -fn test_dual_token_by_index() { - let (dispatcher, _) = setup_snake(); - assert_eq!(dispatcher.token_by_index(0), TOKEN_ID); -} - -#[test] -#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] -fn test_dual_no_token_by_index() { - let dispatcher = setup_non_erc721_enumerable(); - dispatcher.token_by_index(0); -} - -#[test] -#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] -fn test_dual_token_by_index_exists_and_panics() { - let (dispatcher, _) = setup_erc721_enumerable_panic(); - dispatcher.token_by_index(0); -} - -#[test] -fn test_dual_token_of_owner_by_index() { - let (dispatcher, _) = setup_snake(); - assert_eq!(dispatcher.token_of_owner_by_index(OWNER(), 0), TOKEN_ID); -} - -#[test] -#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND',))] -fn test_dual_no_token_of_owner_by_index() { - let dispatcher = setup_non_erc721_enumerable(); - dispatcher.token_of_owner_by_index(OWNER(), 0); -} - -#[test] -#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] -fn test_dual_token_of_owner_by_index_exists_and_panics() { - let (dispatcher, _) = setup_erc721_enumerable_panic(); - dispatcher.token_of_owner_by_index(OWNER(), 0); -} - -// -// camelCase target -// - -#[test] -fn test_dual_totalSupply() { - let (dispatcher, _) = setup_camel(); - assert_eq!(dispatcher.total_supply(), 1); -} - -#[test] -#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] -fn test_dual_totalSupply_exists_and_panics() { - let (_, dispatcher) = setup_erc721_enumerable_panic(); - dispatcher.total_supply(); -} - -#[test] -fn test_dual_tokenByIndex() { - let (dispatcher, _) = setup_camel(); - assert_eq!(dispatcher.token_by_index(0), TOKEN_ID); -} - -#[test] -#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] -fn test_dual_tokenByIndex_exists_and_panics() { - let (_, dispatcher) = setup_erc721_enumerable_panic(); - dispatcher.token_by_index(0); -} - -#[test] -fn test_dual_tokenOfOwnerByIndex() { - let (dispatcher, _) = setup_camel(); - assert_eq!(dispatcher.token_of_owner_by_index(OWNER(), 0), TOKEN_ID); -} - -#[test] -#[should_panic(expected: ("Some error", 'ENTRYPOINT_FAILED',))] -fn test_dual_tokenOfOwnerByIndex_exists_and_panics() { - let (_, dispatcher) = setup_erc721_enumerable_panic(); - dispatcher.token_of_owner_by_index(OWNER(), 0); -} diff --git a/src/tests/token/test_erc721_enumerable.cairo b/src/tests/token/test_erc721_enumerable.cairo index a5eba5bb8..e3eb674b1 100644 --- a/src/tests/token/test_erc721_enumerable.cairo +++ b/src/tests/token/test_erc721_enumerable.cairo @@ -1,10 +1,10 @@ use openzeppelin::introspection::src5::SRC5Component::SRC5Impl; use openzeppelin::introspection; -use openzeppelin::tests::mocks::erc721_enumerable_mocks::DualCaseERC721EnumerableMock; +use openzeppelin::tests::mocks::erc721_enumerable_mocks::ERC721EnumerableMock; use openzeppelin::tests::utils::constants::{OWNER, RECIPIENT, OTHER, ZERO}; use openzeppelin::token::erc721::ERC721Component::{ERC721Impl, InternalImpl as ERC721InternalImpl}; use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent::{ - ERC721EnumerableImpl, ERC721EnumerableCamelImpl, InternalImpl + ERC721EnumerableImpl, InternalImpl }; use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent; use openzeppelin::token::erc721::extensions::erc721_enumerable::erc721_enumerable::ERC721EnumerableComponent::PrivateTrait; @@ -24,10 +24,10 @@ const TOKENS_LEN: u256 = 3; // type ComponentState = - ERC721EnumerableComponent::ComponentState; + ERC721EnumerableComponent::ComponentState; -fn CONTRACT_STATE() -> DualCaseERC721EnumerableMock::ContractState { - DualCaseERC721EnumerableMock::contract_state_for_testing() +fn CONTRACT_STATE() -> ERC721EnumerableMock::ContractState { + ERC721EnumerableMock::contract_state_for_testing() } fn COMPONENT_STATE() -> ComponentState { @@ -99,30 +99,8 @@ fn test_total_supply() { assert_eq!(no_supply, 0); } -#[test] -fn test_totalSupply() { - let mut state = COMPONENT_STATE(); - let mut contract_state = CONTRACT_STATE(); - let token = TOKEN_1; - - let no_supply = state.totalSupply(); - assert_eq!(no_supply, 0); - - // Mint - contract_state.erc721._mint(OWNER(), token); - - let new_supply = state.totalSupply(); - assert_eq!(new_supply, 1); - - // Burn - contract_state.erc721._burn(token); - - let no_supply = state.totalSupply(); - assert_eq!(no_supply, 0); -} - // -// token_by_index & tokenByIndex +// token_by_index // #[test] @@ -197,7 +175,7 @@ fn test_token_by_index_burn_and_mint_all() { } // -// token_of_owner_by_index & tokenOfOwnerByIndex +// token_of_owner_by_index // #[test] @@ -541,14 +519,9 @@ fn assert_dual_token_of_owner_by_index(owner: ContractAddress, expected_token_li if i == expected_token_list.len() { break; }; - // snake_case let token = state.token_of_owner_by_index(owner, i.into()); assert_eq!(token, *expected_token_list.at(i)); - // camelCase - let token = state.tokenOfOwnerByIndex(owner, i.into()); - assert_eq!(token, *expected_token_list.at(i)); - i = i + 1; }; } @@ -561,14 +534,9 @@ fn assert_dual_token_by_index(expected_token_list: Span) { if i == expected_token_list.len() { break; }; - // snake_case let token = state.token_by_index(i.into()); assert_eq!(token, *expected_token_list.at(i)); - // camelCase - let token = state.tokenByIndex(i.into()); - assert_eq!(token, *expected_token_list.at(i)); - i = i + 1; }; } From e7e14596e9f84c9d9c6d5999ca6eae6497bd3f46 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 21 May 2024 10:35:53 -0400 Subject: [PATCH 44/73] remove camel impl from erc721_enum --- docs/modules/ROOT/pages/api/erc721.adoc | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 4dbf712c0..df47ed7ca 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -857,12 +857,6 @@ This extension allows contracts to publish their entire list of NFTs and make th * xref:#ERC721EnumerableComponent-total_supply[`++total_supply(self)++`] * xref:#ERC721EnumerableComponent-token_by_index[`++token_by_index(self, index)++`] * xref:#ERC721EnumerableComponent-token_of_owner_by_index[`++token_of_owner_by_index(self, address, index)++`] - -[.sub-index#ERC721EnumerableComponent-Embeddable-Impls-ERC721EnumerableCamelImpl] -.ERC721EnumerableCamelImpl -* xref:#ERC721EnumerableComponent-total_Spply[`++totalSupply(self)++`] -* xref:#ERC721EnumerableComponent-tokenByIndex[`++tokenByIndex(self, index)++`] -* xref:#ERC721EnumerableComponent-tokenOfOwnerByIndex[`++tokenOfOwnerByIndex(self, owner, index)++`] -- [.contract-index] @@ -909,24 +903,6 @@ Requirements: - `index` is less than ``owner``'s token balance. - `owner` is not the zero address. -[.contract-item] -[[ERC721EnumerableComponent-totalSupply]] -==== `[.contract-item-name]#++totalSupply++#++(self: @ContractState) → u256++` [.item-kind]#external# - -See <>. - -[.contract-item] -[[ERC721EnumerableComponent-tokenByIndex]] -==== `[.contract-item-name]#++tokenByIndex++#++(self: @ContractState, index: u256) → u256++` [.item-kind]#external# - -See <>. - -[.contract-item] -[[ERC721EnumerableComponent-tokenOfOwnerByIndex]] -==== `[.contract-item-name]#++tokenOfOwnerByIndex++#++(self: @ContractState, owner: ContractAddress, index: u256) → u256++` [.item-kind]#external# - -See <>. - [#ERC721EnumerableComponent-Internal-functions] ==== Internal functions From 68db79ae7017b6e30ce42bf811305d45418af28b Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 21 May 2024 10:46:16 -0400 Subject: [PATCH 45/73] fix changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d89ac31f4..070ba98a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- ERC721Enumerable component (#983) + ## 0.13.0 (2024-05-20) ### Added - Sending transactions section in account docs (#981) - before_update and after_update hooks to ERC721Component (#978) -- ERC721Enumerable component (#983) - before_update and after_update hooks to ERC1155Component (#982) ### Changed (Breaking) From 640faab4518734a92dc1a89a9be48e5e45e49e8a Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 12 Jul 2024 20:02:03 -0500 Subject: [PATCH 46/73] bump scarb to rc.2 --- Scarb.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Scarb.toml b/Scarb.toml index 1b79be6b4..21be19db4 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -2,8 +2,8 @@ name = "openzeppelin" version = "0.15.0-rc.0" edition = "2023_11" -cairo-version = "2.7.0-rc.1" -scarb-version = "2.7.0-rc.1" +cairo-version = "2.7.0-rc.2" +scarb-version = "2.7.0-rc.2" authors = ["OpenZeppelin Community "] description = "OpenZeppelin Contracts written in Cairo for StarkNet, a decentralized ZK Rollup" documentation = "https://docs.openzeppelin.com/contracts-cairo" @@ -13,10 +13,10 @@ license-file = "LICENSE" keywords = ["openzeppelin", "starknet", "cairo", "contracts", "security", "standards"] [dependencies] -starknet = "2.7.0-rc.1" +starknet = "2.7.0-rc.2" [dev-dependencies] -cairo_test = "2.7.0-rc.1" +cairo_test = "2.7.0-rc.2" [lib] From 2a3af3f9f29f9115c99758b8349be878ba6b3cda Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 12 Jul 2024 20:04:08 -0500 Subject: [PATCH 47/73] use Map, allow tests to read storage --- src/tests/token/erc721/test_erc721_enumerable.cairo | 13 ++++++------- .../erc721_enumerable/erc721_enumerable.cairo | 9 +++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/tests/token/erc721/test_erc721_enumerable.cairo b/src/tests/token/erc721/test_erc721_enumerable.cairo index 66b558b4a..4a8b8fdbb 100644 --- a/src/tests/token/erc721/test_erc721_enumerable.cairo +++ b/src/tests/token/erc721/test_erc721_enumerable.cairo @@ -10,7 +10,6 @@ use openzeppelin::token::erc721::extensions::erc721_enumerable::ERC721Enumerable use openzeppelin::token::erc721::extensions::erc721_enumerable::erc721_enumerable::ERC721EnumerableComponent::PrivateTrait; use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; use starknet::ContractAddress; -use starknet::storage::{StorageMemberAccessTrait, StorageMapMemberAccessTrait}; // Token IDs const TOKEN_1: u256 = 'TOKEN_1'; @@ -542,7 +541,7 @@ fn assert_dual_token_by_index(expected_token_list: Span) { } fn assert_after_update_all_tokens_list(expected_list: Span) { - let state = COMPONENT_STATE(); + let state = @COMPONENT_STATE(); let mut i = 0; loop { @@ -558,7 +557,7 @@ fn assert_after_update_all_tokens_list(expected_list: Span) { } fn assert_after_update_owned_tokens_list(owner: ContractAddress, expected_list: Span) { - let state = COMPONENT_STATE(); + let state = @COMPONENT_STATE(); let mut i = 0; loop { @@ -574,28 +573,28 @@ fn assert_after_update_owned_tokens_list(owner: ContractAddress, expected_list: } fn assert_all_tokens_index_to_id(index: u256, exp_token_id: u256) { - let state = COMPONENT_STATE(); + let state = @COMPONENT_STATE(); let index_to_id = state.ERC721Enumerable_all_tokens.read(index); assert_eq!(index_to_id, exp_token_id); } fn assert_all_tokens_id_to_index(token_id: u256, exp_index: u256) { - let state = COMPONENT_STATE(); + let state = @COMPONENT_STATE(); let id_to_index = state.ERC721Enumerable_all_tokens_index.read(token_id); assert_eq!(id_to_index, exp_index); } fn assert_owner_tokens_index_to_id(owner: ContractAddress, index: u256, exp_token_id: u256) { - let state = COMPONENT_STATE(); + let state = @COMPONENT_STATE(); let index_to_id = state.ERC721Enumerable_owned_tokens.read((owner, index)); assert_eq!(index_to_id, exp_token_id); } fn assert_owner_tokens_id_to_index(token_id: u256, exp_index: u256) { - let state = COMPONENT_STATE(); + let state = @COMPONENT_STATE(); let id_to_index = state.ERC721Enumerable_owned_tokens_index.read(token_id); assert_eq!(id_to_index, exp_index); diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index fc7bf189a..b55ae4cc5 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -15,14 +15,15 @@ pub mod ERC721EnumerableComponent { use openzeppelin::token::erc721::extensions::erc721_enumerable::interface::IERC721Enumerable; use openzeppelin::token::erc721::extensions::erc721_enumerable::interface; use starknet::ContractAddress; + use starknet::storage::Map; #[storage] struct Storage { - ERC721Enumerable_owned_tokens: LegacyMap<(ContractAddress, u256), u256>, - ERC721Enumerable_owned_tokens_index: LegacyMap, + ERC721Enumerable_owned_tokens: Map<(ContractAddress, u256), u256>, + ERC721Enumerable_owned_tokens_index: Map, ERC721Enumerable_all_tokens_len: u256, - ERC721Enumerable_all_tokens: LegacyMap, - ERC721Enumerable_all_tokens_index: LegacyMap + ERC721Enumerable_all_tokens: Map, + ERC721Enumerable_all_tokens_index: Map } pub mod Errors { From a2e9bf7f010c64801b803293f3b49bacc2035695 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 12 Jul 2024 20:06:05 -0500 Subject: [PATCH 48/73] fix fmt --- .../erc721_enumerable/erc721_enumerable.cairo | 16 +++++++++------- .../extensions/erc721_enumerable/interface.cairo | 3 ++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index b55ae4cc5..c0c3a4766 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.12.0 (token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo) +// OpenZeppelin Contracts for Cairo v0.12.0 +// (token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo) /// # ERC721Enumerable Component /// @@ -96,10 +97,11 @@ pub mod ERC721EnumerableComponent { /// When a token is minted (or burned), `token_id` is added to (or removed from) /// the token-tracking structures. /// - /// When a token is transferred, minted, or burned, the ownership-tracking data structures reflect - /// the change in ownership of `token_id`. + /// When a token is transferred, minted, or burned, the ownership-tracking data structures + /// reflect the change in ownership of `token_id`. /// - /// This must be added to the implementing contract's `ERC721HooksTrait::before_update` hook. + /// This must be added to the implementing contract's `ERC721HooksTrait::before_update` + /// hook. fn before_update( ref self: ComponentState, to: ContractAddress, token_id: u256 ) { @@ -200,9 +202,9 @@ pub mod ERC721EnumerableComponent { // Remove one from total supply self.ERC721Enumerable_all_tokens_len.write(last_token_index); - // When the token to delete is the last token, the swap operation is unnecessary. However, - // since this occurs rarely (when the last minted token is burnt), we still do the swap - // which avoids the additional expense of including an `if` statement + // When the token to delete is the last token, the swap operation is unnecessary. + // However, since this occurs rarely (when the last minted token is burnt), we still do + // the swap which avoids the additional expense of including an `if` statement self.ERC721Enumerable_all_tokens_index.write(last_token_id, this_token_index); self.ERC721Enumerable_all_tokens.write(this_token_index, last_token_id); } diff --git a/src/token/erc721/extensions/erc721_enumerable/interface.cairo b/src/token/erc721/extensions/erc721_enumerable/interface.cairo index 4c7a6ff63..f50fc1dfc 100644 --- a/src/token/erc721/extensions/erc721_enumerable/interface.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/interface.cairo @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.12.0 (token/erc721/extensions/erc721_enumerable/interface.cairo) +// OpenZeppelin Contracts for Cairo v0.12.0 +// (token/erc721/extensions/erc721_enumerable/interface.cairo) use starknet::ContractAddress; From 404c945830d55f36606dc2afe22d9c51a3473a50 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 12 Jul 2024 20:08:44 -0500 Subject: [PATCH 49/73] fix Contracts version --- docs/modules/ROOT/pages/api/erc721.adoc | 4 ++-- src/access/accesscontrol/accesscontrol.cairo | 2 +- .../extensions/erc721_enumerable/erc721_enumerable.cairo | 2 +- src/token/erc721/extensions/erc721_enumerable/interface.cairo | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index e98bbed75..5ef793a30 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -181,7 +181,7 @@ If the URI is not set for `token_id`, the return value will be an empty `ByteArr [.contract] [[IERC721Enumerable]] -=== `++IERC721Enumerable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.12.0/src/token/erc721/extensions/erc721_enumerable/interface.cairo[{github-icon},role=heading-link] +=== `++IERC721Enumerable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/extensions/erc721_enumerable/interface.cairo[{github-icon},role=heading-link] Interface for the optional enumerable functions in {eip721}. @@ -834,7 +834,7 @@ Registers the `IERC721Receiver` interface ID as supported through introspection. [.contract] [[ERC721EnumerableComponent]] -=== `++ERC721EnumerableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.12.0/src/token/erc721/extensions/erc721_enumerable.cairo[{github-icon},role=heading-link] +=== `++ERC721EnumerableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/extensions/erc721_enumerable.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::token::extensions::ERC721EnumerableComponent; diff --git a/src/access/accesscontrol/accesscontrol.cairo b/src/access/accesscontrol/accesscontrol.cairo index 891b3a270..4d8b461dc 100644 --- a/src/access/accesscontrol/accesscontrol.cairo +++ b/src/access/accesscontrol/accesscontrol.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.12.0 (access/accesscontrol/accesscontrol.cairo) +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 (access/accesscontrol/accesscontrol.cairo) /// # AccessControl Component /// diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index c0c3a4766..39c89a4cd 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.12.0 +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 // (token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo) /// # ERC721Enumerable Component diff --git a/src/token/erc721/extensions/erc721_enumerable/interface.cairo b/src/token/erc721/extensions/erc721_enumerable/interface.cairo index f50fc1dfc..60461d117 100644 --- a/src/token/erc721/extensions/erc721_enumerable/interface.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.12.0 +// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 // (token/erc721/extensions/erc721_enumerable/interface.cairo) use starknet::ContractAddress; From 867c2cfdadee7ac475419e89f93d8c90edec8531 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 22 Jul 2024 16:14:57 -0500 Subject: [PATCH 50/73] fix test_names --- src/tests/token/erc721/test_erc721_enumerable.cairo | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tests/token/erc721/test_erc721_enumerable.cairo b/src/tests/token/erc721/test_erc721_enumerable.cairo index 4a8b8fdbb..9955589df 100644 --- a/src/tests/token/erc721/test_erc721_enumerable.cairo +++ b/src/tests/token/erc721/test_erc721_enumerable.cairo @@ -128,7 +128,7 @@ fn test_token_by_index_greater_than_supply() { } #[test] -fn test_token_by_indexburn_last_token() { +fn test_token_by_index_burn_last_token() { let (_, _) = setup(); let mut contract_state = CONTRACT_STATE(); let last_token = TOKEN_3; @@ -140,7 +140,7 @@ fn test_token_by_indexburn_last_token() { } #[test] -fn test_token_by_indexburn_first_token() { +fn test_token_by_index_burn_first_token() { let (_, _) = setup(); let mut contract_state = CONTRACT_STATE(); let first_token = TOKEN_1; @@ -154,7 +154,7 @@ fn test_token_by_indexburn_first_token() { } #[test] -fn test_token_by_indexburn_andmint_all() { +fn test_token_by_index_burn_and_mint_all() { let (state, _) = setup(); let mut contract_state = CONTRACT_STATE(); @@ -261,7 +261,7 @@ fn test_token_of_owner_by_index_when_all_tokens_transferred() { // #[test] -fn test__update_whenmint() { +fn test__update_when_mint() { let (mut state, _) = setup(); let initial_supply = state.total_supply(); let new_token = 'TOKEN_4'; @@ -282,7 +282,7 @@ fn test__update_whenmint() { } #[test] -fn test__update_when_last_tokenburned() { +fn test__update_when_last_token_burned() { let (mut state, tokens_list) = setup(); let initial_supply = state.total_supply(); let last_token_toburn = *tokens_list.at(initial_supply.try_into().unwrap() - 1); @@ -303,7 +303,7 @@ fn test__update_when_last_tokenburned() { } #[test] -fn test__update_when_first_tokenburned() { +fn test__update_when_first_token_burned() { let (mut state, tokens_list) = setup(); let initial_supply = state.total_supply(); let first_token_toburn = *tokens_list.at(0); From c4c78dc8f84b7d15fde8a437182adec5488aacd4 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 22 Jul 2024 16:18:46 -0500 Subject: [PATCH 51/73] fix spelling --- docs/modules/ROOT/pages/api/erc721.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 5ef793a30..1e091972d 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -840,7 +840,7 @@ Registers the `IERC721Receiver` interface ID as supported through introspection. use openzeppelin::token::extensions::ERC721EnumerableComponent; ``` -Extension of ERC721 as defined in the EIP that adds enumerability of all the token ids in the contract as well as all thoken ids owned by each account. +Extension of ERC721 as defined in the EIP that adds enumerability of all the token ids in the contract as well as all token ids owned by each account. NOTE: Implementing xref:#ERC721Component[ERC721Component] is a requirement for this component to be implemented. From c8bbf0684128691e9bf4a34f5fc84c4382307329 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 22 Jul 2024 17:11:39 -0500 Subject: [PATCH 52/73] move erc721 enum component section to core --- docs/modules/ROOT/pages/api/erc721.adoc | 196 ++++++++++++------------ 1 file changed, 97 insertions(+), 99 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 1e091972d..f627ae48a 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -733,105 +733,6 @@ See <>. See <>. -== Receiver - -[.contract] -[[IERC721Receiver]] -=== `++IERC721Receiver++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/interface.cairo#L70-L79[{github-icon},role=heading-link] - -[.hljs-theme-dark] -```cairo -use openzeppelin::token::erc721::interface::IERC721Receiver; -``` - -Interface for contracts that support receiving `safe_transfer_from` transfers. - -[.contract-index] -.{inner-src5} --- -0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc --- - -[.contract-index] -.Functions --- -* xref:#IERC721Receiver-on_erc721_received[`++on_erc721_received(operator, from, token_id, data)++`] --- - -==== Functions - -[.contract-item] -[[IERC721Receiver-on_erc721_received]] -==== `[.contract-item-name]#++on_erc721_received++#++(operator: ContractAddress, from: ContractAddress, token_id: u256, data: Span) -> felt252++` [.item-kind]#external# - -Whenever an IERC721 `token_id` token is transferred to this non-account contract via <> by `operator` from `from`, this function is called. - -[.contract] -[[ERC721ReceiverComponent]] -=== `++ERC721ReceiverComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/erc721_receiver.cairo[{github-icon},role=heading-link] - -[.hljs-theme-dark] -```cairo -use openzeppelin::token::erc721::ERC721ReceiverComponent; -``` - -ERC721Receiver component implementing <>. - -NOTE: {src5-component-required-note} - -[.contract-index#ERC721ReceiverComponent-Embeddable-Mixin-Impl] -.{mixin-impls} - --- -.ERCReceiverMixinImpl -* xref:#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverImpl[`++ERC721ReceiverImpl++`] -* xref:#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverCamelImpl[`++ERC721ReceiverCamelImpl++`] -* xref:api/introspection.adoc#SRC5Component-Embeddable-Impls[`++SRC5Impl++`] --- - -[.contract-index#ERC721ReceiverComponent-Embeddable-Impls] -.Embeddable Implementations --- -[.sub-index#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverImpl] -.ERC721ReceiverImpl -* xref:#ERC721ReceiverComponent-on_erc721_received[`++on_erc721_received(self, operator, from, token_id, data)++`] - -[.sub-index#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverCamelImpl] -.ERC721ReceiverCamelImpl -* xref:#ERC721ReceiverComponent-onERC721Received[`++onERC721Received(self, operator, from, tokenId, data)++`] --- - -[.contract-index] -.Internal Functions --- -.InternalImpl -* xref:#ERC721ReceiverComponent-initializer[`++initializer(self)++`] --- - -==== Embeddable functions - -[.contract-item] -[[ERC721ReceiverComponent-on_erc721_received]] -==== `[.contract-item-name]#++on_erc721_received++#++(self: @ContractState, operator: ContractAddress, from: ContractAddress, token_id: u256, data Span) -> felt252++` [.item-kind]#external# - -Returns the `IERC721Receiver` interface ID. - -[.contract-item] -[[ERC721ReceiverComponent-onERC721Received]] -==== `[.contract-item-name]#++onERC721Received++#++(self: @ContractState, operator: ContractAddress, from: ContractAddress, token_id: u256, data Span) -> felt252++` [.item-kind]#external# - -See <>. - -==== Internal functions - -[.contract-item] -[[ERC721ReceiverComponent-initializer]] -==== `[.contract-item-name]#++initializer++#++(ref self: ContractState)++` [.item-kind]#internal# - -Registers the `IERC721Receiver` interface ID as supported through introspection. - -== Extensions - [.contract] [[ERC721EnumerableComponent]] === `++ERC721EnumerableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/extensions/erc721_enumerable.cairo[{github-icon},role=heading-link] @@ -952,6 +853,103 @@ Removes `token_id` from this extension's token-tracking data structures. This has 0(1) time complexity but alters the indexed order by swapping `token_id` and the index thereof with the last token id and the index thereof e.g. removing `1` from `[1, 2, 3, 4]` results in `[4, 2, 3]`. +== Receiver + +[.contract] +[[IERC721Receiver]] +=== `++IERC721Receiver++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/interface.cairo#L70-L79[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```cairo +use openzeppelin::token::erc721::interface::IERC721Receiver; +``` + +Interface for contracts that support receiving `safe_transfer_from` transfers. + +[.contract-index] +.{inner-src5} +-- +0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc +-- + +[.contract-index] +.Functions +-- +* xref:#IERC721Receiver-on_erc721_received[`++on_erc721_received(operator, from, token_id, data)++`] +-- + +==== Functions + +[.contract-item] +[[IERC721Receiver-on_erc721_received]] +==== `[.contract-item-name]#++on_erc721_received++#++(operator: ContractAddress, from: ContractAddress, token_id: u256, data: Span) -> felt252++` [.item-kind]#external# + +Whenever an IERC721 `token_id` token is transferred to this non-account contract via <> by `operator` from `from`, this function is called. + +[.contract] +[[ERC721ReceiverComponent]] +=== `++ERC721ReceiverComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/erc721_receiver.cairo[{github-icon},role=heading-link] + +[.hljs-theme-dark] +```cairo +use openzeppelin::token::erc721::ERC721ReceiverComponent; +``` + +ERC721Receiver component implementing <>. + +NOTE: {src5-component-required-note} + +[.contract-index#ERC721ReceiverComponent-Embeddable-Mixin-Impl] +.{mixin-impls} + +-- +.ERCReceiverMixinImpl +* xref:#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverImpl[`++ERC721ReceiverImpl++`] +* xref:#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverCamelImpl[`++ERC721ReceiverCamelImpl++`] +* xref:api/introspection.adoc#SRC5Component-Embeddable-Impls[`++SRC5Impl++`] +-- + +[.contract-index#ERC721ReceiverComponent-Embeddable-Impls] +.Embeddable Implementations +-- +[.sub-index#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverImpl] +.ERC721ReceiverImpl +* xref:#ERC721ReceiverComponent-on_erc721_received[`++on_erc721_received(self, operator, from, token_id, data)++`] + +[.sub-index#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverCamelImpl] +.ERC721ReceiverCamelImpl +* xref:#ERC721ReceiverComponent-onERC721Received[`++onERC721Received(self, operator, from, tokenId, data)++`] +-- + +[.contract-index] +.Internal Functions +-- +.InternalImpl +* xref:#ERC721ReceiverComponent-initializer[`++initializer(self)++`] +-- + +==== Embeddable functions + +[.contract-item] +[[ERC721ReceiverComponent-on_erc721_received]] +==== `[.contract-item-name]#++on_erc721_received++#++(self: @ContractState, operator: ContractAddress, from: ContractAddress, token_id: u256, data Span) -> felt252++` [.item-kind]#external# + +Returns the `IERC721Receiver` interface ID. + +[.contract-item] +[[ERC721ReceiverComponent-onERC721Received]] +==== `[.contract-item-name]#++onERC721Received++#++(self: @ContractState, operator: ContractAddress, from: ContractAddress, token_id: u256, data Span) -> felt252++` [.item-kind]#external# + +See <>. + +==== Internal functions + +[.contract-item] +[[ERC721ReceiverComponent-initializer]] +==== `[.contract-item-name]#++initializer++#++(ref self: ContractState)++` [.item-kind]#internal# + +Registers the `IERC721Receiver` interface ID as supported through introspection. + == Presets [.contract] From 9fccfea1eaf7b994aa1d8b2f8c50568eb476f346 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 22 Jul 2024 17:17:07 -0500 Subject: [PATCH 53/73] add component description --- .../extensions/erc721_enumerable/erc721_enumerable.cairo | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 39c89a4cd..ce09257e3 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -4,7 +4,16 @@ /// # ERC721Enumerable Component /// +/// Extension of ERC721 as defined in the EIP that adds enumerability of all the token ids in the contract +/// as well as all token ids owned by each account. +/// This extension allows contracts to publish their entire list of NFTs and make them discoverable. /// +/// NOTE: Implementing ERC721Component is a requirement for this component to be implemented. +/// +/// WARNING: To properly track token ids, this extension requires that +/// the ERC721EnumerableComponent::before_update function is called after +/// every transfer, mint, or burn operation. +/// For this, the ERC721HooksTrait::before_update hook must be used. #[starknet::component] pub mod ERC721EnumerableComponent { use core::num::traits::Zero; From 45b66d0241b4fef1f83ad1160ecbe1634a12dfd3 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 22 Jul 2024 17:17:34 -0500 Subject: [PATCH 54/73] fix intro section --- docs/modules/ROOT/pages/api/erc721.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index f627ae48a..3b3113f28 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -742,14 +742,13 @@ use openzeppelin::token::extensions::ERC721EnumerableComponent; ``` Extension of ERC721 as defined in the EIP that adds enumerability of all the token ids in the contract as well as all token ids owned by each account. +This extension allows contracts to publish their entire list of NFTs and make them discoverable. NOTE: Implementing xref:#ERC721Component[ERC721Component] is a requirement for this component to be implemented. WARNING: To properly track token ids, this extension requires that the xref:#ERC721EnumerableComponent-before_update[ERC721EnumerableComponent::before_update] function is called after every transfer, mint, or burn operation. For this, the xref:ERC721Component-before_update[ERC721HooksTrait::before_update] hook must be used. -This extension allows contracts to publish their entire list of NFTs and make them discoverable. - [.contract-index#ERC721EnumerableComponent-Embeddable-Impls] .Embeddable Implementations -- From e462e2ed9f723de062fa814e2d7f058da881347c Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 22 Jul 2024 17:18:11 -0500 Subject: [PATCH 55/73] fix fmt --- .../extensions/erc721_enumerable/erc721_enumerable.cairo | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index ce09257e3..a0987aa2a 100644 --- a/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/src/token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -4,8 +4,8 @@ /// # ERC721Enumerable Component /// -/// Extension of ERC721 as defined in the EIP that adds enumerability of all the token ids in the contract -/// as well as all token ids owned by each account. +/// Extension of ERC721 as defined in the EIP that adds enumerability of all the token ids in the +/// contract as well as all token ids owned by each account. /// This extension allows contracts to publish their entire list of NFTs and make them discoverable. /// /// NOTE: Implementing ERC721Component is a requirement for this component to be implemented. From 3c9c32576bc7cd82de3ae56e46aca3819a03593a Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 2 Aug 2024 16:51:05 -0500 Subject: [PATCH 56/73] change to while loops in assertions --- .../tests/erc721/test_erc721_enumerable.cairo | 48 ++++++------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo index 562903233..99395a7dd 100644 --- a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo +++ b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo @@ -39,16 +39,8 @@ fn setup() -> (ComponentState, Span) { state.initializer(); let tokens_list = array![TOKEN_1, TOKEN_2, TOKEN_3].span(); - let mut i = 0; - - loop { - if i == tokens_list.len() { - break; - }; - - let token = *tokens_list.at(i); - mock_state.erc721.mint(OWNER(), token); - i = i + 1; + for token in tokens_list { + mock_state.erc721.mint(OWNER(), *token); }; (state, tokens_list) @@ -514,62 +506,50 @@ fn assert_dual_token_of_owner_by_index(owner: ContractAddress, expected_token_li let mut state = COMPONENT_STATE(); let mut i = 0; - loop { - if i == expected_token_list.len() { - break; - }; + while i < expected_token_list.len() { let token = state.token_of_owner_by_index(owner, i.into()); assert_eq!(token, *expected_token_list.at(i)); - i = i + 1; - }; + i += 1; + } } fn assert_dual_token_by_index(expected_token_list: Span) { let mut state = COMPONENT_STATE(); let mut i = 0; - loop { - if i == expected_token_list.len() { - break; - }; + while i < expected_token_list.len() { let token = state.token_by_index(i.into()); assert_eq!(token, *expected_token_list.at(i)); - i = i + 1; - }; + i += 1; + } } fn assert_after_update_all_tokens_list(expected_list: Span) { let state = @COMPONENT_STATE(); let mut i = 0; - loop { - if i == expected_list.len() { - break; - }; + while i < expected_list.len() { // Check total tokens list let token = state.ERC721Enumerable_all_tokens.read(i.into()); assert_eq!(token, *expected_list.at(i)); - i = i + 1; - }; + i += 1; + } } fn assert_after_update_owned_tokens_list(owner: ContractAddress, expected_list: Span) { let state = @COMPONENT_STATE(); let mut i = 0; - loop { - if i == expected_list.len() { - break; - }; + while i < expected_list.len() { // Check owned tokens list let token = state.ERC721Enumerable_owned_tokens.read((owner, i.into())); assert_eq!(token, *expected_list.at(i)); - i = i + 1; - }; + i += 1; + } } fn assert_all_tokens_index_to_id(index: u256, exp_token_id: u256) { From 3dd369885a52a6cd0d82c585545ef9b206865976 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 12 Aug 2024 19:51:12 -0500 Subject: [PATCH 57/73] simplify fn calls with self --- .../erc721_enumerable/erc721_enumerable.cairo | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index 9501c7245..c4234de48 100644 --- a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -119,17 +119,15 @@ pub mod ERC721EnumerableComponent { let zero_address = Zero::zero(); if previous_owner == zero_address { - PrivateImpl::_add_token_to_all_tokens_enumeration(ref self, token_id); + self._add_token_to_all_tokens_enumeration(token_id); } else if previous_owner != to { - PrivateImpl::_remove_token_from_owner_enumeration( - ref self, previous_owner, token_id - ); + self._remove_token_from_owner_enumeration(previous_owner, token_id); } if to == zero_address { - PrivateImpl::_remove_token_from_all_tokens_enumeration(ref self, token_id); + self._remove_token_from_all_tokens_enumeration(token_id); } else if previous_owner != to { - PrivateImpl::_add_token_to_owner_enumeration(ref self, to, token_id); + self._add_token_to_owner_enumeration(to, token_id); } } } From dba11f506f6b4cd41e96d5bcd4f226edb37d6f67 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 12 Aug 2024 19:57:22 -0500 Subject: [PATCH 58/73] remove zero var --- .../extensions/erc721_enumerable/erc721_enumerable.cairo | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index c4234de48..d119aa636 100644 --- a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -116,15 +116,14 @@ pub mod ERC721EnumerableComponent { ) { let erc721_component = get_dep_component!(@self, ERC721); let previous_owner = erc721_component._owner_of(token_id); - let zero_address = Zero::zero(); - if previous_owner == zero_address { + if previous_owner.is_zero() { self._add_token_to_all_tokens_enumeration(token_id); } else if previous_owner != to { self._remove_token_from_owner_enumeration(previous_owner, token_id); } - if to == zero_address { + if to.is_zero() { self._remove_token_from_all_tokens_enumeration(token_id); } else if previous_owner != to { self._add_token_to_owner_enumeration(to, token_id); From dd5f9072eceff9e879fae08db6b1086e9b56121c Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 12 Aug 2024 20:24:55 -0500 Subject: [PATCH 59/73] remove dual in helpers --- .../tests/erc721/test_erc721_enumerable.cairo | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo index 99395a7dd..7beb63899 100644 --- a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo +++ b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo @@ -98,7 +98,7 @@ fn test_total_supply() { fn test_token_by_index() { let (_a, token_list) = setup(); - assert_dual_token_by_index(token_list); + assert_token_by_index(token_list); } #[test] @@ -128,7 +128,7 @@ fn test_token_by_index_burn_last_token() { contract_state.erc721.burn(last_token); let expected_list = array![TOKEN_1, TOKEN_2]; - assert_dual_token_by_index(expected_list.span()); + assert_token_by_index(expected_list.span()); } #[test] @@ -142,7 +142,7 @@ fn test_token_by_index_burn_first_token() { // Burnt tokens are replaced by the last token // to prevent indexing gaps let expected_list = array![TOKEN_3, TOKEN_2]; - assert_dual_token_by_index(expected_list.span()); + assert_token_by_index(expected_list.span()); } #[test] @@ -162,7 +162,7 @@ fn test_token_by_index_burn_and_mint_all() { contract_state.erc721.mint(OWNER(), TOKEN_3); let expected_list = array![TOKEN_1, TOKEN_2, TOKEN_3]; - assert_dual_token_by_index(expected_list.span()); + assert_token_by_index(expected_list.span()); } // @@ -173,7 +173,7 @@ fn test_token_by_index_burn_and_mint_all() { fn test_token_of_owner_by_index() { let (_, tokens_list) = setup(); - assert_dual_token_of_owner_by_index(OWNER(), tokens_list); + assert_token_of_owner_by_index(OWNER(), tokens_list); } #[test] @@ -219,7 +219,7 @@ fn test_token_of_owner_by_index_remove_last_token() { contract_state.erc721.transfer(OWNER(), RECIPIENT(), last_token); let expected_list = array![TOKEN_1, TOKEN_2]; - assert_dual_token_of_owner_by_index(OWNER(), expected_list.span()); + assert_token_of_owner_by_index(OWNER(), expected_list.span()); } #[test] @@ -233,7 +233,7 @@ fn test_token_of_owner_by_index_remove_first_token() { // Removed tokens are replaced by the last token // to prevent indexing gaps let expected_list = array![TOKEN_3, TOKEN_2]; - assert_dual_token_of_owner_by_index(OWNER(), expected_list.span()); + assert_token_of_owner_by_index(OWNER(), expected_list.span()); } #[test] @@ -245,7 +245,7 @@ fn test_token_of_owner_by_index_when_all_tokens_transferred() { contract_state.erc721.transfer(OWNER(), RECIPIENT(), TOKEN_2); contract_state.erc721.transfer(OWNER(), RECIPIENT(), TOKEN_3); - assert_dual_token_of_owner_by_index(RECIPIENT(), tokens_list); + assert_token_of_owner_by_index(RECIPIENT(), tokens_list); } // @@ -270,7 +270,7 @@ fn test__update_when_mint() { // Check total tokens list let exp_total_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3, new_token]; - assert_dual_token_by_index(exp_total_tokens.span()); + assert_token_by_index(exp_total_tokens.span()); } #[test] @@ -502,7 +502,7 @@ fn test__remove_token_from_all_tokens_enumeration_with_first_token() { // Helpers // -fn assert_dual_token_of_owner_by_index(owner: ContractAddress, expected_token_list: Span) { +fn assert_token_of_owner_by_index(owner: ContractAddress, expected_token_list: Span) { let mut state = COMPONENT_STATE(); let mut i = 0; @@ -514,7 +514,7 @@ fn assert_dual_token_of_owner_by_index(owner: ContractAddress, expected_token_li } } -fn assert_dual_token_by_index(expected_token_list: Span) { +fn assert_token_by_index(expected_token_list: Span) { let mut state = COMPONENT_STATE(); let mut i = 0; From b983aa0de8a715c964c7fc86681811d99c31ed07 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 13 Aug 2024 12:00:43 -0500 Subject: [PATCH 60/73] check supply/owner bal == expected token list --- .../src/tests/erc721/test_erc721_enumerable.cairo | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo index 7beb63899..4cc77ae8f 100644 --- a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo +++ b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo @@ -96,7 +96,7 @@ fn test_total_supply() { #[test] fn test_token_by_index() { - let (_a, token_list) = setup(); + let (_, token_list) = setup(); assert_token_by_index(token_list); } @@ -504,6 +504,12 @@ fn test__remove_token_from_all_tokens_enumeration_with_first_token() { fn assert_token_of_owner_by_index(owner: ContractAddress, expected_token_list: Span) { let mut state = COMPONENT_STATE(); + let mut contract_state = CONTRACT_STATE(); + + // Check owner balance == expected_token_list + let owner_bal = contract_state.balance_of(owner); + let expected_list_len = expected_token_list.len().into(); + assert_eq!(owner_bal, expected_list_len); let mut i = 0; while i < expected_token_list.len() { @@ -517,6 +523,11 @@ fn assert_token_of_owner_by_index(owner: ContractAddress, expected_token_list: S fn assert_token_by_index(expected_token_list: Span) { let mut state = COMPONENT_STATE(); + // Check total_supply == expected_token_list + let total_supply = state.total_supply(); + let expected_list_len = expected_token_list.len().into(); + assert_eq!(total_supply, expected_list_len); + let mut i = 0; while i < expected_token_list.len() { let token = state.token_by_index(i.into()); From 80cff05c3172fd5d199909117c688348541da3c7 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 15 Aug 2024 14:58:55 -0500 Subject: [PATCH 61/73] fix test names/add comments for ctx --- .../tests/erc721/test_erc721_enumerable.cairo | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo index 4cc77ae8f..b015ac737 100644 --- a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo +++ b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo @@ -252,8 +252,13 @@ fn test_token_of_owner_by_index_when_all_tokens_transferred() { // _update // +// Note that `before_update` only updates the ERC721Enumerable state +// and NOT the ERC721 state. +// The following `before_update` tests are testing the isolated functionality of +// `before_update` and will have an inconsistent state with that of the ERC721 component. + #[test] -fn test__update_when_mint() { +fn test_before_update_when_mint() { let (mut state, _) = setup(); let initial_supply = state.total_supply(); let new_token = 'TOKEN_4'; @@ -274,7 +279,7 @@ fn test__update_when_mint() { } #[test] -fn test__update_when_last_token_burned() { +fn test_before_update_when_last_token_burned() { let (mut state, tokens_list) = setup(); let initial_supply = state.total_supply(); let last_token_toburn = *tokens_list.at(initial_supply.try_into().unwrap() - 1); @@ -291,11 +296,11 @@ fn test__update_when_last_token_burned() { // Check total tokens let exp_total_tokens = array![TOKEN_1, TOKEN_2]; - assert_after_update_all_tokens_list(exp_total_tokens.span()); + assert_token_by_index(exp_total_tokens.span()); } #[test] -fn test__update_when_first_token_burned() { +fn test_before_update_when_first_token_burned() { let (mut state, tokens_list) = setup(); let initial_supply = state.total_supply(); let first_token_toburn = *tokens_list.at(0); @@ -315,11 +320,11 @@ fn test__update_when_first_token_burned() { // Check total tokens let exp_total_tokens = array![TOKEN_3, TOKEN_2]; - assert_after_update_all_tokens_list(exp_total_tokens.span()); + assert_token_by_index(exp_total_tokens.span()); } #[test] -fn test__update_when_transfer_last_token() { +fn test_before_update_when_transfer_last_token() { let (mut state, tokens_list) = setup(); let initial_supply = state.total_supply(); let transfer_token = *tokens_list.at(initial_supply.try_into().unwrap() - 1); @@ -340,11 +345,11 @@ fn test__update_when_transfer_last_token() { // Check total tokens let exp_total_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3]; - assert_after_update_all_tokens_list(exp_total_tokens.span()); + assert_token_by_index(exp_total_tokens.span()); } #[test] -fn test__update_when_transfer_first_token() { +fn test_before_update_when_transfer_first_token() { let (mut state, tokens_list) = setup(); let initial_supply = state.total_supply(); let transfer_token = *tokens_list.at(0); @@ -368,7 +373,7 @@ fn test__update_when_transfer_first_token() { // Check all tokens let exp_total_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3]; - assert_after_update_all_tokens_list(exp_total_tokens.span()); + assert_token_by_index(exp_total_tokens.span()); } // @@ -537,19 +542,10 @@ fn assert_token_by_index(expected_token_list: Span) { } } -fn assert_after_update_all_tokens_list(expected_list: Span) { - let state = @COMPONENT_STATE(); - - let mut i = 0; - while i < expected_list.len() { - // Check total tokens list - let token = state.ERC721Enumerable_all_tokens.read(i.into()); - assert_eq!(token, *expected_list.at(i)); - - i += 1; - } -} - +/// Helper assertion that checks each token id in `expected_list` is stored for `owner` +/// in storage. This assertion reads from storage because it bypasses the out of bounds check +/// in `token_of_owner_by_index`. The `before_update` function does not update the +/// ERC721 state. fn assert_after_update_owned_tokens_list(owner: ContractAddress, expected_list: Span) { let state = @COMPONENT_STATE(); From 7ac8b36396e44e818e454191263c2a34154717e9 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 15 Aug 2024 18:16:30 -0500 Subject: [PATCH 62/73] improve assertion name --- .../tests/erc721/test_erc721_enumerable.cairo | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo index b015ac737..9abee978b 100644 --- a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo +++ b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo @@ -249,7 +249,7 @@ fn test_token_of_owner_by_index_when_all_tokens_transferred() { } // -// _update +// before_update // // Note that `before_update` only updates the ERC721Enumerable state @@ -271,7 +271,7 @@ fn test_before_update_when_mint() { // Check owner's tokens let exp_owner_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3, new_token]; - assert_after_update_owned_tokens_list(OWNER(), exp_owner_tokens.span()); + assert_owned_tokens_list_after_update(OWNER(), exp_owner_tokens.span()); // Check total tokens list let exp_total_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3, new_token]; @@ -292,7 +292,7 @@ fn test_before_update_when_last_token_burned() { // Check owner's tokens let exp_owner_tokens = array![TOKEN_1, TOKEN_2]; - assert_after_update_owned_tokens_list(OWNER(), exp_owner_tokens.span()); + assert_owned_tokens_list_after_update(OWNER(), exp_owner_tokens.span()); // Check total tokens let exp_total_tokens = array![TOKEN_1, TOKEN_2]; @@ -316,7 +316,7 @@ fn test_before_update_when_first_token_burned() { // // Check owner's tokens let exp_owner_tokens = array![TOKEN_3, TOKEN_2]; - assert_after_update_owned_tokens_list(OWNER(), exp_owner_tokens.span()); + assert_owned_tokens_list_after_update(OWNER(), exp_owner_tokens.span()); // Check total tokens let exp_total_tokens = array![TOKEN_3, TOKEN_2]; @@ -337,11 +337,11 @@ fn test_before_update_when_transfer_last_token() { // Check owner's tokens let exp_owner_tokens = array![TOKEN_1, TOKEN_2]; - assert_after_update_owned_tokens_list(OWNER(), exp_owner_tokens.span()); + assert_owned_tokens_list_after_update(OWNER(), exp_owner_tokens.span()); // Check recipient's tokens let exp_recipient_tokens = array![transfer_token]; - assert_after_update_owned_tokens_list(RECIPIENT(), exp_recipient_tokens.span()); + assert_owned_tokens_list_after_update(RECIPIENT(), exp_recipient_tokens.span()); // Check total tokens let exp_total_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3]; @@ -365,11 +365,11 @@ fn test_before_update_when_transfer_first_token() { // // Check owner's tokens let exp_owner_tokens = array![TOKEN_3, TOKEN_2]; - assert_after_update_owned_tokens_list(OWNER(), exp_owner_tokens.span()); + assert_owned_tokens_list_after_update(OWNER(), exp_owner_tokens.span()); // Check recipient's tokens let exp_recipient_tokens = array![transfer_token]; - assert_after_update_owned_tokens_list(RECIPIENT(), exp_recipient_tokens.span()); + assert_owned_tokens_list_after_update(RECIPIENT(), exp_recipient_tokens.span()); // Check all tokens let exp_total_tokens = array![TOKEN_1, TOKEN_2, TOKEN_3]; @@ -546,7 +546,7 @@ fn assert_token_by_index(expected_token_list: Span) { /// in storage. This assertion reads from storage because it bypasses the out of bounds check /// in `token_of_owner_by_index`. The `before_update` function does not update the /// ERC721 state. -fn assert_after_update_owned_tokens_list(owner: ContractAddress, expected_list: Span) { +fn assert_owned_tokens_list_after_update(owner: ContractAddress, expected_list: Span) { let state = @COMPONENT_STATE(); let mut i = 0; From 5b691a711f0a1ee0cd9ad1bfbc52c4cc92536c2b Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 15 Aug 2024 18:31:28 -0500 Subject: [PATCH 63/73] fix fmt --- packages/token/src/tests/erc721/test_erc721_enumerable.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo index b24d566ab..e01beab7d 100644 --- a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo +++ b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo @@ -1,5 +1,6 @@ use openzeppelin_introspection::interface::ISRC5_ID; use openzeppelin_introspection::src5::SRC5Component::SRC5Impl; +use openzeppelin_testing::constants::{OWNER, RECIPIENT, OTHER, ZERO}; use openzeppelin_token::erc721::ERC721Component::{ERC721Impl, InternalImpl as ERC721InternalImpl}; use openzeppelin_token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent::{ ERC721EnumerableImpl, InternalImpl @@ -8,7 +9,6 @@ use openzeppelin_token::erc721::extensions::erc721_enumerable::ERC721EnumerableC use openzeppelin_token::erc721::extensions::erc721_enumerable::erc721_enumerable::ERC721EnumerableComponent::PrivateTrait; use openzeppelin_token::erc721::extensions::erc721_enumerable::interface; use openzeppelin_token::tests::mocks::erc721_enumerable_mocks::ERC721EnumerableMock; -use openzeppelin_testing::constants::{OWNER, RECIPIENT, OTHER, ZERO}; use starknet::ContractAddress; // Token IDs From b6c561172c81373f8559fa85dcfa76e2c64410be Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 15 Aug 2024 18:34:23 -0500 Subject: [PATCH 64/73] update spdx license id --- docs/modules/ROOT/pages/api/erc721.adoc | 4 ++-- .../extensions/erc721_enumerable/erc721_enumerable.cairo | 2 +- .../src/erc721/extensions/erc721_enumerable/interface.cairo | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index e6c409843..5e1c03ee1 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -181,7 +181,7 @@ If the URI is not set for `token_id`, the return value will be an empty `ByteArr [.contract] [[IERC721Enumerable]] -=== `++IERC721Enumerable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/extensions/erc721_enumerable/interface.cairo[{github-icon},role=heading-link] +=== `++IERC721Enumerable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.1/src/token/erc721/extensions/erc721_enumerable/interface.cairo[{github-icon},role=heading-link] Interface for the optional enumerable functions in {eip721}. @@ -735,7 +735,7 @@ See <>. [.contract] [[ERC721EnumerableComponent]] -=== `++ERC721EnumerableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.0-rc.0/src/token/erc721/extensions/erc721_enumerable.cairo[{github-icon},role=heading-link] +=== `++ERC721EnumerableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.1/src/token/erc721/extensions/erc721_enumerable.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::token::extensions::ERC721EnumerableComponent; diff --git a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index d119aa636..bff47a547 100644 --- a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 +// OpenZeppelin Contracts for Cairo v0.15.1 // (token/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo) /// # ERC721Enumerable Component diff --git a/packages/token/src/erc721/extensions/erc721_enumerable/interface.cairo b/packages/token/src/erc721/extensions/erc721_enumerable/interface.cairo index 60461d117..08661977e 100644 --- a/packages/token/src/erc721/extensions/erc721_enumerable/interface.cairo +++ b/packages/token/src/erc721/extensions/erc721_enumerable/interface.cairo @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// OpenZeppelin Contracts for Cairo v0.15.0-rc.0 +// OpenZeppelin Contracts for Cairo v0.15.1 // (token/erc721/extensions/erc721_enumerable/interface.cairo) use starknet::ContractAddress; From 9478f729920cdbccfcfc87f8affb91b229a38490 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 15 Aug 2024 19:02:52 -0500 Subject: [PATCH 65/73] update version in docs --- docs/modules/ROOT/pages/api/erc721.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 5e1c03ee1..c107705d0 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -181,7 +181,7 @@ If the URI is not set for `token_id`, the return value will be an empty `ByteArr [.contract] [[IERC721Enumerable]] -=== `++IERC721Enumerable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.1/src/token/erc721/extensions/erc721_enumerable/interface.cairo[{github-icon},role=heading-link] +=== `++IERC721Enumerable++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.1/packages/token/src/erc721/extensions/erc721_enumerable/interface.cairo[{github-icon},role=heading-link] Interface for the optional enumerable functions in {eip721}. @@ -735,7 +735,7 @@ See <>. [.contract] [[ERC721EnumerableComponent]] -=== `++ERC721EnumerableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.1/src/token/erc721/extensions/erc721_enumerable.cairo[{github-icon},role=heading-link] +=== `++ERC721EnumerableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.1/packages/token/src/erc721/extensions/erc721_enumerable.cairo[{github-icon},role=heading-link] ```cairo use openzeppelin::token::extensions::ERC721EnumerableComponent; From 1ede1054e0d92933ace579a0c5f526256cda4a46 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Tue, 20 Aug 2024 18:35:36 -0500 Subject: [PATCH 66/73] Apply suggestions from code review Co-authored-by: Eric Nordelo --- docs/modules/ROOT/pages/api/erc721.adoc | 10 ++++------ packages/token/src/tests/erc721.cairo | 2 -- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index c107705d0..5576df503 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -209,14 +209,14 @@ Returns the total amount of tokens stored by the contract. [.contract-item] [[IERC721Metadata-token_by_index]] -==== `[.contract-item-name]#++token_by_index++#++(index) -> u256++` [.item-kind]#external# +==== `[.contract-item-name]#++token_by_index++#++(index: u256) -> u256++` [.item-kind]#external# Returns a token id at a given `index` of all the tokens stored by the contract. Use along with xref:#IERC721Enumerable-total_supply[IERC721Enumerable::total_supply] to enumerate all tokens. [.contract-item] [[IERC721Metadata-token_of_owner_by_index]] -==== `[.contract-item-name]#++token_of_owner_by_index++#++(owner, index) -> u256++` [.item-kind]#external# +==== `[.contract-item-name]#++token_of_owner_by_index++#++(owner: ContractAddress, index: u256) -> u256++` [.item-kind]#external# Returns the token id owned by `owner` at a given `index` of its token list. Use along with xref:#IERC721-balance_of[IERC721::balance_of] to enumerate all of ``owner``'s tokens. @@ -671,8 +671,6 @@ Requirements: Emits an <> event. -Emits an <> event. - [.contract-item] [[ERC721Component-_set_base_uri]] ==== `[.contract-item-name]#++_set_base_uri++#++(ref self: ContractState, base_uri: ByteArray)++` [.item-kind]#internal# @@ -738,7 +736,7 @@ See <>. === `++ERC721EnumerableComponent++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.1/packages/token/src/erc721/extensions/erc721_enumerable.cairo[{github-icon},role=heading-link] ```cairo -use openzeppelin::token::extensions::ERC721EnumerableComponent; +use openzeppelin::token::erc721::extensions::ERC721EnumerableComponent; ``` Extension of ERC721 as defined in the EIP that adds enumerability of all the token ids in the contract as well as all token ids owned by each account. @@ -746,7 +744,7 @@ This extension allows contracts to publish their entire list of NFTs and make th NOTE: Implementing xref:#ERC721Component[ERC721Component] is a requirement for this component to be implemented. -WARNING: To properly track token ids, this extension requires that the xref:#ERC721EnumerableComponent-before_update[ERC721EnumerableComponent::before_update] function is called after every transfer, mint, or burn operation. +WARNING: To properly track token ids, this extension requires that the xref:#ERC721EnumerableComponent-before_update[ERC721EnumerableComponent::before_update] function is called before every transfer, mint, or burn operation. For this, the xref:ERC721Component-before_update[ERC721HooksTrait::before_update] hook must be used. [.contract-index#ERC721EnumerableComponent-Embeddable-Impls] diff --git a/packages/token/src/tests/erc721.cairo b/packages/token/src/tests/erc721.cairo index ff68e7631..38933c7cb 100644 --- a/packages/token/src/tests/erc721.cairo +++ b/packages/token/src/tests/erc721.cairo @@ -1,7 +1,5 @@ mod test_dual721; mod test_dual721_receiver; mod test_erc721; -#[cfg(test)] mod test_erc721_enumerable; -#[cfg(test)] mod test_erc721_receiver; From 0d61150c18af009ea05a7ae593cf1007e3f8c3c6 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 20 Aug 2024 18:41:13 -0500 Subject: [PATCH 67/73] remove privateimpl from code --- .../erc721_enumerable/erc721_enumerable.cairo | 10 ---------- .../src/tests/erc721/test_erc721_enumerable.cairo | 1 - 2 files changed, 11 deletions(-) diff --git a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo index bff47a547..e953dad1e 100644 --- a/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo +++ b/packages/token/src/erc721/extensions/erc721_enumerable/erc721_enumerable.cairo @@ -129,17 +129,7 @@ pub mod ERC721EnumerableComponent { self._add_token_to_owner_enumeration(to, token_id); } } - } - #[generate_trait] - pub impl PrivateImpl< - TContractState, - +HasComponent, - impl ERC721: ERC721Component::HasComponent, - +ERC721Component::ERC721HooksTrait, - +SRC5Component::HasComponent, - +Drop - > of PrivateTrait { /// Adds token to this extension's ownership-tracking data structures. fn _add_token_to_owner_enumeration( ref self: ComponentState, to: ContractAddress, token_id: u256 diff --git a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo index e01beab7d..5b1263f02 100644 --- a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo +++ b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo @@ -6,7 +6,6 @@ use openzeppelin_token::erc721::extensions::erc721_enumerable::ERC721EnumerableC ERC721EnumerableImpl, InternalImpl }; use openzeppelin_token::erc721::extensions::erc721_enumerable::ERC721EnumerableComponent; -use openzeppelin_token::erc721::extensions::erc721_enumerable::erc721_enumerable::ERC721EnumerableComponent::PrivateTrait; use openzeppelin_token::erc721::extensions::erc721_enumerable::interface; use openzeppelin_token::tests::mocks::erc721_enumerable_mocks::ERC721EnumerableMock; use starknet::ContractAddress; From 0b5e92c3664b3103c65f31c4a70108831314b9c3 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 20 Aug 2024 18:44:12 -0500 Subject: [PATCH 68/73] remove privateimpl from docs --- docs/modules/ROOT/pages/api/erc721.adoc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 5576df503..3586d9b1c 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -763,8 +763,6 @@ For this, the xref:ERC721Component-before_update[ERC721HooksTrait::before_update .InternalImpl * xref:#ERC721EnumerableComponent-initializer[`++initializer(self)++`] * xref:#ERC721EnumerableComponent-before_update[`++before_update(self, to, token_id)++`] - -.PrivateImpl * xref:#ERC721EnumerableComponent-_add_token_to_owner_enumeration[`++_add_token_to_owner_enumeration(self, to, token_id)++`] * xref:#ERC721EnumerableComponent-_add_token_to_all_tokens_enumeration[`++_add_token_to_all_tokens_enumeration(self, token_id)++`] * xref:#ERC721EnumerableComponent-_remove_token_from_owner_enumeration[`++_remove_token_from_owner_enumeration(self, from, token_id)++`] @@ -824,19 +822,19 @@ This must be added to the implementing contract's xref:ERC721Component-before_up [.contract-item] [[ERC721EnumerableComponent-_add_token_to_owner_enumeration]] -==== `[.contract-item-name]#++_add_token_to_owner_enumeration++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#private# +==== `[.contract-item-name]#++_add_token_to_owner_enumeration++#++(ref self: ContractState, to: ContractAddress, token_id: u256)++` [.item-kind]#internal# Adds token to this extension's ownership-tracking data structures. [.contract-item] [[ERC721EnumerableComponent-_add_token_to_all_tokens_enumeration]] -==== `[.contract-item-name]#++_add_token_to_all_tokens_enumeration++#++(ref self: ContractState, token_id: u256)++` [.item-kind]#private# +==== `[.contract-item-name]#++_add_token_to_all_tokens_enumeration++#++(ref self: ContractState, token_id: u256)++` [.item-kind]#internal# Adds token to this extension's token-tracking data structures. [.contract-item] [[ERC721EnumerableComponent-_remove_token_from_owner_enumeration]] -==== `[.contract-item-name]#++_remove_token_from_owner_enumeration++#++(ref self: ContractState, from: ContractAddress, token_id: u256)++` [.item-kind]#private# +==== `[.contract-item-name]#++_remove_token_from_owner_enumeration++#++(ref self: ContractState, from: ContractAddress, token_id: u256)++` [.item-kind]#internal# Removes a token from this extension's ownership-tracking data structures. @@ -844,7 +842,7 @@ This has 0(1) time complexity but alters the indexed order of owned tokens by sw [.contract-item] [[ERC721EnumerableComponent-_remove_token_from_all_tokens_enumeration]] -==== `[.contract-item-name]#++_remove_token_from_all_tokens_enumeration++#++(ref self: ContractState, token_id: u256)++` [.item-kind]#private# +==== `[.contract-item-name]#++_remove_token_from_all_tokens_enumeration++#++(ref self: ContractState, token_id: u256)++` [.item-kind]#internal# Removes `token_id` from this extension's token-tracking data structures. From a81fcace3aace5fbd12781b313aac948813d8056 Mon Sep 17 00:00:00 2001 From: andrew Date: Tue, 20 Aug 2024 18:46:53 -0500 Subject: [PATCH 69/73] remove after_update hook from mocks --- .../src/tests/mocks/erc721_enumerable_mocks.cairo | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/token/src/tests/mocks/erc721_enumerable_mocks.cairo b/packages/token/src/tests/mocks/erc721_enumerable_mocks.cairo index 3252a6ea6..ae3e7286c 100644 --- a/packages/token/src/tests/mocks/erc721_enumerable_mocks.cairo +++ b/packages/token/src/tests/mocks/erc721_enumerable_mocks.cairo @@ -66,13 +66,6 @@ pub(crate) mod ERC721EnumerableMock { ); erc721_enumerable_component.before_update(to, token_id); } - - fn after_update( - ref self: ERC721Component::ComponentState, - to: ContractAddress, - token_id: u256, - auth: ContractAddress - ) {} } #[constructor] @@ -156,13 +149,6 @@ pub(crate) mod SnakeERC721EnumerableMock { ); erc721_enumerable_component.before_update(to, token_id); } - - fn after_update( - ref self: ERC721Component::ComponentState, - to: ContractAddress, - token_id: u256, - auth: ContractAddress - ) {} } #[constructor] From 4bb50b65cbc3531dc9542df4faf62d25eec06989 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Fri, 23 Aug 2024 16:33:06 -0500 Subject: [PATCH 70/73] Apply suggestions from code review Co-authored-by: Eric Nordelo --- docs/modules/ROOT/pages/api/erc721.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index 3586d9b1c..f2db2f2f8 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -758,7 +758,7 @@ For this, the xref:ERC721Component-before_update[ERC721HooksTrait::before_update -- [.contract-index] -.Internal implementations +.Internal functions -- .InternalImpl * xref:#ERC721EnumerableComponent-initializer[`++initializer(self)++`] From 0f863baab6e4462be540b5c2fedf68d72d3413e4 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 23 Aug 2024 18:23:21 -0500 Subject: [PATCH 71/73] use simpler hook impl --- .../tests/mocks/erc721_enumerable_mocks.cairo | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/packages/token/src/tests/mocks/erc721_enumerable_mocks.cairo b/packages/token/src/tests/mocks/erc721_enumerable_mocks.cairo index ae3e7286c..7c38fee45 100644 --- a/packages/token/src/tests/mocks/erc721_enumerable_mocks.cairo +++ b/packages/token/src/tests/mocks/erc721_enumerable_mocks.cairo @@ -48,23 +48,15 @@ pub(crate) mod ERC721EnumerableMock { SRC5Event: SRC5Component::Event } - impl ERC721EnumerableHooksImpl< - TContractState, - impl ERC721Enumerable: ERC721EnumerableComponent::HasComponent, - impl HasComponent: ERC721Component::HasComponent, - +SRC5Component::HasComponent, - +Drop - > of ERC721Component::ERC721HooksTrait { + impl ERC721HooksImpl of ERC721Component::ERC721HooksTrait { fn before_update( - ref self: ERC721Component::ComponentState, + ref self: ERC721Component::ComponentState, to: ContractAddress, token_id: u256, auth: ContractAddress ) { - let mut erc721_enumerable_component = get_dep_component_mut!( - ref self, ERC721Enumerable - ); - erc721_enumerable_component.before_update(to, token_id); + let mut contract_state = ERC721Component::HasComponent::get_contract_mut(ref self); + contract_state.erc721_enumerable.before_update(to, token_id); } } @@ -131,23 +123,15 @@ pub(crate) mod SnakeERC721EnumerableMock { SRC5Event: SRC5Component::Event } - impl ERC721EnumerableHooksImpl< - TContractState, - impl ERC721Enumerable: ERC721EnumerableComponent::HasComponent, - impl HasComponent: ERC721Component::HasComponent, - +SRC5Component::HasComponent, - +Drop - > of ERC721Component::ERC721HooksTrait { + impl ERC721HooksImpl of ERC721Component::ERC721HooksTrait { fn before_update( - ref self: ERC721Component::ComponentState, + ref self: ERC721Component::ComponentState, to: ContractAddress, token_id: u256, auth: ContractAddress ) { - let mut erc721_enumerable_component = get_dep_component_mut!( - ref self, ERC721Enumerable - ); - erc721_enumerable_component.before_update(to, token_id); + let mut contract_state = ERC721Component::HasComponent::get_contract_mut(ref self); + contract_state.erc721_enumerable.before_update(to, token_id); } } From 9ae2345f42cef6106eeb90fa13899ae0d1f0f026 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 23 Aug 2024 18:23:47 -0500 Subject: [PATCH 72/73] add before_update code example --- docs/modules/ROOT/pages/api/erc721.adoc | 26 ++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/api/erc721.adoc b/docs/modules/ROOT/pages/api/erc721.adoc index f2db2f2f8..f96f5e722 100644 --- a/docs/modules/ROOT/pages/api/erc721.adoc +++ b/docs/modules/ROOT/pages/api/erc721.adoc @@ -744,8 +744,32 @@ This extension allows contracts to publish their entire list of NFTs and make th NOTE: Implementing xref:#ERC721Component[ERC721Component] is a requirement for this component to be implemented. -WARNING: To properly track token ids, this extension requires that the xref:#ERC721EnumerableComponent-before_update[ERC721EnumerableComponent::before_update] function is called before every transfer, mint, or burn operation. +To properly track token ids, this extension requires that the xref:#ERC721EnumerableComponent-before_update[ERC721EnumerableComponent::before_update] function is called before every transfer, mint, or burn operation. For this, the xref:ERC721Component-before_update[ERC721HooksTrait::before_update] hook must be used. +Here's how the hook should be implemented in a contract: + +```[,cairo] +#[starknet::contract] +mod ERC721EnumerableContract { + (...) + + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + impl ERC721HooksImpl of ERC721Component::ERC721HooksTrait { + fn before_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { + let mut contract_state = ERC721Component::HasComponent::get_contract_mut(ref self); + contract_state.erc721_enumerable.before_update(to, token_id); + } + } +} +``` [.contract-index#ERC721EnumerableComponent-Embeddable-Impls] .Embeddable Implementations From abf6b6647d7b7e6801b43304d504120384305065 Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Thu, 29 Aug 2024 12:57:14 -0500 Subject: [PATCH 73/73] Apply suggestions from code review Co-authored-by: immrsd <103599616+immrsd@users.noreply.github.com> --- .../token/src/tests/erc721/test_erc721_enumerable.cairo | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo index 5b1263f02..b5563af77 100644 --- a/packages/token/src/tests/erc721/test_erc721_enumerable.cairo +++ b/packages/token/src/tests/erc721/test_erc721_enumerable.cairo @@ -507,8 +507,8 @@ fn test__remove_token_from_all_tokens_enumeration_with_first_token() { // fn assert_token_of_owner_by_index(owner: ContractAddress, expected_token_list: Span) { - let mut state = COMPONENT_STATE(); - let mut contract_state = CONTRACT_STATE(); + let state = @COMPONENT_STATE(); + let contract_state = @CONTRACT_STATE(); // Check owner balance == expected_token_list let owner_bal = contract_state.balance_of(owner); @@ -525,7 +525,7 @@ fn assert_token_of_owner_by_index(owner: ContractAddress, expected_token_list: S } fn assert_token_by_index(expected_token_list: Span) { - let mut state = COMPONENT_STATE(); + let state = @COMPONENT_STATE(); // Check total_supply == expected_token_list let total_supply = state.total_supply();