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 scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use nft_data_structs::{38 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39 VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,40 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,41 NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,42 CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,43};44use pallet_common::{45 account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,46 Error as CommonError, CommonWeightInfo, Allowlist,47};48use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};49use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};50use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5152#[cfg(test)]53mod mock;5455#[cfg(test)]56mod tests;5758mod eth;59mod sponsorship;60pub use sponsorship::NftSponsorshipHandler;61pub use eth::sponsoring::NftEthSponsorshipHandler;6263pub use eth::NftErcSupport;6465pub mod common;66use common::CommonWeights;67pub mod dispatch;68use dispatch::dispatch_call;6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72pub mod weights;73use weights::WeightInfo;7475decl_error! {76 77 pub enum Error for Module<T: Config> {78 79 CollectionDecimalPointLimitExceeded,80 81 ConfirmUnsetSponsorFail,82 83 EmptyArgument,84 85 CollectionLimitBoundsExceeded,86 87 OwnerPermissionsCantBeReverted,88 }89}90pub trait Config:91 system::Config92 + pallet_evm_coder_substrate::Config93 + pallet_common::Config94 + pallet_nonfungible::Config95 + pallet_refungible::Config96 + pallet_fungible::Config97 + Sized98 + TypeInfo99{100 101 type WeightInfo: WeightInfo;102}103104type SelfWeightOf<T> = <T as Config>::WeightInfo;105106107108109110111112113114115116117118119120121122123124125126127128decl_storage! {129 trait Store for Module<T: Config> as Nft {130131 132 133 ChainVersion: u64;134 135136 137 138 139 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;140 141 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;142 143 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;144 145 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;146 147148 149 150 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;151 }152}153154decl_module! {155 pub struct Module<T: Config> for enum Call156 where157 origin: T::Origin158 {159 type Error = Error<T>;160161 fn on_initialize(_now: T::BlockNumber) -> Weight {162 0163 }164165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 #[weight = <SelfWeightOf<T>>::create_collection()]182 #[transactional]183 pub fn create_collection(origin,184 collection_name: Vec<u16>,185 collection_description: Vec<u16>,186 token_prefix: Vec<u8>,187 mode: CollectionMode) -> DispatchResult {188189 190 let who = ensure_signed(origin)?;191192 193 let new_collection = Collection::<T> {194 owner: who.clone(),195 name: collection_name,196 mode: mode.clone(),197 mint_mode: false,198 access: AccessMode::Normal,199 description: collection_description,200 token_prefix,201 offchain_schema: Vec::new(),202 schema_version: SchemaVersion::ImageURL,203 sponsorship: SponsorshipState::Disabled,204 variable_on_chain_schema: Vec::new(),205 const_on_chain_schema: Vec::new(),206 limits: Default::default(),207 meta_update_permission: Default::default(),208 };209210 let _id = match mode {211 CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},212 CollectionMode::Fungible(decimal_points) => {213 214 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);215 PalletFungible::init_collection(new_collection)?216 }217 CollectionMode::ReFungible => {218 PalletRefungible::init_collection(new_collection)?219 }220 };221222 Ok(())223 }224225 226 227 228 229 230 231 232 233 234 #[weight = <SelfWeightOf<T>>::destroy_collection()]235 #[transactional]236 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {237 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);238239 let collection = <CollectionHandle<T>>::try_get(collection_id)?;240 collection.check_is_owner(&sender)?;241242 243244 match collection.mode {245 CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,246 CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,247 CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,248 }249250 <NftTransferBasket<T>>::remove_prefix(collection_id, None);251 <FungibleTransferBasket<T>>::remove_prefix(collection_id, None);252 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);253254 <VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);255256 Ok(())257 }258259 260 261 262 263 264 265 266 267 268 269 270 271 #[weight = <SelfWeightOf<T>>::add_to_allow_list()]272 #[transactional]273 pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{274275 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);276 let collection = <CollectionHandle<T>>::try_get(collection_id)?;277278 <PalletCommon<T>>::toggle_allowlist(279 &collection,280 &sender,281 &address,282 true,283 )?;284285 Ok(())286 }287288 289 290 291 292 293 294 295 296 297 298 299 300 #[weight = <SelfWeightOf<T>>::remove_from_allow_list()]301 #[transactional]302 pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{303304 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);305 let collection = <CollectionHandle<T>>::try_get(collection_id)?;306307 <PalletCommon<T>>::toggle_allowlist(308 &collection,309 &sender,310 &address,311 false,312 )?;313314 Ok(())315 }316317 318 319 320 321 322 323 324 325 326 327 328 #[weight = <SelfWeightOf<T>>::set_public_access_mode()]329 #[transactional]330 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult331 {332 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);333334 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;335 target_collection.check_is_owner(&sender)?;336337 target_collection.access = mode;338 target_collection.save()339 }340341 342 343 344 345 346 347 348 349 350 351 352 353 354 #[weight = <SelfWeightOf<T>>::set_mint_permission()]355 #[transactional]356 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult357 {358 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);359360 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;361 target_collection.check_is_owner(&sender)?;362363 target_collection.mint_mode = mint_permission;364 target_collection.save()365 }366367 368 369 370 371 372 373 374 375 376 377 378 #[weight = <SelfWeightOf<T>>::change_collection_owner()]379 #[transactional]380 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {381382 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);383384 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;385 target_collection.check_is_owner(&sender)?;386387 target_collection.owner = new_owner;388 target_collection.save()389 }390391 392 393 394 395 396 397 398 399 400 401 402 403 404 #[weight = <SelfWeightOf<T>>::add_collection_admin()]405 #[transactional]406 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {407 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);408 let collection = <CollectionHandle<T>>::try_get(collection_id)?;409410 <PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)411 }412413 414 415 416 417 418 419 420 421 422 423 424 425 #[weight = <SelfWeightOf<T>>::remove_collection_admin()]426 #[transactional]427 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {428 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);429 let collection = <CollectionHandle<T>>::try_get(collection_id)?;430431 <PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)432 }433434 435 436 437 438 439 440 441 442 443 #[weight = <SelfWeightOf<T>>::set_collection_sponsor()]444 #[transactional]445 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {446 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);447448 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;449 target_collection.check_is_owner(&sender)?;450451 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);452 target_collection.save()453 }454455 456 457 458 459 460 461 462 #[weight = <SelfWeightOf<T>>::confirm_sponsorship()]463 #[transactional]464 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {465 let sender = ensure_signed(origin)?;466467 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;468 ensure!(469 target_collection.sponsorship.pending_sponsor() == Some(&sender),470 Error::<T>::ConfirmUnsetSponsorFail471 );472473 target_collection.sponsorship = SponsorshipState::Confirmed(sender);474 target_collection.save()475 }476477 478 479 480 481 482 483 484 485 486 #[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]487 #[transactional]488 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {489 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);490491 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;492 target_collection.check_is_owner(&sender)?;493494 target_collection.sponsorship = SponsorshipState::Disabled;495 target_collection.save()496 }497498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 #[weight = <CommonWeights<T>>::create_item()]517 #[transactional]518 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {519 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);520521 dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))522 }523524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 #[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]543 #[transactional]544 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {545 ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);546 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);547548 dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))549 }550551 552553 554 555 556 557 558 559 560 561 562 563 564 #[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]565 #[transactional]566 pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {567 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);568 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;569 target_collection.check_is_owner(&sender)?;570571 572573 target_collection.limits.transfers_enabled = Some(value);574 target_collection.save()575 }576577 578 579 580 581 582 583 584 585 586 587 588 589 590 #[weight = <CommonWeights<T>>::burn_item()]591 #[transactional]592 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {593 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);594595 dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))596 }597598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 #[weight = <CommonWeights<T>>::burn_from()]615 #[transactional]616 pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {617 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);618619 dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))620 }621622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 #[weight = <CommonWeights<T>>::transfer()]646 #[transactional]647 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {648 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);649650 dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))651 }652653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 #[weight = <CommonWeights<T>>::approve()]669 #[transactional]670 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {671 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);672673 dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))674 }675676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 #[weight = <CommonWeights<T>>::transfer_from()]696 #[transactional]697 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {698 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);699700 dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))701 }702703 704 705 706 707 708 709 710 711 712 713 714 715 #[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]716 #[transactional]717 pub fn set_variable_meta_data (718 origin,719 collection_id: CollectionId,720 item_id: TokenId,721 data: Vec<u8>722 ) -> DispatchResultWithPostInfo {723 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);724725 dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))726 }727728 729 730 731 732 733 734 735 736 737 738 739 #[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]740 #[transactional]741 pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {742 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);743 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;744745 ensure!(746 target_collection.meta_update_permission != MetaUpdatePermission::None,747 <CommonError<T>>::MetadataFlagFrozen,748 );749 target_collection.check_is_owner(&sender)?;750751 target_collection.meta_update_permission = value;752753 target_collection.save()754 }755756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 #[weight = <SelfWeightOf<T>>::set_schema_version()]771 #[transactional]772 pub fn set_schema_version(773 origin,774 collection_id: CollectionId,775 version: SchemaVersion776 ) -> DispatchResult {777 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);778 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;779 target_collection.check_is_owner_or_admin(&sender)?;780 target_collection.schema_version = version;781 target_collection.save()782 }783784 785 786 787 788 789 790 791 792 793 794 795 796 #[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]797 #[transactional]798 pub fn set_offchain_schema(799 origin,800 collection_id: CollectionId,801 schema: Vec<u8>802 ) -> DispatchResult {803 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);804 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;805 target_collection.check_is_owner_or_admin(&sender)?;806807 808 ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");809810 target_collection.offchain_schema = schema;811 target_collection.save()812 }813814 815 816 817 818 819 820 821 822 823 824 825 826 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]827 #[transactional]828 pub fn set_const_on_chain_schema (829 origin,830 collection_id: CollectionId,831 schema: Vec<u8>832 ) -> DispatchResult {833 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);834 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;835 target_collection.check_is_owner_or_admin(&sender)?;836837 838 ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");839840 target_collection.const_on_chain_schema = schema;841 target_collection.save()842 }843844 845 846 847 848 849 850 851 852 853 854 855 856 #[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]857 #[transactional]858 pub fn set_variable_on_chain_schema (859 origin,860 collection_id: CollectionId,861 schema: Vec<u8>862 ) -> DispatchResult {863 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);864 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;865 target_collection.check_is_owner_or_admin(&sender)?;866867 868 ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");869870 target_collection.variable_on_chain_schema = schema;871 target_collection.save()872 }873874 #[weight = <SelfWeightOf<T>>::set_collection_limits()]875 #[transactional]876 pub fn set_collection_limits(877 origin,878 collection_id: CollectionId,879 new_limit: CollectionLimits,880 ) -> DispatchResult {881 let mut new_limit = new_limit;882 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);883 let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;884 target_collection.check_is_owner(&sender)?;885 let old_limit = &target_collection.limits;886887 macro_rules! limit_default {888 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{889 $(890 if let Some($new) = $new.$field {891 let $old = $old.$field($($arg)?);892 let _ = $new;893 let _ = $old;894 $check895 } else {896 $new.$field = $old.$field897 }898 )*899 }};900 }901902 limit_default!(old_limit, new_limit,903 account_token_ownership_limit => ensure!(904 new_limit <= MAX_TOKEN_OWNERSHIP,905 <Error<T>>::CollectionLimitBoundsExceeded,906 ),907 sponsor_transfer_timeout(match target_collection.mode {908 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,909 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,910 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,911 }) => ensure!(912 new_limit <= MAX_SPONSOR_TIMEOUT,913 <Error<T>>::CollectionLimitBoundsExceeded,914 ),915 sponsored_data_size => ensure!(916 new_limit <= CUSTOM_DATA_LIMIT,917 <Error<T>>::CollectionLimitBoundsExceeded,918 ),919 token_limit => ensure!(920 old_limit >= new_limit && new_limit > 0,921 <CommonError<T>>::CollectionTokenLimitExceeded922 ),923 owner_can_transfer => ensure!(924 old_limit || !new_limit,925 <Error<T>>::OwnerPermissionsCantBeReverted,926 ),927 owner_can_destroy => ensure!(928 old_limit || !new_limit,929 <Error<T>>::OwnerPermissionsCantBeReverted,930 ),931 sponsored_data_rate_limit => {},932 transfers_enabled => {},933 );934935 target_collection.limits = new_limit;936937 target_collection.save()938 }939 }940}941942943impl<T: Config> Pallet<T> {944 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {945 <IsAdmin<T>>::iter_prefix((collection,))946 .map(|(a, _)| a)947 .collect()948 }949 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {950 <Allowlist<T>>::iter_prefix((collection,))951 .map(|(a, _)| a)952 .collect()953 }954}