From 1ef0fe4fa7037ff716736e78ce806cca2eb56cd6 Mon Sep 17 00:00:00 2001 From: raizo07 Date: Tue, 4 Jun 2024 23:13:54 +0100 Subject: [PATCH 01/12] feat: added an ERC721 NFT Contract --- listings/applications/ERC721/ERC721.cairo | 198 ++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 listings/applications/ERC721/ERC721.cairo diff --git a/listings/applications/ERC721/ERC721.cairo b/listings/applications/ERC721/ERC721.cairo new file mode 100644 index 00000000..e2dc6e07 --- /dev/null +++ b/listings/applications/ERC721/ERC721.cairo @@ -0,0 +1,198 @@ +#[starknet::contract] +mod ERC721Contract { + + use starknet::ContractAddress; + use starknet::get_caller_address; + use zeroable::Zeroable; + use starknet::contract_address_to_felt252; + use traits::Into; + use traits::TryInto; + use option::OptionTrait; + + #[storage] + struct Storage { + name: felt252, + symbol: felt252, + owners: LegacyMap::, + balances: LegacyMap::, + token_approvals: LegacyMap::, + operator_approvals: LegacyMap::<(ContractAddress, ContractAddress), bool>, + token_uri: LegacyMap::, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Approval: Approval, + Transfer: Transfer, + ApprovalForAll: ApprovalForAll + } + + #[derive(Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + to: ContractAddress, + token_id: u256 + } + + #[derive(Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + token_id: u256 + } + + #[derive(Drop, starknet::Event)] + struct ApprovalForAll { + owner: ContractAddress, + operator: ContractAddress, + approved: bool + } + + #[constructor] + fn constructor(ref self: ContractState, _name: felt252, _symbol: felt252) { + self.name.write(_name); + self.symbol.write(_symbol); + } + + #[external(v0)] + #[generate_trait] + impl IERC721Impl of IERC721Trait { + // Returns the name of the token collection + fn get_name(self: @ContractState) -> felt252 { + self.name.read() + } + + // Returns the symbol of the token collection + fn get_symbol(self: @ContractState) -> felt252 { + self.symbol.read() + } + + // Returns the metadata URI for a given token ID + fn get_token_uri(self: @ContractState, token_id: u256) -> felt252 { + assert(self._exists(token_id), 'ERC721: invalid token ID'); + self.token_uri.read(token_id) + } + + // Returns the number of tokens owned by a specific address + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + assert(account.is_non_zero(), 'ERC721: address zero'); + self.balances.read(account) + } + + // Returns the owner of the specified token ID + fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { + let owner = self.owners.read(token_id); + assert(owner.is_non_zero(), 'ERC721: invalid token ID'); + owner + } + + // Returns the approved address for a specific token ID + fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { + assert(self._exists(token_id), 'ERC721: invalid token ID'); + self.token_approvals.read(token_id) + } + + // Checks if an operator is approved to manage all of the assets of an owner + fn is_approved_for_all(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool { + self.operator_approvals.read((owner, operator)) + } + + // Approves another address to transfer the given token ID + fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { + let owner = self.owner_of(token_id); + assert(to != owner, 'Approval to current owner'); + assert(get_caller_address() == owner || self.is_approved_for_all(owner, get_caller_address()), 'Not token owner'); + self.token_approvals.write(token_id, to); + self.emit( + Approval{ owner: self.owner_of(token_id), to: to, token_id: token_id } + ); + } + + // Sets or unsets the approval of a given operator + fn set_approval_for_all(ref self: ContractState, operator: ContractAddress, approved: bool) { + let owner = get_caller_address(); + assert(owner != operator, 'ERC721: approve to caller'); + self.operator_approvals.write((owner, operator), approved); + self.emit( + ApprovalForAll{ owner: owner, operator: operator, approved: approved } + ); + } + + // Transfers a specific token ID to another address + fn transfer_from(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256) { + assert(self._is_approved_or_owner(get_caller_address(), token_id), 'neither owner nor approved'); + self._transfer(from, to, token_id); + } + } + + #[generate_trait] + impl ERC721HelperImpl of ERC721HelperTrait { + // Checks if a specific token ID exists + fn _exists(self: @ContractState, token_id: u256) -> bool { + self.owner_of(token_id).is_non_zero() + } + + // Checks if a spender is the owner or an approved operator of a specific token ID + fn _is_approved_or_owner(self: @ContractState, spender: ContractAddress, token_id: u256) -> bool { + let owner = self.owners.read(token_id); + spender == owner + || self.is_approved_for_all(owner, spender) + || self.get_approved(token_id) == spender + } + + // Sets the metadata URI for a specific token ID + fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { + assert(self._exists(token_id), 'ERC721: invalid token ID'); + self.token_uri.write(token_id, token_uri) + } + + // Transfers a specific token ID from one address to another + fn _transfer(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256) { + assert(from == self.owner_of(token_id), 'ERC721: Caller is not owner'); + assert(to.is_non_zero(), 'ERC721: transfer to 0 address'); + + self.token_approvals.write(token_id, Zeroable::zero()); + + self.balances.write(from, self.balances.read(from) - 1.into()); + self.balances.write(to, self.balances.read(to) + 1.into()); + + self.owners.write(token_id, to); + + self.emit( + Transfer{ from: from, to: to, token_id: token_id } + ); + } + + // Mints a new token with a specific ID to a specified address + fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { + assert(to.is_non_zero(), 'TO_IS_ZERO_ADDRESS'); + assert(!self.owner_of(token_id).is_non_zero(), 'ERC721: Token already minted'); + + let receiver_balance = self.balances.read(to); + self.balances.write(to, receiver_balance + 1.into()); + + self.owners.write(token_id, to); + + self.emit( + Transfer{ from: Zeroable::zero(), to: to, token_id: token_id } + ); + } + + // Burns a specific token ID, removing it from existence + fn _burn(ref self: ContractState, token_id: u256) { + let owner = self.owner_of(token_id); + + self.token_approvals.write(token_id, Zeroable::zero()); + + let owner_balance = self.balances.read(owner); + self.balances.write(owner, owner_balance - 1.into()); + + self.owners.write(token_id, Zeroable::zero()); + + self.emit( + Transfer{ from: owner, to: Zeroable::zero(), token_id: token_id } + ); + } + } +} From 0c25f826e4507d9f77c96921092a09d7c1a3f9c9 Mon Sep 17 00:00:00 2001 From: raizo07 Date: Thu, 6 Jun 2024 15:38:20 +0100 Subject: [PATCH 02/12] feat:test for ERC721 --- listings/applications/ERC721/ERC721.cairo | 44 ++++++++++ listings/applications/ERC721/test_erc721.rs | 90 +++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 listings/applications/ERC721/test_erc721.rs diff --git a/listings/applications/ERC721/ERC721.cairo b/listings/applications/ERC721/ERC721.cairo index e2dc6e07..b57ce6d5 100644 --- a/listings/applications/ERC721/ERC721.cairo +++ b/listings/applications/ERC721/ERC721.cairo @@ -196,3 +196,47 @@ mod ERC721Contract { } } } + + +#[cfg(test)] +mod tests { + use super::*; + use starknet::testing::ContractState; + + #[test] + fn test_mint() { + let mut contract = ERC721Contract::new(); + let to = ContractAddress::from(1); + let token_id = 1.into(); + + contract._mint(to, token_id); + + assert_eq!(contract.owner_of(token_id), to); + assert_eq!(contract.balance_of(to), 1.into()); + } + + #[test] + fn test_transfer() { + let mut contract = ERC721Contract::new(); + let to = ContractAddress::from(1); + let from = ContractAddress::from(2); + let token_id = 1.into(); + + contract._mint(from, token_id); + contract._transfer(from, to, token_id); + + assert_eq!(contract.owner_of(token_id), to); + assert_eq!(contract.balance_of(to), 1.into()); + assert_eq!(contract.balance_of(from), 0.into()); + } + + #[test] + fn test_approve() { + let mut contract = ERC721Contract::new(); + let owner = ContractAddress::from(1); + let approved = ContractAddress::from(2); + let token_id = 1.into(); + + contract._mint(owner, token_id); + contract.approve(approved, + diff --git a/listings/applications/ERC721/test_erc721.rs b/listings/applications/ERC721/test_erc721.rs new file mode 100644 index 00000000..041af338 --- /dev/null +++ b/listings/applications/ERC721/test_erc721.rs @@ -0,0 +1,90 @@ +#[cfg(test)] +mod tests { + use super::*; + use starknet::testing::ContractState; + use starknet::ContractAddress; + use starknet::get_caller_address; + + #[test] + fn test_constructor() { + let contract_state = ContractState::default(); + let contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); + + assert_eq!(contract.get_name(), "MyToken".into()); + assert_eq!(contract.get_symbol(), "MTK".into()); + } + + #[test] + fn test_mint() { + let contract_state = ContractState::default(); + let mut contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); + + let to = ContractAddress::from(1); + let token_id = 1.into(); + + contract._mint(to, token_id); + + assert_eq!(contract.owner_of(token_id), to); + assert_eq!(contract.balance_of(to), 1.into()); + } + + #[test] + fn test_transfer() { + let contract_state = ContractState::default(); + let mut contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); + + let from = ContractAddress::from(1); + let to = ContractAddress::from(2); + let token_id = 1.into(); + + contract._mint(from, token_id); + contract._transfer(from, to, token_id); + + assert_eq!(contract.owner_of(token_id), to); + assert_eq!(contract.balance_of(to), 1.into()); + assert_eq!(contract.balance_of(from), 0.into()); + } + + #[test] + fn test_approve() { + let contract_state = ContractState::default(); + let mut contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); + + let owner = ContractAddress::from(1); + let approved = ContractAddress::from(2); + let token_id = 1.into(); + + contract._mint(owner, token_id); + contract.approve(approved, token_id); + + assert_eq!(contract.get_approved(token_id), approved); + } + + #[test] + fn test_set_approval_for_all() { + let contract_state = ContractState::default(); + let mut contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); + + let owner = get_caller_address(); + let operator = ContractAddress::from(2); + + contract.set_approval_for_all(operator, true); + + assert!(contract.is_approved_for_all(owner, operator)); + } + + #[test] + fn test_burn() { + let contract_state = ContractState::default(); + let mut contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); + + let owner = ContractAddress::from(1); + let token_id = 1.into(); + + contract._mint(owner, token_id); + contract._burn(token_id); + + assert_eq!(contract.owner_of(token_id).is_zero(), true); + assert_eq!(contract.balance_of(owner), 0.into()); + } +} From bddc71cc0e8121999957e81b7f32d5bc32274493 Mon Sep 17 00:00:00 2001 From: Wolf <81079370+raizo07@users.noreply.github.com> Date: Mon, 10 Jun 2024 17:36:30 +0000 Subject: [PATCH 03/12] code fix --- listings/applications/ERC721/ERC721.cairo | 43 ----------------------- 1 file changed, 43 deletions(-) diff --git a/listings/applications/ERC721/ERC721.cairo b/listings/applications/ERC721/ERC721.cairo index b57ce6d5..552a0903 100644 --- a/listings/applications/ERC721/ERC721.cairo +++ b/listings/applications/ERC721/ERC721.cairo @@ -197,46 +197,3 @@ mod ERC721Contract { } } - -#[cfg(test)] -mod tests { - use super::*; - use starknet::testing::ContractState; - - #[test] - fn test_mint() { - let mut contract = ERC721Contract::new(); - let to = ContractAddress::from(1); - let token_id = 1.into(); - - contract._mint(to, token_id); - - assert_eq!(contract.owner_of(token_id), to); - assert_eq!(contract.balance_of(to), 1.into()); - } - - #[test] - fn test_transfer() { - let mut contract = ERC721Contract::new(); - let to = ContractAddress::from(1); - let from = ContractAddress::from(2); - let token_id = 1.into(); - - contract._mint(from, token_id); - contract._transfer(from, to, token_id); - - assert_eq!(contract.owner_of(token_id), to); - assert_eq!(contract.balance_of(to), 1.into()); - assert_eq!(contract.balance_of(from), 0.into()); - } - - #[test] - fn test_approve() { - let mut contract = ERC721Contract::new(); - let owner = ContractAddress::from(1); - let approved = ContractAddress::from(2); - let token_id = 1.into(); - - contract._mint(owner, token_id); - contract.approve(approved, - From f30fb44dfd9ff8c7845fc8bfe75276b4d6aca3dc Mon Sep 17 00:00:00 2001 From: raizo07 Date: Fri, 14 Jun 2024 07:18:48 +0000 Subject: [PATCH 04/12] updated the IERC721 interface at the top of the fileand used u128 instead of u256 --- listings/applications/ERC721/ERC721.cairo | 69 +++++++++++++++++------ 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/listings/applications/ERC721/ERC721.cairo b/listings/applications/ERC721/ERC721.cairo index 552a0903..57a1df6b 100644 --- a/listings/applications/ERC721/ERC721.cairo +++ b/listings/applications/ERC721/ERC721.cairo @@ -1,3 +1,38 @@ +#[starknet::interface] +namespace IERC721 { + // Returns the name of the token collection + fn get_name(self: @ContractState) -> felt252; + + // Returns the symbol of the token collection + fn get_symbol(self: @ContractState) -> felt252; + + // Returns the metadata URI for a given token ID + fn get_token_uri(self: @ContractState, token_id: u128) -> felt252; + + // Returns the number of tokens owned by a specific address + fn balance_of(self: @ContractState, account: ContractAddress) -> u256; + + // Returns the owner of the specified token ID + fn owner_of(self: @ContractState, token_id: u128) -> ContractAddress; + + // Returns the approved address for a specific token ID + fn get_approved(self: @ContractState, token_id: u128) -> ContractAddress; + + // Checks if an operator is approved to manage all of the assets of an owner + fn is_approved_for_all(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool; + + // Approves another address to transfer the given token ID + fn approve(self: @ContractState, to: ContractAddress, token_id: u128); + + // Sets or unsets the approval of a given operator + fn set_approval_for_all(self: @ContractState, operator: ContractAddress, approved: bool); + + // Transfers a specific token ID to another address + fn transfer_from(self: @ContractState, from: ContractAddress, to: ContractAddress, token_id: u128); +} + +// ERC721Contract.cairo + #[starknet::contract] mod ERC721Contract { @@ -13,11 +48,11 @@ mod ERC721Contract { struct Storage { name: felt252, symbol: felt252, - owners: LegacyMap::, - balances: LegacyMap::, - token_approvals: LegacyMap::, + owners: LegacyMap::, + balances: LegacyMap::, + token_approvals: LegacyMap::, operator_approvals: LegacyMap::<(ContractAddress, ContractAddress), bool>, - token_uri: LegacyMap::, + token_uri: LegacyMap::, } #[event] @@ -32,14 +67,14 @@ mod ERC721Contract { struct Approval { owner: ContractAddress, to: ContractAddress, - token_id: u256 + token_id: u128 } #[derive(Drop, starknet::Event)] struct Transfer { from: ContractAddress, to: ContractAddress, - token_id: u256 + token_id: u128 } #[derive(Drop, starknet::Event)] @@ -69,7 +104,7 @@ mod ERC721Contract { } // Returns the metadata URI for a given token ID - fn get_token_uri(self: @ContractState, token_id: u256) -> felt252 { + fn get_token_uri(self: @ContractState, token_id: u128) -> felt252 { assert(self._exists(token_id), 'ERC721: invalid token ID'); self.token_uri.read(token_id) } @@ -81,14 +116,14 @@ mod ERC721Contract { } // Returns the owner of the specified token ID - fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { + fn owner_of(self: @ContractState, token_id: u128) -> ContractAddress { let owner = self.owners.read(token_id); assert(owner.is_non_zero(), 'ERC721: invalid token ID'); owner } // Returns the approved address for a specific token ID - fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { + fn get_approved(self: @ContractState, token_id: u128) -> ContractAddress { assert(self._exists(token_id), 'ERC721: invalid token ID'); self.token_approvals.read(token_id) } @@ -99,7 +134,7 @@ mod ERC721Contract { } // Approves another address to transfer the given token ID - fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { + fn approve(ref self: ContractState, to: ContractAddress, token_id: u128) { let owner = self.owner_of(token_id); assert(to != owner, 'Approval to current owner'); assert(get_caller_address() == owner || self.is_approved_for_all(owner, get_caller_address()), 'Not token owner'); @@ -120,7 +155,7 @@ mod ERC721Contract { } // Transfers a specific token ID to another address - fn transfer_from(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256) { + fn transfer_from(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u128) { assert(self._is_approved_or_owner(get_caller_address(), token_id), 'neither owner nor approved'); self._transfer(from, to, token_id); } @@ -129,12 +164,12 @@ mod ERC721Contract { #[generate_trait] impl ERC721HelperImpl of ERC721HelperTrait { // Checks if a specific token ID exists - fn _exists(self: @ContractState, token_id: u256) -> bool { + fn _exists(self: @ContractState, token_id: u128) -> bool { self.owner_of(token_id).is_non_zero() } // Checks if a spender is the owner or an approved operator of a specific token ID - fn _is_approved_or_owner(self: @ContractState, spender: ContractAddress, token_id: u256) -> bool { + fn _is_approved_or_owner(self: @ContractState, spender: ContractAddress, token_id: u128) -> bool { let owner = self.owners.read(token_id); spender == owner || self.is_approved_for_all(owner, spender) @@ -142,13 +177,13 @@ mod ERC721Contract { } // Sets the metadata URI for a specific token ID - fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { + fn _set_token_uri(ref self: ContractState, token_id: u128, token_uri: felt252) { assert(self._exists(token_id), 'ERC721: invalid token ID'); self.token_uri.write(token_id, token_uri) } // Transfers a specific token ID from one address to another - fn _transfer(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256) { + fn _transfer(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u128) { assert(from == self.owner_of(token_id), 'ERC721: Caller is not owner'); assert(to.is_non_zero(), 'ERC721: transfer to 0 address'); @@ -165,7 +200,7 @@ mod ERC721Contract { } // Mints a new token with a specific ID to a specified address - fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { + fn _mint(ref self: ContractState, to: ContractAddress, token_id: u128) { assert(to.is_non_zero(), 'TO_IS_ZERO_ADDRESS'); assert(!self.owner_of(token_id).is_non_zero(), 'ERC721: Token already minted'); @@ -180,7 +215,7 @@ mod ERC721Contract { } // Burns a specific token ID, removing it from existence - fn _burn(ref self: ContractState, token_id: u256) { + fn _burn(ref self: ContractState, token_id: u128) { let owner = self.owner_of(token_id); self.token_approvals.write(token_id, Zeroable::zero()); From 7f8b9730c630504faca25b54f4bcb0b97ad04d94 Mon Sep 17 00:00:00 2001 From: raizo07 Date: Fri, 21 Jun 2024 12:05:09 +0000 Subject: [PATCH 05/12] fix u256 --- listings/applications/ERC721/ERC721.cairo | 44 +++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/listings/applications/ERC721/ERC721.cairo b/listings/applications/ERC721/ERC721.cairo index 57a1df6b..7b93fba9 100644 --- a/listings/applications/ERC721/ERC721.cairo +++ b/listings/applications/ERC721/ERC721.cairo @@ -1,5 +1,5 @@ #[starknet::interface] -namespace IERC721 { +pub trait { // Returns the name of the token collection fn get_name(self: @ContractState) -> felt252; @@ -7,28 +7,28 @@ namespace IERC721 { fn get_symbol(self: @ContractState) -> felt252; // Returns the metadata URI for a given token ID - fn get_token_uri(self: @ContractState, token_id: u128) -> felt252; + fn get_token_uri(self: @ContractState, token_id: u256) -> felt252; // Returns the number of tokens owned by a specific address fn balance_of(self: @ContractState, account: ContractAddress) -> u256; // Returns the owner of the specified token ID - fn owner_of(self: @ContractState, token_id: u128) -> ContractAddress; + fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress; // Returns the approved address for a specific token ID - fn get_approved(self: @ContractState, token_id: u128) -> ContractAddress; + fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress; // Checks if an operator is approved to manage all of the assets of an owner fn is_approved_for_all(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool; // Approves another address to transfer the given token ID - fn approve(self: @ContractState, to: ContractAddress, token_id: u128); + fn approve(self: @ContractState, to: ContractAddress, token_id: u256); // Sets or unsets the approval of a given operator fn set_approval_for_all(self: @ContractState, operator: ContractAddress, approved: bool); // Transfers a specific token ID to another address - fn transfer_from(self: @ContractState, from: ContractAddress, to: ContractAddress, token_id: u128); + fn transfer_from(self: @ContractState, from: ContractAddress, to: ContractAddress, token_id: u256); } // ERC721Contract.cairo @@ -48,11 +48,11 @@ mod ERC721Contract { struct Storage { name: felt252, symbol: felt252, - owners: LegacyMap::, - balances: LegacyMap::, - token_approvals: LegacyMap::, + owners: LegacyMap::, + balances: LegacyMap::, + token_approvals: LegacyMap::, operator_approvals: LegacyMap::<(ContractAddress, ContractAddress), bool>, - token_uri: LegacyMap::, + token_uri: LegacyMap::, } #[event] @@ -67,14 +67,14 @@ mod ERC721Contract { struct Approval { owner: ContractAddress, to: ContractAddress, - token_id: u128 + token_id: u256 } #[derive(Drop, starknet::Event)] struct Transfer { from: ContractAddress, to: ContractAddress, - token_id: u128 + token_id: u256 } #[derive(Drop, starknet::Event)] @@ -104,7 +104,7 @@ mod ERC721Contract { } // Returns the metadata URI for a given token ID - fn get_token_uri(self: @ContractState, token_id: u128) -> felt252 { + fn get_token_uri(self: @ContractState, token_id: u256) -> felt252 { assert(self._exists(token_id), 'ERC721: invalid token ID'); self.token_uri.read(token_id) } @@ -123,7 +123,7 @@ mod ERC721Contract { } // Returns the approved address for a specific token ID - fn get_approved(self: @ContractState, token_id: u128) -> ContractAddress { + fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { assert(self._exists(token_id), 'ERC721: invalid token ID'); self.token_approvals.read(token_id) } @@ -134,7 +134,7 @@ mod ERC721Contract { } // Approves another address to transfer the given token ID - fn approve(ref self: ContractState, to: ContractAddress, token_id: u128) { + fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { let owner = self.owner_of(token_id); assert(to != owner, 'Approval to current owner'); assert(get_caller_address() == owner || self.is_approved_for_all(owner, get_caller_address()), 'Not token owner'); @@ -155,7 +155,7 @@ mod ERC721Contract { } // Transfers a specific token ID to another address - fn transfer_from(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u128) { + fn transfer_from(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256) { assert(self._is_approved_or_owner(get_caller_address(), token_id), 'neither owner nor approved'); self._transfer(from, to, token_id); } @@ -164,12 +164,12 @@ mod ERC721Contract { #[generate_trait] impl ERC721HelperImpl of ERC721HelperTrait { // Checks if a specific token ID exists - fn _exists(self: @ContractState, token_id: u128) -> bool { + fn _exists(self: @ContractState, token_id: u256) -> bool { self.owner_of(token_id).is_non_zero() } // Checks if a spender is the owner or an approved operator of a specific token ID - fn _is_approved_or_owner(self: @ContractState, spender: ContractAddress, token_id: u128) -> bool { + fn _is_approved_or_owner(self: @ContractState, spender: ContractAddress, token_id: u256) -> bool { let owner = self.owners.read(token_id); spender == owner || self.is_approved_for_all(owner, spender) @@ -177,13 +177,13 @@ mod ERC721Contract { } // Sets the metadata URI for a specific token ID - fn _set_token_uri(ref self: ContractState, token_id: u128, token_uri: felt252) { + fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { assert(self._exists(token_id), 'ERC721: invalid token ID'); self.token_uri.write(token_id, token_uri) } // Transfers a specific token ID from one address to another - fn _transfer(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u128) { + fn _transfer(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256) { assert(from == self.owner_of(token_id), 'ERC721: Caller is not owner'); assert(to.is_non_zero(), 'ERC721: transfer to 0 address'); @@ -200,7 +200,7 @@ mod ERC721Contract { } // Mints a new token with a specific ID to a specified address - fn _mint(ref self: ContractState, to: ContractAddress, token_id: u128) { + fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { assert(to.is_non_zero(), 'TO_IS_ZERO_ADDRESS'); assert(!self.owner_of(token_id).is_non_zero(), 'ERC721: Token already minted'); @@ -215,7 +215,7 @@ mod ERC721Contract { } // Burns a specific token ID, removing it from existence - fn _burn(ref self: ContractState, token_id: u128) { + fn _burn(ref self: ContractState, token_id: u256) { let owner = self.owner_of(token_id); self.token_approvals.write(token_id, Zeroable::zero()); From 3aaae7281e2a6927ae395295e500bd915f9b3a14 Mon Sep 17 00:00:00 2001 From: raizo07 Date: Wed, 26 Jun 2024 11:44:27 +0100 Subject: [PATCH 06/12] fix: project structure, add Scarb.toml --- listings/applications/erc721/.gitignore | 1 + listings/applications/erc721/Scarb.toml | 19 +++++ .../ERC721.cairo => erc721/src/erc721.cairo} | 70 ++++++------------- listings/applications/erc721/src/lib.cairo | 1 + .../test_erc721.rs => erc721/src/tests.cairo} | 2 +- 5 files changed, 45 insertions(+), 48 deletions(-) create mode 100644 listings/applications/erc721/.gitignore create mode 100644 listings/applications/erc721/Scarb.toml rename listings/applications/{ERC721/ERC721.cairo => erc721/src/erc721.cairo} (77%) create mode 100644 listings/applications/erc721/src/lib.cairo rename listings/applications/{ERC721/test_erc721.rs => erc721/src/tests.cairo} (99%) diff --git a/listings/applications/erc721/.gitignore b/listings/applications/erc721/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/listings/applications/erc721/.gitignore @@ -0,0 +1 @@ +target diff --git a/listings/applications/erc721/Scarb.toml b/listings/applications/erc721/Scarb.toml new file mode 100644 index 00000000..ab8f1cb2 --- /dev/null +++ b/listings/applications/erc721/Scarb.toml @@ -0,0 +1,19 @@ +[package] +name = "erc721" +version = "0.1.0" +edition = "2023_11" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +erc20 = { path = "../erc20" } +starknet.workspace = true + +[dev-dependencies] +snforge_std.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] +build-external-contracts = ["erc20::token::erc20"] diff --git a/listings/applications/ERC721/ERC721.cairo b/listings/applications/erc721/src/erc721.cairo similarity index 77% rename from listings/applications/ERC721/ERC721.cairo rename to listings/applications/erc721/src/erc721.cairo index 7b93fba9..977a0281 100644 --- a/listings/applications/ERC721/ERC721.cairo +++ b/listings/applications/erc721/src/erc721.cairo @@ -1,48 +1,26 @@ -#[starknet::interface] -pub trait { - // Returns the name of the token collection - fn get_name(self: @ContractState) -> felt252; - - // Returns the symbol of the token collection - fn get_symbol(self: @ContractState) -> felt252; - - // Returns the metadata URI for a given token ID - fn get_token_uri(self: @ContractState, token_id: u256) -> felt252; - - // Returns the number of tokens owned by a specific address - fn balance_of(self: @ContractState, account: ContractAddress) -> u256; - - // Returns the owner of the specified token ID - fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress; - - // Returns the approved address for a specific token ID - fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress; - - // Checks if an operator is approved to manage all of the assets of an owner - fn is_approved_for_all(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool; +use starknet::ContractAddress; - // Approves another address to transfer the given token ID - fn approve(self: @ContractState, to: ContractAddress, token_id: u256); - - // Sets or unsets the approval of a given operator - fn set_approval_for_all(self: @ContractState, operator: ContractAddress, approved: bool); - - // Transfers a specific token ID to another address - fn transfer_from(self: @ContractState, from: ContractAddress, to: ContractAddress, token_id: u256); +#[starknet::interface] +pub trait IERC721 { + fn get_name(self: @TContractState) -> felt252; + fn get_symbol(self: @TContractState) -> felt252; + fn get_token_uri(self: @TContractState, token_id: u256) -> felt252; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; + fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; + fn is_approved_for_all(self: @TContractState, owner: ContractAddress, operator: ContractAddress) -> bool; + fn approve(self: @TContractState, to: ContractAddress, token_id: u256); + fn set_approval_for_all(self: @TContractState, operator: ContractAddress, approved: bool); + fn transfer_from(self: @TContractState, from: ContractAddress, to: ContractAddress, token_id: u256); + fn mint(ref self: TContractState, to: ContractAddress, token_id: u256); } -// ERC721Contract.cairo - #[starknet::contract] -mod ERC721Contract { +mod ERC721 { use starknet::ContractAddress; use starknet::get_caller_address; - use zeroable::Zeroable; - use starknet::contract_address_to_felt252; - use traits::Into; - use traits::TryInto; - use option::OptionTrait; + use core::num::traits::zero::Zero; #[storage] struct Storage { @@ -90,7 +68,6 @@ mod ERC721Contract { self.symbol.write(_symbol); } - #[external(v0)] #[generate_trait] impl IERC721Impl of IERC721Trait { // Returns the name of the token collection @@ -116,7 +93,7 @@ mod ERC721Contract { } // Returns the owner of the specified token ID - fn owner_of(self: @ContractState, token_id: u128) -> ContractAddress { + fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { let owner = self.owners.read(token_id); assert(owner.is_non_zero(), 'ERC721: invalid token ID'); owner @@ -187,7 +164,7 @@ mod ERC721Contract { assert(from == self.owner_of(token_id), 'ERC721: Caller is not owner'); assert(to.is_non_zero(), 'ERC721: transfer to 0 address'); - self.token_approvals.write(token_id, Zeroable::zero()); + self.token_approvals.write(token_id, Zero::zero()); self.balances.write(from, self.balances.read(from) - 1.into()); self.balances.write(to, self.balances.read(to) + 1.into()); @@ -210,7 +187,7 @@ mod ERC721Contract { self.owners.write(token_id, to); self.emit( - Transfer{ from: Zeroable::zero(), to: to, token_id: token_id } + Transfer{ from: Zero::zero(), to: to, token_id: token_id } ); } @@ -218,17 +195,16 @@ mod ERC721Contract { fn _burn(ref self: ContractState, token_id: u256) { let owner = self.owner_of(token_id); - self.token_approvals.write(token_id, Zeroable::zero()); + self.token_approvals.write(token_id, Zero::zero()); let owner_balance = self.balances.read(owner); self.balances.write(owner, owner_balance - 1.into()); - self.owners.write(token_id, Zeroable::zero()); + self.owners.write(token_id, Zero::zero()); self.emit( - Transfer{ from: owner, to: Zeroable::zero(), token_id: token_id } + Transfer{ from: owner, to: Zero::zero(), token_id: token_id } ); } } -} - +} \ No newline at end of file diff --git a/listings/applications/erc721/src/lib.cairo b/listings/applications/erc721/src/lib.cairo new file mode 100644 index 00000000..8030eaac --- /dev/null +++ b/listings/applications/erc721/src/lib.cairo @@ -0,0 +1 @@ +mod erc721; diff --git a/listings/applications/ERC721/test_erc721.rs b/listings/applications/erc721/src/tests.cairo similarity index 99% rename from listings/applications/ERC721/test_erc721.rs rename to listings/applications/erc721/src/tests.cairo index 041af338..a7cbdbcb 100644 --- a/listings/applications/ERC721/test_erc721.rs +++ b/listings/applications/erc721/src/tests.cairo @@ -87,4 +87,4 @@ mod tests { assert_eq!(contract.owner_of(token_id).is_zero(), true); assert_eq!(contract.balance_of(owner), 0.into()); } -} +} \ No newline at end of file From 2b0a306a1679d0ae53a577e6101023ca8f438711 Mon Sep 17 00:00:00 2001 From: raizo07 Date: Tue, 2 Jul 2024 06:47:35 +0100 Subject: [PATCH 07/12] fix revieved changes --- listings/applications/erc721/Scarb.toml | 1 - listings/applications/erc721/src/erc721.cairo | 79 +++++---- listings/applications/erc721/src/tests.cairo | 167 ++++++++---------- 3 files changed, 124 insertions(+), 123 deletions(-) diff --git a/listings/applications/erc721/Scarb.toml b/listings/applications/erc721/Scarb.toml index ab8f1cb2..cf8bff51 100644 --- a/listings/applications/erc721/Scarb.toml +++ b/listings/applications/erc721/Scarb.toml @@ -6,7 +6,6 @@ edition = "2023_11" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html [dependencies] -erc20 = { path = "../erc20" } starknet.workspace = true [dev-dependencies] diff --git a/listings/applications/erc721/src/erc721.cairo b/listings/applications/erc721/src/erc721.cairo index 977a0281..a4fd0ebc 100644 --- a/listings/applications/erc721/src/erc721.cairo +++ b/listings/applications/erc721/src/erc721.cairo @@ -2,22 +2,30 @@ use starknet::ContractAddress; #[starknet::interface] pub trait IERC721 { - fn get_name(self: @TContractState) -> felt252; - fn get_symbol(self: @TContractState) -> felt252; - fn get_token_uri(self: @TContractState, token_id: u256) -> felt252; fn balance_of(self: @TContractState, account: ContractAddress) -> u256; fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; - fn is_approved_for_all(self: @TContractState, owner: ContractAddress, operator: ContractAddress) -> bool; + fn is_approved_for_all( + self: @TContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool; fn approve(self: @TContractState, to: ContractAddress, token_id: u256); fn set_approval_for_all(self: @TContractState, operator: ContractAddress, approved: bool); - fn transfer_from(self: @TContractState, from: ContractAddress, to: ContractAddress, token_id: u256); + fn transfer_from( + self: @TContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ); fn mint(ref self: TContractState, to: ContractAddress, token_id: u256); } + +#[starknet::interface] +pub trait IERC721Metadata { + fn name(self: @TContractState) -> felt252; + fn symbol(self: @TContractState) -> felt252; + fn token_uri(self: @TContractState, token_id: u256) -> felt252; +} + #[starknet::contract] mod ERC721 { - use starknet::ContractAddress; use starknet::get_caller_address; use core::num::traits::zero::Zero; @@ -71,17 +79,17 @@ mod ERC721 { #[generate_trait] impl IERC721Impl of IERC721Trait { // Returns the name of the token collection - fn get_name(self: @ContractState) -> felt252 { + fn name(self: @ContractState) -> felt252 { self.name.read() } // Returns the symbol of the token collection - fn get_symbol(self: @ContractState) -> felt252 { + fn symbol(self: @ContractState) -> felt252 { self.symbol.read() } // Returns the metadata URI for a given token ID - fn get_token_uri(self: @ContractState, token_id: u256) -> felt252 { + fn token_uri(self: @ContractState, token_id: u256) -> felt252 { assert(self._exists(token_id), 'ERC721: invalid token ID'); self.token_uri.read(token_id) } @@ -106,7 +114,9 @@ mod ERC721 { } // Checks if an operator is approved to manage all of the assets of an owner - fn is_approved_for_all(self: @ContractState, owner: ContractAddress, operator: ContractAddress) -> bool { + fn is_approved_for_all( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { self.operator_approvals.read((owner, operator)) } @@ -114,26 +124,33 @@ mod ERC721 { fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { let owner = self.owner_of(token_id); assert(to != owner, 'Approval to current owner'); - assert(get_caller_address() == owner || self.is_approved_for_all(owner, get_caller_address()), 'Not token owner'); - self.token_approvals.write(token_id, to); - self.emit( - Approval{ owner: self.owner_of(token_id), to: to, token_id: token_id } + assert( + get_caller_address() == owner + || self.is_approved_for_all(owner, get_caller_address()), + 'Not token owner' ); + self.token_approvals.write(token_id, to); + self.emit(Approval { owner: self.owner_of(token_id), to: to, token_id: token_id }); } // Sets or unsets the approval of a given operator - fn set_approval_for_all(ref self: ContractState, operator: ContractAddress, approved: bool) { + fn set_approval_for_all( + ref self: ContractState, operator: ContractAddress, approved: bool + ) { let owner = get_caller_address(); assert(owner != operator, 'ERC721: approve to caller'); self.operator_approvals.write((owner, operator), approved); - self.emit( - ApprovalForAll{ owner: owner, operator: operator, approved: approved } - ); + self.emit(ApprovalForAll { owner: owner, operator: operator, approved: approved }); } // Transfers a specific token ID to another address - fn transfer_from(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256) { - assert(self._is_approved_or_owner(get_caller_address(), token_id), 'neither owner nor approved'); + fn transfer_from( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + assert( + self._is_approved_or_owner(get_caller_address(), token_id), + 'neither owner nor approved' + ); self._transfer(from, to, token_id); } } @@ -146,10 +163,12 @@ mod ERC721 { } // Checks if a spender is the owner or an approved operator of a specific token ID - fn _is_approved_or_owner(self: @ContractState, spender: ContractAddress, token_id: u256) -> bool { + fn _is_approved_or_owner( + self: @ContractState, spender: ContractAddress, token_id: u256 + ) -> bool { let owner = self.owners.read(token_id); spender == owner - || self.is_approved_for_all(owner, spender) + || self.is_approved_for_all(owner, spender) || self.get_approved(token_id) == spender } @@ -160,7 +179,9 @@ mod ERC721 { } // Transfers a specific token ID from one address to another - fn _transfer(ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256) { + fn _transfer( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { assert(from == self.owner_of(token_id), 'ERC721: Caller is not owner'); assert(to.is_non_zero(), 'ERC721: transfer to 0 address'); @@ -171,9 +192,7 @@ mod ERC721 { self.owners.write(token_id, to); - self.emit( - Transfer{ from: from, to: to, token_id: token_id } - ); + self.emit(Transfer { from: from, to: to, token_id: token_id }); } // Mints a new token with a specific ID to a specified address @@ -186,9 +205,7 @@ mod ERC721 { self.owners.write(token_id, to); - self.emit( - Transfer{ from: Zero::zero(), to: to, token_id: token_id } - ); + self.emit(Transfer { from: Zero::zero(), to: to, token_id: token_id }); } // Burns a specific token ID, removing it from existence @@ -202,9 +219,7 @@ mod ERC721 { self.owners.write(token_id, Zero::zero()); - self.emit( - Transfer{ from: owner, to: Zero::zero(), token_id: token_id } - ); + self.emit(Transfer { from: owner, to: Zero::zero(), token_id: token_id }); } } } \ No newline at end of file diff --git a/listings/applications/erc721/src/tests.cairo b/listings/applications/erc721/src/tests.cairo index a7cbdbcb..3c8821d8 100644 --- a/listings/applications/erc721/src/tests.cairo +++ b/listings/applications/erc721/src/tests.cairo @@ -1,90 +1,77 @@ -#[cfg(test)] -mod tests { - use super::*; - use starknet::testing::ContractState; - use starknet::ContractAddress; - use starknet::get_caller_address; - - #[test] - fn test_constructor() { - let contract_state = ContractState::default(); - let contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); - - assert_eq!(contract.get_name(), "MyToken".into()); - assert_eq!(contract.get_symbol(), "MTK".into()); - } - - #[test] - fn test_mint() { - let contract_state = ContractState::default(); - let mut contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); - - let to = ContractAddress::from(1); - let token_id = 1.into(); - - contract._mint(to, token_id); - - assert_eq!(contract.owner_of(token_id), to); - assert_eq!(contract.balance_of(to), 1.into()); - } - - #[test] - fn test_transfer() { - let contract_state = ContractState::default(); - let mut contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); - - let from = ContractAddress::from(1); - let to = ContractAddress::from(2); - let token_id = 1.into(); - - contract._mint(from, token_id); - contract._transfer(from, to, token_id); - - assert_eq!(contract.owner_of(token_id), to); - assert_eq!(contract.balance_of(to), 1.into()); - assert_eq!(contract.balance_of(from), 0.into()); - } - - #[test] - fn test_approve() { - let contract_state = ContractState::default(); - let mut contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); - - let owner = ContractAddress::from(1); - let approved = ContractAddress::from(2); - let token_id = 1.into(); - - contract._mint(owner, token_id); - contract.approve(approved, token_id); - - assert_eq!(contract.get_approved(token_id), approved); - } - - #[test] - fn test_set_approval_for_all() { - let contract_state = ContractState::default(); - let mut contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); - - let owner = get_caller_address(); - let operator = ContractAddress::from(2); - - contract.set_approval_for_all(operator, true); - - assert!(contract.is_approved_for_all(owner, operator)); - } - - #[test] - fn test_burn() { - let contract_state = ContractState::default(); - let mut contract = ERC721Contract::constructor(&contract_state, "MyToken".into(), "MTK".into()); - - let owner = ContractAddress::from(1); - let token_id = 1.into(); - - contract._mint(owner, token_id); - contract._burn(token_id); - - assert_eq!(contract.owner_of(token_id).is_zero(), true); - assert_eq!(contract.balance_of(owner), 0.into()); - } -} \ No newline at end of file +use starknet::ContractAddress; +use starknet::get_caller_address; +use snforge_std::{declare, ContractClassTrait, ContractClass, cheat_caller_address, CheatSpan}; + +pub const erc721_name: felt252 = 'My NFT'; +pub const erc721_symbol: felt252 = 'MNFT'; + +fn get_erc721_contract_address() -> ContractAddress { + let erc721 = declare("ERC721").unwrap(); + let erc721_constructor_calldata = array![erc721_name, erc721_symbol]; + let (erc721_address, _) = erc721.deploy(@erc721_constructor_calldata).unwrap(); + erc721_address +} + +#[test] +fn test_erc721_contract() { + let erc721_address = get_erc721_contract_address(); + let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; + + // Test get_name + assert_eq!(erc721_dispatcher.get_name(), erc721_name); + + // Test get_symbol + assert_eq!(erc721_dispatcher.get_symbol(), erc721_symbol); + + // Mint a token and test balance_of, owner_of, get_token_uri + let minter: ContractAddress = 'minter'.try_into().unwrap(); + let token_id = 1.into(); + let token_uri = "ipfs://token-uri"; + + cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); + erc721_dispatcher.mint(minter, token_id); + erc721_dispatcher._set_token_uri(token_id, token_uri.into()); + + assert_eq!(erc721_dispatcher.balance_of(minter), 1.into()); + assert_eq!(erc721_dispatcher.owner_of(token_id), minter); + assert_eq!(erc721_dispatcher.get_token_uri(token_id), token_uri.into()); + + // Approve another address and test get_approved + let approved: ContractAddress = 'approved'.try_into().unwrap(); + cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); + erc721_dispatcher.approve(approved, token_id); + assert_eq!(erc721_dispatcher.get_approved(token_id), approved); + + // Set approval for all and test is_approved_for_all + let operator: ContractAddress = 'operator'.try_into().unwrap(); + erc721_dispatcher.set_approval_for_all(operator, true); + assert!(erc721_dispatcher.is_approved_for_all(minter, operator)); + + // Transfer the token and test owner_of + let recipient: ContractAddress = 'recipient'.try_into().unwrap(); + cheat_caller_address(erc721_address, operator, CheatSpan::TargetCalls(1)); + erc721_dispatcher.transfer_from(minter, recipient, token_id); + assert_eq!(erc721_dispatcher.owner_of(token_id), recipient); +} + +#[test] +fn test_erc721_burn() { + let erc721_address = get_erc721_contract_address(); + let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; + + // Mint a token and burn it + let minter: ContractAddress = 'minter'.try_into().unwrap(); + let token_id = 1.into(); + + cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); + erc721_dispatcher.mint(minter, token_id); + + assert_eq!(erc721_dispatcher.balance_of(minter), 1.into()); + assert_eq!(erc721_dispatcher.owner_of(token_id), minter); + + cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); + erc721_dispatcher._burn(token_id); + + assert_eq!(erc721_dispatcher.balance_of(minter), 0.into()); + assert_eq!(erc721_dispatcher.owner_of(token_id), ContractAddress::default()); +} From 1da1af8932b025fa247155a4993fdd1f206e30f1 Mon Sep 17 00:00:00 2001 From: raizo07 Date: Thu, 4 Jul 2024 16:13:37 +0100 Subject: [PATCH 08/12] add mod errors --- Scarb.lock | 7 + listings/applications/erc721/src/erc721.cairo | 64 ++-- listings/applications/erc721/src/tests.cairo | 346 ++++++++++++++---- 3 files changed, 312 insertions(+), 105 deletions(-) diff --git a/Scarb.lock b/Scarb.lock index a055e8a9..d395a6b0 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -75,6 +75,13 @@ version = "0.1.0" name = "erc20" version = "0.1.0" +[[package]] +name = "erc721" +version = "0.1.0" +dependencies = [ + "snforge_std", +] + [[package]] name = "errors" version = "0.1.0" diff --git a/listings/applications/erc721/src/erc721.cairo b/listings/applications/erc721/src/erc721.cairo index a4fd0ebc..abdc8132 100644 --- a/listings/applications/erc721/src/erc721.cairo +++ b/listings/applications/erc721/src/erc721.cairo @@ -70,6 +70,19 @@ mod ERC721 { approved: bool } + mod Errors { + // Error messages for each function + pub const ADDRESS_ZERO: felt252 = 'ERC721: invalid owner'; + pub const INVALID_TOKEN_ID: felt252 = 'ERC721: invalid token ID'; + pub const APPROVAL_TO_CURRENT_OWNER: felt252 = 'ERC721: approval to owner'; + pub const NOT_TOKEN_OWNER: felt252 = 'ERC721: not token owner'; + pub const APPROVE_TO_CALLER: felt252 = 'ERC721: approve to caller'; + pub const CALLER_NOT_OWNER_NOR_APPROVED: felt252 = 'ERC721: caller is not approved'; + pub const CALLER_IS_NOT_OWNER: felt252 = 'ERC721: caller is not owner'; + pub const TRANSFER_TO_ZERO_ADDRESS: felt252 = 'ERC721: to the zero address'; + pub const TOKEN_ALREADY_MINTED: felt252 = 'ERC721: token already minted'; + } + #[constructor] fn constructor(ref self: ContractState, _name: felt252, _symbol: felt252) { self.name.write(_name); @@ -78,78 +91,55 @@ mod ERC721 { #[generate_trait] impl IERC721Impl of IERC721Trait { - // Returns the name of the token collection - fn name(self: @ContractState) -> felt252 { - self.name.read() - } - - // Returns the symbol of the token collection - fn symbol(self: @ContractState) -> felt252 { - self.symbol.read() - } - - // Returns the metadata URI for a given token ID - fn token_uri(self: @ContractState, token_id: u256) -> felt252 { - assert(self._exists(token_id), 'ERC721: invalid token ID'); - self.token_uri.read(token_id) - } - - // Returns the number of tokens owned by a specific address fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { - assert(account.is_non_zero(), 'ERC721: address zero'); + assert(account.is_non_zero(), Errors::ADDRESS_ZERO); self.balances.read(account) } - // Returns the owner of the specified token ID fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { let owner = self.owners.read(token_id); - assert(owner.is_non_zero(), 'ERC721: invalid token ID'); + assert(owner.is_non_zero(), Errors::INVALID_TOKEN_ID); owner } - // Returns the approved address for a specific token ID fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { - assert(self._exists(token_id), 'ERC721: invalid token ID'); + assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); self.token_approvals.read(token_id) } - // Checks if an operator is approved to manage all of the assets of an owner fn is_approved_for_all( self: @ContractState, owner: ContractAddress, operator: ContractAddress ) -> bool { self.operator_approvals.read((owner, operator)) } - // Approves another address to transfer the given token ID fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { let owner = self.owner_of(token_id); - assert(to != owner, 'Approval to current owner'); + assert(to != owner, Errors::APPROVAL_TO_CURRENT_OWNER); assert( get_caller_address() == owner || self.is_approved_for_all(owner, get_caller_address()), - 'Not token owner' + Errors::NOT_TOKEN_OWNER ); self.token_approvals.write(token_id, to); self.emit(Approval { owner: self.owner_of(token_id), to: to, token_id: token_id }); } - // Sets or unsets the approval of a given operator fn set_approval_for_all( ref self: ContractState, operator: ContractAddress, approved: bool ) { let owner = get_caller_address(); - assert(owner != operator, 'ERC721: approve to caller'); + assert(owner != operator, Errors::APPROVE_TO_CALLER); self.operator_approvals.write((owner, operator), approved); self.emit(ApprovalForAll { owner: owner, operator: operator, approved: approved }); } - // Transfers a specific token ID to another address fn transfer_from( ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 ) { assert( self._is_approved_or_owner(get_caller_address(), token_id), - 'neither owner nor approved' + Errors::CALLER_NOT_OWNER_NOR_APPROVED ); self._transfer(from, to, token_id); } @@ -157,12 +147,10 @@ mod ERC721 { #[generate_trait] impl ERC721HelperImpl of ERC721HelperTrait { - // Checks if a specific token ID exists fn _exists(self: @ContractState, token_id: u256) -> bool { self.owner_of(token_id).is_non_zero() } - // Checks if a spender is the owner or an approved operator of a specific token ID fn _is_approved_or_owner( self: @ContractState, spender: ContractAddress, token_id: u256 ) -> bool { @@ -172,18 +160,16 @@ mod ERC721 { || self.get_approved(token_id) == spender } - // Sets the metadata URI for a specific token ID fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { - assert(self._exists(token_id), 'ERC721: invalid token ID'); + assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); self.token_uri.write(token_id, token_uri) } - // Transfers a specific token ID from one address to another fn _transfer( ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 ) { - assert(from == self.owner_of(token_id), 'ERC721: Caller is not owner'); - assert(to.is_non_zero(), 'ERC721: transfer to 0 address'); + assert(from == self.owner_of(token_id), Errors::CALLER_IS_NOT_OWNER); + assert(to.is_non_zero(), Errors::TRANSFER_TO_ZERO_ADDRESS); self.token_approvals.write(token_id, Zero::zero()); @@ -195,10 +181,9 @@ mod ERC721 { self.emit(Transfer { from: from, to: to, token_id: token_id }); } - // Mints a new token with a specific ID to a specified address fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { assert(to.is_non_zero(), 'TO_IS_ZERO_ADDRESS'); - assert(!self.owner_of(token_id).is_non_zero(), 'ERC721: Token already minted'); + assert(!self.owner_of(token_id).is_non_zero(), Errors::TOKEN_ALREADY_MINTED); let receiver_balance = self.balances.read(to); self.balances.write(to, receiver_balance + 1.into()); @@ -208,7 +193,6 @@ mod ERC721 { self.emit(Transfer { from: Zero::zero(), to: to, token_id: token_id }); } - // Burns a specific token ID, removing it from existence fn _burn(ref self: ContractState, token_id: u256) { let owner = self.owner_of(token_id); diff --git a/listings/applications/erc721/src/tests.cairo b/listings/applications/erc721/src/tests.cairo index 3c8821d8..5b7eac83 100644 --- a/listings/applications/erc721/src/tests.cairo +++ b/listings/applications/erc721/src/tests.cairo @@ -1,77 +1,293 @@ -use starknet::ContractAddress; -use starknet::get_caller_address; -use snforge_std::{declare, ContractClassTrait, ContractClass, cheat_caller_address, CheatSpan}; - -pub const erc721_name: felt252 = 'My NFT'; -pub const erc721_symbol: felt252 = 'MNFT'; - -fn get_erc721_contract_address() -> ContractAddress { - let erc721 = declare("ERC721").unwrap(); - let erc721_constructor_calldata = array![erc721_name, erc721_symbol]; - let (erc721_address, _) = erc721.deploy(@erc721_constructor_calldata).unwrap(); - erc721_address +// use starknet::ContractAddress; +// use starknet::get_caller_address; +// use snforge_std::{declare, ContractClassTrait, ContractClass, cheat_caller_address, CheatSpan}; + +// pub const erc721_name: felt252 = 'My NFT'; +// pub const erc721_symbol: felt252 = 'MNFT'; + +// fn get_erc721_contract_address() -> ContractAddress { +// let erc721 = declare("ERC721").unwrap(); +// let erc721_constructor_calldata = array![erc721_name, erc721_symbol]; +// let (erc721_address, _) = erc721.deploy(@erc721_constructor_calldata).unwrap(); +// erc721_address +// } + +// #[test] +// fn test_erc721_contract() { +// let erc721_address = get_erc721_contract_address(); +// let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; + +// // Test get_name +// assert_eq!(erc721_dispatcher.get_name(), erc721_name); + +// // Test get_symbol +// assert_eq!(erc721_dispatcher.get_symbol(), erc721_symbol); + +// // Mint a token and test balance_of, owner_of, get_token_uri +// let minter: ContractAddress = 'minter'.try_into().unwrap(); +// let token_id = 1.into(); +// let token_uri = "ipfs://token-uri"; + +// cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); +// erc721_dispatcher.mint(minter, token_id); +// erc721_dispatcher._set_token_uri(token_id, token_uri.into()); + +// assert_eq!(erc721_dispatcher.balance_of(minter), 1.into()); +// assert_eq!(erc721_dispatcher.owner_of(token_id), minter); +// assert_eq!(erc721_dispatcher.get_token_uri(token_id), token_uri.into()); + +// // Approve another address and test get_approved +// let approved: ContractAddress = 'approved'.try_into().unwrap(); +// cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); +// erc721_dispatcher.approve(approved, token_id); +// assert_eq!(erc721_dispatcher.get_approved(token_id), approved); + +// // Set approval for all and test is_approved_for_all +// let operator: ContractAddress = 'operator'.try_into().unwrap(); +// erc721_dispatcher.set_approval_for_all(operator, true); +// assert!(erc721_dispatcher.is_approved_for_all(minter, operator)); + +// // Transfer the token and test owner_of +// let recipient: ContractAddress = 'recipient'.try_into().unwrap(); +// cheat_caller_address(erc721_address, operator, CheatSpan::TargetCalls(1)); +// erc721_dispatcher.transfer_from(minter, recipient, token_id); +// assert_eq!(erc721_dispatcher.owner_of(token_id), recipient); +// } + +// #[test] +// fn test_erc721_burn() { +// let erc721_address = get_erc721_contract_address(); +// let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; + +// // Mint a token and burn it +// let minter: ContractAddress = 'minter'.try_into().unwrap(); +// let token_id = 1.into(); + +// cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); +// erc721_dispatcher.mint(minter, token_id); + +// assert_eq!(erc721_dispatcher.balance_of(minter), 1.into()); +// assert_eq!(erc721_dispatcher.owner_of(token_id), minter); + +// cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); +// erc721_dispatcher._burn(token_id); + +// assert_eq!(erc721_dispatcher.balance_of(minter), 0.into()); +// assert_eq!(erc721_dispatcher.owner_of(token_id), ContractAddress::default()); +// } + + +use starknet::{ContractAddress, contract_address_const, get_caller_address}; + +use erc721::contracts::ERC721::{ + ERC721Contract, IExternalDispatcher as IERC721Dispatcher, + IExternalDispatcherTrait as IERC721DispatcherTrait +}; + +use traits::Into; +use traits::TryInto; +use array::ArrayTrait; +use option::OptionTrait; + +use snforge_std::{CheatTarget, ContractClassTrait, EventSpy, SpyOn, start_prank, stop_prank}; + +use super::tests_lib::{deploy_project}; + +#[test] +#[available_gas(2000000)] +fn test_get_name() { + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + + let name = contract.get_name(); + assert(name == 'ApeMonkey', 'wrong name'); + +} + +#[test] +#[available_gas(2000000)] +fn test_get_symbol() { + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + + let symbol = contract.get_symbol(); + assert(symbol == 'APE', 'wrong symbol'); +} + +#[test] +#[available_gas(2000000)] +fn test_balance_of() { + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); + start_prank(CheatTarget::One(project_address), OWNER); + + + let balance = contract.balance_of(OWNER); + assert(balance == 0, 'wrong balance'); + + let nft_id: u256 = 1.into(); + + contract.mint(OWNER, nft_id); + let balance = contract.balance_of(OWNER); + assert(balance == 1, 'wrong balance'); } #[test] -fn test_erc721_contract() { - let erc721_address = get_erc721_contract_address(); - let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; - - // Test get_name - assert_eq!(erc721_dispatcher.get_name(), erc721_name); - - // Test get_symbol - assert_eq!(erc721_dispatcher.get_symbol(), erc721_symbol); - - // Mint a token and test balance_of, owner_of, get_token_uri - let minter: ContractAddress = 'minter'.try_into().unwrap(); - let token_id = 1.into(); - let token_uri = "ipfs://token-uri"; - - cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); - erc721_dispatcher.mint(minter, token_id); - erc721_dispatcher._set_token_uri(token_id, token_uri.into()); - - assert_eq!(erc721_dispatcher.balance_of(minter), 1.into()); - assert_eq!(erc721_dispatcher.owner_of(token_id), minter); - assert_eq!(erc721_dispatcher.get_token_uri(token_id), token_uri.into()); - - // Approve another address and test get_approved - let approved: ContractAddress = 'approved'.try_into().unwrap(); - cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); - erc721_dispatcher.approve(approved, token_id); - assert_eq!(erc721_dispatcher.get_approved(token_id), approved); - - // Set approval for all and test is_approved_for_all - let operator: ContractAddress = 'operator'.try_into().unwrap(); - erc721_dispatcher.set_approval_for_all(operator, true); - assert!(erc721_dispatcher.is_approved_for_all(minter, operator)); - - // Transfer the token and test owner_of - let recipient: ContractAddress = 'recipient'.try_into().unwrap(); - cheat_caller_address(erc721_address, operator, CheatSpan::TargetCalls(1)); - erc721_dispatcher.transfer_from(minter, recipient, token_id); - assert_eq!(erc721_dispatcher.owner_of(token_id), recipient); +#[available_gas(2000000)] +fn test_owner_of() { + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); + start_prank(CheatTarget::One(project_address), OWNER); + + let nft_id: u256 = 1.into(); + contract.mint(OWNER, nft_id); + + let owner = contract.owner_of(nft_id); + assert(owner == OWNER, 'wrong owner'); } #[test] -fn test_erc721_burn() { - let erc721_address = get_erc721_contract_address(); - let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; +#[available_gas(2000000)] +fn test_get_approved() { + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); + start_prank(CheatTarget::One(project_address), OWNER); - // Mint a token and burn it - let minter: ContractAddress = 'minter'.try_into().unwrap(); - let token_id = 1.into(); + // caller address: + let caller = get_caller_address(); - cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); - erc721_dispatcher.mint(minter, token_id); + let nft_id: u256 = 1.into(); + contract.mint(OWNER, nft_id); - assert_eq!(erc721_dispatcher.balance_of(minter), 1.into()); - assert_eq!(erc721_dispatcher.owner_of(token_id), minter); - cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); - erc721_dispatcher._burn(token_id); + let user = starknet::contract_address_const::<'USER'>(); + contract.approve(user, nft_id); - assert_eq!(erc721_dispatcher.balance_of(minter), 0.into()); - assert_eq!(erc721_dispatcher.owner_of(token_id), ContractAddress::default()); + let approved = contract.get_approved(nft_id); + assert(approved == user, 'wrong approved'); } + +#[test] +#[available_gas(2000000)] +fn test_is_approved_for_all() { + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + + let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); + start_prank(CheatTarget::One(project_address), OWNER); + + let user = starknet::contract_address_const::<'USER'>(); + + let is_approved = contract.is_approved_for_all(OWNER, user); + assert(!is_approved, 'approved for all'); + + contract.set_approval_for_all(user, true); + let is_approved = contract.is_approved_for_all(OWNER, user); + assert(is_approved, 'not approved for all'); +} + +#[test] +#[available_gas(2000000)] +fn test_get_token_uri() { + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); + start_prank(CheatTarget::One(project_address), OWNER); + + let nft_id: u256 = 1.into(); + contract.mint(OWNER, nft_id); + + let uri = contract.get_token_uri(nft_id); + assert(uri == '', 'wrong uri'); + + contract.set_token_uri(nft_id, 'https://example.com/1'); + let uri = contract.get_token_uri(nft_id); + assert(uri == 'https://example.com/1', 'wrong uri'); +} + +#[test] +#[available_gas(2000000)] +fn test_transfer_from() { + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + + let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); + start_prank(CheatTarget::One(project_address), OWNER); + + let user = starknet::contract_address_const::<'USER'>(); + let nft_id: u256 = 1.into(); + contract.mint(OWNER, nft_id); + + contract.transfer_from(OWNER, user, nft_id); + + let new_owner = contract.owner_of(nft_id); + assert(new_owner == user, 'wrong new OWNER'); +} + +#[test] +#[available_gas(2000000)] +fn test_transfer_from_approved() { + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); + start_prank(CheatTarget::One(project_address), OWNER); + + let user = starknet::contract_address_const::<'USER'>(); + let nft_id: u256 = 1.into(); + + contract.mint(OWNER, nft_id); + contract.approve(user, nft_id); + + contract.transfer_from(OWNER, user, nft_id); + + let new_owner = contract.owner_of(nft_id); + assert(new_owner == user, 'wrong new OWNER'); +} + +#[test] +#[available_gas(2000000)] +fn test_transfer_from_approved_for_all() { + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + + let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); + start_prank(CheatTarget::One(project_address), OWNER); + + let user = starknet::contract_address_const::<'USER'>(); + + let nft_id: u256 = 1.into(); + contract.mint(OWNER, nft_id); + contract.set_approval_for_all(user, true); + + contract.transfer_from(OWNER, user, nft_id); + + let new_owner = contract.owner_of(nft_id); + assert(new_owner == user, 'wrong new OWNER'); +} + +#[test] +#[available_gas(2000000)] +#[should_panic] +fn test_transfer_from_not_approved() { + + let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let contract = IERC721Dispatcher{ contract_address: project_address }; + + let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); + start_prank(CheatTarget::One(project_address), OWNER); + + let user = starknet::contract_address_const::<'USER'>(); + let nft_id: u256 = 1.into(); + + contract.mint(OWNER, nft_id); + contract.approve(user, nft_id); + + let random_user = starknet::contract_address_const::<789>(); + contract.transfer_from(random_user, user, nft_id); + + let new_owner = contract.owner_of(nft_id); + assert(new_owner == user, 'wrong new OWNER'); +} \ No newline at end of file From 8815769a2ed9cd4ba1d3422c65fd559105a11a72 Mon Sep 17 00:00:00 2001 From: raizo07 Date: Fri, 5 Jul 2024 12:24:30 +0100 Subject: [PATCH 09/12] fix reviewwd changes --- listings/applications/erc721/src/erc721.cairo | 36 +++--- listings/applications/erc721/src/tests.cairo | 116 +++--------------- 2 files changed, 38 insertions(+), 114 deletions(-) diff --git a/listings/applications/erc721/src/erc721.cairo b/listings/applications/erc721/src/erc721.cairo index abdc8132..2d439d7c 100644 --- a/listings/applications/erc721/src/erc721.cairo +++ b/listings/applications/erc721/src/erc721.cairo @@ -17,11 +17,12 @@ pub trait IERC721 { } + #[starknet::interface] pub trait IERC721Metadata { - fn name(self: @TContractState) -> felt252; - fn symbol(self: @TContractState) -> felt252; - fn token_uri(self: @TContractState, token_id: u256) -> felt252; + fn name(self: @TContractState) -> ByteArray; + fn symbol(self: @TContractState) -> ByteArray; + fn token_uri(self: @TContractState, token_id: u256) -> ByteArray; } #[starknet::contract] @@ -72,14 +73,15 @@ mod ERC721 { mod Errors { // Error messages for each function - pub const ADDRESS_ZERO: felt252 = 'ERC721: invalid owner'; + pub const ADDRESS_ZERO: felt252 = 'ERC721: balance is zero'; + pub const INVALID_OWNER: felt252 = 'ERC721: invalid owner'; pub const INVALID_TOKEN_ID: felt252 = 'ERC721: invalid token ID'; pub const APPROVAL_TO_CURRENT_OWNER: felt252 = 'ERC721: approval to owner'; pub const NOT_TOKEN_OWNER: felt252 = 'ERC721: not token owner'; pub const APPROVE_TO_CALLER: felt252 = 'ERC721: approve to caller'; - pub const CALLER_NOT_OWNER_NOR_APPROVED: felt252 = 'ERC721: caller is not approved'; + pub const CALLER_NOT_APPROVED: felt252 = 'ERC721: caller is not approved'; pub const CALLER_IS_NOT_OWNER: felt252 = 'ERC721: caller is not owner'; - pub const TRANSFER_TO_ZERO_ADDRESS: felt252 = 'ERC721: to the zero address'; + pub const TRANSFER_TO_ZERO_ADDRESS: felt252 = 'ERC721: invalid receiver'; pub const TOKEN_ALREADY_MINTED: felt252 = 'ERC721: token already minted'; } @@ -98,7 +100,7 @@ mod ERC721 { fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { let owner = self.owners.read(token_id); - assert(owner.is_non_zero(), Errors::INVALID_TOKEN_ID); + assert(owner.is_non_zero(), Errors::INVALID_OWNER); owner } @@ -116,20 +118,23 @@ mod ERC721 { fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { let owner = self.owner_of(token_id); assert(to != owner, Errors::APPROVAL_TO_CURRENT_OWNER); + + // Split the combined assert into two separate asserts + assert(get_caller_address() == owner, Errors::NOT_TOKEN_OWNER); assert( - get_caller_address() == owner - || self.is_approved_for_all(owner, get_caller_address()), - Errors::NOT_TOKEN_OWNER + self.is_approved_for_all(owner, get_caller_address()), Errors::CALLER_NOT_APPROVED ); + self.token_approvals.write(token_id, to); self.emit(Approval { owner: self.owner_of(token_id), to: to, token_id: token_id }); } + fn set_approval_for_all( ref self: ContractState, operator: ContractAddress, approved: bool ) { let owner = get_caller_address(); - assert(owner != operator, Errors::APPROVE_TO_CALLER); + // assert(owner != operator, Errors::APPROVE_TO_CALLER); self.operator_approvals.write((owner, operator), approved); self.emit(ApprovalForAll { owner: owner, operator: operator, approved: approved }); } @@ -139,7 +144,7 @@ mod ERC721 { ) { assert( self._is_approved_or_owner(get_caller_address(), token_id), - Errors::CALLER_NOT_OWNER_NOR_APPROVED + Errors::CALLER_NOT_APPROVED ); self._transfer(from, to, token_id); } @@ -160,11 +165,6 @@ mod ERC721 { || self.get_approved(token_id) == spender } - fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { - assert(self._exists(token_id), Errors::INVALID_TOKEN_ID); - self.token_uri.write(token_id, token_uri) - } - fn _transfer( ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 ) { @@ -206,4 +206,4 @@ mod ERC721 { self.emit(Transfer { from: owner, to: Zero::zero(), token_id: token_id }); } } -} \ No newline at end of file +} diff --git a/listings/applications/erc721/src/tests.cairo b/listings/applications/erc721/src/tests.cairo index 5b7eac83..5e55b935 100644 --- a/listings/applications/erc721/src/tests.cairo +++ b/listings/applications/erc721/src/tests.cairo @@ -1,82 +1,3 @@ -// use starknet::ContractAddress; -// use starknet::get_caller_address; -// use snforge_std::{declare, ContractClassTrait, ContractClass, cheat_caller_address, CheatSpan}; - -// pub const erc721_name: felt252 = 'My NFT'; -// pub const erc721_symbol: felt252 = 'MNFT'; - -// fn get_erc721_contract_address() -> ContractAddress { -// let erc721 = declare("ERC721").unwrap(); -// let erc721_constructor_calldata = array![erc721_name, erc721_symbol]; -// let (erc721_address, _) = erc721.deploy(@erc721_constructor_calldata).unwrap(); -// erc721_address -// } - -// #[test] -// fn test_erc721_contract() { -// let erc721_address = get_erc721_contract_address(); -// let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; - -// // Test get_name -// assert_eq!(erc721_dispatcher.get_name(), erc721_name); - -// // Test get_symbol -// assert_eq!(erc721_dispatcher.get_symbol(), erc721_symbol); - -// // Mint a token and test balance_of, owner_of, get_token_uri -// let minter: ContractAddress = 'minter'.try_into().unwrap(); -// let token_id = 1.into(); -// let token_uri = "ipfs://token-uri"; - -// cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); -// erc721_dispatcher.mint(minter, token_id); -// erc721_dispatcher._set_token_uri(token_id, token_uri.into()); - -// assert_eq!(erc721_dispatcher.balance_of(minter), 1.into()); -// assert_eq!(erc721_dispatcher.owner_of(token_id), minter); -// assert_eq!(erc721_dispatcher.get_token_uri(token_id), token_uri.into()); - -// // Approve another address and test get_approved -// let approved: ContractAddress = 'approved'.try_into().unwrap(); -// cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); -// erc721_dispatcher.approve(approved, token_id); -// assert_eq!(erc721_dispatcher.get_approved(token_id), approved); - -// // Set approval for all and test is_approved_for_all -// let operator: ContractAddress = 'operator'.try_into().unwrap(); -// erc721_dispatcher.set_approval_for_all(operator, true); -// assert!(erc721_dispatcher.is_approved_for_all(minter, operator)); - -// // Transfer the token and test owner_of -// let recipient: ContractAddress = 'recipient'.try_into().unwrap(); -// cheat_caller_address(erc721_address, operator, CheatSpan::TargetCalls(1)); -// erc721_dispatcher.transfer_from(minter, recipient, token_id); -// assert_eq!(erc721_dispatcher.owner_of(token_id), recipient); -// } - -// #[test] -// fn test_erc721_burn() { -// let erc721_address = get_erc721_contract_address(); -// let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; - -// // Mint a token and burn it -// let minter: ContractAddress = 'minter'.try_into().unwrap(); -// let token_id = 1.into(); - -// cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); -// erc721_dispatcher.mint(minter, token_id); - -// assert_eq!(erc721_dispatcher.balance_of(minter), 1.into()); -// assert_eq!(erc721_dispatcher.owner_of(token_id), minter); - -// cheat_caller_address(erc721_address, minter, CheatSpan::TargetCalls(1)); -// erc721_dispatcher._burn(token_id); - -// assert_eq!(erc721_dispatcher.balance_of(minter), 0.into()); -// assert_eq!(erc721_dispatcher.owner_of(token_id), ContractAddress::default()); -// } - - use starknet::{ContractAddress, contract_address_const, get_caller_address}; use erc721::contracts::ERC721::{ @@ -97,18 +18,17 @@ use super::tests_lib::{deploy_project}; #[available_gas(2000000)] fn test_get_name() { let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let name = contract.get_name(); assert(name == 'ApeMonkey', 'wrong name'); - } #[test] #[available_gas(2000000)] fn test_get_symbol() { let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let symbol = contract.get_symbol(); assert(symbol == 'APE', 'wrong symbol'); @@ -118,11 +38,10 @@ fn test_get_symbol() { #[available_gas(2000000)] fn test_balance_of() { let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); - let balance = contract.balance_of(OWNER); assert(balance == 0, 'wrong balance'); @@ -137,7 +56,7 @@ fn test_balance_of() { #[available_gas(2000000)] fn test_owner_of() { let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -152,7 +71,7 @@ fn test_owner_of() { #[available_gas(2000000)] fn test_get_approved() { let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -162,7 +81,6 @@ fn test_get_approved() { let nft_id: u256 = 1.into(); contract.mint(OWNER, nft_id); - let user = starknet::contract_address_const::<'USER'>(); contract.approve(user, nft_id); @@ -174,7 +92,7 @@ fn test_get_approved() { #[available_gas(2000000)] fn test_is_approved_for_all() { let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -193,7 +111,7 @@ fn test_is_approved_for_all() { #[available_gas(2000000)] fn test_get_token_uri() { let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -212,7 +130,7 @@ fn test_get_token_uri() { #[available_gas(2000000)] fn test_transfer_from() { let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -231,7 +149,7 @@ fn test_transfer_from() { #[available_gas(2000000)] fn test_transfer_from_approved() { let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -251,11 +169,11 @@ fn test_transfer_from_approved() { #[available_gas(2000000)] fn test_transfer_from_approved_for_all() { let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); - + let user = starknet::contract_address_const::<'USER'>(); let nft_id: u256 = 1.into(); @@ -272,9 +190,8 @@ fn test_transfer_from_approved_for_all() { #[available_gas(2000000)] #[should_panic] fn test_transfer_from_not_approved() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); - let contract = IERC721Dispatcher{ contract_address: project_address }; + let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -290,4 +207,11 @@ fn test_transfer_from_not_approved() { let new_owner = contract.owner_of(nft_id); assert(new_owner == user, 'wrong new OWNER'); -} \ No newline at end of file +} + +#[test] +#[available_gas(2000000)] +fn test_erc721_burn() { + let erc721_address = get_erc721_contract_address(); + let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; +} From b5ae02b75d26db3fc22f409bcf8d30a739379300 Mon Sep 17 00:00:00 2001 From: raizo07 Date: Mon, 15 Jul 2024 18:19:11 +0100 Subject: [PATCH 10/12] fixed placeholder names --- listings/applications/erc721/src/tests.cairo | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/listings/applications/erc721/src/tests.cairo b/listings/applications/erc721/src/tests.cairo index 5e55b935..bc95b31a 100644 --- a/listings/applications/erc721/src/tests.cairo +++ b/listings/applications/erc721/src/tests.cairo @@ -17,27 +17,27 @@ use super::tests_lib::{deploy_project}; #[test] #[available_gas(2000000)] fn test_get_name() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let name = contract.get_name(); - assert(name == 'ApeMonkey', 'wrong name'); + assert(name == 'Foo', 'wrong name'); } #[test] #[available_gas(2000000)] fn test_get_symbol() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let symbol = contract.get_symbol(); - assert(symbol == 'APE', 'wrong symbol'); + assert(symbol == 'BAR', 'wrong symbol'); } #[test] #[available_gas(2000000)] fn test_balance_of() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -55,7 +55,7 @@ fn test_balance_of() { #[test] #[available_gas(2000000)] fn test_owner_of() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -70,7 +70,7 @@ fn test_owner_of() { #[test] #[available_gas(2000000)] fn test_get_approved() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -91,7 +91,7 @@ fn test_get_approved() { #[test] #[available_gas(2000000)] fn test_is_approved_for_all() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); @@ -110,7 +110,7 @@ fn test_is_approved_for_all() { #[test] #[available_gas(2000000)] fn test_get_token_uri() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -129,7 +129,7 @@ fn test_get_token_uri() { #[test] #[available_gas(2000000)] fn test_transfer_from() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo','BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); @@ -148,7 +148,7 @@ fn test_transfer_from() { #[test] #[available_gas(2000000)] fn test_transfer_from_approved() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); start_prank(CheatTarget::One(project_address), OWNER); @@ -168,7 +168,7 @@ fn test_transfer_from_approved() { #[test] #[available_gas(2000000)] fn test_transfer_from_approved_for_all() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); @@ -190,7 +190,7 @@ fn test_transfer_from_approved_for_all() { #[available_gas(2000000)] #[should_panic] fn test_transfer_from_not_approved() { - let (project_address, _) = deploy_project('ApeMonkey', 'APE'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>(); From 8afbe04c4d199e8c067bf430fa4de2fa4560e286 Mon Sep 17 00:00:00 2001 From: raizo07 Date: Mon, 15 Jul 2024 18:37:12 +0100 Subject: [PATCH 11/12] proper naming of test file --- .../applications/erc721/src/{tests.cairo => test_ERC721.cairo} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename listings/applications/erc721/src/{tests.cairo => test_ERC721.cairo} (100%) diff --git a/listings/applications/erc721/src/tests.cairo b/listings/applications/erc721/src/test_ERC721.cairo similarity index 100% rename from listings/applications/erc721/src/tests.cairo rename to listings/applications/erc721/src/test_ERC721.cairo From aa78eb504252a5b53f7b71226de97eb14670ee78 Mon Sep 17 00:00:00 2001 From: raizo07 Date: Tue, 16 Jul 2024 10:09:02 +0100 Subject: [PATCH 12/12] scarb fmt --- listings/applications/erc721/src/erc721.cairo | 1 - listings/applications/erc721/src/test_ERC721.cairo | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/listings/applications/erc721/src/erc721.cairo b/listings/applications/erc721/src/erc721.cairo index 2d439d7c..b05808b9 100644 --- a/listings/applications/erc721/src/erc721.cairo +++ b/listings/applications/erc721/src/erc721.cairo @@ -17,7 +17,6 @@ pub trait IERC721 { } - #[starknet::interface] pub trait IERC721Metadata { fn name(self: @TContractState) -> ByteArray; diff --git a/listings/applications/erc721/src/test_ERC721.cairo b/listings/applications/erc721/src/test_ERC721.cairo index bc95b31a..19779878 100644 --- a/listings/applications/erc721/src/test_ERC721.cairo +++ b/listings/applications/erc721/src/test_ERC721.cairo @@ -129,7 +129,7 @@ fn test_get_token_uri() { #[test] #[available_gas(2000000)] fn test_transfer_from() { - let (project_address, _) = deploy_project('Foo','BAR'); + let (project_address, _) = deploy_project('Foo', 'BAR'); let contract = IERC721Dispatcher { contract_address: project_address }; let OWNER: ContractAddress = contract_address_const::<'OWNER'>();