difftreelog
Merge pull request #250 from UniqueNetwork/feature/CORE-221
in: master
CORE-221
3 files changed
pallets/common/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::{Deref, DerefMut};4use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};5use sp_std::vec::Vec;6use account::CrossAccountId;7use frame_support::{8 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},9 ensure, fail,10 traits::{Imbalance, Get, Currency},11};12use pallet_evm::GasWeightMapping;13use up_data_structs::{14 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,15 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,16 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight,17 WithdrawReasons, CollectionStats,18};19pub use pallet::*;20use sp_core::H160;21use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};22pub mod account;23#[cfg(feature = "runtime-benchmarks")]24pub mod benchmarking;25pub mod erc;26pub mod eth;2728#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]29pub struct CollectionHandle<T: Config> {30 pub id: CollectionId,31 collection: Collection<T::AccountId>,32 pub recorder: SubstrateRecorder<T>,33}34impl<T: Config> WithRecorder<T> for CollectionHandle<T> {35 fn recorder(&self) -> &SubstrateRecorder<T> {36 &self.recorder37 }38 fn into_recorder(self) -> SubstrateRecorder<T> {39 self.recorder40 }41}42impl<T: Config> CollectionHandle<T> {43 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {44 <CollectionById<T>>::get(id).map(|collection| Self {45 id,46 collection,47 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),48 })49 }50 pub fn new(id: CollectionId) -> Option<Self> {51 Self::new_with_gas_limit(id, u64::MAX)52 }53 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {54 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)55 }56 pub fn log(&self, log: impl evm_coder::ToLog) {57 self.recorder.log(log)58 }59 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {60 self.recorder61 .consume_gas(T::GasWeightMapping::weight_to_gas(62 <T as frame_system::Config>::DbWeight::get()63 .read64 .saturating_mul(reads),65 ))66 }67 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {68 self.recorder69 .consume_gas(T::GasWeightMapping::weight_to_gas(70 <T as frame_system::Config>::DbWeight::get()71 .write72 .saturating_mul(writes),73 ))74 }75 pub fn submit_logs(self) {76 self.recorder.submit_logs()77 }78 pub fn save(self) -> DispatchResult {79 self.recorder.submit_logs();80 <CollectionById<T>>::insert(self.id, self.collection);81 Ok(())82 }83}84impl<T: Config> Deref for CollectionHandle<T> {85 type Target = Collection<T::AccountId>;8687 fn deref(&self) -> &Self::Target {88 &self.collection89 }90}9192impl<T: Config> DerefMut for CollectionHandle<T> {93 fn deref_mut(&mut self) -> &mut Self::Target {94 &mut self.collection95 }96}9798impl<T: Config> CollectionHandle<T> {99 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {100 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);101 Ok(())102 }103 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {104 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))105 }106 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {107 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);108 Ok(())109 }110 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {111 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)112 }113 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {114 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)115 }116 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {117 ensure!(118 <Allowlist<T>>::get((self.id, user)),119 <Error<T>>::AddressNotInAllowlist120 );121 Ok(())122 }123124 pub fn check_can_update_meta(125 &self,126 subject: &T::CrossAccountId,127 item_owner: &T::CrossAccountId,128 ) -> DispatchResult {129 match self.meta_update_permission {130 MetaUpdatePermission::ItemOwner => {131 ensure!(subject == item_owner, <Error<T>>::NoPermission);132 Ok(())133 }134 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),135 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),136 }137 }138}139140#[frame_support::pallet]141pub mod pallet {142 use super::*;143 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};144 use account::CrossAccountId;145 use frame_support::traits::Currency;146 use up_data_structs::TokenId;147 use scale_info::TypeInfo;148149 #[pallet::config]150 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {151 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;152153 type CrossAccountId: CrossAccountId<Self::AccountId>;154155 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;156 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;157158 type Currency: Currency<Self::AccountId>;159 type CollectionCreationPrice: Get<160 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,161 >;162 type TreasuryAccountId: Get<Self::AccountId>;163 }164165 #[pallet::pallet]166 #[pallet::generate_store(pub(super) trait Store)]167 pub struct Pallet<T>(_);168169 #[pallet::extra_constants]170 impl<T: Config> Pallet<T> {171 pub fn collection_admins_limit() -> u32 {172 COLLECTION_ADMINS_LIMIT173 }174 }175176 #[pallet::event]177 #[pallet::generate_deposit(pub fn deposit_event)]178 pub enum Event<T: Config> {179 /// New collection was created180 ///181 /// # Arguments182 ///183 /// * collection_id: Globally unique identifier of newly created collection.184 ///185 /// * mode: [CollectionMode] converted into u8.186 ///187 /// * account_id: Collection owner.188 CollectionCreated(CollectionId, u8, T::AccountId),189190 /// New item was created.191 ///192 /// # Arguments193 ///194 /// * collection_id: Id of the collection where item was created.195 ///196 /// * item_id: Id of an item. Unique within the collection.197 ///198 /// * recipient: Owner of newly created item199 ///200 /// * amount: Always 1 for NFT201 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),202203 /// Collection item was burned.204 ///205 /// # Arguments206 ///207 /// * collection_id.208 ///209 /// * item_id: Identifier of burned NFT.210 ///211 /// * owner: which user has destroyed its tokens212 ///213 /// * amount: Always 1 for NFT214 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),215216 /// Item was transferred217 ///218 /// * collection_id: Id of collection to which item is belong219 ///220 /// * item_id: Id of an item221 ///222 /// * sender: Original owner of item223 ///224 /// * recipient: New owner of item225 ///226 /// * amount: Always 1 for NFT227 Transfer(228 CollectionId,229 TokenId,230 T::CrossAccountId,231 T::CrossAccountId,232 u128,233 ),234235 /// * collection_id236 ///237 /// * item_id238 ///239 /// * sender240 ///241 /// * spender242 ///243 /// * amount244 Approved(245 CollectionId,246 TokenId,247 T::CrossAccountId,248 T::CrossAccountId,249 u128,250 ),251 }252253 #[pallet::error]254 pub enum Error<T> {255 /// This collection does not exist.256 CollectionNotFound,257 /// Sender parameter and item owner must be equal.258 MustBeTokenOwner,259 /// No permission to perform action260 NoPermission,261 /// Collection is not in mint mode.262 PublicMintingNotAllowed,263 /// Address is not in allow list.264 AddressNotInAllowlist,265266 /// Collection name can not be longer than 63 char.267 CollectionNameLimitExceeded,268 /// Collection description can not be longer than 255 char.269 CollectionDescriptionLimitExceeded,270 /// Token prefix can not be longer than 15 char.271 CollectionTokenPrefixLimitExceeded,272 /// Total collections bound exceeded.273 TotalCollectionsLimitExceeded,274 /// variable_data exceeded data limit.275 TokenVariableDataLimitExceeded,276 /// Exceeded max admin count277 CollectionAdminCountExceeded,278279 /// Collection settings not allowing items transferring280 TransferNotAllowed,281 /// Account token limit exceeded per collection282 AccountTokenLimitExceeded,283 /// Collection token limit exceeded284 CollectionTokenLimitExceeded,285 /// Metadata flag frozen286 MetadataFlagFrozen,287288 /// Item not exists.289 TokenNotFound,290 /// Item balance not enough.291 TokenValueTooLow,292 /// Requested value more than approved.293 TokenValueNotEnough,294 /// Tried to approve more than owned295 CantApproveMoreThanOwned,296297 /// Can't transfer tokens to ethereum zero address298 AddressIsZero,299 /// Target collection doesn't supports this operation300 UnsupportedOperation,301 }302303 #[pallet::storage]304 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;305 #[pallet::storage]306 pub type DestroyedCollectionCount<T> =307 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;308309 /// Collection info310 #[pallet::storage]311 pub type CollectionById<T> = StorageMap<312 Hasher = Blake2_128Concat,313 Key = CollectionId,314 Value = Collection<<T as frame_system::Config>::AccountId>,315 QueryKind = OptionQuery,316 >;317318 #[pallet::storage]319 pub type AdminAmount<T> = StorageMap<320 Hasher = Blake2_128Concat,321 Key = CollectionId,322 Value = u32,323 QueryKind = ValueQuery,324 >;325326 /// List of collection admins327 #[pallet::storage]328 pub type IsAdmin<T: Config> = StorageNMap<329 Key = (330 Key<Blake2_128Concat, CollectionId>,331 Key<Blake2_128Concat, T::CrossAccountId>,332 ),333 Value = bool,334 QueryKind = ValueQuery,335 >;336337 /// Allowlisted collection users338 #[pallet::storage]339 pub type Allowlist<T: Config> = StorageNMap<340 Key = (341 Key<Blake2_128Concat, CollectionId>,342 Key<Blake2_128Concat, T::CrossAccountId>,343 ),344 Value = bool,345 QueryKind = ValueQuery,346 >;347348 /// Not used by code, exists only to provide some types to metadata349 #[pallet::storage]350 pub type DummyStorageValue<T> =351 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;352}353354impl<T: Config> Pallet<T> {355 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens356 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {357 ensure!(358 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,359 <Error<T>>::AddressIsZero360 );361 Ok(())362 }363 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {364 <IsAdmin<T>>::iter_prefix((collection,))365 .map(|(a, _)| a)366 .collect()367 }368 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {369 <Allowlist<T>>::iter_prefix((collection,))370 .map(|(a, _)| a)371 .collect()372 }373 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {374 <Allowlist<T>>::get((collection, user))375 }376 pub fn collection_stats() -> CollectionStats {377 let created = <CreatedCollectionCount<T>>::get();378 let destroyed = <DestroyedCollectionCount<T>>::get();379 CollectionStats {380 created: created.0,381 destroyed: destroyed.0,382 alive: created.0 - destroyed.0,383 }384 }385}386387impl<T: Config> Pallet<T> {388 pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {389 {390 ensure!(391 data.name.len() <= MAX_COLLECTION_NAME_LENGTH,392 Error::<T>::CollectionNameLimitExceeded393 );394 ensure!(395 data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,396 Error::<T>::CollectionDescriptionLimitExceeded397 );398 ensure!(399 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,400 Error::<T>::CollectionTokenPrefixLimitExceeded401 );402 }403404 let created_count = <CreatedCollectionCount<T>>::get()405 .0406 .checked_add(1)407 .ok_or(ArithmeticError::Overflow)?;408 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;409 let id = CollectionId(created_count);410411 // bound Total number of collections412 ensure!(413 created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,414 <Error<T>>::TotalCollectionsLimitExceeded415 );416417 // =========418419 // Take a (non-refundable) deposit of collection creation420 {421 let mut imbalance =422 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();423 imbalance.subsume(424 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(425 &T::TreasuryAccountId::get(),426 T::CollectionCreationPrice::get(),427 ),428 );429 <T as Config>::Currency::settle(430 &data.owner,431 imbalance,432 WithdrawReasons::TRANSFER,433 ExistenceRequirement::KeepAlive,434 )435 .map_err(|_| Error::<T>::NoPermission)?;436 }437438 <CreatedCollectionCount<T>>::put(created_count);439 <Pallet<T>>::deposit_event(Event::CollectionCreated(440 id,441 data.mode.id(),442 data.owner.clone(),443 ));444 <CollectionById<T>>::insert(id, data);445 Ok(id)446 }447448 pub fn destroy_collection(449 collection: CollectionHandle<T>,450 sender: &T::CrossAccountId,451 ) -> DispatchResult {452 ensure!(453 collection.limits.owner_can_destroy(),454 <Error<T>>::NoPermission,455 );456 collection.check_is_owner(sender)?;457458 let destroyed_collections = <DestroyedCollectionCount<T>>::get()459 .0460 .checked_add(1)461 .ok_or(ArithmeticError::Overflow)?;462463 // =========464465 <DestroyedCollectionCount<T>>::put(destroyed_collections);466 <CollectionById<T>>::remove(collection.id);467 <AdminAmount<T>>::remove(collection.id);468 <IsAdmin<T>>::remove_prefix((collection.id,), None);469 <Allowlist<T>>::remove_prefix((collection.id,), None);470 Ok(())471 }472473 pub fn toggle_allowlist(474 collection: &CollectionHandle<T>,475 sender: &T::CrossAccountId,476 user: &T::CrossAccountId,477 allowed: bool,478 ) -> DispatchResult {479 collection.check_is_owner_or_admin(sender)?;480481 // =========482483 if allowed {484 <Allowlist<T>>::insert((collection.id, user), true);485 } else {486 <Allowlist<T>>::remove((collection.id, user));487 }488489 Ok(())490 }491492 pub fn toggle_admin(493 collection: &CollectionHandle<T>,494 sender: &T::CrossAccountId,495 user: &T::CrossAccountId,496 admin: bool,497 ) -> DispatchResult {498 collection.check_is_owner_or_admin(sender)?;499500 let was_admin = <IsAdmin<T>>::get((collection.id, user));501 if was_admin == admin {502 return Ok(());503 }504 let amount = <AdminAmount<T>>::get(collection.id);505506 if admin {507 let amount = amount508 .checked_add(1)509 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;510 ensure!(511 amount <= Self::collection_admins_limit(),512 <Error<T>>::CollectionAdminCountExceeded,513 );514515 // =========516517 <AdminAmount<T>>::insert(collection.id, amount);518 <IsAdmin<T>>::insert((collection.id, user), true);519 } else {520 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));521 <IsAdmin<T>>::remove((collection.id, user));522 }523524 Ok(())525 }526}527528#[macro_export]529macro_rules! unsupported {530 () => {531 Err(<Error<T>>::UnsupportedOperation.into())532 };533}534535/// Worst cases536pub trait CommonWeightInfo {537 fn create_item() -> Weight;538 fn create_multiple_items(amount: u32) -> Weight;539 fn burn_item() -> Weight;540 fn transfer() -> Weight;541 fn approve() -> Weight;542 fn transfer_from() -> Weight;543 fn burn_from() -> Weight;544 fn set_variable_metadata(bytes: u32) -> Weight;545}546547pub trait CommonCollectionOperations<T: Config> {548 fn create_item(549 &self,550 sender: T::CrossAccountId,551 to: T::CrossAccountId,552 data: CreateItemData,553 ) -> DispatchResultWithPostInfo;554 fn create_multiple_items(555 &self,556 sender: T::CrossAccountId,557 to: T::CrossAccountId,558 data: Vec<CreateItemData>,559 ) -> DispatchResultWithPostInfo;560 fn burn_item(561 &self,562 sender: T::CrossAccountId,563 token: TokenId,564 amount: u128,565 ) -> DispatchResultWithPostInfo;566567 fn transfer(568 &self,569 sender: T::CrossAccountId,570 to: T::CrossAccountId,571 token: TokenId,572 amount: u128,573 ) -> DispatchResultWithPostInfo;574 fn approve(575 &self,576 sender: T::CrossAccountId,577 spender: T::CrossAccountId,578 token: TokenId,579 amount: u128,580 ) -> DispatchResultWithPostInfo;581 fn transfer_from(582 &self,583 sender: T::CrossAccountId,584 from: T::CrossAccountId,585 to: T::CrossAccountId,586 token: TokenId,587 amount: u128,588 ) -> DispatchResultWithPostInfo;589 fn burn_from(590 &self,591 sender: T::CrossAccountId,592 from: T::CrossAccountId,593 token: TokenId,594 amount: u128,595 ) -> DispatchResultWithPostInfo;596597 fn set_variable_metadata(598 &self,599 sender: T::CrossAccountId,600 token: TokenId,601 data: Vec<u8>,602 ) -> DispatchResultWithPostInfo;603604 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;605 fn token_exists(&self, token: TokenId) -> bool;606 fn last_token_id(&self) -> TokenId;607608 fn token_owner(&self, token: TokenId) -> T::CrossAccountId;609 fn const_metadata(&self, token: TokenId) -> Vec<u8>;610 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;611612 /// How many tokens collection contains (Applicable to nonfungible/refungible)613 fn collection_tokens(&self) -> u32;614 /// Amount of different tokens account has (Applicable to nonfungible/refungible)615 fn account_balance(&self, account: T::CrossAccountId) -> u32;616 /// Amount of specific token account have (Applicable to fungible/refungible)617 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;618 fn allowance(619 &self,620 sender: T::CrossAccountId,621 spender: T::CrossAccountId,622 token: TokenId,623 ) -> u128;624}625626// Flexible enough for implementing CommonCollectionOperations627pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {628 let post_info = PostDispatchInfo {629 actual_weight: Some(weight),630 pays_fee: Pays::Yes,631 };632 match res {633 Ok(()) => Ok(post_info),634 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),635 }636}1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::{Deref, DerefMut};4use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};5use sp_std::vec::Vec;6use account::CrossAccountId;7use frame_support::{8 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},9 ensure, fail,10 traits::{Imbalance, Get, Currency},11};12use pallet_evm::GasWeightMapping;13use up_data_structs::{14 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,15 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,16 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight,17 WithdrawReasons, CollectionStats,18};19pub use pallet::*;20use sp_core::H160;21use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};22pub mod account;23#[cfg(feature = "runtime-benchmarks")]24pub mod benchmarking;25pub mod erc;26pub mod eth;2728#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]29pub struct CollectionHandle<T: Config> {30 pub id: CollectionId,31 collection: Collection<T::AccountId>,32 pub recorder: SubstrateRecorder<T>,33}34impl<T: Config> WithRecorder<T> for CollectionHandle<T> {35 fn recorder(&self) -> &SubstrateRecorder<T> {36 &self.recorder37 }38 fn into_recorder(self) -> SubstrateRecorder<T> {39 self.recorder40 }41}42impl<T: Config> CollectionHandle<T> {43 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {44 <CollectionById<T>>::get(id).map(|collection| Self {45 id,46 collection,47 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),48 })49 }50 pub fn new(id: CollectionId) -> Option<Self> {51 Self::new_with_gas_limit(id, u64::MAX)52 }53 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {54 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)55 }56 pub fn log(&self, log: impl evm_coder::ToLog) {57 self.recorder.log(log)58 }59 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {60 self.recorder61 .consume_gas(T::GasWeightMapping::weight_to_gas(62 <T as frame_system::Config>::DbWeight::get()63 .read64 .saturating_mul(reads),65 ))66 }67 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {68 self.recorder69 .consume_gas(T::GasWeightMapping::weight_to_gas(70 <T as frame_system::Config>::DbWeight::get()71 .write72 .saturating_mul(writes),73 ))74 }75 pub fn submit_logs(self) {76 self.recorder.submit_logs()77 }78 pub fn save(self) -> DispatchResult {79 self.recorder.submit_logs();80 <CollectionById<T>>::insert(self.id, self.collection);81 Ok(())82 }83}84impl<T: Config> Deref for CollectionHandle<T> {85 type Target = Collection<T::AccountId>;8687 fn deref(&self) -> &Self::Target {88 &self.collection89 }90}9192impl<T: Config> DerefMut for CollectionHandle<T> {93 fn deref_mut(&mut self) -> &mut Self::Target {94 &mut self.collection95 }96}9798impl<T: Config> CollectionHandle<T> {99 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {100 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);101 Ok(())102 }103 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {104 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))105 }106 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {107 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);108 Ok(())109 }110 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {111 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)112 }113 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {114 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)115 }116 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {117 ensure!(118 <Allowlist<T>>::get((self.id, user)),119 <Error<T>>::AddressNotInAllowlist120 );121 Ok(())122 }123124 pub fn check_can_update_meta(125 &self,126 subject: &T::CrossAccountId,127 item_owner: &T::CrossAccountId,128 ) -> DispatchResult {129 match self.meta_update_permission {130 MetaUpdatePermission::ItemOwner => {131 ensure!(subject == item_owner, <Error<T>>::NoPermission);132 Ok(())133 }134 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),135 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),136 }137 }138}139140#[frame_support::pallet]141pub mod pallet {142 use super::*;143 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};144 use account::CrossAccountId;145 use frame_support::traits::Currency;146 use up_data_structs::TokenId;147 use scale_info::TypeInfo;148149 #[pallet::config]150 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {151 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;152153 type CrossAccountId: CrossAccountId<Self::AccountId>;154155 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;156 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;157158 type Currency: Currency<Self::AccountId>;159 type CollectionCreationPrice: Get<160 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,161 >;162 type TreasuryAccountId: Get<Self::AccountId>;163 }164165 #[pallet::pallet]166 #[pallet::generate_store(pub(super) trait Store)]167 pub struct Pallet<T>(_);168169 #[pallet::extra_constants]170 impl<T: Config> Pallet<T> {171 pub fn collection_admins_limit() -> u32 {172 COLLECTION_ADMINS_LIMIT173 }174 }175176 #[pallet::event]177 #[pallet::generate_deposit(pub fn deposit_event)]178 pub enum Event<T: Config> {179 /// New collection was created180 ///181 /// # Arguments182 ///183 /// * collection_id: Globally unique identifier of newly created collection.184 ///185 /// * mode: [CollectionMode] converted into u8.186 ///187 /// * account_id: Collection owner.188 CollectionCreated(CollectionId, u8, T::AccountId),189190 /// New collection was destroyed191 ///192 /// # Arguments193 ///194 /// * collection_id: Globally unique identifier of collection.195 CollectionDestroyed(CollectionId),196197 /// New item was created.198 ///199 /// # Arguments200 ///201 /// * collection_id: Id of the collection where item was created.202 ///203 /// * item_id: Id of an item. Unique within the collection.204 ///205 /// * recipient: Owner of newly created item206 ///207 /// * amount: Always 1 for NFT208 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),209210 /// Collection item was burned.211 ///212 /// # Arguments213 ///214 /// * collection_id.215 ///216 /// * item_id: Identifier of burned NFT.217 ///218 /// * owner: which user has destroyed its tokens219 ///220 /// * amount: Always 1 for NFT221 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),222223 /// Item was transferred224 ///225 /// * collection_id: Id of collection to which item is belong226 ///227 /// * item_id: Id of an item228 ///229 /// * sender: Original owner of item230 ///231 /// * recipient: New owner of item232 ///233 /// * amount: Always 1 for NFT234 Transfer(235 CollectionId,236 TokenId,237 T::CrossAccountId,238 T::CrossAccountId,239 u128,240 ),241242 /// * collection_id243 ///244 /// * item_id245 ///246 /// * sender247 ///248 /// * spender249 ///250 /// * amount251 Approved(252 CollectionId,253 TokenId,254 T::CrossAccountId,255 T::CrossAccountId,256 u128,257 ),258 }259260 #[pallet::error]261 pub enum Error<T> {262 /// This collection does not exist.263 CollectionNotFound,264 /// Sender parameter and item owner must be equal.265 MustBeTokenOwner,266 /// No permission to perform action267 NoPermission,268 /// Collection is not in mint mode.269 PublicMintingNotAllowed,270 /// Address is not in allow list.271 AddressNotInAllowlist,272273 /// Collection name can not be longer than 63 char.274 CollectionNameLimitExceeded,275 /// Collection description can not be longer than 255 char.276 CollectionDescriptionLimitExceeded,277 /// Token prefix can not be longer than 15 char.278 CollectionTokenPrefixLimitExceeded,279 /// Total collections bound exceeded.280 TotalCollectionsLimitExceeded,281 /// variable_data exceeded data limit.282 TokenVariableDataLimitExceeded,283 /// Exceeded max admin count284 CollectionAdminCountExceeded,285286 /// Collection settings not allowing items transferring287 TransferNotAllowed,288 /// Account token limit exceeded per collection289 AccountTokenLimitExceeded,290 /// Collection token limit exceeded291 CollectionTokenLimitExceeded,292 /// Metadata flag frozen293 MetadataFlagFrozen,294295 /// Item not exists.296 TokenNotFound,297 /// Item balance not enough.298 TokenValueTooLow,299 /// Requested value more than approved.300 TokenValueNotEnough,301 /// Tried to approve more than owned302 CantApproveMoreThanOwned,303304 /// Can't transfer tokens to ethereum zero address305 AddressIsZero,306 /// Target collection doesn't supports this operation307 UnsupportedOperation,308 }309310 #[pallet::storage]311 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;312 #[pallet::storage]313 pub type DestroyedCollectionCount<T> =314 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;315316 /// Collection info317 #[pallet::storage]318 pub type CollectionById<T> = StorageMap<319 Hasher = Blake2_128Concat,320 Key = CollectionId,321 Value = Collection<<T as frame_system::Config>::AccountId>,322 QueryKind = OptionQuery,323 >;324325 #[pallet::storage]326 pub type AdminAmount<T> = StorageMap<327 Hasher = Blake2_128Concat,328 Key = CollectionId,329 Value = u32,330 QueryKind = ValueQuery,331 >;332333 /// List of collection admins334 #[pallet::storage]335 pub type IsAdmin<T: Config> = StorageNMap<336 Key = (337 Key<Blake2_128Concat, CollectionId>,338 Key<Blake2_128Concat, T::CrossAccountId>,339 ),340 Value = bool,341 QueryKind = ValueQuery,342 >;343344 /// Allowlisted collection users345 #[pallet::storage]346 pub type Allowlist<T: Config> = StorageNMap<347 Key = (348 Key<Blake2_128Concat, CollectionId>,349 Key<Blake2_128Concat, T::CrossAccountId>,350 ),351 Value = bool,352 QueryKind = ValueQuery,353 >;354355 /// Not used by code, exists only to provide some types to metadata356 #[pallet::storage]357 pub type DummyStorageValue<T> =358 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;359}360361impl<T: Config> Pallet<T> {362 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens363 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {364 ensure!(365 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,366 <Error<T>>::AddressIsZero367 );368 Ok(())369 }370 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {371 <IsAdmin<T>>::iter_prefix((collection,))372 .map(|(a, _)| a)373 .collect()374 }375 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {376 <Allowlist<T>>::iter_prefix((collection,))377 .map(|(a, _)| a)378 .collect()379 }380 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {381 <Allowlist<T>>::get((collection, user))382 }383 pub fn collection_stats() -> CollectionStats {384 let created = <CreatedCollectionCount<T>>::get();385 let destroyed = <DestroyedCollectionCount<T>>::get();386 CollectionStats {387 created: created.0,388 destroyed: destroyed.0,389 alive: created.0 - destroyed.0,390 }391 }392}393394impl<T: Config> Pallet<T> {395 pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {396 {397 ensure!(398 data.name.len() <= MAX_COLLECTION_NAME_LENGTH,399 Error::<T>::CollectionNameLimitExceeded400 );401 ensure!(402 data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,403 Error::<T>::CollectionDescriptionLimitExceeded404 );405 ensure!(406 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,407 Error::<T>::CollectionTokenPrefixLimitExceeded408 );409 }410411 let created_count = <CreatedCollectionCount<T>>::get()412 .0413 .checked_add(1)414 .ok_or(ArithmeticError::Overflow)?;415 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;416 let id = CollectionId(created_count);417418 // bound Total number of collections419 ensure!(420 created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,421 <Error<T>>::TotalCollectionsLimitExceeded422 );423424 // =========425426 // Take a (non-refundable) deposit of collection creation427 {428 let mut imbalance =429 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();430 imbalance.subsume(431 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(432 &T::TreasuryAccountId::get(),433 T::CollectionCreationPrice::get(),434 ),435 );436 <T as Config>::Currency::settle(437 &data.owner,438 imbalance,439 WithdrawReasons::TRANSFER,440 ExistenceRequirement::KeepAlive,441 )442 .map_err(|_| Error::<T>::NoPermission)?;443 }444445 <CreatedCollectionCount<T>>::put(created_count);446 <Pallet<T>>::deposit_event(Event::CollectionCreated(447 id,448 data.mode.id(),449 data.owner.clone(),450 ));451 <CollectionById<T>>::insert(id, data);452 Ok(id)453 }454455 pub fn destroy_collection(456 collection: CollectionHandle<T>,457 sender: &T::CrossAccountId,458 ) -> DispatchResult {459 ensure!(460 collection.limits.owner_can_destroy(),461 <Error<T>>::NoPermission,462 );463 collection.check_is_owner(sender)?;464465 let destroyed_collections = <DestroyedCollectionCount<T>>::get()466 .0467 .checked_add(1)468 .ok_or(ArithmeticError::Overflow)?;469470 // =========471472 <DestroyedCollectionCount<T>>::put(destroyed_collections);473 <CollectionById<T>>::remove(collection.id);474 <AdminAmount<T>>::remove(collection.id);475 <IsAdmin<T>>::remove_prefix((collection.id,), None);476 <Allowlist<T>>::remove_prefix((collection.id,), None);477478 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));479 Ok(())480 }481482 pub fn toggle_allowlist(483 collection: &CollectionHandle<T>,484 sender: &T::CrossAccountId,485 user: &T::CrossAccountId,486 allowed: bool,487 ) -> DispatchResult {488 collection.check_is_owner_or_admin(sender)?;489490 // =========491492 if allowed {493 <Allowlist<T>>::insert((collection.id, user), true);494 } else {495 <Allowlist<T>>::remove((collection.id, user));496 }497498 Ok(())499 }500501 pub fn toggle_admin(502 collection: &CollectionHandle<T>,503 sender: &T::CrossAccountId,504 user: &T::CrossAccountId,505 admin: bool,506 ) -> DispatchResult {507 collection.check_is_owner_or_admin(sender)?;508509 let was_admin = <IsAdmin<T>>::get((collection.id, user));510 if was_admin == admin {511 return Ok(());512 }513 let amount = <AdminAmount<T>>::get(collection.id);514515 if admin {516 let amount = amount517 .checked_add(1)518 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;519 ensure!(520 amount <= Self::collection_admins_limit(),521 <Error<T>>::CollectionAdminCountExceeded,522 );523524 // =========525526 <AdminAmount<T>>::insert(collection.id, amount);527 <IsAdmin<T>>::insert((collection.id, user), true);528 } else {529 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));530 <IsAdmin<T>>::remove((collection.id, user));531 }532533 Ok(())534 }535}536537#[macro_export]538macro_rules! unsupported {539 () => {540 Err(<Error<T>>::UnsupportedOperation.into())541 };542}543544/// Worst cases545pub trait CommonWeightInfo {546 fn create_item() -> Weight;547 fn create_multiple_items(amount: u32) -> Weight;548 fn burn_item() -> Weight;549 fn transfer() -> Weight;550 fn approve() -> Weight;551 fn transfer_from() -> Weight;552 fn burn_from() -> Weight;553 fn set_variable_metadata(bytes: u32) -> Weight;554}555556pub trait CommonCollectionOperations<T: Config> {557 fn create_item(558 &self,559 sender: T::CrossAccountId,560 to: T::CrossAccountId,561 data: CreateItemData,562 ) -> DispatchResultWithPostInfo;563 fn create_multiple_items(564 &self,565 sender: T::CrossAccountId,566 to: T::CrossAccountId,567 data: Vec<CreateItemData>,568 ) -> DispatchResultWithPostInfo;569 fn burn_item(570 &self,571 sender: T::CrossAccountId,572 token: TokenId,573 amount: u128,574 ) -> DispatchResultWithPostInfo;575576 fn transfer(577 &self,578 sender: T::CrossAccountId,579 to: T::CrossAccountId,580 token: TokenId,581 amount: u128,582 ) -> DispatchResultWithPostInfo;583 fn approve(584 &self,585 sender: T::CrossAccountId,586 spender: T::CrossAccountId,587 token: TokenId,588 amount: u128,589 ) -> DispatchResultWithPostInfo;590 fn transfer_from(591 &self,592 sender: T::CrossAccountId,593 from: T::CrossAccountId,594 to: T::CrossAccountId,595 token: TokenId,596 amount: u128,597 ) -> DispatchResultWithPostInfo;598 fn burn_from(599 &self,600 sender: T::CrossAccountId,601 from: T::CrossAccountId,602 token: TokenId,603 amount: u128,604 ) -> DispatchResultWithPostInfo;605606 fn set_variable_metadata(607 &self,608 sender: T::CrossAccountId,609 token: TokenId,610 data: Vec<u8>,611 ) -> DispatchResultWithPostInfo;612613 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;614 fn token_exists(&self, token: TokenId) -> bool;615 fn last_token_id(&self) -> TokenId;616617 fn token_owner(&self, token: TokenId) -> T::CrossAccountId;618 fn const_metadata(&self, token: TokenId) -> Vec<u8>;619 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;620621 /// How many tokens collection contains (Applicable to nonfungible/refungible)622 fn collection_tokens(&self) -> u32;623 /// Amount of different tokens account has (Applicable to nonfungible/refungible)624 fn account_balance(&self, account: T::CrossAccountId) -> u32;625 /// Amount of specific token account have (Applicable to fungible/refungible)626 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;627 fn allowance(628 &self,629 sender: T::CrossAccountId,630 spender: T::CrossAccountId,631 token: TokenId,632 ) -> u128;633}634635// Flexible enough for implementing CommonCollectionOperations636pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {637 let post_info = PostDispatchInfo {638 actual_weight: Some(weight),639 pays_fee: Pays::Yes,640 };641 match res {642 Ok(()) => Ok(post_info),643 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),644 }645}pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -16,7 +16,7 @@
pub use serde::{Serialize, Deserialize};
pub use frame_support::{
- construct_runtime, decl_module, decl_storage, decl_error,
+ construct_runtime, decl_module, decl_storage, decl_error, decl_event,
dispatch::DispatchResult,
ensure, fail, parameter_types,
traits::{
@@ -87,6 +87,7 @@
OwnerPermissionsCantBeReverted,
}
}
+
pub trait Config:
system::Config
+ pallet_evm_coder_substrate::Config
@@ -97,10 +98,141 @@
+ Sized
+ TypeInfo
{
+ type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
+
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
+decl_event! {
+ pub enum Event<T>
+ where
+ <T as frame_system::Config>::AccountId,
+ <T as pallet_common::Config>::CrossAccountId,
+ {
+ /// Collection sponsor was removed
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ CollectionSponsorRemoved(CollectionId),
+
+ /// Collection admin was added
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ ///
+ /// * admin: Admin address.
+ CollectionAdminAdded(CollectionId, CrossAccountId),
+
+ /// Collection owned was change
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ ///
+ /// * owner: New owner address.
+ CollectionOwnedChanged(CollectionId, AccountId),
+
+ /// Collection sponsor was set
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ ///
+ /// * owner: New sponsor address.
+ CollectionSponsorSet(CollectionId, AccountId),
+
+ /// const on chain schema was set
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ ConstOnChainSchemaSet(CollectionId),
+
+ /// New sponsor was confirm
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ ///
+ /// * sponsor: New sponsor address.
+ SponsorshipConfirmed(CollectionId, AccountId),
+
+ /// Collection admin was removed
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ ///
+ /// * admin: Admin address.
+ CollectionAdminRemoved(CollectionId, CrossAccountId),
+
+ /// Address was remove from allow list
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ ///
+ /// * user: Address.
+ AllowListAddressRemoved(CollectionId, CrossAccountId),
+
+ /// Address was add to allow list
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ ///
+ /// * user: Address.
+ AllowListAddressAdded(CollectionId, CrossAccountId),
+
+ /// Collection limits was set
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ CollectionLimitSet(CollectionId),
+
+ /// Mint permission was set
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ MintPermissionSet(CollectionId),
+
+ /// Offchain schema was set
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ OffchainSchemaSet(CollectionId),
+
+ /// Public access mode was set
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ ///
+ /// * mode: New access state.
+ PublicAccessModeSet(CollectionId, AccessMode),
+
+ /// Schema version was set
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ SchemaVersionSet(CollectionId),
+
+ /// Variable on chain schema was set
+ ///
+ /// # Arguments
+ ///
+ /// * collection_id: Globally unique collection identifier.
+ VariableOnChainSchemaSet(CollectionId),
+ }
+}
+
type SelfWeightOf<T> = <T as Config>::WeightInfo;
// # Used definitions
@@ -162,6 +294,8 @@
{
type Error = Error<T>;
+ fn deposit_event() = default;
+
fn on_initialize(_now: T::BlockNumber) -> Weight {
0
}
@@ -289,6 +423,11 @@
true,
)?;
+ Self::deposit_event(Event::<T>::AllowListAddressAdded(
+ collection_id,
+ address
+ ));
+
Ok(())
}
@@ -318,6 +457,11 @@
false,
)?;
+ <Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(
+ collection_id,
+ address
+ ));
+
Ok(())
}
@@ -341,7 +485,13 @@
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.check_is_owner(&sender)?;
- target_collection.access = mode;
+ target_collection.access = mode.clone();
+
+ <Pallet<T>>::deposit_event(Event::<T>::PublicAccessModeSet(
+ collection_id,
+ mode
+ ));
+
target_collection.save()
}
@@ -368,6 +518,11 @@
target_collection.check_is_owner(&sender)?;
target_collection.mint_mode = mint_permission;
+
+ <Pallet<T>>::deposit_event(Event::<T>::MintPermissionSet(
+ collection_id
+ ));
+
target_collection.save()
}
@@ -391,7 +546,12 @@
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.check_is_owner(&sender)?;
- target_collection.owner = new_owner;
+ target_collection.owner = new_owner.clone();
+ <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(
+ collection_id,
+ new_owner
+ ));
+
target_collection.save()
}
@@ -414,6 +574,11 @@
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+ <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(
+ collection_id,
+ new_admin_id.clone()
+ ));
+
<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)
}
@@ -435,6 +600,11 @@
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+ <Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(
+ collection_id,
+ account_id.clone()
+ ));
+
<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)
}
@@ -455,7 +625,13 @@
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.check_is_owner(&sender)?;
- target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);
+ target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor.clone());
+
+ <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(
+ collection_id,
+ new_sponsor
+ ));
+
target_collection.save()
}
@@ -477,7 +653,13 @@
Error::<T>::ConfirmUnsetSponsorFail
);
- target_collection.sponsorship = SponsorshipState::Confirmed(sender);
+ target_collection.sponsorship = SponsorshipState::Confirmed(sender.clone());
+
+ <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(
+ collection_id,
+ sender
+ ));
+
target_collection.save()
}
@@ -499,6 +681,10 @@
target_collection.check_is_owner(&sender)?;
target_collection.sponsorship = SponsorshipState::Disabled;
+
+ <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(
+ collection_id
+ ));
target_collection.save()
}
@@ -793,6 +979,11 @@
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.check_is_owner_or_admin(&sender)?;
target_collection.schema_version = version;
+
+ <Pallet<T>>::deposit_event(Event::<T>::SchemaVersionSet(
+ collection_id
+ ));
+
target_collection.save()
}
@@ -823,6 +1014,11 @@
ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");
target_collection.offchain_schema = schema;
+
+ <Pallet<T>>::deposit_event(Event::<T>::OffchainSchemaSet(
+ collection_id
+ ));
+
target_collection.save()
}
@@ -853,6 +1049,11 @@
ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");
target_collection.const_on_chain_schema = schema;
+
+ <Pallet<T>>::deposit_event(Event::<T>::ConstOnChainSchemaSet(
+ collection_id
+ ));
+
target_collection.save()
}
@@ -883,6 +1084,11 @@
ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");
target_collection.variable_on_chain_schema = schema;
+
+ <Pallet<T>>::deposit_event(Event::<T>::VariableOnChainSchemaSet(
+ collection_id
+ ));
+
target_collection.save()
}
@@ -949,6 +1155,10 @@
target_collection.limits = new_limit;
+ <Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(
+ collection_id
+ ));
+
target_collection.save()
}
}
runtime/src/lib.rsdiffbeforeafterboth--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -781,6 +781,7 @@
}
impl pallet_unique::Config for Runtime {
+ type Event = Event;
type WeightInfo = pallet_unique::weights::SubstrateWeight<Self>;
}
@@ -881,7 +882,7 @@
// Unique Pallets
Inflation: pallet_inflation::{Pallet, Call, Storage} = 60,
- Unique: pallet_unique::{Pallet, Call, Storage} = 61,
+ Unique: pallet_unique::{Pallet, Call, Storage, Event<T>} = 61,
// Scheduler: pallet_unq_scheduler::{Pallet, Call, Storage, Event<T>} = 62,
// free = 63
Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64,