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 MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight, WithdrawReasons,15};16pub use pallet::*;17use sp_core::H160;18use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};19pub mod account;20pub mod benchmarking;21pub mod erc;22pub mod eth;2324#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]25pub struct CollectionHandle<T: Config> {26 pub id: CollectionId,27 collection: Collection<T>,28 pub recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,29}30impl<T: Config> CollectionHandle<T> {31 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {32 <CollectionById<T>>::get(id).map(|collection| Self {33 id,34 collection,35 recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(36 eth::collection_id_to_address(id),37 gas_limit,38 ),39 })40 }41 pub fn new(id: CollectionId) -> Option<Self> {42 Self::new_with_gas_limit(id, u64::MAX)43 }44 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {45 Ok(Self::new(id).ok_or_else(|| <Error<T>>::CollectionNotFound)?)46 }47 pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {48 self.recorder.log_sub(log)49 }50 pub fn log_infallible(&self, log: impl evm_coder::ToLog) {51 self.recorder.log_infallible(log)52 }53 #[allow(dead_code)]54 fn consume_gas(&self, gas: u64) -> DispatchResult {55 self.recorder.consume_gas_sub(gas)56 }57 pub fn consume_sload(&self) -> DispatchResult {58 self.recorder.consume_sload_sub()59 }60 pub fn consume_sstores(&self, amount: usize) -> DispatchResult {61 self.recorder.consume_sstores_sub(amount)62 }63 pub fn consume_sstore(&self) -> DispatchResult {64 self.recorder.consume_sstore_sub()65 }66 pub fn consume_log(&self, topics: usize, data: usize) -> DispatchResult {67 self.recorder.consume_log_sub(topics, data)68 }69 pub fn submit_logs(self) -> DispatchResult {70 self.recorder.submit_logs()71 }72 pub fn save(self) -> DispatchResult {73 self.recorder.submit_logs()?;74 <CollectionById<T>>::insert(self.id, self.collection);75 Ok(())76 }77}78impl<T: Config> Deref for CollectionHandle<T> {79 type Target = Collection<T>;8081 fn deref(&self) -> &Self::Target {82 &self.collection83 }84}8586impl<T: Config> DerefMut for CollectionHandle<T> {87 fn deref_mut(&mut self) -> &mut Self::Target {88 &mut self.collection89 }90}9192impl<T: Config> CollectionHandle<T> {93 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {94 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);95 Ok(())96 }97 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> Result<bool, DispatchError> {98 self.consume_sload()?;99100 Ok(*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject.as_sub())))101 }102 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {103 ensure!(self.is_owner_or_admin(subject)?, <Error<T>>::NoPermission);104 Ok(())105 }106 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {107 Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)108 }109 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {110 Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)111 }112 pub fn check_whitelist(&self, user: &T::CrossAccountId) -> DispatchResult {113 self.consume_sload()?;114115 ensure!(116 <WhiteList<T>>::get((self.id, user.as_sub())),117 <Error<T>>::AddressNotInWhiteList118 );119 Ok(())120 }121122 pub fn check_can_update_meta(123 &self,124 subject: &T::CrossAccountId,125 item_owner: &T::CrossAccountId,126 ) -> DispatchResult {127 match self.meta_update_permission {128 MetaUpdatePermission::ItemOwner => {129 ensure!(subject == item_owner, <Error<T>>::NoPermission);130 Ok(())131 }132 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),133 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),134 }135 }136}137138#[frame_support::pallet]139pub mod pallet {140 use super::*;141 use frame_support::{pallet_prelude::*};142 use frame_support::{Blake2_128Concat, storage::Key};143 use account::{EvmBackwardsAddressMapping, CrossAccountId};144 use frame_support::traits::Currency;145 use nft_data_structs::TokenId;146147 #[pallet::config]148 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config {149 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;150151 type CrossAccountId: CrossAccountId<Self::AccountId>;152153 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;154 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;155156 type Currency: Currency<Self::AccountId>;157 type CollectionCreationPrice: Get<158 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,159 >;160 type TreasuryAccountId: Get<Self::AccountId>;161 }162163 #[pallet::pallet]164 #[pallet::generate_store(pub(super) trait Store)]165 pub struct Pallet<T>(_);166167 #[pallet::event]168 #[pallet::generate_deposit(pub fn deposit_event)]169 pub enum Event<T: Config> {170 171 172 173 174 175 176 177 178 179 CollectionCreated(CollectionId, u8, T::AccountId),180181 182 183 184 185 186 187 188 189 190 191 192 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),193194 195 196 197 198 199 200 201 202 203 204 205 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),206207 208 209 210 211 212 213 214 215 216 217 218 Transfer(219 CollectionId,220 TokenId,221 T::CrossAccountId,222 T::CrossAccountId,223 u128,224 ),225226 227 228 229 230 231 232 233 234 235 Approved(236 CollectionId,237 TokenId,238 T::CrossAccountId,239 T::CrossAccountId,240 u128,241 ),242 }243244 #[pallet::error]245 pub enum Error<T> {246 247 CollectionNotFound,248 249 MustBeTokenOwner,250 251 NoPermission,252 253 PublicMintingNotAllowed,254 255 AddressNotInWhiteList,256257 258 CollectionNameLimitExceeded,259 260 CollectionDescriptionLimitExceeded,261 262 CollectionTokenPrefixLimitExceeded,263 264 TotalCollectionsLimitExceeded,265 266 TokenVariableDataLimitExceeded,267268 269 TransferNotAllowed,270 271 AccountTokenLimitExceeded,272 273 CollectionTokenLimitExceeded,274 275 MetadataFlagFrozen,276277 278 TokenNotFound,279 280 TokenValueTooLow,281 282 TokenValueNotEnough,283 284 CantApproveMoreThanOwned,285286 287 AddressIsZero,288 289 UnsupportedOperation,290 }291292 #[pallet::storage]293 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;294 #[pallet::storage]295 pub type DestroyedCollectionCount<T> =296 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;297298 299 #[pallet::storage]300 pub type CollectionById<T> = StorageMap<301 Hasher = Blake2_128Concat,302 Key = CollectionId,303 Value = Collection<T>,304 QueryKind = OptionQuery,305 >;306307 308 #[pallet::storage]309 pub type IsAdmin<T: Config> = StorageNMap<310 Key = (311 Key<Blake2_128Concat, CollectionId>,312 Key<Blake2_128Concat, T::AccountId>,313 ),314 Value = bool,315 QueryKind = ValueQuery,316 >;317318 319 #[pallet::storage]320 pub type WhiteList<T: Config> = StorageNMap<321 Key = (322 Key<Blake2_128Concat, CollectionId>,323 Key<Blake2_128Concat, T::AccountId>,324 ),325 Value = bool,326 QueryKind = ValueQuery,327 >;328}329330impl<T: Config> Pallet<T> {331 332 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {333 ensure!(334 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,335 <Error<T>>::AddressIsZero336 );337 Ok(())338 }339}340341impl<T: Config> Pallet<T> {342 pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {343 {344 ensure!(345 data.name.len() <= MAX_COLLECTION_NAME_LENGTH,346 Error::<T>::CollectionNameLimitExceeded347 );348 ensure!(349 data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,350 Error::<T>::CollectionDescriptionLimitExceeded351 );352 ensure!(353 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,354 Error::<T>::CollectionTokenPrefixLimitExceeded355 );356 }357358 let created_count = <CreatedCollectionCount<T>>::get()359 .0360 .checked_add(1)361 .ok_or(ArithmeticError::Overflow)?;362 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;363 let id = CollectionId(created_count);364365 366 ensure!(367 created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,368 <Error<T>>::TotalCollectionsLimitExceeded369 );370371 372373 374 {375 let mut imbalance =376 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();377 imbalance.subsume(378 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(379 &T::TreasuryAccountId::get(),380 T::CollectionCreationPrice::get(),381 ),382 );383 <T as Config>::Currency::settle(384 &data.owner,385 imbalance,386 WithdrawReasons::TRANSFER,387 ExistenceRequirement::KeepAlive,388 )389 .map_err(|_| Error::<T>::NoPermission)?;390 }391392 <CreatedCollectionCount<T>>::put(created_count);393 <Pallet<T>>::deposit_event(Event::CollectionCreated(394 id,395 data.mode.id(),396 data.owner.clone(),397 ));398 <CollectionById<T>>::insert(id, data);399 Ok(id)400 }401 pub fn destroy_collection(402 collection: CollectionHandle<T>,403 sender: &T::CrossAccountId,404 ) -> DispatchResult {405 if !collection.limits.owner_can_destroy {406 fail!(Error::<T>::NoPermission);407 }408 collection.check_is_owner(&sender)?;409410 let destroyed_collections = <DestroyedCollectionCount<T>>::get()411 .0412 .checked_add(1)413 .ok_or(ArithmeticError::Overflow)?;414415 416417 <DestroyedCollectionCount<T>>::put(destroyed_collections);418 <CollectionById<T>>::remove(collection.id);419 <IsAdmin<T>>::remove_prefix((collection.id,), None);420 <WhiteList<T>>::remove_prefix((collection.id,), None);421 Ok(())422 }423424 pub fn toggle_whitelist(425 collection: &CollectionHandle<T>,426 sender: &T::CrossAccountId,427 user: &T::CrossAccountId,428 allowed: bool,429 ) -> DispatchResult {430 collection.check_is_owner_or_admin(&sender)?;431432 433434 if allowed {435 <WhiteList<T>>::insert((collection.id, user.as_sub()), true);436 } else {437 <WhiteList<T>>::remove((collection.id, user.as_sub()));438 }439440 Ok(())441 }442}443444#[macro_export]445macro_rules! unsupported {446 () => {447 Err(<Error<T>>::UnsupportedOperation.into())448 };449}450451452pub trait CommonWeightInfo {453 fn create_item() -> Weight;454 fn create_multiple_items(amount: u32) -> Weight;455 fn burn_item() -> Weight;456 fn transfer() -> Weight;457 fn approve() -> Weight;458 fn transfer_from() -> Weight;459 fn set_variable_metadata(bytes: u32) -> Weight;460}461462pub trait CommonCollectionOperations<T: Config> {463 fn create_item(464 &self,465 sender: T::CrossAccountId,466 to: T::CrossAccountId,467 data: CreateItemData,468 ) -> DispatchResultWithPostInfo;469 fn create_multiple_items(470 &self,471 sender: T::CrossAccountId,472 to: T::CrossAccountId,473 data: Vec<CreateItemData>,474 ) -> DispatchResultWithPostInfo;475 fn burn_item(476 &self,477 sender: T::CrossAccountId,478 token: TokenId,479 amount: u128,480 ) -> DispatchResultWithPostInfo;481482 fn transfer(483 &self,484 sender: T::CrossAccountId,485 to: T::CrossAccountId,486 token: TokenId,487 amount: u128,488 ) -> DispatchResultWithPostInfo;489 fn approve(490 &self,491 sender: T::CrossAccountId,492 spender: T::CrossAccountId,493 token: TokenId,494 amount: u128,495 ) -> DispatchResultWithPostInfo;496 fn transfer_from(497 &self,498 sender: T::CrossAccountId,499 from: T::CrossAccountId,500 to: T::CrossAccountId,501 token: TokenId,502 amount: u128,503 ) -> DispatchResultWithPostInfo;504505 fn set_variable_metadata(506 &self,507 sender: T::CrossAccountId,508 token: TokenId,509 data: Vec<u8>,510 ) -> DispatchResultWithPostInfo;511512 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;513 fn token_exists(&self, token: TokenId) -> bool;514515 fn token_owner(&self, token: TokenId) -> T::CrossAccountId;516 fn const_metadata(&self, token: TokenId) -> Vec<u8>;517 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;518519 520 fn collection_tokens(&self) -> u32;521 522 fn account_balance(&self, account: T::CrossAccountId) -> u32;523 524 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;525 fn allowance(526 &self,527 sender: T::CrossAccountId,528 spender: T::CrossAccountId,529 token: TokenId,530 ) -> u128;531}532533534pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {535 let post_info = PostDispatchInfo {536 actual_weight: Some(weight),537 pays_fee: Pays::Yes,538 };539 match res {540 Ok(()) => Ok(post_info),541 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),542 }543}