--- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -222,12 +222,11 @@ fn set_collection_sponsor(&mut self, caller: caller, sponsor: address) -> Result { self.consume_store_reads_and_writes(1, 1)?; - check_is_owner_or_admin(caller, self)?; + let caller = T::CrossAccountId::from_eth(caller); let sponsor = T::CrossAccountId::from_eth(sponsor); - self.set_sponsor(sponsor.as_sub().clone()) - .map_err(dispatch_to_evm::)?; - save(self) + self.set_sponsor(&caller, sponsor.as_sub().clone()) + .map_err(dispatch_to_evm::) } /// Set the sponsor of the collection. @@ -242,12 +241,11 @@ ) -> Result { self.consume_store_reads_and_writes(1, 1)?; - check_is_owner_or_admin(caller, self)?; + let caller = T::CrossAccountId::from_eth(caller); let sponsor = sponsor.into_sub_cross_account::()?; - self.set_sponsor(sponsor.as_sub().clone()) - .map_err(dispatch_to_evm::)?; - save(self) + self.set_sponsor(&caller, sponsor.as_sub().clone()) + .map_err(dispatch_to_evm::) } /// Whether there is a pending sponsor. @@ -265,21 +263,15 @@ self.consume_store_writes(1)?; let caller = T::CrossAccountId::from_eth(caller); - if !self - .confirm_sponsorship(caller.as_sub()) - .map_err(dispatch_to_evm::)? - { - return Err("caller is not set as sponsor".into()); - } - save(self) + self.confirm_sponsorship(caller.as_sub()) + .map_err(dispatch_to_evm::) } /// Remove collection sponsor. fn remove_collection_sponsor(&mut self, caller: caller) -> Result { self.consume_store_reads_and_writes(1, 1)?; - check_is_owner_or_admin(caller, self)?; - self.remove_sponsor().map_err(dispatch_to_evm::)?; - save(self) + let caller = T::CrossAccountId::from_eth(caller); + self.remove_sponsor(&caller).map_err(dispatch_to_evm::) } /// Get current sponsor. @@ -333,7 +325,6 @@ } }; - check_is_owner_or_admin(caller, self)?; let mut limits = self.limits.clone(); match limit.as_str() { @@ -366,9 +357,9 @@ } _ => return Err(Error::Revert(format!("unknown limit \"{}\"", limit))), } - self.limits = >::clamp_limits(self.mode.clone(), &self.limits, limits) - .map_err(dispatch_to_evm::)?; - save(self) + + let caller = T::CrossAccountId::from_eth(caller); + >::update_limits(&caller, self, limits).map_err(dispatch_to_evm::) } /// Get contract address. @@ -383,7 +374,7 @@ caller: caller, new_admin: EthCrossAccount, ) -> Result { - self.consume_store_writes(2)?; + self.consume_store_reads_and_writes(2, 2)?; let caller = T::CrossAccountId::from_eth(caller); let new_admin = new_admin.into_sub_cross_account::()?; @@ -398,7 +389,7 @@ caller: caller, admin: EthCrossAccount, ) -> Result { - self.consume_store_writes(2)?; + self.consume_store_reads_and_writes(2, 2)?; let caller = T::CrossAccountId::from_eth(caller); let admin = admin.into_sub_cross_account::()?; @@ -410,7 +401,7 @@ /// @param newAdmin Address of the added administrator. #[solidity(hide)] fn add_collection_admin(&mut self, caller: caller, new_admin: address) -> Result { - self.consume_store_writes(2)?; + self.consume_store_reads_and_writes(2, 2)?; let caller = T::CrossAccountId::from_eth(caller); let new_admin = T::CrossAccountId::from_eth(new_admin); @@ -423,7 +414,7 @@ /// @param admin Address of the removed administrator. #[solidity(hide)] fn remove_collection_admin(&mut self, caller: caller, admin: address) -> Result { - self.consume_store_writes(2)?; + self.consume_store_reads_and_writes(2, 2)?; let caller = T::CrossAccountId::from_eth(caller); let admin = T::CrossAccountId::from_eth(admin); @@ -438,7 +429,7 @@ fn set_nesting_bool(&mut self, caller: caller, enable: bool) -> Result { self.consume_store_reads_and_writes(1, 1)?; - check_is_owner_or_admin(caller, self)?; + let caller = T::CrossAccountId::from_eth(caller); let mut permissions = self.collection.permissions.clone(); let mut nesting = permissions.nesting().clone(); @@ -446,14 +437,7 @@ nesting.restricted = None; permissions.nesting = Some(nesting); - self.collection.permissions = >::clamp_permissions( - self.collection.mode.clone(), - &self.collection.permissions, - permissions, - ) - .map_err(dispatch_to_evm::)?; - - save(self) + >::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::) } /// Toggle accessibility of collection nesting. @@ -472,7 +456,7 @@ if collections.is_empty() { return Err("no addresses provided".into()); } - check_is_owner_or_admin(caller, self)?; + let caller = T::CrossAccountId::from_eth(caller); let mut permissions = self.collection.permissions.clone(); match enable { @@ -497,14 +481,7 @@ } }; - self.collection.permissions = >::clamp_permissions( - self.collection.mode.clone(), - &self.collection.permissions, - permissions, - ) - .map_err(dispatch_to_evm::)?; - - save(self) + >::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::) } /// Set the collection access method. @@ -514,7 +491,7 @@ fn set_collection_access(&mut self, caller: caller, mode: uint8) -> Result { self.consume_store_reads_and_writes(1, 1)?; - check_is_owner_or_admin(caller, self)?; + let caller = T::CrossAccountId::from_eth(caller); let permissions = CollectionPermissions { access: Some(match mode { 0 => AccessMode::Normal, @@ -523,14 +500,7 @@ }), ..Default::default() }; - self.collection.permissions = >::clamp_permissions( - self.collection.mode.clone(), - &self.collection.permissions, - permissions, - ) - .map_err(dispatch_to_evm::)?; - - save(self) + >::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::) } /// Checks that user allowed to operate with collection. @@ -605,19 +575,12 @@ fn set_collection_mint_mode(&mut self, caller: caller, mode: bool) -> Result { self.consume_store_reads_and_writes(1, 1)?; - check_is_owner_or_admin(caller, self)?; + let caller = T::CrossAccountId::from_eth(caller); let permissions = CollectionPermissions { mint_mode: Some(mode), ..Default::default() }; - self.collection.permissions = >::clamp_permissions( - self.collection.mode.clone(), - &self.collection.permissions, - permissions, - ) - .map_err(dispatch_to_evm::)?; - - save(self) + >::update_permissions(&caller, self, permissions).map_err(dispatch_to_evm::) } /// Check that account is the owner or admin of the collection @@ -671,7 +634,7 @@ let caller = T::CrossAccountId::from_eth(caller); let new_owner = T::CrossAccountId::from_eth(new_owner); - self.set_owner_internal(caller, new_owner) + self.change_owner(caller, new_owner) .map_err(dispatch_to_evm::) } @@ -699,7 +662,7 @@ let caller = T::CrossAccountId::from_eth(caller); let new_owner = new_owner.into_sub_cross_account::()?; - self.set_owner_internal(caller, new_owner) + self.change_owner(caller, new_owner) .map_err(dispatch_to_evm::) } } --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -229,28 +229,111 @@ /// Unique collections allows sponsoring for certain actions. /// This method allows you to set the sponsor of the collection. /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`]. - pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult { - self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor); - Ok(()) + pub fn set_sponsor( + &mut self, + sender: &T::CrossAccountId, + sponsor: T::AccountId, + ) -> DispatchResult { + self.check_is_internal()?; + self.check_is_owner_or_admin(sender)?; + + self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone()); + + >::deposit_event(Event::::CollectionSponsorSet(self.id, sponsor)); + >::deposit_log( + erc::CollectionHelpersEvents::CollectionChanged { + collection_id: eth::collection_id_to_address(self.id), + } + .to_log(T::ContractAddress::get()), + ); + + self.save() + } + + /// Force set `sponsor`. + /// + /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation + /// from the `sponsor` is not required. + /// + /// # Arguments + /// + /// * `sender`: Caller's account. + /// * `sponsor`: ID of the account of the sponsor-to-be. + pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult { + self.check_is_internal()?; + + self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone()); + + >::deposit_event(Event::::CollectionSponsorSet(self.id, sponsor.clone())); + >::deposit_event(Event::::SponsorshipConfirmed(self.id, sponsor)); + >::deposit_log( + erc::CollectionHelpersEvents::CollectionChanged { + collection_id: eth::collection_id_to_address(self.id), + } + .to_log(T::ContractAddress::get()), + ); + + self.save() } /// Confirm sponsorship /// /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation. /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`]. - pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result { - if self.collection.sponsorship.pending_sponsor() != Some(sender) { - return Ok(false); - } + pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult { + self.check_is_internal()?; + ensure!( + self.collection.sponsorship.pending_sponsor() == Some(sender), + Error::::ConfirmUnsetSponsorFail + ); self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone()); - Ok(true) + + >::deposit_event(Event::::SponsorshipConfirmed(self.id, sender.clone())); + >::deposit_log( + erc::CollectionHelpersEvents::CollectionChanged { + collection_id: eth::collection_id_to_address(self.id), + } + .to_log(T::ContractAddress::get()), + ); + + self.save() } /// Remove collection sponsor. - pub fn remove_sponsor(&mut self) -> DispatchResult { + pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult { + self.check_is_internal()?; + self.check_is_owner(sender)?; + + self.collection.sponsorship = SponsorshipState::Disabled; + + >::deposit_event(Event::::CollectionSponsorRemoved(self.id)); + >::deposit_log( + erc::CollectionHelpersEvents::CollectionChanged { + collection_id: eth::collection_id_to_address(self.id), + } + .to_log(T::ContractAddress::get()), + ); + self.save() + } + + /// Force remove `sponsor`. + /// + /// Differs from `remove_sponsor` in that + /// it doesn't require consent from the `owner` of the collection. + pub fn force_remove_sponsor(&mut self) -> DispatchResult { + self.check_is_internal()?; + self.collection.sponsorship = SponsorshipState::Disabled; - Ok(()) + + >::deposit_event(Event::::CollectionSponsorRemoved(self.id)); + >::deposit_log( + erc::CollectionHelpersEvents::CollectionChanged { + collection_id: eth::collection_id_to_address(self.id), + } + .to_log(T::ContractAddress::get()), + ); + self.save() } /// Checks that the collection was created with, and must be operated upon through **Unique API**. @@ -328,13 +411,26 @@ /// Changes collection owner to another account /// #### Store read/writes /// 1 writes - fn set_owner_internal( + pub fn change_owner( &mut self, caller: T::CrossAccountId, new_owner: T::CrossAccountId, ) -> DispatchResult { + self.check_is_internal()?; self.check_is_owner(&caller)?; self.collection.owner = new_owner.as_sub().clone(); + + >::deposit_event(Event::::CollectionOwnedChanged( + self.id, + new_owner.as_sub().clone(), + )); + >::deposit_log( + erc::CollectionHelpersEvents::CollectionChanged { + collection_id: eth::collection_id_to_address(self.id), + } + .to_log(T::ContractAddress::get()), + ); + self.save() } } @@ -527,6 +623,80 @@ /// The property permission that was set. PropertyKey, ), + + /// Address was added to the allow list. + AllowListAddressAdded( + /// ID of the affected collection. + CollectionId, + /// Address of the added account. + T::CrossAccountId, + ), + + /// Address was removed from the allow list. + AllowListAddressRemoved( + /// ID of the affected collection. + CollectionId, + /// Address of the removed account. + T::CrossAccountId, + ), + + /// Collection admin was added. + CollectionAdminAdded( + /// ID of the affected collection. + CollectionId, + /// Admin address. + T::CrossAccountId, + ), + + /// Collection admin was removed. + CollectionAdminRemoved( + /// ID of the affected collection. + CollectionId, + /// Removed admin address. + T::CrossAccountId, + ), + + /// Collection limits were set. + CollectionLimitSet( + /// ID of the affected collection. + CollectionId, + ), + + /// Collection owned was changed. + CollectionOwnedChanged( + /// ID of the affected collection. + CollectionId, + /// New owner address. + T::AccountId, + ), + + /// Collection permissions were set. + CollectionPermissionSet( + /// ID of the affected collection. + CollectionId, + ), + + /// Collection sponsor was set. + CollectionSponsorSet( + /// ID of the affected collection. + CollectionId, + /// New sponsor address. + T::AccountId, + ), + + /// New sponsor was confirm. + SponsorshipConfirmed( + /// ID of the affected collection. + CollectionId, + /// New sponsor address. + T::AccountId, + ), + + /// Collection sponsor was removed. + CollectionSponsorRemoved( + /// ID of the affected collection. + CollectionId, + ), } #[pallet::error] @@ -613,6 +783,12 @@ /// Tried to access an internal collection with an external API CollectionIsInternal, + + /// This address is not set as sponsor, use setCollectionSponsor first. + ConfirmUnsetSponsorFail, + + /// The user is not an administrator. + UserIsNotAdmin, } /// Storage of the count of created collections. Essentially contains the last collection ID. @@ -1363,27 +1539,47 @@ if allowed { >::insert((collection.id, user), true); + Self::deposit_event(Event::::AllowListAddressAdded( + collection.id, + user.clone(), + )); } else { >::remove((collection.id, user)); + Self::deposit_event(Event::::AllowListAddressRemoved( + collection.id, + user.clone(), + )); } + >::deposit_log( + erc::CollectionHelpersEvents::CollectionChanged { + collection_id: eth::collection_id_to_address(collection.id), + } + .to_log(T::ContractAddress::get()), + ); + Ok(()) } /// Toggle `user` participation in the `collection`'s admin list. /// #### Store read/writes - /// 2 writes + /// 2 reads, 2 writes pub fn toggle_admin( collection: &CollectionHandle, sender: &T::CrossAccountId, user: &T::CrossAccountId, admin: bool, ) -> DispatchResult { + collection.check_is_internal()?; collection.check_is_owner(sender)?; - let was_admin = >::get((collection.id, user)); - if was_admin == admin { - return Ok(()); + let is_admin = >::get((collection.id, user)); + if is_admin == admin { + if admin { + return Ok(()); + } else { + ensure!(false, Error::::UserIsNotAdmin); + } } let amount = >::get(collection.id); @@ -1400,16 +1596,56 @@ >::insert(collection.id, amount); >::insert((collection.id, user), true); + + Self::deposit_event(Event::::CollectionAdminAdded( + collection.id, + user.clone(), + )); } else { >::insert(collection.id, amount.saturating_sub(1)); >::remove((collection.id, user)); + + Self::deposit_event(Event::::CollectionAdminRemoved( + collection.id, + user.clone(), + )); } + >::deposit_log( + erc::CollectionHelpersEvents::CollectionChanged { + collection_id: eth::collection_id_to_address(collection.id), + } + .to_log(T::ContractAddress::get()), + ); + Ok(()) } + /// Update collection limits. + pub fn update_limits( + user: &T::CrossAccountId, + collection: &mut CollectionHandle, + new_limit: CollectionLimits, + ) -> DispatchResult { + collection.check_is_internal()?; + collection.check_is_owner_or_admin(user)?; + + collection.limits = + Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?; + + Self::deposit_event(Event::::CollectionLimitSet(collection.id)); + >::deposit_log( + erc::CollectionHelpersEvents::CollectionChanged { + collection_id: eth::collection_id_to_address(collection.id), + } + .to_log(T::ContractAddress::get()), + ); + + collection.save() + } + /// Merge set fields from `new_limit` to `old_limit`. - pub fn clamp_limits( + fn clamp_limits( mode: CollectionMode, old_limit: &CollectionLimits, mut new_limit: CollectionLimits, @@ -1454,8 +1690,33 @@ Ok(new_limit) } + /// Update collection permissions. + pub fn update_permissions( + user: &T::CrossAccountId, + collection: &mut CollectionHandle, + new_permission: CollectionPermissions, + ) -> DispatchResult { + collection.check_is_internal()?; + collection.check_is_owner_or_admin(user)?; + collection.permissions = Self::clamp_permissions( + collection.mode.clone(), + &collection.permissions, + new_permission, + )?; + + Self::deposit_event(Event::::CollectionPermissionSet(collection.id)); + >::deposit_log( + erc::CollectionHelpersEvents::CollectionChanged { + collection_id: eth::collection_id_to_address(collection.id), + } + .to_log(T::ContractAddress::get()), + ); + + collection.save() + } + /// Merge set fields from `new_permission` to `old_permission`. - pub fn clamp_permissions( + fn clamp_permissions( _mode: CollectionMode, old_permission: &CollectionPermissions, mut new_permission: CollectionPermissions, --- a/pallets/unique/src/lib.rs +++ b/pallets/unique/src/lib.rs @@ -89,10 +89,9 @@ MAX_PROPERTIES_PER_ITEM, MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH, MAX_COLLECTION_PROPERTIES_SIZE, COLLECTION_ADMINS_LIMIT, MAX_TOKEN_PROPERTIES_SIZE, CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId, - SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey, - PropertyKeyPermission, + CreateCollectionData, CreateItemExData, budget, Property, PropertyKey, PropertyKeyPermission, }; -use pallet_evm::account::CrossAccountId; +use pallet_evm::{account::CrossAccountId}; use pallet_common::{ CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx, dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo, @@ -112,8 +111,6 @@ pub enum Error for Module { /// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`]. CollectionDecimalPointLimitExceeded, - /// This address is not set as sponsor, use setCollectionSponsor first. - ConfirmUnsetSponsorFail, /// Length of items properties must be greater than 0. EmptyArgument, /// Repertition is only supported by refungible collection. @@ -140,27 +137,12 @@ pub enum Event where ::AccountId, - ::CrossAccountId, { /// Collection sponsor was removed /// /// # Arguments /// * collection_id: ID of the affected collection. CollectionSponsorRemoved(CollectionId), - - /// Collection admin was added - /// - /// # Arguments - /// * collection_id: ID of the affected collection. - /// * admin: Admin address. - CollectionAdminAdded(CollectionId, CrossAccountId), - - /// Collection owned was changed - /// - /// # Arguments - /// * collection_id: ID of the affected collection. - /// * owner: New owner address. - CollectionOwnedChanged(CollectionId, AccountId), /// Collection sponsor was set /// @@ -168,46 +150,7 @@ /// * collection_id: ID of the affected collection. /// * owner: New sponsor address. CollectionSponsorSet(CollectionId, AccountId), - - /// New sponsor was confirm - /// - /// # Arguments - /// * collection_id: ID of the affected collection. - /// * sponsor: New sponsor address. - SponsorshipConfirmed(CollectionId, AccountId), - - /// Collection admin was removed - /// - /// # Arguments - /// * collection_id: ID of the affected collection. - /// * admin: Removed admin address. - CollectionAdminRemoved(CollectionId, CrossAccountId), - /// Address was removed from the allow list - /// - /// # Arguments - /// * collection_id: ID of the affected collection. - /// * user: Address of the removed account. - AllowListAddressRemoved(CollectionId, CrossAccountId), - - /// Address was added to the allow list - /// - /// # Arguments - /// * collection_id: ID of the affected collection. - /// * user: Address of the added account. - AllowListAddressAdded(CollectionId, CrossAccountId), - - /// Collection limits were set - /// - /// # Arguments - /// * collection_id: ID of the affected collection. - CollectionLimitSet(CollectionId), - - /// Collection permissions were set - /// - /// # Arguments - /// * collection_id: ID of the affected collection. - CollectionPermissionSet(CollectionId), } } @@ -432,11 +375,6 @@ &address, true, )?; - - Self::deposit_event(Event::::AllowListAddressAdded( - collection_id, - address - )); Ok(()) } @@ -466,11 +404,6 @@ false, )?; - >::deposit_event(Event::::AllowListAddressRemoved( - collection_id, - address - )); - Ok(()) } @@ -486,20 +419,10 @@ /// * `new_owner`: ID of the account that will become the owner. #[weight = >::change_collection_owner()] pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult { - let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - + let new_owner = T::CrossAccountId::from_sub(new_owner); let mut target_collection = >::try_get(collection_id)?; - target_collection.check_is_internal()?; - target_collection.check_is_owner(&sender)?; - - target_collection.owner = new_owner.clone(); - >::deposit_event(Event::::CollectionOwnedChanged( - collection_id, - new_owner - )); - - target_collection.save() + target_collection.change_owner(sender, new_owner.clone()) } /// Add an admin to a collection. @@ -522,13 +445,6 @@ pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); let collection = >::try_get(collection_id)?; - collection.check_is_internal()?; - - >::deposit_event(Event::::CollectionAdminAdded( - collection_id, - new_admin_id.clone() - )); - >::toggle_admin(&collection, &sender, &new_admin_id, true) } @@ -550,13 +466,6 @@ pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); let collection = >::try_get(collection_id)?; - collection.check_is_internal()?; - - >::deposit_event(Event::::CollectionAdminRemoved( - collection_id, - account_id.clone() - )); - >::toggle_admin(&collection, &sender, &account_id, false) } @@ -576,19 +485,8 @@ #[weight = >::set_collection_sponsor()] pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let mut target_collection = >::try_get(collection_id)?; - target_collection.check_is_owner_or_admin(&sender)?; - target_collection.check_is_internal()?; - - target_collection.set_sponsor(new_sponsor.clone())?; - - >::deposit_event(Event::::CollectionSponsorSet( - collection_id, - new_sponsor - )); - - target_collection.save() + target_collection.set_sponsor(&sender, new_sponsor.clone()) } /// Confirm own sponsorship of a collection, becoming the sponsor. @@ -607,20 +505,8 @@ #[weight = >::confirm_sponsorship()] pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult { let sender = ensure_signed(origin)?; - let mut target_collection = >::try_get(collection_id)?; - target_collection.check_is_internal()?; - ensure!( - target_collection.confirm_sponsorship(&sender)?, - Error::::ConfirmUnsetSponsorFail - ); - - >::deposit_event(Event::::SponsorshipConfirmed( - collection_id, - sender - )); - - target_collection.save() + target_collection.confirm_sponsorship(&sender) } /// Remove a collection's a sponsor, making everyone pay for their own transactions. @@ -635,17 +521,8 @@ #[weight = >::remove_collection_sponsor()] pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); - let mut target_collection = >::try_get(collection_id)?; - target_collection.check_is_internal()?; - target_collection.check_is_owner(&sender)?; - - target_collection.sponsorship = SponsorshipState::Disabled; - - >::deposit_event(Event::::CollectionSponsorRemoved( - collection_id - )); - target_collection.save() + target_collection.remove_sponsor(&sender) } /// Mint an item within a collection. @@ -1053,17 +930,7 @@ ) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); let mut target_collection = >::try_get(collection_id)?; - target_collection.check_is_internal()?; - target_collection.check_is_owner_or_admin(&sender)?; - let old_limit = &target_collection.limits; - - target_collection.limits = >::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?; - - >::deposit_event(Event::::CollectionLimitSet( - collection_id - )); - - target_collection.save() + >::update_limits(&sender, &mut target_collection, new_limit) } /// Set specific permissions of a collection. Empty, or None fields mean chain default. @@ -1086,17 +953,11 @@ ) -> DispatchResult { let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); let mut target_collection = >::try_get(collection_id)?; - target_collection.check_is_internal()?; - target_collection.check_is_owner_or_admin(&sender)?; - let old_limit = &target_collection.permissions; - - target_collection.permissions = >::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?; - - >::deposit_event(Event::::CollectionPermissionSet( - collection_id - )); - - target_collection.save() + >::update_permissions( + &sender, + &mut target_collection, + new_permission + ) } /// Re-partition a refungible token, while owning all of its parts/pieces. @@ -1163,22 +1024,7 @@ /// * `collection_id`: ID of the modified collection. pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult { let mut target_collection = >::try_get(collection_id)?; - target_collection.check_is_internal()?; - target_collection.set_sponsor(sponsor.clone())?; - - Self::deposit_event(Event::::CollectionSponsorSet( - collection_id, - sponsor.clone(), - )); - - ensure!( - target_collection.confirm_sponsorship(&sponsor)?, - Error::::ConfirmUnsetSponsorFail - ); - - Self::deposit_event(Event::::SponsorshipConfirmed(collection_id, sponsor)); - - target_collection.save() + target_collection.force_set_sponsor(sponsor.clone()) } /// Force remove `sponsor` for `collection`. @@ -1191,12 +1037,7 @@ /// * `collection_id`: ID of the modified collection. pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult { let mut target_collection = >::try_get(collection_id)?; - target_collection.check_is_internal()?; - target_collection.sponsorship = SponsorshipState::Disabled; - - Self::deposit_event(Event::::CollectionSponsorRemoved(collection_id)); - - target_collection.save() + target_collection.force_remove_sponsor() } #[inline(always)] --- a/tests/src/change-collection-owner.test.ts +++ b/tests/src/change-collection-owner.test.ts @@ -146,7 +146,7 @@ const confirmSponsorshipTx = () => collection.confirmSponsorship(alice); const removeSponsorTx = () => collection.removeSponsor(alice); await expect(setSponsorTx()).to.be.rejectedWith(/common\.NoPermission/); - await expect(confirmSponsorshipTx()).to.be.rejectedWith(/unique\.ConfirmUnsetSponsorFail/); + await expect(confirmSponsorshipTx()).to.be.rejectedWith(/common\.ConfirmUnsetSponsorFail/); await expect(removeSponsorTx()).to.be.rejectedWith(/common\.NoPermission/); const limits = { --- a/tests/src/confirmSponsorship.test.ts +++ b/tests/src/confirmSponsorship.test.ts @@ -207,14 +207,14 @@ const collection = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}); await collection.setSponsor(alice, bob.address); const confirmSponsorshipTx = () => collection.confirmSponsorship(charlie); - await expect(confirmSponsorshipTx()).to.be.rejectedWith(/unique\.ConfirmUnsetSponsorFail/); + await expect(confirmSponsorshipTx()).to.be.rejectedWith(/common\.ConfirmUnsetSponsorFail/); }); itSub('(!negative test!) Confirm sponsorship using owner address', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}); await collection.setSponsor(alice, bob.address); const confirmSponsorshipTx = () => collection.confirmSponsorship(alice); - await expect(confirmSponsorshipTx()).to.be.rejectedWith(/unique\.ConfirmUnsetSponsorFail/); + await expect(confirmSponsorshipTx()).to.be.rejectedWith(/common\.ConfirmUnsetSponsorFail/); }); itSub('(!negative test!) Confirm sponsorship by collection admin', async ({helper}) => { @@ -222,13 +222,13 @@ await collection.setSponsor(alice, bob.address); await collection.addAdmin(alice, {Substrate: charlie.address}); const confirmSponsorshipTx = () => collection.confirmSponsorship(charlie); - await expect(confirmSponsorshipTx()).to.be.rejectedWith(/unique\.ConfirmUnsetSponsorFail/); + await expect(confirmSponsorshipTx()).to.be.rejectedWith(/common\.ConfirmUnsetSponsorFail/); }); itSub('(!negative test!) Confirm sponsorship without sponsor being set with setCollectionSponsor', async ({helper}) => { const collection = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}); const confirmSponsorshipTx = () => collection.confirmSponsorship(charlie); - await expect(confirmSponsorshipTx()).to.be.rejectedWith(/unique\.ConfirmUnsetSponsorFail/); + await expect(confirmSponsorshipTx()).to.be.rejectedWith(/common\.ConfirmUnsetSponsorFail/); }); itSub('(!negative test!) Confirm sponsorship in a collection that was destroyed', async ({helper}) => { --- a/tests/src/eth/collectionSponsoring.test.ts +++ b/tests/src/eth/collectionSponsoring.test.ts @@ -258,7 +258,7 @@ await collectionEvm.methods[testCase](testCase === 'setCollectionSponsor' ? sponsor : sponsorCross).send(); let collectionData = (await collectionSub.getData())!; expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); - await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor'); + await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); collectionData = (await collectionSub.getData())!; --- a/tests/src/eth/createFTCollection.test.ts +++ b/tests/src/eth/createFTCollection.test.ts @@ -46,7 +46,7 @@ let data = (await helper.rft.getData(collectionId))!; expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); - await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor'); + await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor, true); await sponsorCollection.methods.confirmCollectionSponsorship().send(); @@ -69,7 +69,7 @@ let data = (await helper.rft.getData(collectionId))!; expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); - await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor'); + await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor); await sponsorCollection.methods.confirmCollectionSponsorship().send(); @@ -192,11 +192,11 @@ const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'ft', sponsor, true); await expect(sponsorCollection.methods .confirmCollectionSponsorship() - .call()).to.be.rejectedWith('caller is not set as sponsor'); + .call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); } { await expect(peasantCollection.methods - .setCollectionLimit('account_token_ownership_limit', '1000') + .setCollectionLimit('accountTokenOwnershipLimit', '1000') .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); @@ -217,11 +217,11 @@ const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'ft', sponsor); await expect(sponsorCollection.methods .confirmCollectionSponsorship() - .call()).to.be.rejectedWith('caller is not set as sponsor'); + .call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); } { await expect(peasantCollection.methods - .setCollectionLimit('account_token_ownership_limit', '1000') + .setCollectionLimit('accountTokenOwnershipLimit', '1000') .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); --- a/tests/src/eth/createNFTCollection.test.ts +++ b/tests/src/eth/createNFTCollection.test.ts @@ -86,7 +86,7 @@ let data = (await helper.nft.getData(collectionId))!; expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); - await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor'); + await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor, true); await sponsorCollection.methods.confirmCollectionSponsorship().send(); @@ -109,7 +109,7 @@ let data = (await helper.nft.getData(collectionId))!; expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); - await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor'); + await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor); await sponsorCollection.methods.confirmCollectionSponsorship().send(); @@ -203,11 +203,11 @@ const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor, true); await expect(sponsorCollection.methods .confirmCollectionSponsorship() - .call()).to.be.rejectedWith('caller is not set as sponsor'); + .call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); } { await expect(malfeasantCollection.methods - .setCollectionLimit('account_token_ownership_limit', '1000') + .setCollectionLimit('accountTokenOwnershipLimit', '1000') .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); @@ -228,11 +228,11 @@ const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'nft', sponsor); await expect(sponsorCollection.methods .confirmCollectionSponsorship() - .call()).to.be.rejectedWith('caller is not set as sponsor'); + .call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); } { await expect(malfeasantCollection.methods - .setCollectionLimit('account_token_ownership_limit', '1000') + .setCollectionLimit('accountTokenOwnershipLimit', '1000') .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); --- a/tests/src/eth/createRFTCollection.test.ts +++ b/tests/src/eth/createRFTCollection.test.ts @@ -121,7 +121,7 @@ let data = (await helper.rft.getData(collectionId))!; expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); - await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor'); + await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor, true); await sponsorCollection.methods.confirmCollectionSponsorship().send(); @@ -143,7 +143,7 @@ let data = (await helper.rft.getData(collectionId))!; expect(data.raw.sponsorship.Unconfirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); - await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor'); + await expect(collection.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor); await sponsorCollection.methods.confirmCollectionSponsorship().send(); @@ -235,11 +235,11 @@ const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor, true); await expect(sponsorCollection.methods .confirmCollectionSponsorship() - .call()).to.be.rejectedWith('caller is not set as sponsor'); + .call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); } { await expect(peasantCollection.methods - .setCollectionLimit('account_token_ownership_limit', '1000') + .setCollectionLimit('accountTokenOwnershipLimit', '1000') .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); @@ -260,11 +260,11 @@ const sponsorCollection = helper.ethNativeContract.collection(collectionAddress, 'rft', sponsor); await expect(sponsorCollection.methods .confirmCollectionSponsorship() - .call()).to.be.rejectedWith('caller is not set as sponsor'); + .call()).to.be.rejectedWith('ConfirmUnsetSponsorFail'); } { await expect(peasantCollection.methods - .setCollectionLimit('account_token_ownership_limit', '1000') + .setCollectionLimit('accountTokenOwnershipLimit', '1000') .call()).to.be.rejectedWith(EXPECTED_ERROR); } }); --- a/tests/src/eth/events.test.ts +++ b/tests/src/eth/events.test.ts @@ -14,11 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . +import { expect } from 'chai'; import {IKeyringPair} from '@polkadot/types/types'; -import { expect } from 'chai'; import { itEth, usingEthPlaygrounds } from './util'; -describe.only('NFT events', () => { +describe('NFT events', () => { let donor: IKeyringPair; before(async function () { @@ -29,8 +29,9 @@ itEth('Create event', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress, events} = await helper.eth.createCollecion('createNFTCollection', owner, 'A', 'B', 'C'); - expect(events).to.be.like([ + const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['CollectionCreated']}]); + const {collectionAddress, events: ethEvents} = await helper.eth.createCollecion('createNFTCollection', owner, 'A', 'B', 'C'); + expect(ethEvents).to.be.like([ { event: 'CollectionCreated', args: { @@ -39,20 +40,25 @@ } } ]); + expect(subEvents).to.be.like([{method: 'CollectionCreated'}]); + unsubscribe(); }); itEth('Destroy event', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionAddress} = await helper.eth.createCollecion('createNFTCollection', owner, 'A', 'B', 'C'); const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); - let resutl = await collectionHelper.methods.destroyCollection(collectionAddress).send({from:owner}); - expect(resutl.events).to.be.like({ + const {unsubscribe, collectedEvents} = await helper.subscribeEvents([{section: 'common', names: ['CollectionDestroyed']}]); + let result = await collectionHelper.methods.destroyCollection(collectionAddress).send({from:owner}); + expect(result.events).to.be.like({ CollectionDestroyed: { returnValues: { collectionId: collectionAddress } } }); + expect(collectedEvents).to.be.like([{method: 'CollectionDestroyed'}]); + unsubscribe(); }); itEth('CollectionChanged event for CollectionPropertySet and CollectionPropertyDeleted', async ({helper}) => { @@ -61,13 +67,14 @@ const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); + let {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['CollectionPropertySet', 'CollectionPropertyDeleted']}]); { - const events: any = []; + const ethEvents: any = []; collectionHelper.events.allEvents((_: any, event: any) => { - events.push(event); + ethEvents.push(event); }); await collection.methods.setCollectionProperties([{key: 'A', value: [0,1,2,3]}]).send({from:owner}); - expect(events).to.be.like([ + expect(ethEvents).to.be.like([ { event: 'CollectionChanged', returnValues: { @@ -75,14 +82,16 @@ } } ]); + expect(subEvents).to.be.like([{method: 'CollectionPropertySet'}]); + subEvents.pop(); } { - const events: any = []; + const ethEvents: any = []; collectionHelper.events.allEvents((_: any, event: any) => { - events.push(event); + ethEvents.push(event); }); await collection.methods.deleteCollectionProperties(['A']).send({from:owner}); - expect(events).to.be.like([ + expect(ethEvents).to.be.like([ { event: 'CollectionChanged', returnValues: { @@ -90,8 +99,9 @@ } } ]); + expect(subEvents).to.be.like([{method: 'CollectionPropertyDeleted'}]); } - + unsubscribe(); }); itEth('CollectionChanged event for PropertyPermissionSet', async ({helper}) => { @@ -99,12 +109,13 @@ const {collectionAddress} = await helper.eth.createCollecion('createNFTCollection', owner, 'A', 'B', 'C'); const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); - const events: any = []; + const eethEvents: any = []; collectionHelper.events.allEvents((_: any, event: any) => { - events.push(event); + eethEvents.push(event); }); + const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['PropertyPermissionSet']}]); await collection.methods.setTokenPropertyPermission('testKey', true, true, true).send({from: owner}); - expect(events).to.be.like([ + expect(eethEvents).to.be.like([ { event: 'CollectionChanged', returnValues: { @@ -112,28 +123,236 @@ } } ]); + expect(subEvents).to.be.like([{method: 'PropertyPermissionSet'}]); + unsubscribe(); + }); + + itEth('CollectionChanged event for AllowListAddressAdded, AllowListAddressRemoved', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const user = helper.ethCrossAccount.createAccount(); + const {collectionAddress} = await helper.eth.createCollecion('createNFTCollection', owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); + const ethEvents: any[] = []; + collectionHelper.events.allEvents((_: any, event: any) => { + ethEvents.push(event); + }); + + const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['AllowListAddressAdded', 'AllowListAddressRemoved']}]); + { + await collection.methods.addToCollectionAllowListCross(user).send({from: owner}); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'AllowListAddressAdded'}]); + ethEvents.pop(); + subEvents.pop(); + } + { + await collection.methods.removeFromCollectionAllowListCross(user).send({from: owner}); + expect(ethEvents.length).to.be.eq(1); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'AllowListAddressRemoved'}]); + } + unsubscribe(); }); - // itEth('CollectionChanged event for AllowListAddressAdded', async ({helper}) => { - // const owner = await helper.eth.createAccountWithBalance(donor); - // const user = await helper.eth.createAccount(); - // const userCross = helper.ethCrossAccount.fromAddress(user); - // const {collectionId} = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}); - // // allow list does not need to be enabled to add someone in advance - // const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - // const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); - // const events: any = []; - // collectionHelper.events.allEvents((_: any, event: any) => { - // events.push(event); - // }); - // await helper.nft.addToAllowList(alice, collectionId, {Substrate: bob.address}); - // expect(events).to.be.like([ - // { - // event: 'CollectionChanged', - // returnValues: { - // collectionId: collectionAddress - // } - // } - // ]); - // }); + itEth('CollectionChanged event for CollectionAdminAdded, CollectionAdminRemoved', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const user = helper.ethCrossAccount.createAccount(); + const {collectionAddress} = await helper.eth.createCollecion('createNFTCollection', owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); + const ethEvents: any = []; + collectionHelper.events.allEvents((_: any, event: any) => { + ethEvents.push(event); + }); + const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['CollectionAdminAdded', 'CollectionAdminRemoved']}]); + { + await collection.methods.addCollectionAdminCross(user).send({from: owner}); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'CollectionAdminAdded'}]); + ethEvents.pop(); + subEvents.pop(); + } + { + await collection.methods.removeCollectionAdminCross(user).send({from: owner}); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'CollectionAdminRemoved'}]); + } + unsubscribe(); + }); + + itEth('CollectionChanged event for CollectionLimitSet', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createCollecion('createNFTCollection', owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); + const ethEvents: any = []; + collectionHelper.events.allEvents((_: any, event: any) => { + ethEvents.push(event); + }); + const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['CollectionLimitSet']}]); + { + await collection.methods.setCollectionLimit('ownerCanTransfer', 0n).send({from: owner}); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'CollectionLimitSet'}]); + } + unsubscribe(); + }); + + itEth('CollectionChanged event for CollectionOwnedChanged', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const new_owner = helper.ethCrossAccount.createAccount(); + const {collectionAddress} = await helper.eth.createCollecion('createNFTCollection', owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); + const ethEvents: any = []; + collectionHelper.events.allEvents((_: any, event: any) => { + ethEvents.push(event); + }); + const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['CollectionOwnedChanged']}]); + { + await collection.methods.changeCollectionOwnerCross(new_owner).send({from: owner}); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'CollectionOwnedChanged'}]); + } + unsubscribe(); + }); + + itEth('CollectionChanged event for CollectionPermissionSet', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createCollecion('createNFTCollection', owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); + const ethEvents: any = []; + collectionHelper.events.allEvents((_: any, event: any) => { + ethEvents.push(event); + }); + const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['CollectionPermissionSet']}]); + { + await collection.methods.setCollectionMintMode(true).send({from: owner}); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'CollectionPermissionSet'}]); + ethEvents.pop(); + subEvents.pop(); + } + { + await collection.methods.setCollectionAccess(1).send({from: owner}); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'CollectionPermissionSet'}]); + } + unsubscribe(); + }); + + itEth('CollectionChanged event for CollectionSponsorSet, SponsorshipConfirmed, CollectionSponsorRemoved', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.ethCrossAccount.createAccountWithBalance(donor); + const {collectionAddress} = await helper.eth.createCollecion('createNFTCollection', owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); + const ethEvents: any = []; + collectionHelper.events.allEvents((_: any, event: any) => { + ethEvents.push(event); + }); + const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{ + section: 'common', names: ['CollectionSponsorSet', 'SponsorshipConfirmed', 'CollectionSponsorRemoved' + ]}]); + { + await collection.methods.setCollectionSponsorCross(sponsor).send({from: owner}); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'CollectionSponsorSet'}]); + ethEvents.pop(); + subEvents.pop(); + } + { + await collection.methods.confirmCollectionSponsorship().send({from: sponsor.eth}); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'SponsorshipConfirmed'}]); + ethEvents.pop(); + subEvents.pop(); + } + { + await collection.methods.removeCollectionSponsor().send({from: owner}); + expect(ethEvents).to.be.like([ + { + event: 'CollectionChanged', + returnValues: { + collectionId: collectionAddress + } + } + ]); + expect(subEvents).to.be.like([{method: 'CollectionSponsorRemoved'}]); + } + unsubscribe(); + }); + }); \ No newline at end of file --- a/tests/src/removeCollectionAdmin.test.ts +++ b/tests/src/removeCollectionAdmin.test.ts @@ -51,7 +51,7 @@ const adminListBeforeAddAdmin = await collection.getAdmins(); expect(adminListBeforeAddAdmin).to.have.lengthOf(0); - await collection.removeAdmin(alice, {Substrate: alice.address}); + await expect(collection.removeAdmin(alice, {Substrate: alice.address})).to.be.rejectedWith('common.UserIsNotAdmin'); }); }); --- a/tests/src/removeCollectionSponsor.test.ts +++ b/tests/src/removeCollectionSponsor.test.ts @@ -112,7 +112,7 @@ const collection = await helper.nft.mintCollection(alice, {name: 'RemoveCollectionSponsor-Neg-4', tokenPrefix: 'RCS'}); await collection.setSponsor(alice, bob.address); await collection.removeSponsor(alice); - await expect(collection.confirmSponsorship(bob)).to.be.rejectedWith(/unique\.ConfirmUnsetSponsorFail/); + await expect(collection.confirmSponsorship(bob)).to.be.rejectedWith(/common\.ConfirmUnsetSponsorFail/); }); itSub('Set - confirm - remove - confirm: Sponsor cannot come back', async ({helper}) => { @@ -120,6 +120,6 @@ await collection.setSponsor(alice, bob.address); await collection.confirmSponsorship(bob); await collection.removeSponsor(alice); - await expect(collection.confirmSponsorship(bob)).to.be.rejectedWith(/unique\.ConfirmUnsetSponsorFail/); + await expect(collection.confirmSponsorship(bob)).to.be.rejectedWith(/common\.ConfirmUnsetSponsorFail/); }); }); --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -43,6 +43,8 @@ IEthCrossAccountId, } from './types'; import {RuntimeDispatchInfo} from '@polkadot/types/interfaces'; +import type {Vec} from '@polkadot/types-codec'; +import { FrameSystemEventRecord } from '@polkadot/types/lookup'; export class CrossAccountId implements ICrossAccountId { Substrate?: TSubstrateAccount; @@ -404,6 +406,21 @@ return this.api; } + async subscribeEvents(expectedEvents: {section: string, names: string[]}[]) { + const collectedEvents: IEvent[] = []; + const unsubscribe = await this.getApi().query.system.events((events: Vec) => { + const ievents = this.eventHelper.extractEvents(events); + ievents.forEach((event) => { + expectedEvents.forEach((e => { + if (event.section === e.section && e.names.includes(event.method)) { + collectedEvents.push(event); + } + })) + }); + }); + return {unsubscribe: unsubscribe as any, collectedEvents}; +} + clearChainLog(): void { this.chainLog = []; } @@ -834,7 +851,7 @@ true, ); - return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorSet'); + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorSet'); } /** @@ -852,7 +869,7 @@ true, ); - return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'SponsorshipConfirmed'); + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'SponsorshipConfirmed'); } /** @@ -870,7 +887,7 @@ true, ); - return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionSponsorRemoved'); + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionSponsorRemoved'); } /** @@ -897,7 +914,7 @@ true, ); - return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionLimitSet'); + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionLimitSet'); } /** @@ -916,7 +933,7 @@ true, ); - return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionOwnedChanged'); + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionOwnedChanged'); } /** @@ -935,7 +952,7 @@ true, ); - return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminAdded'); + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminAdded'); } /** @@ -954,7 +971,7 @@ true, ); - return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionAdminRemoved'); + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionAdminRemoved'); } /** @@ -983,7 +1000,7 @@ true, ); - return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressAdded'); + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressAdded'); } /** @@ -1001,7 +1018,7 @@ true, ); - return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'AllowListAddressRemoved'); + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'AllowListAddressRemoved'); } /** @@ -1020,7 +1037,7 @@ true, ); - return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'unique', 'CollectionPermissionSet'); + return this.helper.util.findCollectionInEvents(result.result.events, collectionId, 'common', 'CollectionPermissionSet'); } /**