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 BoundedVec,12};13use pallet_evm::GasWeightMapping;14use up_data_structs::{15 COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,16 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo,17 TokenId, Weight, WithdrawReasons, CollectionStats, CustomDataLimit,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_mirrored(&self, log: impl evm_coder::ToLog) {57 self.recorder.log_mirrored(log)58 }59 pub fn log_direct(&self, log: impl evm_coder::ToLog) {60 self.recorder.log_direct(log)61 }62 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {63 self.recorder64 .consume_gas(T::GasWeightMapping::weight_to_gas(65 <T as frame_system::Config>::DbWeight::get()66 .read67 .saturating_mul(reads),68 ))69 }70 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {71 self.recorder72 .consume_gas(T::GasWeightMapping::weight_to_gas(73 <T as frame_system::Config>::DbWeight::get()74 .write75 .saturating_mul(writes),76 ))77 }78 pub fn submit_logs(self) {79 self.recorder.submit_logs()80 }81 pub fn save(self) -> DispatchResult {82 self.recorder.submit_logs();83 <CollectionById<T>>::insert(self.id, self.collection);84 Ok(())85 }86}87impl<T: Config> Deref for CollectionHandle<T> {88 type Target = Collection<T::AccountId>;8990 fn deref(&self) -> &Self::Target {91 &self.collection92 }93}9495impl<T: Config> DerefMut for CollectionHandle<T> {96 fn deref_mut(&mut self) -> &mut Self::Target {97 &mut self.collection98 }99}100101impl<T: Config> CollectionHandle<T> {102 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {103 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);104 Ok(())105 }106 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {107 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))108 }109 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {110 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);111 Ok(())112 }113 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {114 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)115 }116 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {117 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)118 }119 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {120 ensure!(121 <Allowlist<T>>::get((self.id, user)),122 <Error<T>>::AddressNotInAllowlist123 );124 Ok(())125 }126127 pub fn check_can_update_meta(128 &self,129 subject: &T::CrossAccountId,130 item_owner: &T::CrossAccountId,131 ) -> DispatchResult {132 match self.meta_update_permission {133 MetaUpdatePermission::ItemOwner => {134 ensure!(subject == item_owner, <Error<T>>::NoPermission);135 Ok(())136 }137 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),138 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),139 }140 }141}142143#[frame_support::pallet]144pub mod pallet {145 use super::*;146 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};147 use account::CrossAccountId;148 use frame_support::traits::Currency;149 use up_data_structs::TokenId;150 use scale_info::TypeInfo;151152 #[pallet::config]153 pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {154 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;155156 type CrossAccountId: CrossAccountId<Self::AccountId>;157158 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;159 type EvmBackwardsAddressMapping: up_evm_mapping::EvmBackwardsAddressMapping<Self::AccountId>;160161 type Currency: Currency<Self::AccountId>;162 type CollectionCreationPrice: Get<163 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,164 >;165 type TreasuryAccountId: Get<Self::AccountId>;166 }167168 #[pallet::pallet]169 #[pallet::generate_store(pub(super) trait Store)]170 pub struct Pallet<T>(_);171172 #[pallet::extra_constants]173 impl<T: Config> Pallet<T> {174 pub fn collection_admins_limit() -> u32 {175 COLLECTION_ADMINS_LIMIT176 }177 }178179 #[pallet::event]180 #[pallet::generate_deposit(pub fn deposit_event)]181 pub enum Event<T: Config> {182 183 184 185 186 187 188 189 190 191 CollectionCreated(CollectionId, u8, T::AccountId),192193 194 195 196 197 198 CollectionDestroyed(CollectionId),199200 201 202 203 204 205 206 207 208 209 210 211 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),212213 214 215 216 217 218 219 220 221 222 223 224 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),225226 227 228 229 230 231 232 233 234 235 236 237 Transfer(238 CollectionId,239 TokenId,240 T::CrossAccountId,241 T::CrossAccountId,242 u128,243 ),244245 246 247 248 249 250 251 252 253 254 Approved(255 CollectionId,256 TokenId,257 T::CrossAccountId,258 T::CrossAccountId,259 u128,260 ),261 }262263 #[pallet::error]264 pub enum Error<T> {265 266 CollectionNotFound,267 268 MustBeTokenOwner,269 270 NoPermission,271 272 PublicMintingNotAllowed,273 274 AddressNotInAllowlist,275276 277 CollectionNameLimitExceeded,278 279 CollectionDescriptionLimitExceeded,280 281 CollectionTokenPrefixLimitExceeded,282 283 TotalCollectionsLimitExceeded,284 285 TokenVariableDataLimitExceeded,286 287 CollectionAdminCountExceeded,288289 290 TransferNotAllowed,291 292 AccountTokenLimitExceeded,293 294 CollectionTokenLimitExceeded,295 296 MetadataFlagFrozen,297298 299 TokenNotFound,300 301 TokenValueTooLow,302 303 TokenValueNotEnough,304 305 CantApproveMoreThanOwned,306307 308 AddressIsZero,309 310 UnsupportedOperation,311 }312313 #[pallet::storage]314 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;315 #[pallet::storage]316 pub type DestroyedCollectionCount<T> =317 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;318319 320 #[pallet::storage]321 pub type CollectionById<T> = StorageMap<322 Hasher = Blake2_128Concat,323 Key = CollectionId,324 Value = Collection<<T as frame_system::Config>::AccountId>,325 QueryKind = OptionQuery,326 >;327328 #[pallet::storage]329 pub type AdminAmount<T> = StorageMap<330 Hasher = Blake2_128Concat,331 Key = CollectionId,332 Value = u32,333 QueryKind = ValueQuery,334 >;335336 337 #[pallet::storage]338 pub type IsAdmin<T: Config> = StorageNMap<339 Key = (340 Key<Blake2_128Concat, CollectionId>,341 Key<Blake2_128Concat, T::CrossAccountId>,342 ),343 Value = bool,344 QueryKind = ValueQuery,345 >;346347 348 #[pallet::storage]349 pub type Allowlist<T: Config> = StorageNMap<350 Key = (351 Key<Blake2_128Concat, CollectionId>,352 Key<Blake2_128Concat, T::CrossAccountId>,353 ),354 Value = bool,355 QueryKind = ValueQuery,356 >;357358 359 #[pallet::storage]360 pub type DummyStorageValue<T> =361 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;362}363364impl<T: Config> Pallet<T> {365 366 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {367 ensure!(368 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,369 <Error<T>>::AddressIsZero370 );371 Ok(())372 }373 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {374 <IsAdmin<T>>::iter_prefix((collection,))375 .map(|(a, _)| a)376 .collect()377 }378 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {379 <Allowlist<T>>::iter_prefix((collection,))380 .map(|(a, _)| a)381 .collect()382 }383 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {384 <Allowlist<T>>::get((collection, user))385 }386 pub fn collection_stats() -> CollectionStats {387 let created = <CreatedCollectionCount<T>>::get();388 let destroyed = <DestroyedCollectionCount<T>>::get();389 CollectionStats {390 created: created.0,391 destroyed: destroyed.0,392 alive: created.0 - destroyed.0,393 }394 }395}396397impl<T: Config> Pallet<T> {398 pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {399 {400 ensure!(401 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,402 Error::<T>::CollectionTokenPrefixLimitExceeded403 );404 }405406 let created_count = <CreatedCollectionCount<T>>::get()407 .0408 .checked_add(1)409 .ok_or(ArithmeticError::Overflow)?;410 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;411 let id = CollectionId(created_count);412413 414 ensure!(415 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,416 <Error<T>>::TotalCollectionsLimitExceeded417 );418419 420421 422 {423 let mut imbalance =424 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();425 imbalance.subsume(426 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(427 &T::TreasuryAccountId::get(),428 T::CollectionCreationPrice::get(),429 ),430 );431 <T as Config>::Currency::settle(432 &data.owner,433 imbalance,434 WithdrawReasons::TRANSFER,435 ExistenceRequirement::KeepAlive,436 )437 .map_err(|_| Error::<T>::NoPermission)?;438 }439440 <CreatedCollectionCount<T>>::put(created_count);441 <Pallet<T>>::deposit_event(Event::CollectionCreated(442 id,443 data.mode.id(),444 data.owner.clone(),445 ));446 <CollectionById<T>>::insert(id, data);447 Ok(id)448 }449450 pub fn destroy_collection(451 collection: CollectionHandle<T>,452 sender: &T::CrossAccountId,453 ) -> DispatchResult {454 ensure!(455 collection.limits.owner_can_destroy(),456 <Error<T>>::NoPermission,457 );458 collection.check_is_owner(sender)?;459460 let destroyed_collections = <DestroyedCollectionCount<T>>::get()461 .0462 .checked_add(1)463 .ok_or(ArithmeticError::Overflow)?;464465 466467 <DestroyedCollectionCount<T>>::put(destroyed_collections);468 <CollectionById<T>>::remove(collection.id);469 <AdminAmount<T>>::remove(collection.id);470 <IsAdmin<T>>::remove_prefix((collection.id,), None);471 <Allowlist<T>>::remove_prefix((collection.id,), None);472473 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));474 Ok(())475 }476477 pub fn toggle_allowlist(478 collection: &CollectionHandle<T>,479 sender: &T::CrossAccountId,480 user: &T::CrossAccountId,481 allowed: bool,482 ) -> DispatchResult {483 collection.check_is_owner_or_admin(sender)?;484485 486487 if allowed {488 <Allowlist<T>>::insert((collection.id, user), true);489 } else {490 <Allowlist<T>>::remove((collection.id, user));491 }492493 Ok(())494 }495496 pub fn toggle_admin(497 collection: &CollectionHandle<T>,498 sender: &T::CrossAccountId,499 user: &T::CrossAccountId,500 admin: bool,501 ) -> DispatchResult {502 collection.check_is_owner_or_admin(sender)?;503504 let was_admin = <IsAdmin<T>>::get((collection.id, user));505 if was_admin == admin {506 return Ok(());507 }508 let amount = <AdminAmount<T>>::get(collection.id);509510 if admin {511 let amount = amount512 .checked_add(1)513 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;514 ensure!(515 amount <= Self::collection_admins_limit(),516 <Error<T>>::CollectionAdminCountExceeded,517 );518519 520521 <AdminAmount<T>>::insert(collection.id, amount);522 <IsAdmin<T>>::insert((collection.id, user), true);523 } else {524 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));525 <IsAdmin<T>>::remove((collection.id, user));526 }527528 Ok(())529 }530}531532#[macro_export]533macro_rules! unsupported {534 () => {535 Err(<Error<T>>::UnsupportedOperation.into())536 };537}538539540pub trait CommonWeightInfo {541 fn create_item() -> Weight;542 fn create_multiple_items(amount: u32) -> Weight;543 fn burn_item() -> Weight;544 fn transfer() -> Weight;545 fn approve() -> Weight;546 fn transfer_from() -> Weight;547 fn burn_from() -> Weight;548 fn set_variable_metadata(bytes: u32) -> Weight;549}550551pub trait CommonCollectionOperations<T: Config> {552 fn create_item(553 &self,554 sender: T::CrossAccountId,555 to: T::CrossAccountId,556 data: CreateItemData,557 ) -> DispatchResultWithPostInfo;558 fn create_multiple_items(559 &self,560 sender: T::CrossAccountId,561 to: T::CrossAccountId,562 data: Vec<CreateItemData>,563 ) -> DispatchResultWithPostInfo;564 fn burn_item(565 &self,566 sender: T::CrossAccountId,567 token: TokenId,568 amount: u128,569 ) -> DispatchResultWithPostInfo;570571 fn transfer(572 &self,573 sender: T::CrossAccountId,574 to: T::CrossAccountId,575 token: TokenId,576 amount: u128,577 ) -> DispatchResultWithPostInfo;578 fn approve(579 &self,580 sender: T::CrossAccountId,581 spender: T::CrossAccountId,582 token: TokenId,583 amount: u128,584 ) -> DispatchResultWithPostInfo;585 fn transfer_from(586 &self,587 sender: T::CrossAccountId,588 from: T::CrossAccountId,589 to: T::CrossAccountId,590 token: TokenId,591 amount: u128,592 ) -> DispatchResultWithPostInfo;593 fn burn_from(594 &self,595 sender: T::CrossAccountId,596 from: T::CrossAccountId,597 token: TokenId,598 amount: u128,599 ) -> DispatchResultWithPostInfo;600601 fn set_variable_metadata(602 &self,603 sender: T::CrossAccountId,604 token: TokenId,605 data: BoundedVec<u8, CustomDataLimit>,606 ) -> DispatchResultWithPostInfo;607608 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;609 fn token_exists(&self, token: TokenId) -> bool;610 fn last_token_id(&self) -> TokenId;611612 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;613 fn const_metadata(&self, token: TokenId) -> Vec<u8>;614 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;615616 617 fn collection_tokens(&self) -> u32;618 619 fn account_balance(&self, account: T::CrossAccountId) -> u32;620 621 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;622 fn allowance(623 &self,624 sender: T::CrossAccountId,625 spender: T::CrossAccountId,626 token: TokenId,627 ) -> u128;628}629630631pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {632 let post_info = PostDispatchInfo {633 actual_weight: Some(weight),634 pays_fee: Pays::Yes,635 };636 match res {637 Ok(()) => Ok(post_info),638 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),639 }640}