123456#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use serde::*;1213use core::ops::{Deref, DerefMut};14use codec::{Decode, Encode};15pub use frame_support::{16 construct_runtime, decl_event, decl_module, decl_storage, decl_error,17 dispatch::DispatchResult,18 ensure, fail, parameter_types,19 traits::{20 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,21 Randomness, IsSubType, WithdrawReasons,22 },23 weights::{24 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},25 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,26 WeightToFeePolynomial, DispatchClass,27 },28 StorageValue,29 transactional,30};3132use frame_system::{self as system, ensure_signed, ensure_root};33use sp_core::{H160, H256};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 Hash, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use sp_runtime::traits::StaticLookup;45use pallet_contracts::chain_extension::UncheckedFrom;46use pallet_ethereum::EthereumTransactionSender;47use pallet_transaction_payment::OnChargeTransaction;4849#[cfg(test)]50mod mock;5152#[cfg(test)]53mod tests;5455mod default_weights;56mod eth;5758pub use eth::NftErcSupport;59pub use eth::account::*;6061pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;62pub const MAX_REFUNGIBLE_PIECES: u128 = 1_000_000_000_000_000_000_000;63pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;64pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;6566676869pub type CollectionId = u32;70pub type TokenId = u32;71pub type DecimalPoints = u8;7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]75pub enum CollectionMode {76 Invalid,77 NFT,78 79 Fungible(DecimalPoints),80 ReFungible,81}8283impl Default for CollectionMode {84 fn default() -> Self {85 Self::Invalid86 }87}8889impl Into<u8> for CollectionMode {90 fn into(self) -> u8 {91 match self {92 CollectionMode::Invalid => 0,93 CollectionMode::NFT => 1,94 CollectionMode::Fungible(_) => 2,95 CollectionMode::ReFungible => 3,96 }97 }98}99100#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]101#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]102pub enum AccessMode {103 Normal,104 WhiteList,105}106impl Default for AccessMode {107 fn default() -> Self {108 Self::Normal109 }110}111112#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]113#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]114pub enum SchemaVersion {115 ImageURL,116 Unique,117}118impl Default for SchemaVersion {119 fn default() -> Self {120 Self::ImageURL121 }122}123124#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]125#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]126pub struct Ownership<AccountId> {127 pub owner: AccountId,128 pub fraction: u128,129}130131#[derive(Encode, Decode, Debug, Clone, PartialEq)]132#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]133pub enum SponsorshipState<AccountId> {134 135 Disabled,136 Unconfirmed(AccountId),137 138 Confirmed(AccountId),139}140141impl<AccountId> SponsorshipState<AccountId> {142 fn sponsor(&self) -> Option<&AccountId> {143 match self {144 Self::Confirmed(sponsor) => Some(sponsor),145 _ => None,146 }147 }148149 fn pending_sponsor(&self) -> Option<&AccountId> {150 match self {151 Self::Unconfirmed(sponsor) | Self::Confirmed(sponsor) => Some(sponsor),152 _ => None,153 }154 }155156 fn confirmed(&self) -> bool {157 matches!(self, Self::Confirmed(_))158 }159}160161impl<T> Default for SponsorshipState<T> {162 fn default() -> Self {163 Self::Disabled164 }165}166167#[derive(Encode, Decode, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct Collection<T: Config> {170 pub owner: T::CrossAccountId,171 pub mode: CollectionMode,172 pub access: AccessMode,173 pub decimal_points: DecimalPoints,174 pub name: Vec<u16>, 175 pub description: Vec<u16>, 176 pub token_prefix: Vec<u8>, 177 pub mint_mode: bool,178 pub offchain_schema: Vec<u8>,179 pub schema_version: SchemaVersion,180 pub sponsorship: SponsorshipState<T::AccountId>,181 pub limits: CollectionLimits<T::BlockNumber>, 182 pub variable_on_chain_schema: Vec<u8>, 183 pub const_on_chain_schema: Vec<u8>, 184}185186pub struct CollectionHandle<T: Config> {187 pub id: CollectionId,188 collection: Collection<T>,189 logs: eth::log::LogRecorder,190}191impl<T: Config> CollectionHandle<T> {192 pub fn get(id: CollectionId) -> Option<Self> {193 <CollectionById<T>>::get(id)194 .map(|collection| Self {195 id,196 collection,197 logs: eth::log::LogRecorder::default(),198 })199 }200 pub fn log(&self, topics: Vec<H256>, data: eth::abi::AbiWriter) {201 self.logs.log(topics, data)202 }203 pub fn into_inner(self) -> Collection<T> {204 self.collection.clone()205 }206}207208impl<T: Config> Deref for CollectionHandle<T> {209 type Target = Collection<T>;210211 fn deref(&self) -> &Self::Target {212 &self.collection213 }214}215216impl<T: Config> DerefMut for CollectionHandle<T> {217 fn deref_mut(&mut self) -> &mut Self::Target {218 &mut self.collection219 }220}221222#[derive(Encode, Decode, Debug, Clone, PartialEq)]223#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]224pub struct NftItemType<AccountId> {225 pub owner: AccountId,226 pub const_data: Vec<u8>,227 pub variable_data: Vec<u8>,228}229230#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]231#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]232pub struct FungibleItemType {233 pub value: u128,234}235236#[derive(Encode, Decode, Debug, Clone, PartialEq)]237#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]238pub struct ReFungibleItemType<AccountId> {239 pub owner: Vec<Ownership<AccountId>>,240 pub const_data: Vec<u8>,241 pub variable_data: Vec<u8>,242}243244245246247248249250251252253254255#[derive(Encode, Decode, Debug, Clone, PartialEq)]256#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]257pub struct CollectionLimits<BlockNumber: Encode + Decode> {258 pub account_token_ownership_limit: u32,259 pub sponsored_data_size: u32,260 261 262 263 pub sponsored_data_rate_limit: Option<BlockNumber>,264 pub token_limit: u32,265266 267 pub sponsor_transfer_timeout: u32,268 pub owner_can_transfer: bool,269 pub owner_can_destroy: bool,270}271272impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {273 fn default() -> Self {274 Self { 275 account_token_ownership_limit: 10_000_000, 276 token_limit: u32::max_value(),277 sponsored_data_size: u32::MAX, 278 sponsored_data_rate_limit: None,279 sponsor_transfer_timeout: 14400,280 owner_can_transfer: true,281 owner_can_destroy: true282 }283 }284}285286#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]287#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]288pub struct ChainLimits {289 pub collection_numbers_limit: u32,290 pub account_token_ownership_limit: u32,291 pub collections_admins_limit: u64,292 pub custom_data_limit: u32,293294 295 pub nft_sponsor_transfer_timeout: u32,296 pub fungible_sponsor_transfer_timeout: u32,297 pub refungible_sponsor_transfer_timeout: u32,298299 300 pub offchain_schema_limit: u32,301 pub variable_on_chain_schema_limit: u32,302 pub const_on_chain_schema_limit: u32,303}304305pub trait WeightInfo {306 fn create_collection() -> Weight;307 fn destroy_collection() -> Weight;308 fn add_to_white_list() -> Weight;309 fn remove_from_white_list() -> Weight;310 fn set_public_access_mode() -> Weight;311 fn set_mint_permission() -> Weight;312 fn change_collection_owner() -> Weight;313 fn add_collection_admin() -> Weight;314 fn remove_collection_admin() -> Weight;315 fn set_collection_sponsor() -> Weight;316 fn confirm_sponsorship() -> Weight;317 fn remove_collection_sponsor() -> Weight;318 fn create_item(s: usize) -> Weight;319 fn burn_item() -> Weight;320 fn transfer() -> Weight;321 fn approve() -> Weight;322 fn transfer_from() -> Weight;323 fn set_offchain_schema() -> Weight;324 fn set_const_on_chain_schema() -> Weight;325 fn set_variable_on_chain_schema() -> Weight;326 fn set_variable_meta_data() -> Weight;327 fn enable_contract_sponsoring() -> Weight;328 fn set_schema_version() -> Weight;329 fn set_chain_limits() -> Weight;330 fn set_contract_sponsoring_rate_limit() -> Weight;331 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;332 fn toggle_contract_white_list() -> Weight;333 fn add_to_contract_white_list() -> Weight;334 fn remove_from_contract_white_list() -> Weight;335 fn set_collection_limits() -> Weight;336}337338#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]339#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]340pub struct CreateNftData {341 pub const_data: Vec<u8>,342 pub variable_data: Vec<u8>,343}344345#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]346#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]347pub struct CreateFungibleData {348 pub value: u128,349}350351#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]352#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]353pub struct CreateReFungibleData {354 pub const_data: Vec<u8>,355 pub variable_data: Vec<u8>,356 pub pieces: u128,357}358359#[derive(Encode, Decode, Debug, Clone, PartialEq)]360#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]361pub enum CreateItemData {362 NFT(CreateNftData),363 Fungible(CreateFungibleData),364 ReFungible(CreateReFungibleData),365}366367impl CreateItemData {368 pub fn len(&self) -> usize {369 let len = match self {370 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),371 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),372 _ => 0373 };374 375 return len;376 }377}378379impl From<CreateNftData> for CreateItemData {380 fn from(item: CreateNftData) -> Self {381 CreateItemData::NFT(item)382 }383}384385impl From<CreateReFungibleData> for CreateItemData {386 fn from(item: CreateReFungibleData) -> Self {387 CreateItemData::ReFungible(item)388 }389}390391impl From<CreateFungibleData> for CreateItemData {392 fn from(item: CreateFungibleData) -> Self {393 CreateItemData::Fungible(item)394 }395}396397398decl_error! {399 400 pub enum Error for Module<T: Config> {401 402 TotalCollectionsLimitExceeded,403 404 CollectionDecimalPointLimitExceeded, 405 406 CollectionNameLimitExceeded, 407 408 CollectionDescriptionLimitExceeded, 409 410 CollectionTokenPrefixLimitExceeded,411 412 CollectionNotFound,413 414 TokenNotFound,415 416 AdminNotFound,417 418 NumOverflow, 419 420 AlreadyAdmin, 421 422 NoPermission,423 424 ConfirmUnsetSponsorFail,425 426 PublicMintingNotAllowed,427 428 MustBeTokenOwner,429 430 TokenValueTooLow,431 432 NftSizeLimitExceeded,433 434 ApproveNotFound,435 436 TokenValueNotEnough,437 438 ApproveRequired,439 440 AddresNotInWhiteList,441 442 CollectionAdminsLimitExceeded,443 444 AddressOwnershipLimitExceeded,445 446 EmptyArgument,447 448 TokenConstDataLimitExceeded,449 450 TokenVariableDataLimitExceeded,451 452 NotNftDataUsedToMintNftCollectionToken,453 454 NotFungibleDataUsedToMintFungibleCollectionToken,455 456 NotReFungibleDataUsedToMintReFungibleCollectionToken,457 458 UnexpectedCollectionType,459 460 CantStoreMetadataInFungibleTokens,461 462 CollectionTokenLimitExceeded,463 464 AccountTokenLimitExceeded,465 466 CollectionLimitBoundsExceeded,467 468 OwnerPermissionsCantBeReverted,469 470 SchemaDataLimitExceeded,471 472 WrongRefungiblePieces,473 474 BadCreateRefungibleCall,475 }476}477478pub trait Config: system::Config + Sized + pallet_transaction_payment::Config + pallet_contracts::Config {479 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;480481 482 type WeightInfo: WeightInfo;483484 type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;485 type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;486 type EvmWithdrawOrigin: pallet_evm::EnsureAddressOrigin<Self::Origin, Success = Self::AccountId>;487488 type CrossAccountId: CrossAccountId<Self::AccountId>;489 type Currency: Currency<Self::AccountId>;490 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;491 type TreasuryAccountId: Get<Self::AccountId>;492493 type EthereumChainId: Get<u64>;494 type EthereumTransactionSender: pallet_ethereum::EthereumTransactionSender;495}496497#[cfg(feature = "runtime-benchmarks")]498mod benchmarking;499500501502503504505506507508509510511512513514515516517518519520521522523524decl_storage! {525 trait Store for Module<T: Config> as Nft {526527 528 529 CreatedCollectionCount: u32;530 531 ChainVersion: u64;532 533 534 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;535 536537 538 pub ChainLimit get(fn chain_limit) config(): ChainLimits;539 540541 542 543 544 DestroyedCollectionCount: u32;545 546 547 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;548 549550 551 552 553 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;554 555 556 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;557 558 559 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;560 561562 563 564 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;565566 567 568 569 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;570571 572 573 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;574 575 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;576 577 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;578 579580 581 582 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;583 584585 586 587 588 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;589 590 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;591 592 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;593 594 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;595 596597 598 599 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;600 601 602 603 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;604 605 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;606 607 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;608 609 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;610 611 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 612 613 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 614 615 }616 add_extra_genesis {617 build(|config: &GenesisConfig<T>| {618 619 for (_num, _c) in &config.collection_id {620 <Module<T>>::init_collection(_c);621 }622623 for (_num, _c, _i) in &config.nft_item_id {624 <Module<T>>::init_nft_token(*_c, _i);625 }626627 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {628 <Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);629 }630631 for (_num, _c, _i) in &config.refungible_item_id {632 <Module<T>>::init_refungible_token(*_c, _i);633 }634 })635 }636}637638decl_event!(639 pub enum Event<T>640 where641 CrossAccountId = <T as Config>::CrossAccountId,642 {643 644 645 646 647 648 649 650 651 652 CollectionCreated(CollectionId, u8, CrossAccountId),653654 655 656 657 658 659 660 661 662 663 ItemCreated(CollectionId, TokenId, CrossAccountId),664665 666 667 668 669 670 671 672 ItemDestroyed(CollectionId, TokenId),673674 675 676 677 678 679 680 681 682 683 684 685 Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),686687 688 689 690 691 692 693 694 695 696 Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),697 }698);699700decl_module! {701 pub struct Module<T: Config> for enum Call 702 where 703 origin: T::Origin704 {705 fn deposit_event() = default;706 type Error = Error<T>;707708 fn on_initialize(now: T::BlockNumber) -> Weight {709 0710 }711712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 #[weight = <T as Config>::WeightInfo::create_collection()]729 #[transactional]730 pub fn create_collection(origin,731 collection_name: Vec<u16>,732 collection_description: Vec<u16>,733 token_prefix: Vec<u8>,734 mode: CollectionMode) -> DispatchResult {735736 737 let who = T::CrossAccountId::from_sub(ensure_signed(origin)?);738739 740 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();741 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(742 &T::TreasuryAccountId::get(),743 T::CollectionCreationPrice::get(),744 ));745 <T as Config>::Currency::settle(746 who.as_sub(),747 imbalance,748 WithdrawReasons::TRANSFER,749 ExistenceRequirement::KeepAlive,750 ).map_err(|_| Error::<T>::NoPermission)?;751752 let decimal_points = match mode {753 CollectionMode::Fungible(points) => points,754 _ => 0755 };756757 let chain_limit = ChainLimit::get();758759 let created_count = CreatedCollectionCount::get();760 let destroyed_count = DestroyedCollectionCount::get();761762 763 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);764765 766 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);767 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);768 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);769 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);770771 772 let next_id = created_count773 .checked_add(1)774 .ok_or(Error::<T>::NumOverflow)?;775776 CreatedCollectionCount::put(next_id);777778 let limits = CollectionLimits {779 sponsored_data_size: chain_limit.custom_data_limit,780 ..Default::default()781 };782783 784 let new_collection = Collection {785 owner: who.clone(),786 name: collection_name,787 mode: mode.clone(),788 mint_mode: false,789 access: AccessMode::Normal,790 description: collection_description,791 decimal_points: decimal_points,792 token_prefix: token_prefix,793 offchain_schema: Vec::new(),794 schema_version: SchemaVersion::ImageURL,795 sponsorship: SponsorshipState::Disabled,796 variable_on_chain_schema: Vec::new(),797 const_on_chain_schema: Vec::new(),798 limits,799 };800801 802 <CollectionById<T>>::insert(next_id, new_collection);803804 805 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who));806807 Ok(())808 }809810 811 812 813 814 815 816 817 818 819 #[weight = <T as Config>::WeightInfo::destroy_collection()]820 #[transactional]821 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {822823 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);824 let collection = Self::get_collection(collection_id)?;825 Self::check_owner_permissions(&collection, &sender)?;826 if !collection.limits.owner_can_destroy {827 fail!(Error::<T>::NoPermission);828 }829830 <AddressTokens<T>>::remove_prefix(collection_id);831 <Allowances<T>>::remove_prefix(collection_id);832 <Balance<T>>::remove_prefix(collection_id);833 <ItemListIndex>::remove(collection_id);834 <AdminList<T>>::remove(collection_id);835 <CollectionById<T>>::remove(collection_id);836 <WhiteList<T>>::remove_prefix(collection_id);837838 <NftItemList<T>>::remove_prefix(collection_id);839 <FungibleItemList<T>>::remove_prefix(collection_id);840 <ReFungibleItemList<T>>::remove_prefix(collection_id);841842 <NftTransferBasket<T>>::remove_prefix(collection_id);843 <FungibleTransferBasket<T>>::remove_prefix(collection_id);844 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);845846 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);847848 DestroyedCollectionCount::put(DestroyedCollectionCount::get()849 .checked_add(1)850 .ok_or(Error::<T>::NumOverflow)?);851852 Ok(())853 }854855 856 857 858 859 860 861 862 863 864 865 866 867 #[weight = <T as Config>::WeightInfo::add_to_white_list()]868 #[transactional]869 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{870871 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);872 let collection = Self::get_collection(collection_id)?;873 Self::check_owner_or_admin_permissions(&collection, &sender)?;874875 <WhiteList<T>>::insert(collection_id, address.as_sub(), true);876 877 Ok(())878 }879880 881 882 883 884 885 886 887 888 889 890 891 892 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]893 #[transactional]894 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{895896 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);897 let collection = Self::get_collection(collection_id)?;898 Self::check_owner_or_admin_permissions(&collection, &sender)?;899900 <WhiteList<T>>::remove(collection_id, address.as_sub());901902 Ok(())903 }904905 906 907 908 909 910 911 912 913 914 915 916 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]917 #[transactional]918 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult919 {920 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);921922 let mut target_collection = Self::get_collection(collection_id)?;923 Self::check_owner_permissions(&target_collection, &sender)?;924 target_collection.access = mode;925 Self::save_collection(target_collection);926927 Ok(())928 }929930 931 932 933 934 935 936 937 938 939 940 941 942 943 #[weight = <T as Config>::WeightInfo::set_mint_permission()]944 #[transactional]945 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult946 {947 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);948949 let mut target_collection = Self::get_collection(collection_id)?;950 Self::check_owner_permissions(&target_collection, &sender)?;951 target_collection.mint_mode = mint_permission;952 Self::save_collection(target_collection);953954 Ok(())955 }956957 958 959 960 961 962 963 964 965 966 967 968 #[weight = <T as Config>::WeightInfo::change_collection_owner()]969 #[transactional]970 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::CrossAccountId) -> DispatchResult {971972 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);973 let mut target_collection = Self::get_collection(collection_id)?;974 Self::check_owner_permissions(&target_collection, &sender)?;975 target_collection.owner = new_owner;976 Self::save_collection(target_collection);977978 Ok(())979 }980981 982 983 984 985 986 987 988 989 990 991 992 993 994 #[weight = <T as Config>::WeightInfo::add_collection_admin()]995 #[transactional]996 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {997 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);998 let collection = Self::get_collection(collection_id)?;999 Self::check_owner_or_admin_permissions(&collection, &sender)?;1000 let mut admin_arr = <AdminList<T>>::get(collection_id);10011002 match admin_arr.binary_search(&new_admin_id) {1003 Ok(_) => {},1004 Err(idx) => {1005 let limits = ChainLimit::get();1006 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);1007 admin_arr.insert(idx, new_admin_id);1008 <AdminList<T>>::insert(collection_id, admin_arr);1009 }1010 }1011 Ok(())1012 }10131014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]1027 #[transactional]1028 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {1029 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1030 let collection = Self::get_collection(collection_id)?;1031 Self::check_owner_or_admin_permissions(&collection, &sender)?;1032 let mut admin_arr = <AdminList<T>>::get(collection_id);10331034 match admin_arr.binary_search(&account_id) {1035 Ok(idx) => {1036 admin_arr.remove(idx);1037 <AdminList<T>>::insert(collection_id, admin_arr);1038 },1039 Err(_) => {}1040 }1041 Ok(())1042 }10431044 1045 1046 1047 1048 1049 1050 1051 1052 1053 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]1054 #[transactional]1055 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {1056 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1057 let mut target_collection = Self::get_collection(collection_id)?;1058 Self::check_owner_permissions(&target_collection, &sender)?;10591060 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);1061 Self::save_collection(target_collection);10621063 Ok(())1064 }10651066 1067 1068 1069 1070 1071 1072 1073 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]1074 #[transactional]1075 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {1076 let sender = ensure_signed(origin)?;10771078 let mut target_collection = Self::get_collection(collection_id)?;1079 ensure!(1080 target_collection.sponsorship.pending_sponsor() == Some(&sender),1081 Error::<T>::ConfirmUnsetSponsorFail1082 );10831084 target_collection.sponsorship = SponsorshipState::Confirmed(sender);1085 Self::save_collection(target_collection);10861087 Ok(())1088 }10891090 1091 1092 1093 1094 1095 1096 1097 1098 1099 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]1100 #[transactional]1101 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {1102 let sender = ensure_signed(origin)?;11031104 let mut target_collection = Self::get_collection(collection_id)?;1105 Self::check_owner_permissions(&target_collection, &T::CrossAccountId::from_sub(sender))?;11061107 target_collection.sponsorship = SponsorshipState::Disabled;1108 Self::save_collection(target_collection);11091110 Ok(())1111 }11121113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 11361137 #[weight = <T as Config>::WeightInfo::create_item(data.len())]1138 #[transactional]1139 pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {11401141 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);11421143 let target_collection = Self::get_collection(collection_id)?;11441145 Self::can_create_items_in_collection(&target_collection, &sender, &owner, 1)?;1146 Self::validate_create_item_args(&target_collection, &data)?;1147 Self::create_item_no_validation(&target_collection, owner, data)?;11481149 Self::submit_logs(target_collection)?;1150 Ok(())1151 }11521153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()1172 .map(|data| { data.len() })1173 .sum())]1174 #[transactional]1175 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {11761177 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);1178 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1179 let collection = Self::get_collection(collection_id)?;11801181 Self::create_multiple_items_internal(sender, &collection, owner, items_data)?;11821183 Self::submit_logs(collection)?;1184 Ok(())1185 }11861187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 #[weight = <T as Config>::WeightInfo::burn_item()]1201 #[transactional]1202 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {12031204 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1205 let target_collection = Self::get_collection(collection_id)?;12061207 Self::burn_item_internal(&sender, &target_collection, item_id, value)?;12081209 Self::submit_logs(target_collection)?;1210 Ok(())1211 }12121213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 #[weight = <T as Config>::WeightInfo::transfer()]1237 #[transactional]1238 pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {1239 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1240 let collection = Self::get_collection(collection_id)?;12411242 Self::transfer_internal(sender, recipient, &collection, item_id, value)?;12431244 Self::submit_logs(collection)?;1245 Ok(())1246 }12471248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 #[weight = <T as Config>::WeightInfo::approve()]1264 #[transactional]1265 pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {12661267 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1268 let collection = Self::get_collection(collection_id)?;12691270 Self::approve_internal(sender, spender, &collection, item_id, amount)?;12711272 Self::submit_logs(collection)?;1273 Ok(())1274 }1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 #[weight = <T as Config>::WeightInfo::transfer_from()]1296 #[transactional]1297 pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {12981299 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1300 let collection = Self::get_collection(collection_id)?;13011302 Self::transfer_from_internal(sender, from, recipient, &collection, item_id, value)?;13031304 Self::submit_logs(collection)?;1305 Ok(())1306 }13071308 1309 13101311 1312 1313 1314 13151316 13171318 13191320 1321 13221323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1336 #[transactional]1337 pub fn set_variable_meta_data (1338 origin,1339 collection_id: CollectionId,1340 item_id: TokenId,1341 data: Vec<u8>1342 ) -> DispatchResult {1343 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1344 1345 let target_collection = Self::get_collection(collection_id)?;1346 Self::set_variable_meta_data_internal(sender, &target_collection, item_id, data)?;13471348 Ok(())1349 }1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 #[weight = <T as Config>::WeightInfo::set_schema_version()]1366 #[transactional]1367 pub fn set_schema_version(1368 origin,1369 collection_id: CollectionId,1370 version: SchemaVersion1371 ) -> DispatchResult {1372 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1373 let mut target_collection = Self::get_collection(collection_id)?;1374 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1375 target_collection.schema_version = version;1376 Self::save_collection(target_collection);13771378 Ok(())1379 }13801381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1394 #[transactional]1395 pub fn set_offchain_schema(1396 origin,1397 collection_id: CollectionId,1398 schema: Vec<u8>1399 ) -> DispatchResult {1400 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1401 let mut target_collection = Self::get_collection(collection_id)?;1402 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14031404 1405 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");14061407 target_collection.offchain_schema = schema;1408 Self::save_collection(target_collection);14091410 Ok(())1411 }14121413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1426 #[transactional]1427 pub fn set_const_on_chain_schema (1428 origin,1429 collection_id: CollectionId,1430 schema: Vec<u8>1431 ) -> DispatchResult {1432 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1433 let mut target_collection = Self::get_collection(collection_id)?;1434 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14351436 1437 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");14381439 target_collection.const_on_chain_schema = schema;1440 Self::save_collection(target_collection);14411442 Ok(())1443 }14441445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1458 #[transactional]1459 pub fn set_variable_on_chain_schema (1460 origin,1461 collection_id: CollectionId,1462 schema: Vec<u8>1463 ) -> DispatchResult {1464 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1465 let mut target_collection = Self::get_collection(collection_id)?;1466 Self::check_owner_or_admin_permissions(&target_collection, &sender)?;14671468 1469 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");14701471 target_collection.variable_on_chain_schema = schema;1472 Self::save_collection(target_collection);14731474 Ok(())1475 }14761477 1478 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1479 #[transactional]1480 pub fn set_chain_limits(1481 origin,1482 limits: ChainLimits1483 ) -> DispatchResult {14841485 #[cfg(not(feature = "runtime-benchmarks"))]1486 ensure_root(origin)?;14871488 <ChainLimit>::put(limits);1489 Ok(())1490 }14911492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1504 #[transactional]1505 pub fn enable_contract_sponsoring(1506 origin,1507 contract_address: T::AccountId,1508 enable: bool1509 ) -> DispatchResult {15101511 let sender = ensure_signed(origin)?;15121513 #[cfg(feature = "runtime-benchmarks")]1514 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15151516 Self::ensure_contract_owned(sender, &contract_address)?;15171518 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1519 Ok(())1520 }15211522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1540 #[transactional]1541 pub fn set_contract_sponsoring_rate_limit(1542 origin,1543 contract_address: T::AccountId,1544 rate_limit: T::BlockNumber1545 ) -> DispatchResult {1546 let sender = ensure_signed(origin)?;15471548 #[cfg(feature = "runtime-benchmarks")]1549 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15501551 Self::ensure_contract_owned(sender, &contract_address)?;1552 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1553 Ok(())1554 }15551556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1568 #[transactional]1569 pub fn toggle_contract_white_list(1570 origin,1571 contract_address: T::AccountId,1572 enable: bool1573 ) -> DispatchResult {1574 let sender = ensure_signed(origin)?;15751576 #[cfg(feature = "runtime-benchmarks")]1577 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15781579 Self::ensure_contract_owned(sender, &contract_address)?;1580 if enable {1581 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1582 } else {1583 <ContractWhiteListEnabled<T>>::remove(contract_address);1584 }1585 Ok(())1586 }1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1600 #[transactional]1601 pub fn add_to_contract_white_list(1602 origin,1603 contract_address: T::AccountId,1604 account_address: T::AccountId1605 ) -> DispatchResult {1606 let sender = ensure_signed(origin)?;16071608 #[cfg(feature = "runtime-benchmarks")]1609 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1610 1611 Self::ensure_contract_owned(sender, &contract_address)?; 1612 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1613 Ok(())1614 }16151616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1628 #[transactional]1629 pub fn remove_from_contract_white_list(1630 origin,1631 contract_address: T::AccountId,1632 account_address: T::AccountId1633 ) -> DispatchResult {1634 let sender = ensure_signed(origin)?;16351636 #[cfg(feature = "runtime-benchmarks")]1637 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());16381639 Self::ensure_contract_owned(sender, &contract_address)?;1640 <ContractWhiteList<T>>::remove(contract_address, account_address);1641 Ok(())1642 }16431644 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1645 #[transactional]1646 pub fn set_collection_limits(1647 origin,1648 collection_id: u32,1649 new_limits: CollectionLimits<T::BlockNumber>,1650 ) -> DispatchResult {1651 let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1652 let mut target_collection = Self::get_collection(collection_id)?;1653 Self::check_owner_permissions(&target_collection, &sender)?;1654 let old_limits = &target_collection.limits;1655 let chain_limits = ChainLimit::get();16561657 1658 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1659 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1660 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1661 Error::<T>::CollectionLimitBoundsExceeded);16621663 1664 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1665 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);16661667 ensure!(1668 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1669 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1670 Error::<T>::OwnerPermissionsCantBeReverted,1671 );16721673 target_collection.limits = new_limits;1674 Self::save_collection(target_collection);16751676 Ok(())1677 } 1678 }1679}16801681impl<T: Config> Module<T> {16821683 pub fn transfer_internal(sender: T::CrossAccountId, recipient: T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1684 1685 Self::is_correct_transfer(target_collection, &recipient)?;16861687 1688 ensure!(Self::is_item_owner(&sender, target_collection, item_id) ||1689 Self::is_owner_or_admin_permissions(target_collection, &sender),1690 Error::<T>::NoPermission);16911692 if target_collection.access == AccessMode::WhiteList {1693 Self::check_white_list(target_collection, &sender)?;1694 Self::check_white_list(target_collection, &recipient)?;1695 }16961697 match target_collection.mode1698 {1699 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1700 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1701 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1702 _ => ()1703 };17041705 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender, recipient, value));17061707 Ok(())1708 }17091710 pub fn approve_internal(1711 sender: T::CrossAccountId,1712 spender: T::CrossAccountId,1713 collection: &CollectionHandle<T>,1714 item_id: TokenId,1715 amount: u1281716 ) -> DispatchResult {1717 Self::token_exists(&collection, item_id)?;17181719 1720 let bypasses_limits = collection.limits.owner_can_transfer &&1721 Self::is_owner_or_admin_permissions(1722 &collection,1723 &sender,1724 );17251726 let allowance_limit = if bypasses_limits {1727 None1728 } else if let Some(amount) = Self::owned_amount(1729 &sender,1730 &collection,1731 item_id,1732 ) {1733 Some(amount)1734 } else {1735 fail!(Error::<T>::NoPermission);1736 };17371738 if collection.access == AccessMode::WhiteList {1739 Self::check_white_list(&collection, &sender)?;1740 Self::check_white_list(&collection, &spender)?;1741 }17421743 let allowance: u128 = amount1744 .checked_add(<Allowances<T>>::get(collection.id, (item_id, sender.as_sub(), spender.as_sub())))1745 .ok_or(Error::<T>::NumOverflow)?;1746 if let Some(limit) = allowance_limit {1747 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1748 }1749 <Allowances<T>>::insert(collection.id, (item_id, sender.as_sub(), spender.as_sub()), allowance);17501751 if matches!(collection.mode, CollectionMode::NFT) {1752 1753 collection.log(1754 Vec::from([1755 eth::APPROVAL_NFT_TOPIC,1756 eth::address_to_topic(sender.as_eth()),1757 eth::address_to_topic(spender.as_eth()),1758 ]),1759 abi_encode!(uint256(item_id.into())),1760 );1761 }17621763 if matches!(collection.mode, CollectionMode::Fungible(_)) {1764 1765 collection.log(1766 Vec::from([1767 eth::APPROVAL_FUNGIBLE_TOPIC,1768 eth::address_to_topic(sender.as_eth()),1769 eth::address_to_topic(spender.as_eth()),1770 ]),1771 abi_encode!(uint256(allowance.into())),1772 );1773 }17741775 Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender, spender, allowance));1776 Ok(())1777 }17781779 pub fn transfer_from_internal(1780 sender: T::CrossAccountId,1781 from: T::CrossAccountId,1782 recipient: T::CrossAccountId,1783 collection: &CollectionHandle<T>,1784 item_id: TokenId,1785 amount: u128,1786 ) -> DispatchResult {1787 1788 let approval: u128 = <Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));17891790 1791 Self::is_correct_transfer(&collection, &recipient)?;17921793 1794 ensure!(1795 approval >= amount || 1796 (1797 collection.limits.owner_can_transfer &&1798 Self::is_owner_or_admin_permissions(&collection, &sender)1799 ),1800 Error::<T>::NoPermission1801 );18021803 if collection.access == AccessMode::WhiteList {1804 Self::check_white_list(&collection, &sender)?;1805 Self::check_white_list(&collection, &recipient)?;1806 }18071808 1809 let allowance = approval.saturating_sub(amount);1810 if allowance > 0 {1811 <Allowances<T>>::insert(collection.id, (item_id, from.as_sub(), sender.as_sub()), allowance);1812 } else {1813 <Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1814 }18151816 match collection.mode {1817 CollectionMode::NFT => {1818 Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())?1819 }1820 CollectionMode::Fungible(_) => {1821 Self::transfer_fungible(&collection, amount, &from, &recipient)?1822 }1823 CollectionMode::ReFungible => {1824 Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())?1825 }1826 _ => ()1827 };18281829 if matches!(collection.mode, CollectionMode::Fungible(_)) {1830 collection.log(1831 Vec::from([1832 eth::APPROVAL_FUNGIBLE_TOPIC,1833 eth::address_to_topic(from.as_eth()),1834 eth::address_to_topic(sender.as_eth()),1835 ]),1836 abi_encode!(uint256(allowance.into())),1837 );1838 }18391840 Ok(())1841 }18421843 pub fn set_variable_meta_data_internal(1844 sender: T::CrossAccountId,1845 collection: &CollectionHandle<T>, 1846 item_id: TokenId,1847 data: Vec<u8>,1848 ) -> DispatchResult {1849 Self::token_exists(&collection, item_id)?;18501851 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);18521853 1854 ensure!(Self::is_item_owner(&sender, &collection, item_id) ||1855 Self::is_owner_or_admin_permissions(&collection, &sender),1856 Error::<T>::NoPermission);18571858 match collection.mode1859 {1860 CollectionMode::NFT => Self::set_nft_variable_data(&collection, item_id, data)?,1861 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&collection, item_id, data)?,1862 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1863 _ => fail!(Error::<T>::UnexpectedCollectionType)1864 };18651866 Ok(())1867 }18681869 pub fn create_multiple_items_internal(1870 sender: T::CrossAccountId,1871 collection: &CollectionHandle<T>,1872 owner: T::CrossAccountId,1873 items_data: Vec<CreateItemData>,1874 ) -> DispatchResult {1875 Self::can_create_items_in_collection(&collection, &sender, &owner, items_data.len() as u32)?;18761877 for data in &items_data {1878 Self::validate_create_item_args(&collection, data)?;1879 }1880 for data in &items_data {1881 Self::create_item_no_validation(&collection, owner.clone(), data.clone())?;1882 }18831884 Ok(())1885 }18861887 pub fn burn_item_internal(1888 sender: &T::CrossAccountId,1889 collection: &CollectionHandle<T>,1890 item_id: TokenId,1891 value: u128,1892 ) -> DispatchResult {1893 ensure!(1894 Self::is_item_owner(&sender, &collection, item_id) ||1895 (1896 collection.limits.owner_can_transfer &&1897 Self::is_owner_or_admin_permissions(&collection, &sender)1898 ),1899 Error::<T>::NoPermission1900 );19011902 if collection.access == AccessMode::WhiteList {1903 Self::check_white_list(&collection, &sender)?;1904 }19051906 match collection.mode1907 {1908 CollectionMode::NFT => Self::burn_nft_item(&collection, item_id)?,1909 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &collection, value)?,1910 CollectionMode::ReFungible => Self::burn_refungible_item(&collection, item_id, &sender)?,1911 _ => ()1912 };19131914 Ok(())1915 }19161917 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::CrossAccountId) -> DispatchResult {1918 let collection_id = collection.id;19191920 1921 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1922 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1923 1924 Ok(())1925 }19261927 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::CrossAccountId, owner: &T::CrossAccountId, amount: u32) -> DispatchResult {1928 let collection_id = collection.id;19291930 1931 let total_items: u32 = ItemListIndex::get(collection_id)1932 .checked_add(amount)1933 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1934 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len() as u32)1935 .checked_add(amount)1936 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1937 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1938 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);19391940 if !Self::is_owner_or_admin_permissions(collection, &sender) {1941 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1942 Self::check_white_list(collection, owner)?;1943 Self::check_white_list(collection, sender)?;1944 }19451946 Ok(())1947 }19481949 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1950 match target_collection.mode1951 {1952 CollectionMode::NFT => {1953 if let CreateItemData::NFT(data) = data {1954 1955 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1956 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1957 } else {1958 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1959 }1960 },1961 CollectionMode::Fungible(_) => {1962 if let CreateItemData::Fungible(_) = data {1963 } else {1964 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1965 }1966 },1967 CollectionMode::ReFungible => {1968 if let CreateItemData::ReFungible(data) = data {19691970 1971 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1972 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);19731974 1975 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1976 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1977 } else {1978 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1979 }1980 },1981 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1982 };19831984 Ok(())1985 }19861987 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {1988 match data1989 {1990 CreateItemData::NFT(data) => {1991 let item = NftItemType {1992 owner: owner.clone(),1993 const_data: data.const_data,1994 variable_data: data.variable_data1995 };19961997 Self::add_nft_item(collection, item)?;1998 },1999 CreateItemData::Fungible(data) => {2000 Self::add_fungible_item(collection, &owner, data.value)?;2001 },2002 CreateItemData::ReFungible(data) => {2003 let mut owner_list = Vec::new();2004 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});20052006 let item = ReFungibleItemType {2007 owner: owner_list,2008 const_data: data.const_data,2009 variable_data: data.variable_data2010 };20112012 Self::add_refungible_item(collection, item)?;2013 }2014 };20152016 Ok(())2017 }20182019 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::CrossAccountId, value: u128) -> DispatchResult {2020 let collection_id = collection.id;20212022 2023 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;20242025 2026 let item = FungibleItemType {2027 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,2028 };2029 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);20302031 2032 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2033 .checked_add(value)2034 .ok_or(Error::<T>::NumOverflow)?;2035 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20362037 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));2038 Ok(())2039 }20402041 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::CrossAccountId>) -> DispatchResult {2042 let collection_id = collection.id;20432044 let current_index = <ItemListIndex>::get(collection_id)2045 .checked_add(1)2046 .ok_or(Error::<T>::NumOverflow)?;2047 let itemcopy = item.clone();20482049 ensure!(2050 item.owner.len() == 1,2051 Error::<T>::BadCreateRefungibleCall,2052 );2053 let item_owner = item.owner.first().expect("only one owner is defined");20542055 let value = item_owner.fraction;2056 let owner = item_owner.owner.clone();20572058 Self::add_token_index(collection_id, current_index, &owner)?;20592060 <ItemListIndex>::insert(collection_id, current_index);2061 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);20622063 2064 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2065 .checked_add(value)2066 .ok_or(Error::<T>::NumOverflow)?;2067 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);20682069 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));2070 Ok(())2071 }20722073 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::CrossAccountId>) -> DispatchResult {2074 let collection_id = collection.id;20752076 let current_index = <ItemListIndex>::get(collection_id)2077 .checked_add(1)2078 .ok_or(Error::<T>::NumOverflow)?;20792080 let item_owner = item.owner.clone();2081 Self::add_token_index(collection_id, current_index, &item.owner)?;20822083 <ItemListIndex>::insert(collection_id, current_index);2084 <NftItemList<T>>::insert(collection_id, current_index, item);20852086 2087 let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())2088 .checked_add(1)2089 .ok_or(Error::<T>::NumOverflow)?;2090 <Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);20912092 collection.log(2093 Vec::from([2094 eth::TRANSFER_NFT_TOPIC,2095 eth::address_to_topic(&H160::default()),2096 eth::address_to_topic(item_owner.as_eth()),2097 ]),2098 abi_encode!(uint256(current_index.into())),2099 );2100 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));2101 Ok(())2102 }21032104 fn burn_refungible_item(2105 collection: &CollectionHandle<T>,2106 item_id: TokenId,2107 owner: &T::CrossAccountId,2108 ) -> DispatchResult {2109 let collection_id = collection.id;21102111 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)2112 .ok_or(Error::<T>::TokenNotFound)?;2113 let rft_balance = token2114 .owner2115 .iter()2116 .find(|&i| i.owner == *owner)2117 .ok_or(Error::<T>::TokenNotFound)?;2118 Self::remove_token_index(collection_id, item_id, owner)?;21192120 2121 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())2122 .checked_sub(rft_balance.fraction)2123 .ok_or(Error::<T>::NumOverflow)?;2124 <Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);21252126 2127 let index = token2128 .owner2129 .iter()2130 .position(|i| i.owner == *owner)2131 .expect("owned item is exists");2132 token.owner.remove(index);2133 let owner_count = token.owner.len();21342135 2136 if owner_count == 0 {2137 <ReFungibleItemList<T>>::remove(collection_id, item_id);2138 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);2139 }2140 else {2141 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);2142 }21432144 Ok(())2145 }21462147 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {2148 let collection_id = collection.id;21492150 let item = <NftItemList<T>>::get(collection_id, item_id)2151 .ok_or(Error::<T>::TokenNotFound)?;2152 Self::remove_token_index(collection_id, item_id, &item.owner)?;21532154 2155 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2156 .checked_sub(1)2157 .ok_or(Error::<T>::NumOverflow)?;2158 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2159 <NftItemList<T>>::remove(collection_id, item_id);2160 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);21612162 Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));2163 Ok(())2164 }21652166 fn burn_fungible_item(owner: &T::CrossAccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {2167 let collection_id = collection.id;21682169 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2170 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);21712172 2173 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2174 .checked_sub(value)2175 .ok_or(Error::<T>::NumOverflow)?;2176 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);21772178 if balance.value - value > 0 {2179 balance.value -= value;2180 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2181 }2182 else {2183 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2184 }21852186 collection.log(2187 Vec::from([2188 eth::TRANSFER_FUNGIBLE_TOPIC,2189 eth::address_to_topic(owner.as_eth()),2190 eth::address_to_topic(&H160::default()),2191 ]),2192 abi_encode!(uint256(value.into())),2193 );2194 Ok(())2195 }21962197 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {2198 Ok(<CollectionHandle<T>>::get(collection_id)2199 .ok_or(Error::<T>::CollectionNotFound)?)2200 }22012202 fn save_collection(collection: CollectionHandle<T>) {2203 <CollectionById<T>>::insert(collection.id, collection.into_inner());2204 }22052206 fn submit_logs(collection: CollectionHandle<T>) -> DispatchResult {2207 if collection.logs.is_empty() {2208 return Ok(())2209 }2210 T::EthereumTransactionSender::submit_logs_transaction(2211 eth::generate_transaction(collection.id, T::EthereumChainId::get()),2212 collection.logs.retrieve_logs_for_contract(eth::collection_id_to_address(collection.id)),2213 )2214 }22152216 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> DispatchResult {2217 ensure!(2218 *subject == target_collection.owner,2219 Error::<T>::NoPermission2220 );22212222 Ok(())2223 }22242225 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: &T::CrossAccountId) -> bool {2226 *subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)2227 }22282229 fn check_owner_or_admin_permissions(2230 collection: &CollectionHandle<T>,2231 subject: &T::CrossAccountId,2232 ) -> DispatchResult {2233 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);22342235 Ok(())2236 }22372238 fn owned_amount(2239 subject: &T::CrossAccountId,2240 target_collection: &CollectionHandle<T>,2241 item_id: TokenId,2242 ) -> Option<u128> {2243 let collection_id = target_collection.id;22442245 match target_collection.mode {2246 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject)2247 .then(|| 1),2248 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub())2249 .value),2250 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?2251 .owner2252 .iter()2253 .find(|i| i.owner == *subject)2254 .map(|i| i.fraction),2255 CollectionMode::Invalid => None,2256 }2257 }22582259 fn is_item_owner(subject: &T::CrossAccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {2260 match target_collection.mode {2261 CollectionMode::Fungible(_) => true,2262 _ => Self::owned_amount(&subject, target_collection, item_id).is_some(),2263 }2264 }22652266 fn check_white_list(collection: &CollectionHandle<T>, address: &T::CrossAccountId) -> DispatchResult {2267 let collection_id = collection.id;22682269 let mes = Error::<T>::AddresNotInWhiteList;2270 ensure!(<WhiteList<T>>::contains_key(collection_id, address.as_sub()), mes);22712272 Ok(())2273 }22742275 2276 2277 fn token_exists(2278 target_collection: &CollectionHandle<T>,2279 item_id: TokenId,2280 ) -> DispatchResult {2281 let collection_id = target_collection.id;2282 let exists = match target_collection.mode2283 {2284 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),2285 CollectionMode::Fungible(_) => true,2286 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),2287 _ => false2288 };22892290 ensure!(exists == true, Error::<T>::TokenNotFound);2291 Ok(())2292 }22932294 fn transfer_fungible(2295 collection: &CollectionHandle<T>,2296 value: u128,2297 owner: &T::CrossAccountId,2298 recipient: &T::CrossAccountId,2299 ) -> DispatchResult {2300 let collection_id = collection.id;23012302 let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());2303 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);23042305 2306 Self::add_fungible_item(collection, recipient, value)?;23072308 2309 <Balance<T>>::insert(collection_id, owner.as_sub(), balance.value - value);23102311 2312 if balance.value == value {2313 <FungibleItemList<T>>::remove(collection_id, owner.as_sub());2314 }2315 else {2316 balance.value -= value;2317 <FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2318 }23192320 collection.log(2321 Vec::from([2322 eth::TRANSFER_FUNGIBLE_TOPIC,2323 eth::address_to_topic(owner.as_eth()),2324 eth::address_to_topic(recipient.as_eth()),2325 ]),2326 abi_encode!(uint256(value.into())),2327 );2328 Self::deposit_event(RawEvent::Transfer(collection.id, 1, owner.clone(), recipient.clone(), value));23292330 Ok(())2331 }23322333 fn transfer_refungible(2334 collection: &CollectionHandle<T>,2335 item_id: TokenId,2336 value: u128,2337 owner: T::CrossAccountId,2338 new_owner: T::CrossAccountId,2339 ) -> DispatchResult {2340 let collection_id = collection.id;2341 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2342 .ok_or(Error::<T>::TokenNotFound)?;23432344 let item = full_item2345 .owner2346 .iter()2347 .filter(|i| i.owner == owner)2348 .next()2349 .ok_or(Error::<T>::TokenNotFound)?;2350 let amount = item.fraction;23512352 ensure!(amount >= value, Error::<T>::TokenValueTooLow);23532354 2355 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2356 .checked_sub(value)2357 .ok_or(Error::<T>::NumOverflow)?;2358 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);23592360 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2361 .checked_add(value)2362 .ok_or(Error::<T>::NumOverflow)?;2363 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);23642365 let old_owner = item.owner.clone();2366 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);23672368 2369 if amount == value && !new_owner_has_account {2370 2371 2372 let mut new_full_item = full_item.clone();2373 new_full_item2374 .owner2375 .iter_mut()2376 .find(|i| i.owner == owner)2377 .expect("old owner does present in refungible")2378 .owner = new_owner.clone();2379 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);23802381 2382 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;2383 } else {2384 let mut new_full_item = full_item.clone();2385 new_full_item2386 .owner2387 .iter_mut()2388 .find(|i| i.owner == owner)2389 .expect("old owner does present in refungible")2390 .fraction -= value;23912392 2393 if new_owner_has_account {2394 2395 new_full_item2396 .owner2397 .iter_mut()2398 .find(|i| i.owner == new_owner)2399 .expect("new owner has account")2400 .fraction += value;2401 } else {2402 2403 new_full_item.owner.push(Ownership {2404 owner: new_owner.clone(),2405 fraction: value,2406 });2407 Self::add_token_index(collection_id, item_id, &new_owner)?;2408 }24092410 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2411 }24122413 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, owner, new_owner, amount));24142415 Ok(())2416 }24172418 fn transfer_nft(2419 collection: &CollectionHandle<T>,2420 item_id: TokenId,2421 sender: T::CrossAccountId,2422 new_owner: T::CrossAccountId,2423 ) -> DispatchResult {2424 let collection_id = collection.id;2425 let mut item = <NftItemList<T>>::get(collection_id, item_id)2426 .ok_or(Error::<T>::TokenNotFound)?;24272428 ensure!(2429 sender == item.owner,2430 Error::<T>::MustBeTokenOwner2431 );24322433 2434 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2435 .checked_sub(1)2436 .ok_or(Error::<T>::NumOverflow)?;2437 <Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);24382439 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2440 .checked_add(1)2441 .ok_or(Error::<T>::NumOverflow)?;2442 <Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);24432444 2445 let old_owner = item.owner.clone();2446 item.owner = new_owner.clone();2447 <NftItemList<T>>::insert(collection_id, item_id, item);24482449 2450 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;24512452 collection.log(2453 Vec::from([2454 eth::TRANSFER_NFT_TOPIC,2455 eth::address_to_topic(sender.as_eth()),2456 eth::address_to_topic(new_owner.as_eth()),2457 ]),2458 abi_encode!(uint256(item_id.into())),2459 );2460 Self::deposit_event(RawEvent::Transfer(collection.id, item_id, sender, new_owner, 1));24612462 Ok(())2463 }2464 2465 fn set_re_fungible_variable_data(2466 collection: &CollectionHandle<T>,2467 item_id: TokenId,2468 data: Vec<u8>2469 ) -> DispatchResult {2470 let collection_id = collection.id;2471 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2472 .ok_or(Error::<T>::TokenNotFound)?;24732474 item.variable_data = data;24752476 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);24772478 Ok(())2479 }24802481 fn set_nft_variable_data(2482 collection: &CollectionHandle<T>,2483 item_id: TokenId,2484 data: Vec<u8>2485 ) -> DispatchResult {2486 let collection_id = collection.id;2487 let mut item = <NftItemList<T>>::get(collection_id, item_id)2488 .ok_or(Error::<T>::TokenNotFound)?;2489 2490 item.variable_data = data;24912492 <NftItemList<T>>::insert(collection_id, item_id, item);2493 2494 Ok(())2495 }24962497 fn init_collection(item: &Collection<T>) {2498 2499 assert!(2500 item.decimal_points <= MAX_DECIMAL_POINTS,2501 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2502 );2503 assert!(2504 item.name.len() <= 64,2505 "Collection name can not be longer than 63 char"2506 );2507 assert!(2508 item.name.len() <= 256,2509 "Collection description can not be longer than 255 char"2510 );2511 assert!(2512 item.token_prefix.len() <= 16,2513 "Token prefix can not be longer than 15 char"2514 );25152516 2517 let next_id = CreatedCollectionCount::get()2518 .checked_add(1)2519 .unwrap();25202521 CreatedCollectionCount::put(next_id);2522 }25232524 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2525 let current_index = <ItemListIndex>::get(collection_id)2526 .checked_add(1)2527 .unwrap();25282529 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();25302531 <ItemListIndex>::insert(collection_id, current_index);25322533 2534 let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2535 .checked_add(1)2536 .unwrap();2537 <Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2538 }25392540 fn init_fungible_token(collection_id: CollectionId, owner: &T::CrossAccountId, item: &FungibleItemType) {2541 let current_index = <ItemListIndex>::get(collection_id)2542 .checked_add(1)2543 .unwrap();25442545 Self::add_token_index(collection_id, current_index, owner).unwrap();25462547 <ItemListIndex>::insert(collection_id, current_index);25482549 2550 let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2551 .checked_add(item.value)2552 .unwrap();2553 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2554 }25552556 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::CrossAccountId>) {2557 let current_index = <ItemListIndex>::get(collection_id)2558 .checked_add(1)2559 .unwrap();25602561 let value = item.owner.first().unwrap().fraction;2562 let owner = item.owner.first().unwrap().owner.clone();25632564 Self::add_token_index(collection_id, current_index, &owner).unwrap();25652566 <ItemListIndex>::insert(collection_id, current_index);25672568 2569 let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2570 .checked_add(value)2571 .unwrap();2572 <Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2573 }25742575 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::CrossAccountId) -> DispatchResult {2576 2577 if <AccountItemCount<T>>::contains_key(owner.as_sub()) {25782579 2580 let count = <AccountItemCount<T>>::get(owner.as_sub());2581 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);25822583 <AccountItemCount<T>>::insert(owner.as_sub(), count2584 .checked_add(1)2585 .ok_or(Error::<T>::NumOverflow)?);2586 }2587 else {2588 <AccountItemCount<T>>::insert(owner.as_sub(), 1);2589 }25902591 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2592 if list_exists {2593 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2594 let item_contains = list.contains(&item_index.clone());25952596 if !item_contains {2597 list.push(item_index.clone());2598 }25992600 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2601 } else {2602 let mut itm = Vec::new();2603 itm.push(item_index.clone());2604 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), itm);2605 }26062607 Ok(())2608 }26092610 fn remove_token_index(2611 collection_id: CollectionId,2612 item_index: TokenId,2613 owner: &T::CrossAccountId,2614 ) -> DispatchResult {26152616 2617 <AccountItemCount<T>>::insert(owner.as_sub(), 2618 <AccountItemCount<T>>::get(owner.as_sub())2619 .checked_sub(1)2620 .ok_or(Error::<T>::NumOverflow)?);262126222623 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.as_sub());2624 if list_exists {2625 let mut list = <AddressTokens<T>>::get(collection_id, owner.as_sub());2626 let item_contains = list.contains(&item_index.clone());26272628 if item_contains {2629 list.retain(|&item| item != item_index);2630 <AddressTokens<T>>::insert(collection_id, owner.as_sub(), list);2631 }2632 }26332634 Ok(())2635 }26362637 fn move_token_index(2638 collection_id: CollectionId,2639 item_index: TokenId,2640 old_owner: &T::CrossAccountId,2641 new_owner: &T::CrossAccountId,2642 ) -> DispatchResult {2643 Self::remove_token_index(collection_id, item_index, old_owner)?;2644 Self::add_token_index(collection_id, item_index, new_owner)?;26452646 Ok(())2647 }2648 2649 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2650 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);26512652 Ok(())2653 }2654}2655265626572658265926602661pub type Multiplier = FixedU128;26622663type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;2664266526662667#[derive(Encode, Decode, Clone, Eq, PartialEq)]2668pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);26692670impl<T: Config + Send + Sync> sp_std::fmt::Debug 2671 for ChargeTransactionPayment<T>2672{2673 #[cfg(feature = "std")]2674 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2675 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2676 }2677 #[cfg(not(feature = "std"))]2678 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2679 Ok(())2680 }2681}26822683impl<T: Config> ChargeTransactionPayment<T>2684where2685 T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2686 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2687 T::AccountId: AsRef<[u8]>,2688 T::AccountId: UncheckedFrom<T::Hash>,2689{2690 fn traditional_fee(2691 len: usize,2692 info: &DispatchInfoOf<T::Call>,2693 tip: BalanceOf<T>,2694 ) -> BalanceOf<T>2695 where2696 T::Call: Dispatchable<Info = DispatchInfo>,2697 {2698 <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2699 }27002701 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2702 let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);2703 let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);2704 let len_saturation = max_block_length as u64 / (len as u64).max(1);2705 let coefficient: BalanceOf<T> = weight_saturation2706 .min(len_saturation)2707 .saturated_into::<BalanceOf<T>>();2708 final_fee2709 .saturating_mul(coefficient)2710 .saturated_into::<TransactionPriority>()2711 }27122713 fn withdraw_fee(2714 &self,2715 who: &T::AccountId,2716 call: &T::Call,2717 info: &DispatchInfoOf<T::Call>,2718 len: usize,2719 ) -> Result<2720 (2721 BalanceOf<T>,2722 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,2723 ),2724 TransactionValidityError,2725 > {2726 let tip = self.0;27272728 let fee = Self::traditional_fee(len, info, tip);27292730 2731 if fee.is_zero() {2732 return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)2733 .map(|i| (fee, i));2734 }27352736 2737 2738 let mut sponsor: Option<T::AccountId> = (|| match IsSubType::<Call<T>>::is_sub_type(call) {2739 Some(Call::create_item(collection_id, _owner, _properties)) => {2740 let collection = <CollectionById<T>>::get(collection_id)?;27412742 2743 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;27442745 let limit = collection.limits.sponsor_transfer_timeout;2746 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2747 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2748 let limit_time = last_tx_block + limit.into();2749 if block_number <= limit_time {2750 return None;2751 }2752 }2753 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);27542755 2756 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {2757 collection.sponsorship.sponsor()2758 .cloned()2759 } else {2760 None2761 }2762 }2763 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2764 let collection = <CollectionById<T>>::get(collection_id)?;2765 2766 let mut sponsor_transfer = false;2767 if collection.sponsorship.confirmed() {27682769 let collection_limits = collection.limits;2770 let collection_mode = collection.mode;2771 2772 2773 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2774 sponsor_transfer = match collection_mode {2775 CollectionMode::NFT => {2776 2777 2778 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2779 collection_limits.sponsor_transfer_timeout2780 } else {2781 ChainLimit::get().nft_sponsor_transfer_timeout2782 };2783 2784 let mut sponsored = true;2785 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2786 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2787 let limit_time = last_tx_block + limit.into();2788 if block_number <= limit_time {2789 sponsored = false;2790 }2791 }2792 if sponsored {2793 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2794 }27952796 sponsored2797 }2798 CollectionMode::Fungible(_) => {2799 2800 2801 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2802 collection_limits.sponsor_transfer_timeout2803 } else {2804 ChainLimit::get().fungible_sponsor_transfer_timeout2805 };2806 2807 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2808 let mut sponsored = true;2809 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2810 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2811 let limit_time = last_tx_block + limit.into();2812 if block_number <= limit_time {2813 sponsored = false;2814 }2815 }2816 if sponsored {2817 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2818 }28192820 sponsored2821 }2822 CollectionMode::ReFungible => {2823 2824 2825 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2826 collection_limits.sponsor_transfer_timeout2827 } else {2828 ChainLimit::get().refungible_sponsor_transfer_timeout2829 };2830 2831 let mut sponsored = true;2832 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2833 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2834 let limit_time = last_tx_block + limit.into();2835 if block_number <= limit_time {2836 sponsored = false;2837 }2838 }2839 if sponsored {2840 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2841 }28422843 sponsored2844 }2845 _ => {2846 false2847 },2848 };2849 }28502851 if !sponsor_transfer {2852 None2853 } else {2854 collection.sponsorship.sponsor()2855 .cloned()2856 }2857 }28582859 Some(Call::set_variable_meta_data(collection_id, item_id, data)) => {2860 let mut sponsor_metadata_changes = false;28612862 let collection = <CollectionById<T>>::get(collection_id)?;28632864 if2865 collection.sponsorship.confirmed() &&2866 2867 2868 !matches!(collection.mode, CollectionMode::Fungible(_)) &&2869 data.len() <= collection.limits.sponsored_data_size as usize2870 {2871 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {2872 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;28732874 if <VariableMetaDataBasket<T>>::get(collection_id, item_id)2875 .map(|last_block| block_number - last_block > rate_limit)2876 .unwrap_or(true) 2877 {2878 sponsor_metadata_changes = true;2879 <VariableMetaDataBasket<T>>::insert(collection_id, item_id, block_number);2880 }2881 }2882 }28832884 if !sponsor_metadata_changes {2885 None2886 } else {2887 collection.sponsorship.sponsor().cloned()2888 }2889 }28902891 _ => None,2892 })();28932894 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {2895 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {28962897 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());28982899 let owned_contract = <ContractOwner<T>>::get(called_contract.clone()).as_ref() == Some(who);2900 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone());2901 2902 if !owned_contract && white_list_enabled {2903 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2904 return Err(InvalidTransaction::Call.into());2905 }2906 }2907 },2908 _ => {},2909 }29102911 2912 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {29132914 2915 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {29162917 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2918 &who,2919 code_hash,2920 salt,2921 );2922 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29232924 None2925 },29262927 2928 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {29292930 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(2931 &who,2932 &T::Hashing::hash(&_code),2933 _salt,2934 );29352936 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());29372938 None2939 }29402941 2942 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {29432944 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());29452946 let mut sponsor_transfer = false;2947 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2948 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2949 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2950 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2951 let limit_time = last_tx_block + rate_limit;29522953 if block_number >= limit_time {2954 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2955 sponsor_transfer = true;2956 }2957 } else {2958 sponsor_transfer = false;2959 }2960 2961 if sponsor_transfer {2962 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2963 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2964 return Some(called_contract);2965 }2966 }2967 }29682969 None2970 },29712972 _ => None,2973 });29742975 let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());29762977 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)2978 .map(|i| (fee, i))2979 }2980}298129822983impl<T: Config + Send + Sync> SignedExtension2984 for ChargeTransactionPayment<T>2985where2986 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2987 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2988 T::AccountId: AsRef<[u8]>,2989 T::AccountId: UncheckedFrom<T::Hash>,2990{2991 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2992 type AccountId = T::AccountId;2993 type Call = T::Call;2994 type AdditionalSigned = ();2995 type Pre = (2996 2997 BalanceOf<T>,2998 2999 Self::AccountId,3000 3001 <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,3002 );3003 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {3004 Ok(())3005 }30063007 fn validate(3008 &self,3009 who: &Self::AccountId,3010 call: &Self::Call,3011 info: &DispatchInfoOf<Self::Call>,3012 len: usize,3013 ) -> TransactionValidity {3014 let (fee, _) = self.withdraw_fee(who, call, info, len)?;3015 Ok(ValidTransaction {3016 priority: Self::get_priority(len, info, fee),3017 ..Default::default()3018 })3019 }30203021 fn pre_dispatch(3022 self,3023 who: &Self::AccountId,3024 call: &Self::Call,3025 info: &DispatchInfoOf<Self::Call>,3026 len: usize,3027 ) -> Result<Self::Pre, TransactionValidityError> {3028 let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;3029 Ok((self.0, who.clone(), imbalance))3030 }30313032 fn post_dispatch(3033 pre: Self::Pre,3034 info: &DispatchInfoOf<Self::Call>,3035 post_info: &PostDispatchInfoOf<Self::Call>,3036 len: usize,3037 _result: &DispatchResult,3038 ) -> Result<(), TransactionValidityError> {3039 let (tip, who, imbalance) = pre;3040 let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(3041 len as u32,3042 info,3043 post_info,3044 tip,3045 );3046 <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;3047 Ok(())3048 }3049}3050305130523053sp_api::decl_runtime_apis! {3054 pub trait NftApi {3055 3056 fn eth_contract_code(account: H160) -> Option<Vec<u8>>;3057 }3058}