diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index b03ae578..fec7ea70 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -7,7 +7,883 @@ const deployedContracts = { devnet: { YourCollectible: { address: - "0x7e2708a9e40f854e3fc0a484a7ba566d85c3968834c24577a6a44702aedca8a", + "0x1e1965ee2f17bd26cbc12a602469a3784f97933da3f37cd7f46161dd9d11005", + abi: [ + { + type: "impl", + name: "YourCollectibleImpl", + interface_name: "contracts::YourCollectible::IYourCollectible", + }, + { + type: "struct", + name: "core::byte_array::ByteArray", + members: [ + { + name: "data", + type: "core::array::Array::", + }, + { + name: "pending_word", + type: "core::felt252", + }, + { + name: "pending_word_len", + type: "core::integer::u32", + }, + ], + }, + { + type: "struct", + name: "core::integer::u256", + members: [ + { + name: "low", + type: "core::integer::u128", + }, + { + name: "high", + type: "core::integer::u128", + }, + ], + }, + { + type: "interface", + name: "contracts::YourCollectible::IYourCollectible", + items: [ + { + type: "function", + name: "mint_item", + inputs: [ + { + name: "recipient", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "uri", + type: "core::byte_array::ByteArray", + }, + ], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "external", + }, + ], + }, + { + type: "impl", + name: "WrappedIERC721MetadataImpl", + interface_name: + "openzeppelin::token::erc721::interface::IERC721Metadata", + }, + { + type: "interface", + name: "openzeppelin::token::erc721::interface::IERC721Metadata", + items: [ + { + type: "function", + name: "name", + inputs: [], + outputs: [ + { + type: "core::byte_array::ByteArray", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "symbol", + inputs: [], + outputs: [ + { + type: "core::byte_array::ByteArray", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "token_uri", + inputs: [ + { + name: "token_id", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::byte_array::ByteArray", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "WrappedIERC721Impl", + interface_name: "openzeppelin::token::erc721::interface::IERC721", + }, + { + type: "struct", + name: "core::array::Span::", + members: [ + { + name: "snapshot", + type: "@core::array::Array::", + }, + ], + }, + { + type: "enum", + name: "core::bool", + variants: [ + { + name: "False", + type: "()", + }, + { + name: "True", + type: "()", + }, + ], + }, + { + type: "interface", + name: "openzeppelin::token::erc721::interface::IERC721", + items: [ + { + type: "function", + name: "balance_of", + inputs: [ + { + name: "account", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "owner_of", + inputs: [ + { + name: "token_id", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::starknet::contract_address::ContractAddress", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "safe_transfer_from", + inputs: [ + { + name: "from", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "to", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "token_id", + type: "core::integer::u256", + }, + { + name: "data", + type: "core::array::Span::", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "transfer_from", + inputs: [ + { + name: "from", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "to", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "token_id", + type: "core::integer::u256", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "approve", + inputs: [ + { + name: "to", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "token_id", + type: "core::integer::u256", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "set_approval_for_all", + inputs: [ + { + name: "operator", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "approved", + type: "core::bool", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "get_approved", + inputs: [ + { + name: "token_id", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::starknet::contract_address::ContractAddress", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "is_approved_for_all", + inputs: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "operator", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "IERC721EnumerableImpl", + interface_name: "contracts::YourCollectible::IERC721Enumerable", + }, + { + type: "interface", + name: "contracts::YourCollectible::IERC721Enumerable", + items: [ + { + type: "function", + name: "token_of_owner_by_index", + inputs: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + { + name: "index", + type: "core::integer::u256", + }, + ], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "total_supply", + inputs: [], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "OwnableImpl", + interface_name: "openzeppelin::access::ownable::interface::IOwnable", + }, + { + type: "interface", + name: "openzeppelin::access::ownable::interface::IOwnable", + items: [ + { + type: "function", + name: "owner", + inputs: [], + outputs: [ + { + type: "core::starknet::contract_address::ContractAddress", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "transfer_ownership", + inputs: [ + { + name: "new_owner", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "renounce_ownership", + inputs: [], + outputs: [], + state_mutability: "external", + }, + ], + }, + { + type: "impl", + name: "CounterImpl", + interface_name: "contracts::Counter::ICounter", + }, + { + type: "interface", + name: "contracts::Counter::ICounter", + items: [ + { + type: "function", + name: "current", + inputs: [], + outputs: [ + { + type: "core::integer::u256", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "increment", + inputs: [], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "decrement", + inputs: [], + outputs: [], + state_mutability: "external", + }, + ], + }, + { + type: "constructor", + name: "constructor", + inputs: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + }, + { + type: "event", + name: "openzeppelin::token::erc721::erc721::ERC721Component::Transfer", + kind: "struct", + members: [ + { + name: "from", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "to", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "token_id", + type: "core::integer::u256", + kind: "key", + }, + ], + }, + { + type: "event", + name: "openzeppelin::token::erc721::erc721::ERC721Component::Approval", + kind: "struct", + members: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "approved", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "token_id", + type: "core::integer::u256", + kind: "key", + }, + ], + }, + { + type: "event", + name: "openzeppelin::token::erc721::erc721::ERC721Component::ApprovalForAll", + kind: "struct", + members: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "operator", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "approved", + type: "core::bool", + kind: "data", + }, + ], + }, + { + type: "event", + name: "openzeppelin::token::erc721::erc721::ERC721Component::Event", + kind: "enum", + variants: [ + { + name: "Transfer", + type: "openzeppelin::token::erc721::erc721::ERC721Component::Transfer", + kind: "nested", + }, + { + name: "Approval", + type: "openzeppelin::token::erc721::erc721::ERC721Component::Approval", + kind: "nested", + }, + { + name: "ApprovalForAll", + type: "openzeppelin::token::erc721::erc721::ERC721Component::ApprovalForAll", + kind: "nested", + }, + ], + }, + { + type: "event", + name: "openzeppelin::token::erc721::erc721_receiver::ERC721ReceiverComponent::Event", + kind: "enum", + variants: [], + }, + { + type: "event", + name: "openzeppelin::introspection::src5::SRC5Component::Event", + kind: "enum", + variants: [], + }, + { + type: "event", + name: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferred", + kind: "struct", + members: [ + { + name: "previous_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "new_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + ], + }, + { + type: "event", + name: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferStarted", + kind: "struct", + members: [ + { + name: "previous_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "new_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + ], + }, + { + type: "event", + name: "openzeppelin::access::ownable::ownable::OwnableComponent::Event", + kind: "enum", + variants: [ + { + name: "OwnershipTransferred", + type: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferred", + kind: "nested", + }, + { + name: "OwnershipTransferStarted", + type: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferStarted", + kind: "nested", + }, + ], + }, + { + type: "event", + name: "contracts::Counter::CounterComponent::Event", + kind: "enum", + variants: [], + }, + { + type: "event", + name: "contracts::YourCollectible::YourCollectible::Event", + kind: "enum", + variants: [ + { + name: "ERC721Event", + type: "openzeppelin::token::erc721::erc721::ERC721Component::Event", + kind: "flat", + }, + { + name: "ERC721ReceiverEvent", + type: "openzeppelin::token::erc721::erc721_receiver::ERC721ReceiverComponent::Event", + kind: "flat", + }, + { + name: "SRC5Event", + type: "openzeppelin::introspection::src5::SRC5Component::Event", + kind: "flat", + }, + { + name: "OwnableEvent", + type: "openzeppelin::access::ownable::ownable::OwnableComponent::Event", + kind: "flat", + }, + { + name: "CounterEvent", + type: "contracts::Counter::CounterComponent::Event", + kind: "nested", + }, + ], + }, + ], + classHash: + "0x604a5408288ed85d4220cfa7bddf6db718feec9f79455e7391ea81c063023f5", + }, + YourContract: { + address: + "0xb0fd0844236d9476db0380885b452b878131f18169874d8764b3356e36005a", + abi: [ + { + type: "impl", + name: "YourContractImpl", + interface_name: "contracts::YourContract::IYourContract", + }, + { + type: "struct", + name: "core::byte_array::ByteArray", + members: [ + { + name: "data", + type: "core::array::Array::", + }, + { + name: "pending_word", + type: "core::felt252", + }, + { + name: "pending_word_len", + type: "core::integer::u32", + }, + ], + }, + { + type: "struct", + name: "core::integer::u256", + members: [ + { + name: "low", + type: "core::integer::u128", + }, + { + name: "high", + type: "core::integer::u128", + }, + ], + }, + { + type: "enum", + name: "core::bool", + variants: [ + { + name: "False", + type: "()", + }, + { + name: "True", + type: "()", + }, + ], + }, + { + type: "interface", + name: "contracts::YourContract::IYourContract", + items: [ + { + type: "function", + name: "gretting", + inputs: [], + outputs: [ + { + type: "core::byte_array::ByteArray", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "set_gretting", + inputs: [ + { + name: "new_greeting", + type: "core::byte_array::ByteArray", + }, + { + name: "amount_eth", + type: "core::integer::u256", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "withdraw", + inputs: [], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "premium", + inputs: [], + outputs: [ + { + type: "core::bool", + }, + ], + state_mutability: "view", + }, + ], + }, + { + type: "impl", + name: "OwnableImpl", + interface_name: "openzeppelin::access::ownable::interface::IOwnable", + }, + { + type: "interface", + name: "openzeppelin::access::ownable::interface::IOwnable", + items: [ + { + type: "function", + name: "owner", + inputs: [], + outputs: [ + { + type: "core::starknet::contract_address::ContractAddress", + }, + ], + state_mutability: "view", + }, + { + type: "function", + name: "transfer_ownership", + inputs: [ + { + name: "new_owner", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + outputs: [], + state_mutability: "external", + }, + { + type: "function", + name: "renounce_ownership", + inputs: [], + outputs: [], + state_mutability: "external", + }, + ], + }, + { + type: "constructor", + name: "constructor", + inputs: [ + { + name: "owner", + type: "core::starknet::contract_address::ContractAddress", + }, + ], + }, + { + type: "event", + name: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferred", + kind: "struct", + members: [ + { + name: "previous_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "new_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + ], + }, + { + type: "event", + name: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferStarted", + kind: "struct", + members: [ + { + name: "previous_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "new_owner", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + ], + }, + { + type: "event", + name: "openzeppelin::access::ownable::ownable::OwnableComponent::Event", + kind: "enum", + variants: [ + { + name: "OwnershipTransferred", + type: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferred", + kind: "nested", + }, + { + name: "OwnershipTransferStarted", + type: "openzeppelin::access::ownable::ownable::OwnableComponent::OwnershipTransferStarted", + kind: "nested", + }, + ], + }, + { + type: "event", + name: "contracts::YourContract::YourContract::GreetingChanged", + kind: "struct", + members: [ + { + name: "greeting_setter", + type: "core::starknet::contract_address::ContractAddress", + kind: "key", + }, + { + name: "new_greeting", + type: "core::byte_array::ByteArray", + kind: "key", + }, + { + name: "premium", + type: "core::bool", + kind: "data", + }, + { + name: "value", + type: "core::integer::u256", + kind: "data", + }, + ], + }, + { + type: "event", + name: "contracts::YourContract::YourContract::Event", + kind: "enum", + variants: [ + { + name: "OwnableEvent", + type: "openzeppelin::access::ownable::ownable::OwnableComponent::Event", + kind: "flat", + }, + { + name: "GreetingChanged", + type: "contracts::YourContract::YourContract::GreetingChanged", + kind: "nested", + }, + ], + }, + ], + classHash: + "0x63f027df0f2faa7a7dd3ff7ddd301ac67dfe18f947aad36b490ac4fac788b73", + }, + }, + sepolia: { + YourCollectible: { + address: + "0x45305e8c94f02c8ade9c6df459a1c84e98bbbf2ba9d0ad7047652c310d9f5e8", abi: [ { type: "impl", diff --git a/packages/snfoundry/contracts/src/YourCollectible.cairo b/packages/snfoundry/contracts/src/YourCollectible.cairo index bd9b645e..6ed8793a 100644 --- a/packages/snfoundry/contracts/src/YourCollectible.cairo +++ b/packages/snfoundry/contracts/src/YourCollectible.cairo @@ -14,6 +14,13 @@ pub trait IERC721Enumerable { #[starknet::interface] pub trait IERC721 { fn balance_of(self: @T, account: ContractAddress) -> u256; + fn transfer_from(ref self: T, from: ContractAddress, to: ContractAddress, token_id: u256); + fn owner_of(self: @T, token_id: u256) -> ContractAddress; +} + +#[starknet::interface] +pub trait IERC721Metadata { + fn token_uri(self: @T, token_id: u256) -> ByteArray; } #[starknet::contract] @@ -30,8 +37,7 @@ mod YourCollectible { IERC721_ID, IERC721_METADATA_ID, IERC721_RECEIVER_ID, }; use openzeppelin::token::erc721::{ - ERC721ReceiverComponent, ERC721Component, ERC721HooksEmptyImpl, - interface::{IERC721, IERC721Metadata} + ERC721ReceiverComponent, ERC721Component, interface::{IERC721, IERC721Metadata} }; use starknet::get_caller_address; @@ -192,8 +198,10 @@ mod YourCollectible { fn mint(ref self: ContractState, recipient: ContractAddress, token_id: u256) { assert(!recipient.is_zero(), ERC721Component::Errors::INVALID_RECEIVER); assert(!self.erc721.exists(token_id), ERC721Component::Errors::ALREADY_MINTED); - self._before_token_transfer(Zero::zero(), recipient, token_id, 1); + //self._before_token_transfer(Zero::zero(), recipient, token_id, 1); + println!("Minting token: {:?}", token_id); self.erc721.mint(recipient, token_id); + println!("Token minted: {:?}", token_id); } fn _transfer( @@ -203,7 +211,7 @@ mod YourCollectible { let owner = self.erc721._owner_of(token_id); assert(from == owner, ERC721Component::Errors::INVALID_SENDER); - self._before_token_transfer(from, to, token_id, 1); + //self._before_token_transfer(from, to, token_id, 1); assert(from == owner, ERC721Component::Errors::INVALID_SENDER); @@ -300,4 +308,72 @@ mod YourCollectible { } } } + pub impl ERC721HooksEmptyImpl 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); + let token_id_counter = contract_state.token_id_counter.current(); + if (token_id == token_id_counter) { // self._add_token_to_all_tokens_enumeration(first_token_id); + let length = contract_state.all_tokens_counter.read(); + contract_state.all_tokens_index.write(token_id, length); + contract_state.all_tokens.write(length, token_id); + contract_state.all_tokens_counter.write(length + 1); + println!("auth == Zero::zero()"); + } else if (token_id < token_id_counter + + 1) { //self._remove_token_from_owner_enumeration(auth, first_token_id); + // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + println!("auth != to"); + let owner = self.owner_of(token_id); + println!("owner {:?}", owner); + let last_token_index = self.balance_of(owner) - 1; + println!("last_token_index {:?}", last_token_index); + let token_index = contract_state.owned_tokens_index.read(token_id); + + // When the token to delete is the last token, the swap operation is unnecessary + if (token_index != last_token_index) { + let last_token_id = contract_state.owned_tokens.read((owner, last_token_index)); + // Move the last token to the slot of the to-delete token + contract_state.owned_tokens.write((owner, token_index), last_token_id); + // Update the moved token's index + println!("token_index != last_token_index"); + contract_state.owned_tokens_index.write(last_token_id, token_index); + } + + // Clear the last slot + contract_state.owned_tokens.write((owner, last_token_index), 0); + contract_state.owned_tokens_index.write(token_id, 0); + } + if (to == Zero::zero()) { //self._remove_token_from_all_tokens_enumeration(first_token_id); + let last_token_index = contract_state.all_tokens_counter.read() - 1; + let token_index = contract_state.all_tokens_index.read(token_id); + + let last_token_id = contract_state.all_tokens.read(last_token_index); + + contract_state.all_tokens.write(token_index, last_token_id); + contract_state.all_tokens_index.write(last_token_id, token_index); + + contract_state.all_tokens_index.write(token_id, 0); + contract_state.all_tokens.write(last_token_index, 0); + contract_state.all_tokens_counter.write(last_token_index); + println!("to == Zero::zero()"); + } else if (to != auth) { //self._add_token_to_owner_enumeration(to, first_token_id); + let length = self.balance_of(to); + contract_state.owned_tokens.write((to, length), token_id); + contract_state.owned_tokens_index.write(token_id, length); + println!("to != auth"); + } + } + + fn after_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) {} + } } diff --git a/packages/snfoundry/contracts/src/test/TestContract.cairo b/packages/snfoundry/contracts/src/test/TestContract.cairo index f3a95df7..325b978f 100644 --- a/packages/snfoundry/contracts/src/test/TestContract.cairo +++ b/packages/snfoundry/contracts/src/test/TestContract.cairo @@ -1,6 +1,8 @@ +use core::clone::Clone; use contracts::YourCollectible::{ IYourCollectibleDispatcher, IYourCollectibleDispatcherTrait, IERC721Dispatcher, - IERC721DispatcherTrait, IERC721EnumerableDispatcher, IERC721EnumerableDispatcherTrait + IERC721DispatcherTrait, IERC721EnumerableDispatcher, IERC721EnumerableDispatcherTrait, + IERC721MetadataDispatcher, IERC721MetadataDispatcherTrait }; use contracts::mock_contracts::Receiver; @@ -24,6 +26,9 @@ fn OWNER() -> ContractAddress { contract_address_const::<'OWNER'>() } +fn NEW_OWNER() -> ContractAddress { + contract_address_const::<'NEW_OWNER'>() +} fn deploy_receiver() -> ContractAddress { let contract = declare("Receiver").unwrap(); let mut calldata = array![]; @@ -33,6 +38,7 @@ fn deploy_receiver() -> ContractAddress { } #[test] +// Test mint two items and transfer the last item fn test_mint_item() { // Should be able to mint an NFT let contract_address = deploy_contract("YourCollectible"); @@ -44,7 +50,7 @@ fn test_mint_item() { println!("Starting balance: {:?}", starting_balance); println!("Minting..."); let url: ByteArray = "QmfVMAmNM1kDEBYrC2TPzQDoCRFH6F5tE1e9Mr4FkkR5Xr"; - let token_id = dispatcher.mint_item(tester_address, url); + let token_id = dispatcher.mint_item(tester_address, url.clone()); let expected_token_id = 1; assert(token_id == expected_token_id, 'Token ID must be 1'); println!("Item minted! Token ID: {:?}", token_id); @@ -59,4 +65,106 @@ fn test_mint_item() { println!("token of owner by index {:?}", token); assert(token > 0, 'Token must be greater than zero'); println!("Token of owner({:?}) by index({:?}): {:?}", tester_address, index, token); + + // mint another item + let url2: ByteArray = "QmVHi3c4qkZcH3cJynzDXRm5n7dzc9R9TUtUcfnWQvhdcw"; + let token_id = dispatcher.mint_item(tester_address, url2); + let expected_token_id = 2; + assert(token_id == expected_token_id, 'Token ID must be 2'); + println!("Item minted! Token ID: {:?}", token_id); + let new_balance = erc721.balance_of(tester_address); + assert(new_balance == starting_balance + 2, 'Balance must be increased by 2'); + println!("New balance: {:?}", new_balance); + + // transfer item + let new_owner = NEW_OWNER(); + println!("new_owner address: {:?}", new_owner); + let starting_balance = erc721.balance_of(new_owner); + println!("Starting balance new_owner: {:?}", starting_balance); + + erc721.transfer_from(tester_address, new_owner, 1); + let balance_new_owner = erc721.balance_of(new_owner); + assert(balance_new_owner == starting_balance + 1, 'Balance must be increased by 1'); + println!("New balance new_owner: {:?}", balance_new_owner); + + let balance_tester = erc721.balance_of(tester_address); + assert(balance_tester == new_balance - 1, 'Balance must be decreased by 1'); + println!("New balance tester: {:?}", balance_tester); + + let erc721Metadata = IERC721MetadataDispatcher { contract_address }; + let token_uri = erc721Metadata.token_uri(1); + println!("Token URI: {:?}", token_uri); + + // owner_of + let owner = erc721.owner_of(1); + assert(owner == new_owner, 'Owner must be new_owner'); + + let owner = erc721.owner_of(2); + assert(owner == tester_address, 'Owner must be tester_address'); + + let index = erc721Enumerable.token_of_owner_by_index(new_owner, 0); + println!("token of owner by index {:?}", index); + assert(index == 1, 'Token must be 1'); + + let index = erc721Enumerable.token_of_owner_by_index(tester_address, 0); + println!("token of owner by index {:?}", index); + assert(index == 2, 'Token must be 2'); +} + + +#[test] +// Test: mint one item and transfer it to another account +fn test_mint_item2() { + // Should be able to mint an NFT + let contract_address = deploy_contract("YourCollectible"); + let dispatcher = IYourCollectibleDispatcher { contract_address }; + let erc721 = IERC721Dispatcher { contract_address }; + let tester_address = deploy_receiver(); + println!("Tester address: {:?}", tester_address); + let starting_balance = erc721.balance_of(tester_address); + println!("Starting balance: {:?}", starting_balance); + println!("Minting..."); + let url: ByteArray = "QmfVMAmNM1kDEBYrC2TPzQDoCRFH6F5tE1e9Mr4FkkR5Xr"; + let token_id = dispatcher.mint_item(tester_address, url.clone()); + let expected_token_id = 1; + assert(token_id == expected_token_id, 'Token ID must be 1'); + println!("Item minted! Token ID: {:?}", token_id); + let new_balance = erc721.balance_of(tester_address); + assert(new_balance == starting_balance + 1, 'Balance must be increased by 1'); + println!("New balance: {:?}", new_balance); + + // Should track tokens of owner by index + let erc721Enumerable = IERC721EnumerableDispatcher { contract_address }; + let index = new_balance - 1; + let token = erc721Enumerable.token_of_owner_by_index(tester_address, index); + println!("token of owner by index {:?}", token); + assert(token > 0, 'Token must be greater than zero'); + println!("Token of owner({:?}) by index({:?}): {:?}", tester_address, index, token); + + // transfer item + let new_owner = NEW_OWNER(); + println!("new_owner address: {:?}", new_owner); + let starting_balance = erc721.balance_of(new_owner); + println!("Starting balance new_owner: {:?}", starting_balance); + + erc721.transfer_from(tester_address, new_owner, 1); + let balance_new_owner = erc721.balance_of(new_owner); + assert(balance_new_owner == starting_balance + 1, 'Balance must be increased by 1'); + println!("New balance new_owner: {:?}", balance_new_owner); + + let balance_tester = erc721.balance_of(tester_address); + assert(balance_tester == new_balance - 1, 'Balance must be decreased by 1'); + println!("New balance tester: {:?}", balance_tester); + + let erc721Metadata = IERC721MetadataDispatcher { contract_address }; + let token_uri = erc721Metadata.token_uri(1); + println!("Token URI: {:?}", token_uri); + + // owner_of + let owner = erc721.owner_of(1); + assert(owner == new_owner, 'Owner must be new_owner'); + + let index = erc721Enumerable.token_of_owner_by_index(new_owner, 0); + println!("token of owner by index {:?}", index); + assert(index == 1, 'Token must be 1'); }