difftreelog
feat track and limit admin amount
in: master
3 files changed
pallets/common/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::{Deref, DerefMut};4use sp_std::vec::Vec;5use account::CrossAccountId;6use frame_support::{7 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},8 ensure, fail,9 traits::{Imbalance, Get, Currency},10};11use nft_data_structs::{12 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,13 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,14 MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight, WithdrawReasons,15};16pub use pallet::*;17use sp_core::H160;18use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};19pub mod account;20#[cfg(feature = "runtime-benchmarks")]21pub mod benchmarking;22pub mod erc;23pub mod eth;2425#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]26pub struct CollectionHandle<T: Config> {27 pub id: CollectionId,28 collection: Collection<T>,29 pub recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,30}31impl<T: Config> CollectionHandle<T> {32 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {33 <CollectionById<T>>::get(id).map(|collection| Self {34 id,35 collection,36 recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(37 eth::collection_id_to_address(id),38 gas_limit,39 ),40 })41 }42 pub fn new(id: CollectionId) -> Option<Self> {43 Self::new_with_gas_limit(id, u64::MAX)44 }45 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {46 Ok(Self::new(id).ok_or_else(|| <Error<T>>::CollectionNotFound)?)47 }48 pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {49 self.recorder.log_sub(log)50 }51 pub fn log_infallible(&self, log: impl evm_coder::ToLog) {52 self.recorder.log_infallible(log)53 }54 #[allow(dead_code)]55 fn consume_gas(&self, gas: u64) -> DispatchResult {56 self.recorder.consume_gas_sub(gas)57 }58 pub fn consume_sload(&self) -> DispatchResult {59 self.recorder.consume_sload_sub()60 }61 pub fn consume_sstores(&self, amount: usize) -> DispatchResult {62 self.recorder.consume_sstores_sub(amount)63 }64 pub fn consume_sstore(&self) -> DispatchResult {65 self.recorder.consume_sstore_sub()66 }67 pub fn consume_log(&self, topics: usize, data: usize) -> DispatchResult {68 self.recorder.consume_log_sub(topics, data)69 }70 pub fn submit_logs(self) -> DispatchResult {71 self.recorder.submit_logs()72 }73 pub fn save(self) -> DispatchResult {74 self.recorder.submit_logs()?;75 <CollectionById<T>>::insert(self.id, self.collection);76 Ok(())77 }78}79impl<T: Config> Deref for CollectionHandle<T> {80 type Target = Collection<T>;8182 fn deref(&self) -> &Self::Target {83 &self.collection84 }85}8687impl<T: Config> DerefMut for CollectionHandle<T> {88 fn deref_mut(&mut self) -> &mut Self::Target {89 &mut self.collection90 }91}9293impl<T: Config> CollectionHandle<T> {94 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {95 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);96 Ok(())97 }98 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> Result<bool, DispatchError> {99 self.consume_sload()?;100101 Ok(*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject)))102 }103 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {104 ensure!(self.is_owner_or_admin(subject)?, <Error<T>>::NoPermission);105 Ok(())106 }107 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {108 Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)109 }110 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {111 Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)112 }113 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {114 self.consume_sload()?;115116 ensure!(117 <Allowlist<T>>::get((self.id, user)),118 <Error<T>>::AddressNotInAllowlist119 );120 Ok(())121 }122123 pub fn check_can_update_meta(124 &self,125 subject: &T::CrossAccountId,126 item_owner: &T::CrossAccountId,127 ) -> DispatchResult {128 match self.meta_update_permission {129 MetaUpdatePermission::ItemOwner => {130 ensure!(subject == item_owner, <Error<T>>::NoPermission);131 Ok(())132 }133 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),134 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),135 }136 }137}138139#[frame_support::pallet]140pub mod pallet {141 use super::*;142 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};143 use account::{EvmBackwardsAddressMapping, CrossAccountId};144 use frame_support::traits::Currency;145 use nft_data_structs::TokenId;146 use scale_info::TypeInfo;147148 #[pallet::config]149 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {150 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;151152 type CrossAccountId: CrossAccountId<Self::AccountId>;153154 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;155 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;156157 type Currency: Currency<Self::AccountId>;158 type CollectionCreationPrice: Get<159 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,160 >;161 type TreasuryAccountId: Get<Self::AccountId>;162 }163164 #[pallet::pallet]165 #[pallet::generate_store(pub(super) trait Store)]166 pub struct Pallet<T>(_);167168 #[pallet::event]169 #[pallet::generate_deposit(pub fn deposit_event)]170 pub enum Event<T: Config> {171 /// New collection was created172 ///173 /// # Arguments174 ///175 /// * collection_id: Globally unique identifier of newly created collection.176 ///177 /// * mode: [CollectionMode] converted into u8.178 ///179 /// * account_id: Collection owner.180 CollectionCreated(CollectionId, u8, T::AccountId),181182 /// New item was created.183 ///184 /// # Arguments185 ///186 /// * collection_id: Id of the collection where item was created.187 ///188 /// * item_id: Id of an item. Unique within the collection.189 ///190 /// * recipient: Owner of newly created item191 ///192 /// * amount: Always 1 for NFT193 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),194195 /// Collection item was burned.196 ///197 /// # Arguments198 ///199 /// * collection_id.200 ///201 /// * item_id: Identifier of burned NFT.202 ///203 /// * owner: which user has destroyed its tokens204 ///205 /// * amount: Always 1 for NFT206 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),207208 /// Item was transferred209 ///210 /// * collection_id: Id of collection to which item is belong211 ///212 /// * item_id: Id of an item213 ///214 /// * sender: Original owner of item215 ///216 /// * recipient: New owner of item217 ///218 /// * amount: Always 1 for NFT219 Transfer(220 CollectionId,221 TokenId,222 T::CrossAccountId,223 T::CrossAccountId,224 u128,225 ),226227 /// * collection_id228 ///229 /// * item_id230 ///231 /// * sender232 ///233 /// * spender234 ///235 /// * amount236 Approved(237 CollectionId,238 TokenId,239 T::CrossAccountId,240 T::CrossAccountId,241 u128,242 ),243 }244245 #[pallet::error]246 pub enum Error<T> {247 /// This collection does not exist.248 CollectionNotFound,249 /// Sender parameter and item owner must be equal.250 MustBeTokenOwner,251 /// No permission to perform action252 NoPermission,253 /// Collection is not in mint mode.254 PublicMintingNotAllowed,255 /// Address is not in white list.256 AddressNotInAllowlist,257258 /// Collection name can not be longer than 63 char.259 CollectionNameLimitExceeded,260 /// Collection description can not be longer than 255 char.261 CollectionDescriptionLimitExceeded,262 /// Token prefix can not be longer than 15 char.263 CollectionTokenPrefixLimitExceeded,264 /// Total collections bound exceeded.265 TotalCollectionsLimitExceeded,266 /// variable_data exceeded data limit.267 TokenVariableDataLimitExceeded,268269 /// Collection settings not allowing items transferring270 TransferNotAllowed,271 /// Account token limit exceeded per collection272 AccountTokenLimitExceeded,273 /// Collection token limit exceeded274 CollectionTokenLimitExceeded,275 /// Metadata flag frozen276 MetadataFlagFrozen,277278 /// Item not exists.279 TokenNotFound,280 /// Item balance not enough.281 TokenValueTooLow,282 /// Requested value more than approved.283 TokenValueNotEnough,284 /// Tried to approve more than owned285 CantApproveMoreThanOwned,286287 /// Can't transfer tokens to ethereum zero address288 AddressIsZero,289 /// Target collection doesn't supports this operation290 UnsupportedOperation,291 }292293 #[pallet::storage]294 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;295 #[pallet::storage]296 pub type DestroyedCollectionCount<T> =297 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;298299 /// Collection info300 #[pallet::storage]301 pub type CollectionById<T> = StorageMap<302 Hasher = Blake2_128Concat,303 Key = CollectionId,304 Value = Collection<T>,305 QueryKind = OptionQuery,306 >;307308 /// List of collection admins309 #[pallet::storage]310 pub type IsAdmin<T: Config> = StorageNMap<311 Key = (312 Key<Blake2_128Concat, CollectionId>,313 Key<Blake2_128Concat, T::CrossAccountId>,314 ),315 Value = bool,316 QueryKind = ValueQuery,317 >;318319 /// Allowlisted collection users320 #[pallet::storage]321 pub type Allowlist<T: Config> = StorageNMap<322 Key = (323 Key<Blake2_128Concat, CollectionId>,324 Key<Blake2_128Concat, T::CrossAccountId>,325 ),326 Value = bool,327 QueryKind = ValueQuery,328 >;329}330331impl<T: Config> Pallet<T> {332 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens333 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {334 ensure!(335 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,336 <Error<T>>::AddressIsZero337 );338 Ok(())339 }340}341342impl<T: Config> Pallet<T> {343 pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {344 {345 ensure!(346 data.name.len() <= MAX_COLLECTION_NAME_LENGTH,347 Error::<T>::CollectionNameLimitExceeded348 );349 ensure!(350 data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,351 Error::<T>::CollectionDescriptionLimitExceeded352 );353 ensure!(354 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,355 Error::<T>::CollectionTokenPrefixLimitExceeded356 );357 }358359 let created_count = <CreatedCollectionCount<T>>::get()360 .0361 .checked_add(1)362 .ok_or(ArithmeticError::Overflow)?;363 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;364 let id = CollectionId(created_count);365366 // bound Total number of collections367 ensure!(368 created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,369 <Error<T>>::TotalCollectionsLimitExceeded370 );371372 // =========373374 // Take a (non-refundable) deposit of collection creation375 {376 let mut imbalance =377 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();378 imbalance.subsume(379 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(380 &T::TreasuryAccountId::get(),381 T::CollectionCreationPrice::get(),382 ),383 );384 <T as Config>::Currency::settle(385 &data.owner,386 imbalance,387 WithdrawReasons::TRANSFER,388 ExistenceRequirement::KeepAlive,389 )390 .map_err(|_| Error::<T>::NoPermission)?;391 }392393 <CreatedCollectionCount<T>>::put(created_count);394 <Pallet<T>>::deposit_event(Event::CollectionCreated(395 id,396 data.mode.id(),397 data.owner.clone(),398 ));399 <CollectionById<T>>::insert(id, data);400 Ok(id)401 }402403 pub fn destroy_collection(404 collection: CollectionHandle<T>,405 sender: &T::CrossAccountId,406 ) -> DispatchResult {407 ensure!(408 collection.limits.owner_can_destroy(),409 <Error<T>>::NoPermission,410 );411 collection.check_is_owner(&sender)?;412413 let destroyed_collections = <DestroyedCollectionCount<T>>::get()414 .0415 .checked_add(1)416 .ok_or(ArithmeticError::Overflow)?;417418 // =========419420 <DestroyedCollectionCount<T>>::put(destroyed_collections);421 <CollectionById<T>>::remove(collection.id);422 <IsAdmin<T>>::remove_prefix((collection.id,), None);423 <Allowlist<T>>::remove_prefix((collection.id,), None);424 Ok(())425 }426427 pub fn toggle_allowlist(428 collection: &CollectionHandle<T>,429 sender: &T::CrossAccountId,430 user: &T::CrossAccountId,431 allowed: bool,432 ) -> DispatchResult {433 collection.check_is_owner_or_admin(&sender)?;434435 // =========436437 if allowed {438 <Allowlist<T>>::insert((collection.id, user.as_sub()), true);439 } else {440 <Allowlist<T>>::remove((collection.id, user.as_sub()));441 }442443 Ok(())444 }445}446447#[macro_export]448macro_rules! unsupported {449 () => {450 Err(<Error<T>>::UnsupportedOperation.into())451 };452}453454/// Worst cases455pub trait CommonWeightInfo {456 fn create_item() -> Weight;457 fn create_multiple_items(amount: u32) -> Weight;458 fn burn_item() -> Weight;459 fn transfer() -> Weight;460 fn approve() -> Weight;461 fn transfer_from() -> Weight;462 fn burn_from() -> Weight;463 fn set_variable_metadata(bytes: u32) -> Weight;464}465466pub trait CommonCollectionOperations<T: Config> {467 fn create_item(468 &self,469 sender: T::CrossAccountId,470 to: T::CrossAccountId,471 data: CreateItemData,472 ) -> DispatchResultWithPostInfo;473 fn create_multiple_items(474 &self,475 sender: T::CrossAccountId,476 to: T::CrossAccountId,477 data: Vec<CreateItemData>,478 ) -> DispatchResultWithPostInfo;479 fn burn_item(480 &self,481 sender: T::CrossAccountId,482 token: TokenId,483 amount: u128,484 ) -> DispatchResultWithPostInfo;485486 fn transfer(487 &self,488 sender: T::CrossAccountId,489 to: T::CrossAccountId,490 token: TokenId,491 amount: u128,492 ) -> DispatchResultWithPostInfo;493 fn approve(494 &self,495 sender: T::CrossAccountId,496 spender: T::CrossAccountId,497 token: TokenId,498 amount: u128,499 ) -> DispatchResultWithPostInfo;500 fn transfer_from(501 &self,502 sender: T::CrossAccountId,503 from: T::CrossAccountId,504 to: T::CrossAccountId,505 token: TokenId,506 amount: u128,507 ) -> DispatchResultWithPostInfo;508 fn burn_from(509 &self,510 sender: T::CrossAccountId,511 from: T::CrossAccountId,512 token: TokenId,513 amount: u128,514 ) -> DispatchResultWithPostInfo;515516 fn set_variable_metadata(517 &self,518 sender: T::CrossAccountId,519 token: TokenId,520 data: Vec<u8>,521 ) -> DispatchResultWithPostInfo;522523 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;524 fn token_exists(&self, token: TokenId) -> bool;525 fn last_token_id(&self) -> TokenId;526527 fn token_owner(&self, token: TokenId) -> T::CrossAccountId;528 fn const_metadata(&self, token: TokenId) -> Vec<u8>;529 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;530531 /// How many tokens collection contains (Applicable to nonfungible/refungible)532 fn collection_tokens(&self) -> u32;533 /// Amount of different tokens account has (Applicable to nonfungible/refungible)534 fn account_balance(&self, account: T::CrossAccountId) -> u32;535 /// Amount of specific token account have (Applicable to fungible/refungible)536 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;537 fn allowance(538 &self,539 sender: T::CrossAccountId,540 spender: T::CrossAccountId,541 token: TokenId,542 ) -> u128;543}544545// Flexible enough for implementing CommonCollectionOperations546pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {547 let post_info = PostDispatchInfo {548 actual_weight: Some(weight),549 pays_fee: Pays::Yes,550 };551 match res {552 Ok(()) => Ok(post_info),553 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),554 }555}1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::{Deref, DerefMut};4use sp_std::vec::Vec;5use account::CrossAccountId;6use frame_support::{7 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},8 ensure, fail,9 traits::{Imbalance, Get, Currency},10};11use nft_data_structs::{12 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,13 MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,14 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight,15 WithdrawReasons,16};17pub use pallet::*;18use sp_core::H160;19use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};20pub mod account;21#[cfg(feature = "runtime-benchmarks")]22pub mod benchmarking;23pub mod erc;24pub mod eth;2526#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]27pub struct CollectionHandle<T: Config> {28 pub id: CollectionId,29 collection: Collection<T>,30 pub recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,31}32impl<T: Config> CollectionHandle<T> {33 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {34 <CollectionById<T>>::get(id).map(|collection| Self {35 id,36 collection,37 recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(38 eth::collection_id_to_address(id),39 gas_limit,40 ),41 })42 }43 pub fn new(id: CollectionId) -> Option<Self> {44 Self::new_with_gas_limit(id, u64::MAX)45 }46 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {47 Ok(Self::new(id).ok_or_else(|| <Error<T>>::CollectionNotFound)?)48 }49 pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {50 self.recorder.log_sub(log)51 }52 pub fn log_infallible(&self, log: impl evm_coder::ToLog) {53 self.recorder.log_infallible(log)54 }55 #[allow(dead_code)]56 fn consume_gas(&self, gas: u64) -> DispatchResult {57 self.recorder.consume_gas_sub(gas)58 }59 pub fn consume_sload(&self) -> DispatchResult {60 self.recorder.consume_sload_sub()61 }62 pub fn consume_sstores(&self, amount: usize) -> DispatchResult {63 self.recorder.consume_sstores_sub(amount)64 }65 pub fn consume_sstore(&self) -> DispatchResult {66 self.recorder.consume_sstore_sub()67 }68 pub fn consume_log(&self, topics: usize, data: usize) -> DispatchResult {69 self.recorder.consume_log_sub(topics, data)70 }71 pub fn submit_logs(self) -> DispatchResult {72 self.recorder.submit_logs()73 }74 pub fn save(self) -> DispatchResult {75 self.recorder.submit_logs()?;76 <CollectionById<T>>::insert(self.id, self.collection);77 Ok(())78 }79}80impl<T: Config> Deref for CollectionHandle<T> {81 type Target = Collection<T>;8283 fn deref(&self) -> &Self::Target {84 &self.collection85 }86}8788impl<T: Config> DerefMut for CollectionHandle<T> {89 fn deref_mut(&mut self) -> &mut Self::Target {90 &mut self.collection91 }92}9394impl<T: Config> CollectionHandle<T> {95 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {96 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);97 Ok(())98 }99 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> Result<bool, DispatchError> {100 self.consume_sload()?;101102 Ok(*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject)))103 }104 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {105 ensure!(self.is_owner_or_admin(subject)?, <Error<T>>::NoPermission);106 Ok(())107 }108 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {109 Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)110 }111 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {112 Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)113 }114 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {115 self.consume_sload()?;116117 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::{EvmBackwardsAddressMapping, CrossAccountId};145 use frame_support::traits::Currency;146 use nft_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: 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 white 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 amount277 CollectionAdminAmountExceeded,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>,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 >;347}348349impl<T: Config> Pallet<T> {350 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens351 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {352 ensure!(353 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,354 <Error<T>>::AddressIsZero355 );356 Ok(())357 }358}359360impl<T: Config> Pallet<T> {361 pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {362 {363 ensure!(364 data.name.len() <= MAX_COLLECTION_NAME_LENGTH,365 Error::<T>::CollectionNameLimitExceeded366 );367 ensure!(368 data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,369 Error::<T>::CollectionDescriptionLimitExceeded370 );371 ensure!(372 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,373 Error::<T>::CollectionTokenPrefixLimitExceeded374 );375 }376377 let created_count = <CreatedCollectionCount<T>>::get()378 .0379 .checked_add(1)380 .ok_or(ArithmeticError::Overflow)?;381 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;382 let id = CollectionId(created_count);383384 // bound Total number of collections385 ensure!(386 created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,387 <Error<T>>::TotalCollectionsLimitExceeded388 );389390 // =========391392 // Take a (non-refundable) deposit of collection creation393 {394 let mut imbalance =395 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();396 imbalance.subsume(397 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(398 &T::TreasuryAccountId::get(),399 T::CollectionCreationPrice::get(),400 ),401 );402 <T as Config>::Currency::settle(403 &data.owner,404 imbalance,405 WithdrawReasons::TRANSFER,406 ExistenceRequirement::KeepAlive,407 )408 .map_err(|_| Error::<T>::NoPermission)?;409 }410411 <CreatedCollectionCount<T>>::put(created_count);412 <Pallet<T>>::deposit_event(Event::CollectionCreated(413 id,414 data.mode.id(),415 data.owner.clone(),416 ));417 <CollectionById<T>>::insert(id, data);418 Ok(id)419 }420421 pub fn destroy_collection(422 collection: CollectionHandle<T>,423 sender: &T::CrossAccountId,424 ) -> DispatchResult {425 ensure!(426 collection.limits.owner_can_destroy(),427 <Error<T>>::NoPermission,428 );429 collection.check_is_owner(&sender)?;430431 let destroyed_collections = <DestroyedCollectionCount<T>>::get()432 .0433 .checked_add(1)434 .ok_or(ArithmeticError::Overflow)?;435436 // =========437438 <DestroyedCollectionCount<T>>::put(destroyed_collections);439 <CollectionById<T>>::remove(collection.id);440 <AdminAmount<T>>::remove(collection.id);441 <IsAdmin<T>>::remove_prefix((collection.id,), None);442 <Allowlist<T>>::remove_prefix((collection.id,), None);443 Ok(())444 }445446 pub fn toggle_allowlist(447 collection: &CollectionHandle<T>,448 sender: &T::CrossAccountId,449 user: &T::CrossAccountId,450 allowed: bool,451 ) -> DispatchResult {452 collection.check_is_owner_or_admin(&sender)?;453454 // =========455456 if allowed {457 <Allowlist<T>>::insert((collection.id, user), true);458 } else {459 <Allowlist<T>>::remove((collection.id, user));460 }461462 Ok(())463 }464465 pub fn toggle_admin(466 collection: &CollectionHandle<T>,467 sender: &T::CrossAccountId,468 user: &T::CrossAccountId,469 admin: bool,470 ) -> DispatchResult {471 collection.check_is_owner_or_admin(&sender)?;472473 let was_admin = <IsAdmin<T>>::get((collection.id, user));474 if was_admin == admin {475 return Ok(());476 }477 let amount = <AdminAmount<T>>::get(collection.id);478479 if admin {480 let amount = amount481 .checked_add(1)482 .ok_or(<Error<T>>::CollectionAdminAmountExceeded)?;483 ensure!(484 amount <= Self::collection_admins_limit(),485 <Error<T>>::CollectionAdminAmountExceeded,486 );487488 // =========489490 <AdminAmount<T>>::insert(collection.id, amount);491 <IsAdmin<T>>::insert((collection.id, user), true);492 } else {493 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));494 <IsAdmin<T>>::remove((collection.id, user));495 }496497 Ok(())498 }499}500501#[macro_export]502macro_rules! unsupported {503 () => {504 Err(<Error<T>>::UnsupportedOperation.into())505 };506}507508/// Worst cases509pub trait CommonWeightInfo {510 fn create_item() -> Weight;511 fn create_multiple_items(amount: u32) -> Weight;512 fn burn_item() -> Weight;513 fn transfer() -> Weight;514 fn approve() -> Weight;515 fn transfer_from() -> Weight;516 fn burn_from() -> Weight;517 fn set_variable_metadata(bytes: u32) -> Weight;518}519520pub trait CommonCollectionOperations<T: Config> {521 fn create_item(522 &self,523 sender: T::CrossAccountId,524 to: T::CrossAccountId,525 data: CreateItemData,526 ) -> DispatchResultWithPostInfo;527 fn create_multiple_items(528 &self,529 sender: T::CrossAccountId,530 to: T::CrossAccountId,531 data: Vec<CreateItemData>,532 ) -> DispatchResultWithPostInfo;533 fn burn_item(534 &self,535 sender: T::CrossAccountId,536 token: TokenId,537 amount: u128,538 ) -> DispatchResultWithPostInfo;539540 fn transfer(541 &self,542 sender: T::CrossAccountId,543 to: T::CrossAccountId,544 token: TokenId,545 amount: u128,546 ) -> DispatchResultWithPostInfo;547 fn approve(548 &self,549 sender: T::CrossAccountId,550 spender: T::CrossAccountId,551 token: TokenId,552 amount: u128,553 ) -> DispatchResultWithPostInfo;554 fn transfer_from(555 &self,556 sender: T::CrossAccountId,557 from: T::CrossAccountId,558 to: T::CrossAccountId,559 token: TokenId,560 amount: u128,561 ) -> DispatchResultWithPostInfo;562 fn burn_from(563 &self,564 sender: T::CrossAccountId,565 from: T::CrossAccountId,566 token: TokenId,567 amount: u128,568 ) -> DispatchResultWithPostInfo;569570 fn set_variable_metadata(571 &self,572 sender: T::CrossAccountId,573 token: TokenId,574 data: Vec<u8>,575 ) -> DispatchResultWithPostInfo;576577 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;578 fn token_exists(&self, token: TokenId) -> bool;579 fn last_token_id(&self) -> TokenId;580581 fn token_owner(&self, token: TokenId) -> T::CrossAccountId;582 fn const_metadata(&self, token: TokenId) -> Vec<u8>;583 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;584585 /// How many tokens collection contains (Applicable to nonfungible/refungible)586 fn collection_tokens(&self) -> u32;587 /// Amount of different tokens account has (Applicable to nonfungible/refungible)588 fn account_balance(&self, account: T::CrossAccountId) -> u32;589 /// Amount of specific token account have (Applicable to fungible/refungible)590 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;591 fn allowance(592 &self,593 sender: T::CrossAccountId,594 spender: T::CrossAccountId,595 token: TokenId,596 ) -> u128;597}598599// Flexible enough for implementing CommonCollectionOperations600pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {601 let post_info = PostDispatchInfo {602 actual_weight: Some(weight),603 pays_fee: Pays::Yes,604 };605 match res {606 Ok(()) => Ok(post_info),607 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),608 }609}pallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -36,8 +36,8 @@
use sp_runtime::{sp_std::prelude::Vec};
use nft_data_structs::{
MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,
- VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,
- OFFCHAIN_SCHEMA_LIMIT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+ VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,
+ FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,
CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
};
@@ -156,7 +156,6 @@
where
origin: T::Origin
{
- const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;
type Error = Error<T>;
fn on_initialize(_now: T::BlockNumber) -> Weight {
@@ -406,12 +405,9 @@
#[transactional]
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 = <CollectionHandle<T>>::try_get(collection_id)?;
- collection.check_is_owner_or_admin(&sender)?;
- <IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);
- Ok(())
+ <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)
}
/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.
@@ -430,12 +426,9 @@
#[transactional]
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 = <CollectionHandle<T>>::try_get(collection_id)?;
- collection.check_is_owner_or_admin(&sender)?;
- <IsAdmin<T>>::remove((collection_id, account_id.as_sub()));
- Ok(())
+ <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)
}
/// # Permissions
primitives/nft/src/lib.rsdiffbeforeafterboth--- a/primitives/nft/src/lib.rs
+++ b/primitives/nft/src/lib.rs
@@ -41,7 +41,7 @@
} else {
10
};
-pub const COLLECTION_ADMINS_LIMIT: u64 = 5;
+pub const COLLECTION_ADMINS_LIMIT: u32 = 5;
pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;
pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {
1000000