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, CollectionStats,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::AccountId>,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::AccountId>;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::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: 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 180 181 182 183 184 185 186 187 188 CollectionCreated(CollectionId, u8, T::AccountId),189190 191 192 193 194 195 196 197 198 199 200 201 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),202203 204 205 206 207 208 209 210 211 212 213 214 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),215216 217 218 219 220 221 222 223 224 225 226 227 Transfer(228 CollectionId,229 TokenId,230 T::CrossAccountId,231 T::CrossAccountId,232 u128,233 ),234235 236 237 238 239 240 241 242 243 244 Approved(245 CollectionId,246 TokenId,247 T::CrossAccountId,248 T::CrossAccountId,249 u128,250 ),251 }252253 #[pallet::error]254 pub enum Error<T> {255 256 CollectionNotFound,257 258 MustBeTokenOwner,259 260 NoPermission,261 262 PublicMintingNotAllowed,263 264 AddressNotInAllowlist,265266 267 CollectionNameLimitExceeded,268 269 CollectionDescriptionLimitExceeded,270 271 CollectionTokenPrefixLimitExceeded,272 273 TotalCollectionsLimitExceeded,274 275 TokenVariableDataLimitExceeded,276 277 CollectionAdminAmountExceeded,278279 280 TransferNotAllowed,281 282 AccountTokenLimitExceeded,283 284 CollectionTokenLimitExceeded,285 286 MetadataFlagFrozen,287288 289 TokenNotFound,290 291 TokenValueTooLow,292 293 TokenValueNotEnough,294 295 CantApproveMoreThanOwned,296297 298 AddressIsZero,299 300 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 310 #[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 327 #[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 338 #[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 349 #[pallet::storage]350 pub type DummyStorageValue<T> =351 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;352}353354impl<T: Config> Pallet<T> {355 356 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 412 ensure!(413 created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,414 <Error<T>>::TotalCollectionsLimitExceeded415 );416417 418419 420 {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>>::CollectionAdminAmountExceeded)?;510 ensure!(511 amount <= Self::collection_admins_limit(),512 <Error<T>>::CollectionAdminAmountExceeded,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}534535536pub 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 613 fn collection_tokens(&self) -> u32;614 615 fn account_balance(&self, account: T::CrossAccountId) -> u32;616 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}625626627pub 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}