difftreelog
refactor TokenValueNotEnough -> ApprovedValueTooLow
in: master
6 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 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}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 ApprovedValueTooLow,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/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -343,7 +343,7 @@
if allowance.is_none() {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
@@ -374,7 +374,7 @@
if allowance.is_none() {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -494,7 +494,7 @@
if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
@@ -522,7 +522,7 @@
if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -529,7 +529,7 @@
if allowance.is_none() {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
@@ -562,7 +562,7 @@
if allowance.is_none() {
ensure!(
collection.ignores_allowance(spender),
- <CommonError<T>>::TokenValueNotEnough
+ <CommonError<T>>::ApprovedValueTooLow
);
}
pallets/unique/src/tests.rsdiffbeforeafterboth--- a/pallets/unique/src/tests.rs
+++ b/pallets/unique/src/tests.rs
@@ -601,7 +601,7 @@
1
)
.map_err(|e| e.error),
- CommonError::<Test>::TokenValueNotEnough
+ CommonError::<Test>::ApprovedValueTooLow
);
// do approve
@@ -916,7 +916,7 @@
4
)
.map_err(|e| e.error),
- CommonError::<Test>::TokenValueNotEnough
+ CommonError::<Test>::ApprovedValueTooLow
);
});
}
tests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-errors.ts
+++ b/tests/src/interfaces/augment-api-errors.ts
@@ -107,7 +107,7 @@
/**
* Requested value more than approved.
**/
- TokenValueNotEnough: AugmentedError<ApiType>;
+ ApprovedValueTooLow: AugmentedError<ApiType>;
/**
* Item balance not enough.
**/