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 180 181 182 183 184 185 186 187 188 CollectionCreated(CollectionId, u8, T::AccountId),189190 191 192 193 194 195 CollectionDestroyed(CollectionId),196197 198 199 200 201 202 203 204 205 206 207 208 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),209210 211 212 213 214 215 216 217 218 219 220 221 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),222223 224 225 226 227 228 229 230 231 232 233 234 Transfer(235 CollectionId,236 TokenId,237 T::CrossAccountId,238 T::CrossAccountId,239 u128,240 ),241242 243 244 245 246 247 248 249 250 251 Approved(252 CollectionId,253 TokenId,254 T::CrossAccountId,255 T::CrossAccountId,256 u128,257 ),258 }259260 #[pallet::error]261 pub enum Error<T> {262 263 CollectionNotFound,264 265 MustBeTokenOwner,266 267 NoPermission,268 269 PublicMintingNotAllowed,270 271 AddressNotInAllowlist,272273 274 CollectionNameLimitExceeded,275 276 CollectionDescriptionLimitExceeded,277 278 CollectionTokenPrefixLimitExceeded,279 280 TotalCollectionsLimitExceeded,281 282 TokenVariableDataLimitExceeded,283 284 CollectionAdminCountExceeded,285286 287 TransferNotAllowed,288 289 AccountTokenLimitExceeded,290 291 CollectionTokenLimitExceeded,292 293 MetadataFlagFrozen,294295 296 TokenNotFound,297 298 TokenValueTooLow,299 300 ApprovedValueTooLow,301 302 CantApproveMoreThanOwned,303304 305 AddressIsZero,306 307 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 317 #[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 334 #[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 345 #[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 356 #[pallet::storage]357 pub type DummyStorageValue<T> =358 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;359}360361impl<T: Config> Pallet<T> {362 363 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 419 ensure!(420 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,421 <Error<T>>::TotalCollectionsLimitExceeded422 );423424 425426 427 {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}543544545pub 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 622 fn collection_tokens(&self) -> u32;623 624 fn account_balance(&self, account: T::CrossAccountId) -> u32;625 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}634635636pub 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}