diff --git a/flex_marketplace/src/mocks/erc721.cairo b/flex_marketplace/src/mocks/erc721.cairo index d260d34..97117b1 100644 --- a/flex_marketplace/src/mocks/erc721.cairo +++ b/flex_marketplace/src/mocks/erc721.cairo @@ -1,3 +1,5 @@ +use starknet::{ContractAddress, contract_address_const}; + #[starknet::interface] trait IER721CamelOnly { fn transferFrom( @@ -31,6 +33,10 @@ mod ERC721 { struct Storage { #[substorage(v0)] src5: SRC5Component::Storage, + name: felt252, + symbol: felt252, + owners: LegacyMap::, + balances: LegacyMap::, } #[event] @@ -40,9 +46,23 @@ mod ERC721 { SRC5Event: SRC5Component::Event, } + fn RECIPIENT() -> starknet::ContractAddress { + starknet::contract_address_const::<'RECIPIENT'>() + } + fn ACCOUNT1() -> ContractAddress { + starknet::contract_address_const::<'ACCOUNT1'>() + } + fn ACCOUNT2() -> ContractAddress { + starknet::contract_address_const::<'ACCOUNT2'>() + } + #[constructor] - fn constructor(ref self: ContractState,) { + fn constructor(ref self: ContractState) { self.src5.register_interface(0x80ac58cd); + self.name.write('flex'); + self.symbol.write('fNFT'); + self._mint(ACCOUNT1(), 1); + self._mint(ACCOUNT2(), 2); } #[external(v0)] @@ -55,11 +75,6 @@ mod ERC721 { ) {} } - - fn RECIPIENT() -> starknet::ContractAddress { - starknet::contract_address_const::<'RECIPIENT'>() - } - #[external(v0)] impl IERC2981Impl of super::IERC2981 { fn royaltyInfo( @@ -68,4 +83,14 @@ mod ERC721 { (RECIPIENT(), 5000) } } + + #[generate_trait] + impl StorageImpl of StorageTrait { + fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { + assert(!to.is_zero(), 'ERC721: mint to 0'); + self.balances.write(to, self.balances.read(to) + 1.into()); + self.owners.write(token_id, to); + } + } } + diff --git a/flex_marketplace/tests/royalty_fee_managen_test.cairo b/flex_marketplace/tests/royalty_fee_managen_test.cairo deleted file mode 100644 index 970a5d5..0000000 --- a/flex_marketplace/tests/royalty_fee_managen_test.cairo +++ /dev/null @@ -1,15 +0,0 @@ -use tests::utils::{setup, initialize_test}; - -#[test] -fn test_calculate_royalty_fee_and_get_recipient_success() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} - -#[test] -fn test_get_royalty_fee_registry() { - let dsp = setup(); - initialize_test(dsp); -// TODO -} diff --git a/flex_marketplace/tests/royalty_fee_manager_test.cairo b/flex_marketplace/tests/royalty_fee_manager_test.cairo new file mode 100644 index 0000000..f1730ca --- /dev/null +++ b/flex_marketplace/tests/royalty_fee_manager_test.cairo @@ -0,0 +1,112 @@ +use starknet::{ContractAddress, contract_address_const, ClassHash, class_hash_const}; + +use snforge_std::{start_prank, CheatTarget, declare}; + +use flex::marketplace::royalty_fee_manager::{ + RoyaltyFeeManager, IRoyaltyFeeManagerDispatcher, IRoyaltyFeeManagerDispatcherTrait +}; +use flex::marketplace::royalty_fee_registry::{ + RoyaltyFeeRegistry, IRoyaltyFeeRegistryDispatcher, IRoyaltyFeeRegistryDispatcherTrait +}; + +use openzeppelin::upgrades::UpgradeableComponent::Upgraded; + +use tests::utils::{ + setup, initialize_test, deploy_mock_nft, pop_log, assert_no_events_left, OWNER, ACCOUNT1, + RECIPIENT +}; + + +fn UPGRADE_CLASSHASH() -> ClassHash { + RoyaltyFeeManager::TEST_CLASS_HASH.try_into().unwrap() +} + +fn CLASS_HASH_ZERO() -> ClassHash { + class_hash_const::<0>() +} + +fn COLLECTION() -> ContractAddress { + contract_address_const::<'COLLECTION'>() +} + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn given_caller_has_no_owner_role_then_test_upgrade_fails() { + let dsp = setup(); + initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.fee_manager.contract_address), ACCOUNT1()); + dsp.fee_manager.upgrade(UPGRADE_CLASSHASH()); +} + +#[test] +#[should_panic(expected: ('Class hash cannot be zero', 'ENTRYPOINT_FAILED',))] +fn test_upgrade_with_class_hash_zero() { + let dsp = setup(); + initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.fee_manager.contract_address), OWNER()); + dsp.fee_manager.upgrade(CLASS_HASH_ZERO()); +} + +#[test] +fn test_upgrade_event_when_successful() { + let dsp = setup(); + initialize_test(dsp); + + start_prank(CheatTarget::One(dsp.fee_manager.contract_address), OWNER()); + dsp.fee_manager.upgrade(UPGRADE_CLASSHASH()); + + let event = pop_log::(dsp.fee_manager.contract_address).unwrap(); + assert(event.class_hash == UPGRADE_CLASSHASH(), 'Invalid class hash'); + + assert_no_events_left(dsp.fee_manager.contract_address); +} + +#[test] +fn test_INTERFACE_ID_ERC2981() { + let dsp = setup(); + initialize_test(dsp); + + // assert the returned felt252 is equal to 0x2a55205a + assert!(dsp.fee_manager.INTERFACE_ID_ERC2981() == 0x2a55205a, "wrong interface"); +} + +#[test] +fn test_calculate_royalty_fee_and_get_recipient_success() { + let dsp = setup(); + initialize_test(dsp); + + // deploy the nft, mint to ACCOUNT1 and ACCOUNT2 in constructor + let nft_address = deploy_mock_nft(); + + start_prank(CheatTarget::One(dsp.fee_registry.contract_address), OWNER()); + + // set royalty fee info in fee registry contract + dsp.fee_registry.update_royalty_info_collection(COLLECTION(), OWNER(), RECIPIENT(), 1); + dsp.fee_registry.update_royalty_info_collection(COLLECTION(), OWNER(), RECIPIENT(), 2); + + // call fee_manager + start_prank(CheatTarget::One(dsp.fee_manager.contract_address), OWNER()); + + dsp.fee_manager.calculate_royalty_fee_and_get_recipient(COLLECTION(), 1, 1); + dsp.fee_manager.calculate_royalty_fee_and_get_recipient(COLLECTION(), 2, 1); +} + +#[test] +fn test_get_royalty_fee_registry() { + let dsp = setup(); + initialize_test(dsp); + + // ensure the address saved in storage matches the address from the dsp + assert!( + dsp + .fee_manager + .get_royalty_fee_registry() + .contract_address == dsp + .fee_registry + .contract_address, + "wrong fee registry" + ); +} + diff --git a/flex_marketplace/tests/utils.cairo b/flex_marketplace/tests/utils.cairo index 10ea954..213e124 100644 --- a/flex_marketplace/tests/utils.cairo +++ b/flex_marketplace/tests/utils.cairo @@ -3,6 +3,7 @@ use starknet::{ ContractAddress, contract_address_const, get_block_timestamp, get_contract_address, get_caller_address, class_hash::ClassHash }; +use starknet::testing; use snforge_std::{ PrintTrait, declare, ContractClassTrait, start_warp, start_prank, stop_prank, CheatTarget }; @@ -270,3 +271,28 @@ fn deploy_test() { let dsp = setup(); initialize_test(dsp); } +/// Pop the earliest unpopped logged event for the contract as the requested type +/// and checks there's no more keys or data left on the event, preventing unaccounted params. +/// +/// This function also removes the first key from the event, to match the event +/// structure key params without the event ID. +/// +/// This method doesn't currently work for components events that are not flattened +/// because an extra key is added, pushing the event ID key to the second position. +fn pop_log, +starknet::Event>(address: ContractAddress) -> Option { + let (mut keys, mut data) = testing::pop_log_raw(address)?; + + // Remove the event ID from the keys + keys.pop_front(); + + let ret = starknet::Event::deserialize(ref keys, ref data); + assert(data.is_empty(), 'Event has extra data'); + assert(keys.is_empty(), 'Event has extra keys'); + ret +} + +fn assert_no_events_left(address: ContractAddress) { + assert(testing::pop_log_raw(address).is_none(), 'Events remaining on queue'); +} + +