123456#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9 clippy::too_many_arguments,10 clippy::unnecessary_mut_passed,11 clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19 construct_runtime, decl_module, decl_storage, decl_error,20 dispatch::DispatchResult,21 ensure, fail, parameter_types,22 traits::{23 ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24 IsSubType, WithdrawReasons,25 },26 weights::{27 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29 WeightToFeePolynomial, DispatchClass,30 },31 StorageValue, transactional,32 pallet_prelude::DispatchResultWithPostInfo,33};34use frame_system::{self as system, ensure_signed};35use sp_runtime::{sp_std::prelude::Vec};36use nft_data_structs::{37 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,38 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,39 OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,40 CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,41};42use pallet_common::{43 account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,44 Error as CommonError, CommonWeightInfo, Allowlist,45};46use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};47use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};48use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};4950#[cfg(test)]51mod mock;5253#[cfg(test)]54mod tests;5556mod eth;57mod sponsorship;58pub use sponsorship::NftSponsorshipHandler;59pub use eth::sponsoring::NftEthSponsorshipHandler;6061pub use eth::NftErcSupport;6263pub mod common;64use common::CommonWeights;65pub mod dispatch;66use dispatch::dispatch_call;6768#[cfg(feature = "runtime-benchmarks")]69mod benchmarking;70pub mod weights;71use weights::WeightInfo;7273decl_error! {74 75 pub enum Error for Module<T: Config> {76 77 CollectionDecimalPointLimitExceeded,78 79 ConfirmUnsetSponsorFail,80 81 EmptyArgument,82 83 CollectionLimitBoundsExceeded,84 85 OwnerPermissionsCantBeReverted,86 }87}88pub trait Config:89 system::Config90 + pallet_evm_coder_substrate::Config91 + pallet_common::Config92 + pallet_nonfungible::Config93 + pallet_refungible::Config94 + pallet_fungible::Config95 + Sized96{97 98 type WeightInfo: WeightInfo;99}100101type SelfWeightOf<T> = <T as Config>::WeightInfo;102103104105106107108109110111112113114115116117118119120121122123124125decl_storage! {126 trait Store for Module<T: Config> as Nft {127128 129 130 ChainVersion: u64;131 132133 134 135 136 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;137 138 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;139 140 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;141 142 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;143 144145 146 147 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;148 }149}150151decl_module! {152 pub struct Module<T: Config> for enum Call153 where154 origin: T::Origin155 {156 const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;157 type Error = Error<T>;158159 fn on_initialize(_now: T::BlockNumber) -> Weight {160 0161 }162163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 #[weight = <SelfWeightOf<T>>::create_collection()]180 #[transactional]181 pub fn create_collection(origin,182 collection_name: Vec<u16>,183 collection_description: Vec<u16>,184 token_prefix: Vec<u8>,185 mode: CollectionMode) -> DispatchResult {186187 188 let who = ensure_signed(origin)?;189190 let limits = CollectionLimits::<T::BlockNumber> {191 sponsored_data_size: CUSTOM_DATA_LIMIT,192 ..Default::default()193 };194195 196 let new_collection = Collection::<T> {197 owner: who.clone(),198 name: collection_name,199 mode: mode.clone(),200 mint_mode: false,201 access: AccessMode::Normal,202 description: collection_description,203 token_prefix,204 offchain_schema: Vec::new(),205 schema_version: SchemaVersion::ImageURL,206 sponsorship: SponsorshipState::Disabled,207 variable_on_chain_schema: Vec::new(),208 const_on_chain_schema: Vec::new(),209 limits,210 transfers_enabled: true,211 meta_update_permission: Default::default(),212 };213214 let _id = match mode {215 CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},216 CollectionMode::Fungible(decimal_points) => {217 218 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);219 PalletFungible::init_collection(new_collection)?220 }221 CollectionMode::ReFungible => {222 PalletRefungible::init_collection(new_collection)?223 }224 };225226 Ok(())227 }228229 230 231 232 233 234 235 236 237 238 #[weight = <SelfWeightOf<T>>::destroy_collection()]239 #[transactional]240 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {241 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);242243 let collection = <CollectionHandle<T>>::try_get(collection_id)?;244 collection.check_is_owner(&sender)?;245246 247248 match collection.mode {249 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,250 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,251 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,252 }253254 <NftTransferBasket<T>>::remove_prefix(collection_id, None);255 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);256 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);257258 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);259260 Ok(())261 }262263 264 265 266 267 268 269 270 271 272 273 274 275 #[weight = <SelfWeightOf<T>>::add_to_white_list()]276 #[transactional]277 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{278279 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);280 let collection = <CollectionHandle<T>>::try_get(collection_id)?;281282 <PalletCommon<T>>::toggle_allowlist(283 &collection,284 &sender,285 &address,286 true,287 )?;288289 Ok(())290 }291292 293 294 295 296 297 298 299 300 301 302 303 304 #[weight = <SelfWeightOf<T>>::remove_from_white_list()]305 #[transactional]306 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{307308 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);309 let collection = <CollectionHandle<T>>::try_get(collection_id)?;310311 <PalletCommon<T>>::toggle_allowlist(312 &collection,313 &sender,314 &address,315 false,316 )?;317318 Ok(())319 }320321 322 323 324 325 326 327 328 329 330 331 332 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]333 #[transactional]334 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult335 {336 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);337338 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;339 target_collection.check_is_owner(&sender)?;340341 target_collection.access = mode;342 target_collection.save()343 }344345 346 347 348 349 350 351 352 353 354 355 356 357 358 #[weight = <SelfWeightOf<T>>::set_mint_permission()]359 #[transactional]360 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult361 {362 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);363364 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;365 target_collection.check_is_owner(&sender)?;366367 target_collection.mint_mode = mint_permission;368 target_collection.save()369 }370371 372 373 374 375 376 377 378 379 380 381 382 #[weight = <SelfWeightOf<T>>::change_collection_owner()]383 #[transactional]384 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {385386 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);387388 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;389 target_collection.check_is_owner(&sender)?;390391 target_collection.owner = new_owner;392 target_collection.save()393 }394395 396 397 398 399 400 401 402 403 404 405 406 407 408 #[weight = <SelfWeightOf<T>>::add_collection_admin()]409 #[transactional]410 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {411 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);412413 let collection = <CollectionHandle<T>>::try_get(collection_id)?;414 collection.check_is_owner_or_admin(&sender)?;415416 <IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);417 Ok(())418 }419420 421 422 423 424 425 426 427 428 429 430 431 432 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]433 #[transactional]434 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {435 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);436437 let collection = <CollectionHandle<T>>::try_get(collection_id)?;438 collection.check_is_owner_or_admin(&sender)?;439440 <IsAdmin<T>>::remove((collection_id, account_id.as_sub()));441 Ok(())442 }443444 445 446 447 448 449 450 451 452 453 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]454 #[transactional]455 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {456 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);457458 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;459 target_collection.check_is_owner_or_admin(&sender)?;460461 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);462 target_collection.save()463 }464465 466 467 468 469 470 471 472 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]473 #[transactional]474 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {475 let sender = ensure_signed(origin)?;476477 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;478 ensure!(479 target_collection.sponsorship.pending_sponsor() == Some(&sender),480 Error::<T>::ConfirmUnsetSponsorFail481 );482483 target_collection.sponsorship = SponsorshipState::Confirmed(sender);484 target_collection.save()485 }486487 488 489 490 491 492 493 494 495 496 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]497 #[transactional]498 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {499 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);500501 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;502 target_collection.check_is_owner(&sender)?;503504 target_collection.sponsorship = SponsorshipState::Disabled;505 target_collection.save()506 }507508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 #[weight = <CommonWeights<T>>::create_item()]527 #[transactional]528 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {529 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);530531 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))532 }533534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]553 #[transactional]554 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {555 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);556 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);557558 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))559 }560561 562563 564 565 566 567 568 569 570 571 572 573 574 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]575 #[transactional]576 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {577 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);578 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;579 target_collection.check_is_owner(&sender)?;580581 582583 target_collection.transfers_enabled = value;584 target_collection.save()585 }586587 588 589 590 591 592 593 594 595 596 597 598 599 600 #[weight = <CommonWeights<T>>::burn_item()]601 #[transactional]602 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {603 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);604605 dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))606 }607608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 #[weight = <CommonWeights<T>>::transfer()]632 #[transactional]633 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {634 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);635636 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))637 }638639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 #[weight = <CommonWeights<T>>::approve()]655 #[transactional]656 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {657 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);658659 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))660 }661662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 #[weight = <CommonWeights<T>>::transfer_from()]682 #[transactional]683 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {684 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);685686 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))687 }688689 690 691 692 693 694 695 696 697 698 699 700 701 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]702 #[transactional]703 pub fn set_variable_meta_data (704 origin,705 collection_id: CollectionId,706 item_id: TokenId,707 data: Vec<u8>708 ) -> DispatchResultWithPostInfo {709 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);710711 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))712 }713714 715 716 717 718 719 720 721 722 723 724 725 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]726 #[transactional]727 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {728 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);729 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;730731 ensure!(732 target_collection.meta_update_permission != MetaUpdatePermission::None,733 <CommonError<T>>::MetadataFlagFrozen,734 );735 target_collection.check_is_owner(&sender)?;736737 target_collection.meta_update_permission = value;738739 target_collection.save()740 }741742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 #[weight = <SelfWeightOf<T>>::set_schema_version()]757 #[transactional]758 pub fn set_schema_version(759 origin,760 collection_id: CollectionId,761 version: SchemaVersion762 ) -> DispatchResult {763 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);764 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;765 target_collection.check_is_owner_or_admin(&sender)?;766 target_collection.schema_version = version;767 target_collection.save()768 }769770 771 772 773 774 775 776 777 778 779 780 781 782 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]783 #[transactional]784 pub fn set_offchain_schema(785 origin,786 collection_id: CollectionId,787 schema: Vec<u8>788 ) -> DispatchResult {789 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);790 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;791 target_collection.check_is_owner_or_admin(&sender)?;792793 794 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");795796 target_collection.offchain_schema = schema;797 target_collection.save()798 }799800 801 802 803 804 805 806 807 808 809 810 811 812 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]813 #[transactional]814 pub fn set_const_on_chain_schema (815 origin,816 collection_id: CollectionId,817 schema: Vec<u8>818 ) -> DispatchResult {819 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);820 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;821 target_collection.check_is_owner_or_admin(&sender)?;822823 824 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");825826 target_collection.const_on_chain_schema = schema;827 target_collection.save()828 }829830 831 832 833 834 835 836 837 838 839 840 841 842 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]843 #[transactional]844 pub fn set_variable_on_chain_schema (845 origin,846 collection_id: CollectionId,847 schema: Vec<u8>848 ) -> DispatchResult {849 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);850 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;851 target_collection.check_is_owner_or_admin(&sender)?;852853 854 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");855856 target_collection.variable_on_chain_schema = schema;857 target_collection.save()858 }859860 #[weight = <SelfWeightOf<T>>::set_collection_limits()]861 #[transactional]862 pub fn set_collection_limits(863 origin,864 collection_id: CollectionId,865 new_limits: CollectionLimits<T::BlockNumber>,866 ) -> DispatchResult {867 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);868 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;869 target_collection.check_is_owner(&sender)?;870 let old_limits = &target_collection.limits;871872 873 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&874 new_limits.account_token_ownership_limit.unwrap_or(0) <= MAX_TOKEN_OWNERSHIP &&875 new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,876 Error::<T>::CollectionLimitBoundsExceeded);877878 879 ensure!(old_limits.token_limit >= new_limits.token_limit, <CommonError<T>>::CollectionTokenLimitExceeded);880 ensure!(new_limits.token_limit > 0, <CommonError<T>>::CollectionTokenLimitExceeded);881882 ensure!(883 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&884 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),885 Error::<T>::OwnerPermissionsCantBeReverted,886 );887888 target_collection.limits = new_limits;889890 target_collection.save()891 }892 }893}894895896impl<T: Config> Pallet<T> {897 pub fn adminlist(collection: CollectionId) -> Vec<T::AccountId> {898 <IsAdmin<T>>::iter_prefix((collection,))899 .map(|(a, _)| a)900 .collect()901 }902 pub fn allowlist(collection: CollectionId) -> Vec<T::AccountId> {903 <Allowlist<T>>::iter_prefix((collection,))904 .map(|(a, _)| a)905 .collect()906 }907}