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;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.as_sub())))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.as_sub())),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::{pallet_prelude::*};143 use frame_support::{Blake2_128Concat, storage::Key};144 use account::{EvmBackwardsAddressMapping, CrossAccountId};145 use frame_support::traits::Currency;146 use nft_data_structs::TokenId;147148 #[pallet::config]149 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config {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 172 173 174 175 176 177 178 179 180 CollectionCreated(CollectionId, u8, T::AccountId),181182 183 184 185 186 187 188 189 190 191 192 193 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),194195 196 197 198 199 200 201 202 203 204 205 206 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),207208 209 210 211 212 213 214 215 216 217 218 219 Transfer(220 CollectionId,221 TokenId,222 T::CrossAccountId,223 T::CrossAccountId,224 u128,225 ),226227 228 229 230 231 232 233 234 235 236 Approved(237 CollectionId,238 TokenId,239 T::CrossAccountId,240 T::CrossAccountId,241 u128,242 ),243 }244245 #[pallet::error]246 pub enum Error<T> {247 248 CollectionNotFound,249 250 MustBeTokenOwner,251 252 NoPermission,253 254 PublicMintingNotAllowed,255 256 AddressNotInAllowlist,257258 259 CollectionNameLimitExceeded,260 261 CollectionDescriptionLimitExceeded,262 263 CollectionTokenPrefixLimitExceeded,264 265 TotalCollectionsLimitExceeded,266 267 TokenVariableDataLimitExceeded,268269 270 TransferNotAllowed,271 272 AccountTokenLimitExceeded,273 274 CollectionTokenLimitExceeded,275 276 MetadataFlagFrozen,277278 279 TokenNotFound,280 281 TokenValueTooLow,282 283 TokenValueNotEnough,284 285 CantApproveMoreThanOwned,286287 288 AddressIsZero,289 290 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 300 #[pallet::storage]301 pub type CollectionById<T> = StorageMap<302 Hasher = Blake2_128Concat,303 Key = CollectionId,304 Value = Collection<T>,305 QueryKind = OptionQuery,306 >;307308 309 #[pallet::storage]310 pub type IsAdmin<T: Config> = StorageNMap<311 Key = (312 Key<Blake2_128Concat, CollectionId>,313 Key<Blake2_128Concat, T::AccountId>,314 ),315 Value = bool,316 QueryKind = ValueQuery,317 >;318319 320 #[pallet::storage]321 pub type Allowlist<T: Config> = StorageNMap<322 Key = (323 Key<Blake2_128Concat, CollectionId>,324 Key<Blake2_128Concat, T::AccountId>,325 ),326 Value = bool,327 QueryKind = ValueQuery,328 >;329}330331impl<T: Config> Pallet<T> {332 333 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 367 ensure!(368 created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,369 <Error<T>>::TotalCollectionsLimitExceeded370 );371372 373374 375 {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 if !collection.limits.owner_can_destroy {408 fail!(Error::<T>::NoPermission);409 }410 collection.check_is_owner(&sender)?;411412 let destroyed_collections = <DestroyedCollectionCount<T>>::get()413 .0414 .checked_add(1)415 .ok_or(ArithmeticError::Overflow)?;416417 418419 <DestroyedCollectionCount<T>>::put(destroyed_collections);420 <CollectionById<T>>::remove(collection.id);421 <IsAdmin<T>>::remove_prefix((collection.id,), None);422 <Allowlist<T>>::remove_prefix((collection.id,), None);423 Ok(())424 }425426 pub fn toggle_allowlist(427 collection: &CollectionHandle<T>,428 sender: &T::CrossAccountId,429 user: &T::CrossAccountId,430 allowed: bool,431 ) -> DispatchResult {432 collection.check_is_owner_or_admin(&sender)?;433434 435436 if allowed {437 <Allowlist<T>>::insert((collection.id, user.as_sub()), true);438 } else {439 <Allowlist<T>>::remove((collection.id, user.as_sub()));440 }441442 Ok(())443 }444}445446#[macro_export]447macro_rules! unsupported {448 () => {449 Err(<Error<T>>::UnsupportedOperation.into())450 };451}452453454pub trait CommonWeightInfo {455 fn create_item() -> Weight;456 fn create_multiple_items(amount: u32) -> Weight;457 fn burn_item() -> Weight;458 fn transfer() -> Weight;459 fn approve() -> Weight;460 fn transfer_from() -> Weight;461 fn set_variable_metadata(bytes: u32) -> Weight;462}463464pub trait CommonCollectionOperations<T: Config> {465 fn create_item(466 &self,467 sender: T::CrossAccountId,468 to: T::CrossAccountId,469 data: CreateItemData,470 ) -> DispatchResultWithPostInfo;471 fn create_multiple_items(472 &self,473 sender: T::CrossAccountId,474 to: T::CrossAccountId,475 data: Vec<CreateItemData>,476 ) -> DispatchResultWithPostInfo;477 fn burn_item(478 &self,479 sender: T::CrossAccountId,480 token: TokenId,481 amount: u128,482 ) -> DispatchResultWithPostInfo;483484 fn transfer(485 &self,486 sender: T::CrossAccountId,487 to: T::CrossAccountId,488 token: TokenId,489 amount: u128,490 ) -> DispatchResultWithPostInfo;491 fn approve(492 &self,493 sender: T::CrossAccountId,494 spender: T::CrossAccountId,495 token: TokenId,496 amount: u128,497 ) -> DispatchResultWithPostInfo;498 fn transfer_from(499 &self,500 sender: T::CrossAccountId,501 from: T::CrossAccountId,502 to: T::CrossAccountId,503 token: TokenId,504 amount: u128,505 ) -> DispatchResultWithPostInfo;506507 fn set_variable_metadata(508 &self,509 sender: T::CrossAccountId,510 token: TokenId,511 data: Vec<u8>,512 ) -> DispatchResultWithPostInfo;513514 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;515 fn token_exists(&self, token: TokenId) -> bool;516 fn last_token_id(&self) -> TokenId;517518 fn token_owner(&self, token: TokenId) -> T::CrossAccountId;519 fn const_metadata(&self, token: TokenId) -> Vec<u8>;520 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;521522 523 fn collection_tokens(&self) -> u32;524 525 fn account_balance(&self, account: T::CrossAccountId) -> u32;526 527 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;528 fn allowance(529 &self,530 sender: T::CrossAccountId,531 spender: T::CrossAccountId,532 token: TokenId,533 ) -> u128;534}535536537pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {538 let post_info = PostDispatchInfo {539 actual_weight: Some(weight),540 pays_fee: Pays::Yes,541 };542 match res {543 Ok(()) => Ok(post_info),544 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),545 }546}