difftreelog
Merge pull request #56 from usetech-llc/feature/NFTPAR-268_inregration_test_broken
in: master
NFTPAR-268 CreateCollection Tests Broken. Null terminated strings rep…
2 files changed
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, WithdrawReason,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial,29 },30 IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69 Invalid,70 NFT,71 // decimal points72 Fungible(DecimalPoints),73 // decimal points74 ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78 fn into(self) -> u8 {79 match self {80 CollectionMode::Invalid => 0,81 CollectionMode::NFT => 1,82 CollectionMode::Fungible(_) => 2,83 CollectionMode::ReFungible(_) => 3,84 }85 }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91 Normal,92 WhiteList,93}94impl Default for AccessMode {95 fn default() -> Self {96 Self::Normal97 }98}99100impl Default for CollectionMode {101 fn default() -> Self {102 Self::Invalid103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub owner: AccountId,149 pub const_data: Vec<u8>,150 pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType {156 pub value: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct ReFungibleItemType<AccountId> {162 pub owner: Vec<Ownership<AccountId>>,163 pub const_data: Vec<u8>,164 pub variable_data: Vec<u8>,165}166167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169// pub struct VestingItem<AccountId, Moment> {170// pub sender: AccountId,171// pub recipient: AccountId,172// pub collection_id: CollectionId,173// pub item_id: TokenId,174// pub amount: u64,175// pub vesting_date: Moment,176// }177178#[derive(Encode, Decode, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct CollectionLimits {181 pub account_token_ownership_limit: u32,182 pub sponsored_data_size: u32,183 pub token_limit: u32,184185 // Timeouts for item types in passed blocks186 pub sponsor_transfer_timeout: u32,187}188189impl Default for CollectionLimits {190 fn default() -> CollectionLimits {191 CollectionLimits { 192 account_token_ownership_limit: 10_000_000, 193 token_limit: u32::max_value(),194 sponsored_data_size: u32::max_value(), 195 sponsor_transfer_timeout: 14400 }196 }197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]201pub struct ChainLimits {202 pub collection_numbers_limit: u32,203 pub account_token_ownership_limit: u32,204 pub collections_admins_limit: u64,205 pub custom_data_limit: u32,206207 // Timeouts for item types in passed blocks208 pub nft_sponsor_transfer_timeout: u32,209 pub fungible_sponsor_transfer_timeout: u32,210 pub refungible_sponsor_transfer_timeout: u32,211}212213pub trait WeightInfo {214 fn create_collection() -> Weight;215 fn destroy_collection() -> Weight;216 fn add_to_white_list() -> Weight;217 fn remove_from_white_list() -> Weight;218 fn set_public_access_mode() -> Weight;219 fn set_mint_permission() -> Weight;220 fn change_collection_owner() -> Weight;221 fn add_collection_admin() -> Weight;222 fn remove_collection_admin() -> Weight;223 fn set_collection_sponsor() -> Weight;224 fn confirm_sponsorship() -> Weight;225 fn remove_collection_sponsor() -> Weight;226 fn create_item(s: usize) -> Weight;227 fn burn_item() -> Weight;228 fn transfer() -> Weight;229 fn approve() -> Weight;230 fn transfer_from() -> Weight;231 fn set_offchain_schema() -> Weight;232 fn set_const_on_chain_schema() -> Weight;233 fn set_variable_on_chain_schema() -> Weight;234 fn set_variable_meta_data() -> Weight;235 fn enable_contract_sponsoring() -> Weight;236 fn set_schema_version() -> Weight;237 fn set_chain_limits() -> Weight;238 fn set_contract_sponsoring_rate_limit() -> Weight;239 fn toggle_contract_white_list() -> Weight;240 fn add_to_contract_white_list() -> Weight;241 fn remove_from_contract_white_list() -> Weight;242 fn set_collection_limits() -> Weight;243}244245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]247pub struct CreateNftData {248 pub const_data: Vec<u8>,249 pub variable_data: Vec<u8>,250}251252#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]253#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]254pub struct CreateFungibleData {255 pub value: u128,256}257258#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub struct CreateReFungibleData {261 pub const_data: Vec<u8>,262 pub variable_data: Vec<u8>,263}264265#[derive(Encode, Decode, Debug, Clone, PartialEq)]266#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]267pub enum CreateItemData {268 NFT(CreateNftData),269 Fungible(CreateFungibleData),270 ReFungible(CreateReFungibleData),271}272273impl CreateItemData {274 pub fn len(&self) -> usize {275 let len = match self {276 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),277 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),278 _ => 0279 };280 281 return len;282 }283}284285impl From<CreateNftData> for CreateItemData {286 fn from(item: CreateNftData) -> Self {287 CreateItemData::NFT(item)288 }289}290291impl From<CreateReFungibleData> for CreateItemData {292 fn from(item: CreateReFungibleData) -> Self {293 CreateItemData::ReFungible(item)294 }295}296297impl From<CreateFungibleData> for CreateItemData {298 fn from(item: CreateFungibleData) -> Self {299 CreateItemData::Fungible(item)300 }301}302303304decl_error! {305 /// Error for non-fungible-token module.306 pub enum Error for Module<T: Trait> {307 /// Total collections bound exceeded.308 TotalCollectionsLimitExceeded,309 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.310 CollectionDecimalPointLimitExceeded, 311 /// Collection name can not be longer than 63 char.312 CollectionNameLimitExceeded, 313 /// Collection description can not be longer than 255 char.314 CollectionDescriptionLimitExceeded, 315 /// Token prefix can not be longer than 15 char.316 CollectionTokenPrefixLimitExceeded,317 /// This collection does not exist.318 CollectionNotFound,319 /// Item not exists.320 TokenNotFound,321 /// Arithmetic calculation overflow.322 NumOverflow, 323 /// Account already has admin role.324 AlreadyAdmin, 325 /// You do not own this collection.326 NoPermission,327 /// This address is not set as sponsor, use setCollectionSponsor first.328 ConfirmUnsetSponsorFail,329 /// Collection is not in mint mode.330 PublicMintingNotAllowed,331 /// Sender parameter and item owner must be equal.332 MustBeTokenOwner,333 /// Item balance not enough.334 TokenValueTooLow,335 /// Size of item is too large.336 NftSizeLimitExceeded,337 /// No approve found338 ApproveNotFound,339 /// Requested value more than approved.340 TokenValueNotEnough,341 /// Only approved addresses can call this method.342 ApproveRequired,343 /// Address is not in white list.344 AddresNotInWhiteList,345 /// Number of collection admins bound exceeded.346 CollectionAdminsLimitExceeded,347 /// Owned tokens by a single address bound exceeded.348 AddressOwnershipLimitExceeded,349 /// Length of items properties must be greater than 0.350 EmptyArgument,351 /// const_data exceeded data limit.352 TokenConstDataLimitExceeded,353 /// variable_data exceeded data limit.354 TokenVariableDataLimitExceeded,355 /// Not NFT item data used to mint in NFT collection.356 NotNftDataUsedToMintNftCollectionToken,357 /// Not Fungible item data used to mint in Fungible collection.358 NotFungibleDataUsedToMintFungibleCollectionToken,359 /// Not Re Fungible item data used to mint in Re Fungible collection.360 NotReFungibleDataUsedToMintReFungibleCollectionToken,361 /// Unexpected collection type.362 UnexpectedCollectionType,363 /// Can't store metadata in fungible tokens.364 CantStoreMetadataInFungibleTokens,365 /// Collection token limit exceeded366 CollectionTokenLimitExceeded,367 /// Account token limit exceeded per collection368 AccountTokenLimitExceeded,369 /// Collection limit bounds per collection exceeded370 CollectionLimitBoundsExceeded371 }372}373374pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {375 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;376377 /// Weight information for extrinsics in this pallet.378 type WeightInfo: WeightInfo;379}380381#[cfg(feature = "runtime-benchmarks")]382mod benchmarking;383384// #endregion385386decl_storage! {387 trait Store for Module<T: Trait> as Nft {388389 // Private members390 NextCollectionID: CollectionId;391 CreatedCollectionCount: u32;392 ChainVersion: u64;393 ItemListIndex: map hasher(identity) CollectionId => TokenId;394395 // Chain limits struct396 pub ChainLimit get(fn chain_limit) config(): ChainLimits;397398 // Bound counters399 CollectionCount: u32;400 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;401402 // Basic collections403 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;404 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;405 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;406407 /// Balance owner per collection map408 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;409410 /// second parameter: item id + owner account id + spender account id411 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;412413 /// Item collections414 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;415 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;416 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;417418 /// Index list419 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;420421 /// Tokens transfer baskets422 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;423 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;424 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;425426 // Contract Sponsorship and Ownership427 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;428 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;429 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;430 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;431 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 432 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 433 }434 add_extra_genesis {435 build(|config: &GenesisConfig<T>| {436 // Modification of storage437 for (_num, _c) in &config.collection {438 <Module<T>>::init_collection(_c);439 }440441 for (_num, _c, _i) in &config.nft_item_id {442 <Module<T>>::init_nft_token(*_c, _i);443 }444445 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {446 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);447 }448449 for (_num, _c, _i) in &config.refungible_item_id {450 <Module<T>>::init_refungible_token(*_c, _i);451 }452 })453 }454}455456decl_event!(457 pub enum Event<T>458 where459 AccountId = <T as system::Trait>::AccountId,460 {461 /// New collection was created462 /// 463 /// # Arguments464 /// 465 /// * collection_id: Globally unique identifier of newly created collection.466 /// 467 /// * mode: [CollectionMode] converted into u8.468 /// 469 /// * account_id: Collection owner.470 Created(CollectionId, u8, AccountId),471472 /// New item was created.473 /// 474 /// # Arguments475 /// 476 /// * collection_id: Id of the collection where item was created.477 /// 478 /// * item_id: Id of an item. Unique within the collection.479 ItemCreated(CollectionId, TokenId),480481 /// Collection item was burned.482 /// 483 /// # Arguments484 /// 485 /// collection_id.486 /// 487 /// item_id: Identifier of burned NFT.488 ItemDestroyed(CollectionId, TokenId),489 }490);491492decl_module! {493 pub struct Module<T: Trait> for enum Call where origin: T::Origin {494495 fn deposit_event() = default;496 type Error = Error<T>;497498 fn on_initialize(now: T::BlockNumber) -> Weight {499500 if ChainVersion::get() < 2501 {502 let value = NextCollectionID::get();503 CreatedCollectionCount::put(value);504 ChainVersion::put(2);505 }506507 0508 }509510 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.511 /// 512 /// # Permissions513 /// 514 /// * Anyone.515 /// 516 /// # Arguments517 /// 518 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.519 /// 520 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.521 /// 522 /// * token_prefix: UTF-8 string with token prefix.523 /// 524 /// * mode: [CollectionMode] collection type and type dependent data.525 // returns collection ID526 #[weight = T::WeightInfo::create_collection()]527 pub fn create_collection(origin,528 collection_name: Vec<u16>,529 collection_description: Vec<u16>,530 token_prefix: Vec<u8>,531 mode: CollectionMode) -> DispatchResult {532533 // Anyone can create a collection534 let who = ensure_signed(origin)?;535536 let decimal_points = match mode {537 CollectionMode::Fungible(points) => points,538 CollectionMode::ReFungible(points) => points,539 _ => 0540 };541542 // bound Total number of collections543 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);544545 // check params546 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);547548 let mut name = collection_name.to_vec();549 name.push(0);550 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);551552 let mut description = collection_description.to_vec();553 description.push(0);554 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);555556 let mut prefix = token_prefix.to_vec();557 prefix.push(0);558 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);559560 // Generate next collection ID561 let next_id = CreatedCollectionCount::get()562 .checked_add(1)563 .ok_or(Error::<T>::NumOverflow)?;564565 // bound counter566 let total = CollectionCount::get()567 .checked_add(1)568 .ok_or(Error::<T>::NumOverflow)?;569570 CreatedCollectionCount::put(next_id);571 CollectionCount::put(total);572573 // Create new collection574 let new_collection = CollectionType {575 owner: who.clone(),576 name: name,577 mode: mode.clone(),578 mint_mode: false,579 access: AccessMode::Normal,580 description: description,581 decimal_points: decimal_points,582 token_prefix: prefix,583 offchain_schema: Vec::new(),584 schema_version: SchemaVersion::ImageURL,585 sponsor: T::AccountId::default(),586 sponsor_confirmed: false,587 variable_on_chain_schema: Vec::new(),588 const_on_chain_schema: Vec::new(),589 limits: CollectionLimits::default(),590 };591592 // Add new collection to map593 <Collection<T>>::insert(next_id, new_collection);594595 // call event596 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));597598 Ok(())599 }600601 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.602 /// 603 /// # Permissions604 /// 605 /// * Collection Owner.606 /// 607 /// # Arguments608 /// 609 /// * collection_id: collection to destroy.610 #[weight = T::WeightInfo::destroy_collection()]611 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {612613 let sender = ensure_signed(origin)?;614 Self::check_owner_permissions(collection_id, sender)?;615616 <AddressTokens<T>>::remove_prefix(collection_id);617 <Allowances<T>>::remove_prefix(collection_id);618 <Balance<T>>::remove_prefix(collection_id);619 <ItemListIndex>::remove(collection_id);620 <AdminList<T>>::remove(collection_id);621 <Collection<T>>::remove(collection_id);622 <WhiteList<T>>::remove_prefix(collection_id);623624 <NftItemList<T>>::remove_prefix(collection_id);625 <FungibleItemList<T>>::remove_prefix(collection_id);626 <ReFungibleItemList<T>>::remove_prefix(collection_id);627628 <NftTransferBasket<T>>::remove_prefix(collection_id);629 <FungibleTransferBasket<T>>::remove_prefix(collection_id);630 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);631632 if CollectionCount::get() > 0633 {634 // bound couter635 let total = CollectionCount::get()636 .checked_sub(1)637 .ok_or(Error::<T>::NumOverflow)?;638639 CollectionCount::put(total);640 }641642 Ok(())643 }644645 /// Add an address to white list.646 /// 647 /// # Permissions648 /// 649 /// * Collection Owner650 /// * Collection Admin651 /// 652 /// # Arguments653 /// 654 /// * collection_id.655 /// 656 /// * address.657 #[weight = T::WeightInfo::add_to_white_list()]658 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{659660 let sender = ensure_signed(origin)?;661 Self::check_owner_or_admin_permissions(collection_id, sender)?;662663 <WhiteList<T>>::insert(collection_id, address, true);664 665 Ok(())666 }667668 /// Remove an address from white list.669 /// 670 /// # Permissions671 /// 672 /// * Collection Owner673 /// * Collection Admin674 /// 675 /// # Arguments676 /// 677 /// * collection_id.678 /// 679 /// * address.680 #[weight = T::WeightInfo::remove_from_white_list()]681 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{682683 let sender = ensure_signed(origin)?;684 Self::check_owner_or_admin_permissions(collection_id, sender)?;685686 <WhiteList<T>>::remove(collection_id, address);687688 Ok(())689 }690691 /// Toggle between normal and white list access for the methods with access for `Anyone`.692 /// 693 /// # Permissions694 /// 695 /// * Collection Owner.696 /// 697 /// # Arguments698 /// 699 /// * collection_id.700 /// 701 /// * mode: [AccessMode]702 #[weight = T::WeightInfo::set_public_access_mode()]703 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult704 {705 let sender = ensure_signed(origin)?;706707 Self::check_owner_permissions(collection_id, sender)?;708 let mut target_collection = <Collection<T>>::get(collection_id);709 target_collection.access = mode;710 <Collection<T>>::insert(collection_id, target_collection);711712 Ok(())713 }714715 /// Allows Anyone to create tokens if:716 /// * White List is enabled, and717 /// * Address is added to white list, and718 /// * This method was called with True parameter719 /// 720 /// # Permissions721 /// * Collection Owner722 ///723 /// # Arguments724 /// 725 /// * collection_id.726 /// 727 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.728 #[weight = T::WeightInfo::set_mint_permission()]729 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult730 {731 let sender = ensure_signed(origin)?;732733 Self::check_owner_permissions(collection_id, sender)?;734 let mut target_collection = <Collection<T>>::get(collection_id);735 target_collection.mint_mode = mint_permission;736 <Collection<T>>::insert(collection_id, target_collection);737738 Ok(())739 }740741 /// Change the owner of the collection.742 /// 743 /// # Permissions744 /// 745 /// * Collection Owner.746 /// 747 /// # Arguments748 /// 749 /// * collection_id.750 /// 751 /// * new_owner.752 #[weight = T::WeightInfo::change_collection_owner()]753 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {754755 let sender = ensure_signed(origin)?;756 Self::check_owner_permissions(collection_id, sender)?;757 let mut target_collection = <Collection<T>>::get(collection_id);758 target_collection.owner = new_owner;759 <Collection<T>>::insert(collection_id, target_collection);760761 Ok(())762 }763764 /// Adds an admin of the Collection.765 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 766 /// 767 /// # Permissions768 /// 769 /// * Collection Owner.770 /// * Collection Admin.771 /// 772 /// # Arguments773 /// 774 /// * collection_id: ID of the Collection to add admin for.775 /// 776 /// * new_admin_id: Address of new admin to add.777 #[weight = T::WeightInfo::add_collection_admin()]778 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {779780 let sender = ensure_signed(origin)?;781 Self::check_owner_or_admin_permissions(collection_id, sender)?;782 let mut admin_arr: Vec<T::AccountId> = Vec::new();783784 if <AdminList<T>>::contains_key(collection_id)785 {786 admin_arr = <AdminList<T>>::get(collection_id);787 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);788 }789790 // Number of collection admins791 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);792793 admin_arr.push(new_admin_id);794 <AdminList<T>>::insert(collection_id, admin_arr);795796 Ok(())797 }798799 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.800 ///801 /// # Permissions802 /// 803 /// * Collection Owner.804 /// * Collection Admin.805 /// 806 /// # Arguments807 /// 808 /// * collection_id: ID of the Collection to remove admin for.809 /// 810 /// * account_id: Address of admin to remove.811 #[weight = T::WeightInfo::remove_collection_admin()]812 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {813814 let sender = ensure_signed(origin)?;815 Self::check_owner_or_admin_permissions(collection_id, sender)?;816817 if <AdminList<T>>::contains_key(collection_id)818 {819 let mut admin_arr = <AdminList<T>>::get(collection_id);820 admin_arr.retain(|i| *i != account_id);821 <AdminList<T>>::insert(collection_id, admin_arr);822 }823824 Ok(())825 }826827 /// # Permissions828 /// 829 /// * Collection Owner830 /// 831 /// # Arguments832 /// 833 /// * collection_id.834 /// 835 /// * new_sponsor.836 #[weight = T::WeightInfo::set_collection_sponsor()]837 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {838839 let sender = ensure_signed(origin)?;840 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);841842 let mut target_collection = <Collection<T>>::get(collection_id);843 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);844845 target_collection.sponsor = new_sponsor;846 target_collection.sponsor_confirmed = false;847 <Collection<T>>::insert(collection_id, target_collection);848849 Ok(())850 }851852 /// # Permissions853 /// 854 /// * Sponsor.855 /// 856 /// # Arguments857 /// 858 /// * collection_id.859 #[weight = T::WeightInfo::confirm_sponsorship()]860 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {861862 let sender = ensure_signed(origin)?;863 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);864865 let mut target_collection = <Collection<T>>::get(collection_id);866 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);867868 target_collection.sponsor_confirmed = true;869 <Collection<T>>::insert(collection_id, target_collection);870871 Ok(())872 }873874 /// Switch back to pay-per-own-transaction model.875 ///876 /// # Permissions877 ///878 /// * Collection owner.879 /// 880 /// # Arguments881 /// 882 /// * collection_id.883 #[weight = T::WeightInfo::remove_collection_sponsor()]884 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {885886 let sender = ensure_signed(origin)?;887 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);888889 let mut target_collection = <Collection<T>>::get(collection_id);890 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);891892 target_collection.sponsor = T::AccountId::default();893 target_collection.sponsor_confirmed = false;894 <Collection<T>>::insert(collection_id, target_collection);895896 Ok(())897 }898899 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.900 /// 901 /// # Permissions902 /// 903 /// * Collection Owner.904 /// * Collection Admin.905 /// * Anyone if906 /// * White List is enabled, and907 /// * Address is added to white list, and908 /// * MintPermission is enabled (see SetMintPermission method)909 /// 910 /// # Arguments911 /// 912 /// * collection_id: ID of the collection.913 /// 914 /// * owner: Address, initial owner of the NFT.915 ///916 /// * data: Token data to store on chain.917 // #[weight =918 // (130_000_000 as Weight)919 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))920 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))921 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]922923 #[weight = T::WeightInfo::create_item(data.len())]924 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {925926 let sender = ensure_signed(origin)?;927928 Self::collection_exists(collection_id)?;929930 let target_collection = <Collection<T>>::get(collection_id);931932 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;933 Self::validate_create_item_args(&target_collection, &data)?;934 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;935936 Ok(())937 }938939 /// This method creates multiple instances of NFT Collection created with CreateCollection method.940 /// 941 /// # Permissions942 /// 943 /// * Collection Owner.944 /// * Collection Admin.945 /// * Anyone if946 /// * White List is enabled, and947 /// * Address is added to white list, and948 /// * MintPermission is enabled (see SetMintPermission method)949 /// 950 /// # Arguments951 /// 952 /// * collection_id: ID of the collection.953 /// 954 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].955 /// 956 /// * owner: Address, initial owner of the NFT.957 #[weight = T::WeightInfo::create_item(items_data.into_iter()958 .map(|data| { data.len() })959 .sum())]960 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {961962 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);963 let sender = ensure_signed(origin)?;964965 Self::collection_exists(collection_id)?;966 let target_collection = <Collection<T>>::get(collection_id);967968 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;969970 for data in &items_data {971 Self::validate_create_item_args(&target_collection, data)?;972 }973 for data in &items_data {974 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;975 }976977 Ok(())978 }979980 /// Destroys a concrete instance of NFT.981 /// 982 /// # Permissions983 /// 984 /// * Collection Owner.985 /// * Collection Admin.986 /// * Current NFT Owner.987 /// 988 /// # Arguments989 /// 990 /// * collection_id: ID of the collection.991 /// 992 /// * item_id: ID of NFT to burn.993 #[weight = T::WeightInfo::burn_item()]994 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {995996 let sender = ensure_signed(origin)?;997 Self::collection_exists(collection_id)?;998999 // Transfer permissions check1000 let target_collection = <Collection<T>>::get(collection_id);1001 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1002 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1003 Error::<T>::NoPermission);10041005 if target_collection.access == AccessMode::WhiteList {1006 Self::check_white_list(collection_id, &sender)?;1007 }10081009 match target_collection.mode1010 {1011 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1012 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1013 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1014 _ => ()1015 };10161017 // call event1018 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10191020 Ok(())1021 }10221023 /// Change ownership of the token.1024 /// 1025 /// # Permissions1026 /// 1027 /// * Collection Owner1028 /// * Collection Admin1029 /// * Current NFT owner1030 ///1031 /// # Arguments1032 /// 1033 /// * recipient: Address of token recipient.1034 /// 1035 /// * collection_id.1036 /// 1037 /// * item_id: ID of the item1038 /// * Non-Fungible Mode: Required.1039 /// * Fungible Mode: Ignored.1040 /// * Re-Fungible Mode: Required.1041 /// 1042 /// * value: Amount to transfer.1043 /// * Non-Fungible Mode: Ignored1044 /// * Fungible Mode: Must specify transferred amount1045 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1046 #[weight = T::WeightInfo::transfer()]1047 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10481049 let sender = ensure_signed(origin)?;1050 let target_collection = <Collection<T>>::get(collection_id);10511052 // Limits check1053 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10541055 // Transfer permissions check1056 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1057 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1058 Error::<T>::NoPermission);10591060 if target_collection.access == AccessMode::WhiteList {1061 Self::check_white_list(collection_id, &sender)?;1062 Self::check_white_list(collection_id, &recipient)?;1063 }10641065 match target_collection.mode1066 {1067 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1068 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1069 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1070 _ => ()1071 };10721073 Ok(())1074 }10751076 /// Set, change, or remove approved address to transfer the ownership of the NFT.1077 /// 1078 /// # Permissions1079 /// 1080 /// * Collection Owner1081 /// * Collection Admin1082 /// * Current NFT owner1083 /// 1084 /// # Arguments1085 /// 1086 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1087 /// 1088 /// * collection_id.1089 /// 1090 /// * item_id: ID of the item.1091 #[weight = T::WeightInfo::approve()]1092 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10931094 let sender = ensure_signed(origin)?;10951096 // Transfer permissions check1097 let target_collection = <Collection<T>>::get(collection_id);1098 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1099 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1100 Error::<T>::NoPermission);11011102 if target_collection.access == AccessMode::WhiteList {1103 Self::check_white_list(collection_id, &sender)?;1104 Self::check_white_list(collection_id, &spender)?;1105 }11061107 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1108 let mut allowance: u128 = amount;1109 if allowance_exists {1110 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1111 }1112 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11131114 Ok(())1115 }1116 1117 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1118 /// 1119 /// # Permissions1120 /// * Collection Owner1121 /// * Collection Admin1122 /// * Current NFT owner1123 /// * Address approved by current NFT owner1124 /// 1125 /// # Arguments1126 /// 1127 /// * from: Address that owns token.1128 /// 1129 /// * recipient: Address of token recipient.1130 /// 1131 /// * collection_id.1132 /// 1133 /// * item_id: ID of the item.1134 /// 1135 /// * value: Amount to transfer.1136 #[weight = T::WeightInfo::transfer_from()]1137 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11381139 let sender = ensure_signed(origin)?;1140 let mut appoved_transfer = false;11411142 // Check approval1143 let mut approval: u128 = 0;1144 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1145 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1146 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1147 appoved_transfer = true;1148 }11491150 let target_collection = <Collection<T>>::get(collection_id);11511152 // Limits check1153 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11541155 // Transfer permissions check 1156 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1157 Error::<T>::NoPermission);11581159 if target_collection.access == AccessMode::WhiteList {1160 Self::check_white_list(collection_id, &sender)?;1161 Self::check_white_list(collection_id, &recipient)?;1162 }11631164 // Reduce approval by transferred amount or remove if remaining approval drops to 01165 if approval - value > 0 {1166 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1167 }1168 else {1169 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1170 }11711172 match target_collection.mode1173 {1174 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1175 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1176 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1177 _ => ()1178 };11791180 Ok(())1181 }11821183 #[weight = 0]1184 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11851186 // let no_perm_mes = "You do not have permissions to modify this collection";1187 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1188 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1189 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11901191 // // on_nft_received call11921193 // Self::transfer(origin, collection_id, item_id, new_owner)?;11941195 Ok(())1196 }11971198 /// Set off-chain data schema.1199 /// 1200 /// # Permissions1201 /// 1202 /// * Collection Owner1203 /// * Collection Admin1204 /// 1205 /// # Arguments1206 /// 1207 /// * collection_id.1208 /// 1209 /// * schema: String representing the offchain data schema.1210 #[weight = T::WeightInfo::set_variable_meta_data()]1211 pub fn set_variable_meta_data (1212 origin,1213 collection_id: CollectionId,1214 item_id: TokenId,1215 data: Vec<u8>1216 ) -> DispatchResult {1217 let sender = ensure_signed(origin)?;1218 1219 Self::collection_exists(collection_id)?;1220 1221 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12221223 // Modify permissions check1224 let target_collection = <Collection<T>>::get(collection_id);1225 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1226 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1227 Error::<T>::NoPermission);12281229 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12301231 match target_collection.mode1232 {1233 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1234 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1235 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1236 _ => fail!(Error::<T>::UnexpectedCollectionType)1237 };12381239 Ok(())1240 }1241 1242 /// Set schema standard1243 /// ImageURL1244 /// Unique1245 /// 1246 /// # Permissions1247 /// 1248 /// * Collection Owner1249 /// * Collection Admin1250 /// 1251 /// # Arguments1252 /// 1253 /// * collection_id.1254 /// 1255 /// * schema: SchemaVersion: enum1256 #[weight = T::WeightInfo::set_schema_version()]1257 pub fn set_schema_version(1258 origin,1259 collection_id: CollectionId,1260 version: SchemaVersion1261 ) -> DispatchResult {1262 let sender = ensure_signed(origin)?;1263 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1264 let mut target_collection = <Collection<T>>::get(collection_id);1265 target_collection.schema_version = version;1266 <Collection<T>>::insert(collection_id, target_collection);12671268 Ok(())1269 }12701271 /// Set off-chain data schema.1272 /// 1273 /// # Permissions1274 /// 1275 /// * Collection Owner1276 /// * Collection Admin1277 /// 1278 /// # Arguments1279 /// 1280 /// * collection_id.1281 /// 1282 /// * schema: String representing the offchain data schema.1283 #[weight = T::WeightInfo::set_offchain_schema()]1284 pub fn set_offchain_schema(1285 origin,1286 collection_id: CollectionId,1287 schema: Vec<u8>1288 ) -> DispatchResult {1289 let sender = ensure_signed(origin)?;1290 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12911292 let mut target_collection = <Collection<T>>::get(collection_id);1293 target_collection.offchain_schema = schema;1294 <Collection<T>>::insert(collection_id, target_collection);12951296 Ok(())1297 }12981299 /// Set const on-chain data schema.1300 /// 1301 /// # Permissions1302 /// 1303 /// * Collection Owner1304 /// * Collection Admin1305 /// 1306 /// # Arguments1307 /// 1308 /// * collection_id.1309 /// 1310 /// * schema: String representing the const on-chain data schema.1311 #[weight = T::WeightInfo::set_const_on_chain_schema()]1312 pub fn set_const_on_chain_schema (1313 origin,1314 collection_id: CollectionId,1315 schema: Vec<u8>1316 ) -> DispatchResult {1317 let sender = ensure_signed(origin)?;1318 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13191320 let mut target_collection = <Collection<T>>::get(collection_id);1321 target_collection.const_on_chain_schema = schema;1322 <Collection<T>>::insert(collection_id, target_collection);13231324 Ok(())1325 }13261327 /// Set variable on-chain data schema.1328 /// 1329 /// # Permissions1330 /// 1331 /// * Collection Owner1332 /// * Collection Admin1333 /// 1334 /// # Arguments1335 /// 1336 /// * collection_id.1337 /// 1338 /// * schema: String representing the variable on-chain data schema.1339 #[weight = T::WeightInfo::set_const_on_chain_schema()]1340 pub fn set_variable_on_chain_schema (1341 origin,1342 collection_id: CollectionId,1343 schema: Vec<u8>1344 ) -> DispatchResult {1345 let sender = ensure_signed(origin)?;1346 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13471348 let mut target_collection = <Collection<T>>::get(collection_id);1349 target_collection.variable_on_chain_schema = schema;1350 <Collection<T>>::insert(collection_id, target_collection);13511352 Ok(())1353 }13541355 // Sudo permissions function1356 #[weight = T::WeightInfo::set_chain_limits()]1357 pub fn set_chain_limits(1358 origin,1359 limits: ChainLimits1360 ) -> DispatchResult {13611362 #[cfg(not(feature = "runtime-benchmarks"))]1363 ensure_root(origin)?;13641365 <ChainLimit>::put(limits);1366 Ok(())1367 }13681369 /// Enable smart contract self-sponsoring.1370 /// 1371 /// # Permissions1372 /// 1373 /// * Contract Owner1374 /// 1375 /// # Arguments1376 /// 1377 /// * contract address1378 /// * enable flag1379 /// 1380 #[weight = T::WeightInfo::enable_contract_sponsoring()]1381 pub fn enable_contract_sponsoring(1382 origin,1383 contract_address: T::AccountId,1384 enable: bool1385 ) -> DispatchResult {13861387 let sender = ensure_signed(origin)?;13881389 #[cfg(feature = "runtime-benchmarks")]1390 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13911392 Self::ensure_contract_owned(sender, &contract_address)?;13931394 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1395 Ok(())1396 }13971398 /// Set the rate limit for contract sponsoring to specified number of blocks.1399 /// 1400 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1401 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1402 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1403 /// from contract endowment if there are at least B blocks between such transactions. 1404 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1405 /// 1406 /// # Permissions1407 /// 1408 /// * Contract Owner1409 /// 1410 /// # Arguments1411 /// 1412 /// -`contract_address`: Address of the contract to sponsor1413 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1414 /// 1415 #[weight = T::WeightInfo::set_contract_sponsoring_rate_limit()]1416 pub fn set_contract_sponsoring_rate_limit(1417 origin,1418 contract_address: T::AccountId,1419 rate_limit: T::BlockNumber1420 ) -> DispatchResult {1421 let sender = ensure_signed(origin)?;14221423 #[cfg(feature = "runtime-benchmarks")]1424 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14251426 Self::ensure_contract_owned(sender, &contract_address)?;1427 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1428 Ok(())1429 }14301431 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1432 /// 1433 /// # Permissions1434 /// 1435 /// * Address that deployed smart contract.1436 /// 1437 /// # Arguments1438 /// 1439 /// -`contract_address`: Address of the contract.1440 /// 1441 /// - `enable`: . 1442 #[weight = T::WeightInfo::toggle_contract_white_list()]1443 pub fn toggle_contract_white_list(1444 origin,1445 contract_address: T::AccountId,1446 enable: bool1447 ) -> DispatchResult {1448 let sender = ensure_signed(origin)?;14491450 #[cfg(feature = "runtime-benchmarks")]1451 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14521453 Self::ensure_contract_owned(sender, &contract_address)?;1454 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1455 Ok(())1456 }1457 1458 /// Add an address to smart contract white list.1459 /// 1460 /// # Permissions1461 /// 1462 /// * Address that deployed smart contract.1463 /// 1464 /// # Arguments1465 /// 1466 /// -`contract_address`: Address of the contract.1467 ///1468 /// -`account_address`: Address to add.1469 #[weight = T::WeightInfo::add_to_contract_white_list()]1470 pub fn add_to_contract_white_list(1471 origin,1472 contract_address: T::AccountId,1473 account_address: T::AccountId1474 ) -> DispatchResult {1475 let sender = ensure_signed(origin)?;14761477 #[cfg(feature = "runtime-benchmarks")]1478 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1479 1480 Self::ensure_contract_owned(sender, &contract_address)?; 1481 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1482 Ok(())1483 }14841485 /// Remove an address from smart contract white list.1486 /// 1487 /// # Permissions1488 /// 1489 /// * Address that deployed smart contract.1490 /// 1491 /// # Arguments1492 /// 1493 /// -`contract_address`: Address of the contract.1494 ///1495 /// -`account_address`: Address to remove.1496 #[weight = T::WeightInfo::remove_from_contract_white_list()]1497 pub fn remove_from_contract_white_list(1498 origin,1499 contract_address: T::AccountId,1500 account_address: T::AccountId1501 ) -> DispatchResult {1502 let sender = ensure_signed(origin)?;15031504 #[cfg(feature = "runtime-benchmarks")]1505 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15061507 Self::ensure_contract_owned(sender, &contract_address)?;1508 <ContractWhiteList<T>>::remove(contract_address, account_address);1509 Ok(())1510 }15111512 #[weight = T::WeightInfo::set_collection_limits()]1513 pub fn set_collection_limits(1514 origin,1515 collection_id: u32,1516 limits: CollectionLimits,1517 ) -> DispatchResult {1518 let sender = ensure_signed(origin)?;1519 Self::check_owner_permissions(collection_id, sender.clone())?;1520 let mut target_collection = <Collection<T>>::get(collection_id);1521 let chain_limits = ChainLimit::get();1522 let climits = target_collection.limits;15231524 // collection bounds1525 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1526 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1527 Error::<T>::CollectionLimitBoundsExceeded);15281529 // token_limit check prev1530 ensure!(climits.token_limit > limits.token_limit && 1531 limits.token_limit <= chain_limits.account_token_ownership_limit, 1532 Error::<T>::AccountTokenLimitExceeded);15331534 target_collection.limits = limits;1535 <Collection<T>>::insert(collection_id, target_collection);15361537 Ok(())1538 } 1539 }1540}15411542impl<T: Trait> Module<T> {15431544 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15451546 // check token limit and account token limit1547 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1548 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1549 1550 Ok(())1551 }15521553 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15541555 // check token limit and account token limit1556 let total_items: u32 = ItemListIndex::get(collection_id);1557 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1558 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1559 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15601561 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1562 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1563 Self::check_white_list(collection_id, owner)?;1564 Self::check_white_list(collection_id, sender)?;1565 }15661567 Ok(())1568 }15691570 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1571 match target_collection.mode1572 {1573 CollectionMode::NFT => {1574 if let CreateItemData::NFT(data) = data {1575 // check sizes1576 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1577 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1578 } else {1579 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1580 }1581 },1582 CollectionMode::Fungible(_) => {1583 if let CreateItemData::Fungible(_) = data {1584 } else {1585 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1586 }1587 },1588 CollectionMode::ReFungible(_) => {1589 if let CreateItemData::ReFungible(data) = data {15901591 // check sizes1592 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1593 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1594 } else {1595 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1596 }1597 },1598 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1599 };16001601 Ok(())1602 }16031604 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1605 match data1606 {1607 CreateItemData::NFT(data) => {1608 let item = NftItemType {1609 owner,1610 const_data: data.const_data,1611 variable_data: data.variable_data1612 };16131614 Self::add_nft_item(collection_id, item)?;1615 },1616 CreateItemData::Fungible(data) => {1617 Self::add_fungible_item(collection_id, &owner, data.value)?;1618 },1619 CreateItemData::ReFungible(data) => {1620 let mut owner_list = Vec::new();1621 let value = (10 as u128).pow(collection.decimal_points as u32);1622 owner_list.push(Ownership {owner: owner.clone(), fraction: value});16231624 let item = ReFungibleItemType {1625 owner: owner_list,1626 const_data: data.const_data,1627 variable_data: data.variable_data1628 };16291630 Self::add_refungible_item(collection_id, item)?;1631 }1632 };16331634 // call event1635 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16361637 Ok(())1638 }16391640 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16411642 // Does new owner already have an account?1643 let mut balance: u128 = 0;1644 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1645 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1646 } 16471648 // Mint 1649 let item = FungibleItemType {1650 value: balance + value1651 };1652 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16531654 // Update balance1655 let new_balance = <Balance<T>>::get(collection_id, owner)1656 .checked_add(value)1657 .ok_or(Error::<T>::NumOverflow)?;1658 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16591660 Ok(())1661 }16621663 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1664 let current_index = <ItemListIndex>::get(collection_id)1665 .checked_add(1)1666 .ok_or(Error::<T>::NumOverflow)?;1667 let itemcopy = item.clone();16681669 let value = item.owner.first().unwrap().fraction;1670 let owner = item.owner.first().unwrap().owner.clone();16711672 Self::add_token_index(collection_id, current_index, owner.clone())?;16731674 <ItemListIndex>::insert(collection_id, current_index);1675 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16761677 // Update balance1678 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1679 .checked_add(value)1680 .ok_or(Error::<T>::NumOverflow)?;1681 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16821683 Ok(())1684 }16851686 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1687 let current_index = <ItemListIndex>::get(collection_id)1688 .checked_add(1)1689 .ok_or(Error::<T>::NumOverflow)?;16901691 let item_owner = item.owner.clone();1692 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16931694 <ItemListIndex>::insert(collection_id, current_index);1695 <NftItemList<T>>::insert(collection_id, current_index, item);16961697 // Update balance1698 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1699 .checked_add(1)1700 .ok_or(Error::<T>::NumOverflow)?;1701 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17021703 Ok(())1704 }17051706 fn burn_refungible_item(1707 collection_id: CollectionId,1708 item_id: TokenId,1709 owner: T::AccountId,1710 ) -> DispatchResult {1711 ensure!(1712 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1713 Error::<T>::TokenNotFound1714 );1715 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1716 let item = collection1717 .owner1718 .iter()1719 .filter(|&i| i.owner == owner)1720 .next()1721 .unwrap();1722 Self::remove_token_index(collection_id, item_id, owner.clone())?;17231724 // update balance1725 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1726 .checked_sub(item.fraction)1727 .ok_or(Error::<T>::NumOverflow)?;1728 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17291730 <ReFungibleItemList<T>>::remove(collection_id, item_id);17311732 Ok(())1733 }17341735 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1736 ensure!(1737 <NftItemList<T>>::contains_key(collection_id, item_id),1738 Error::<T>::TokenNotFound1739 );1740 let item = <NftItemList<T>>::get(collection_id, item_id);1741 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17421743 // update balance1744 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1745 .checked_sub(1)1746 .ok_or(Error::<T>::NumOverflow)?;1747 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1748 <NftItemList<T>>::remove(collection_id, item_id);17491750 Ok(())1751 }17521753 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1754 ensure!(1755 <FungibleItemList<T>>::contains_key(collection_id, owner),1756 Error::<T>::TokenNotFound1757 );1758 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1759 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17601761 // update balance1762 let new_balance = <Balance<T>>::get(collection_id, owner)1763 .checked_sub(value)1764 .ok_or(Error::<T>::NumOverflow)?;1765 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17661767 if balance.value - value > 0 {1768 balance.value -= value;1769 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1770 }1771 else {1772 <FungibleItemList<T>>::remove(collection_id, owner);1773 }17741775 Ok(())1776 }17771778 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1779 ensure!(1780 <Collection<T>>::contains_key(collection_id),1781 Error::<T>::CollectionNotFound1782 );1783 Ok(())1784 }17851786 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1787 Self::collection_exists(collection_id)?;17881789 let target_collection = <Collection<T>>::get(collection_id);1790 ensure!(1791 subject == target_collection.owner,1792 Error::<T>::NoPermission1793 );17941795 Ok(())1796 }17971798 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1799 let target_collection = <Collection<T>>::get(collection_id);1800 let mut result: bool = subject == target_collection.owner;1801 let exists = <AdminList<T>>::contains_key(collection_id);18021803 if !result & exists {1804 if <AdminList<T>>::get(collection_id).contains(&subject) {1805 result = true1806 }1807 }18081809 result1810 }18111812 fn check_owner_or_admin_permissions(1813 collection_id: CollectionId,1814 subject: T::AccountId,1815 ) -> DispatchResult {1816 Self::collection_exists(collection_id)?;1817 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18181819 ensure!(1820 result,1821 Error::<T>::NoPermission1822 );1823 Ok(())1824 }18251826 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1827 let target_collection = <Collection<T>>::get(collection_id);18281829 match target_collection.mode {1830 CollectionMode::NFT => {1831 <NftItemList<T>>::get(collection_id, item_id).owner == subject1832 }1833 CollectionMode::Fungible(_) => {1834 <FungibleItemList<T>>::contains_key(collection_id, &subject)1835 }1836 CollectionMode::ReFungible(_) => {1837 <ReFungibleItemList<T>>::get(collection_id, item_id)1838 .owner1839 .iter()1840 .any(|i| i.owner == subject)1841 }1842 CollectionMode::Invalid => false,1843 }1844 }18451846 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1847 let mes = Error::<T>::AddresNotInWhiteList;1848 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18491850 Ok(())1851 }18521853 fn transfer_fungible(1854 collection_id: CollectionId,1855 value: u128,1856 owner: &T::AccountId,1857 recipient: &T::AccountId,1858 ) -> DispatchResult {1859 ensure!(1860 <FungibleItemList<T>>::contains_key(collection_id, owner),1861 Error::<T>::TokenNotFound1862 );18631864 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1865 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18661867 // Send balance to recipient (updates balanceOf of recipient)1868 Self::add_fungible_item(collection_id, recipient, value)?;18691870 // update balanceOf of sender1871 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18721873 // Reduce or remove sender1874 if balance.value == value {1875 <FungibleItemList<T>>::remove(collection_id, owner);1876 }1877 else {1878 balance.value -= value;1879 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1880 }18811882 Ok(())1883 }18841885 fn transfer_refungible(1886 collection_id: CollectionId,1887 item_id: TokenId,1888 value: u128,1889 owner: T::AccountId,1890 new_owner: T::AccountId,1891 ) -> DispatchResult {1892 ensure!(1893 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1894 Error::<T>::TokenNotFound1895 );18961897 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1898 let item = full_item1899 .owner1900 .iter()1901 .filter(|i| i.owner == owner)1902 .next()1903 .ok_or(Error::<T>::NumOverflow)?;1904 let amount = item.fraction;19051906 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19071908 // update balance1909 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1910 .checked_sub(value)1911 .ok_or(Error::<T>::NumOverflow)?;1912 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19131914 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1915 .checked_add(value)1916 .ok_or(Error::<T>::NumOverflow)?;1917 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19181919 let old_owner = item.owner.clone();1920 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19211922 // transfer1923 if amount == value && !new_owner_has_account {1924 // change owner1925 // new owner do not have account1926 let mut new_full_item = full_item.clone();1927 new_full_item1928 .owner1929 .iter_mut()1930 .find(|i| i.owner == owner)1931 .unwrap()1932 .owner = new_owner.clone();1933 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19341935 // update index collection1936 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1937 } else {1938 let mut new_full_item = full_item.clone();1939 new_full_item1940 .owner1941 .iter_mut()1942 .find(|i| i.owner == owner)1943 .unwrap()1944 .fraction -= value;19451946 // separate amount1947 if new_owner_has_account {1948 // new owner has account1949 new_full_item1950 .owner1951 .iter_mut()1952 .find(|i| i.owner == new_owner)1953 .unwrap()1954 .fraction += value;1955 } else {1956 // new owner do not have account1957 new_full_item.owner.push(Ownership {1958 owner: new_owner.clone(),1959 fraction: value,1960 });1961 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1962 }19631964 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1965 }19661967 Ok(())1968 }19691970 fn transfer_nft(1971 collection_id: CollectionId,1972 item_id: TokenId,1973 sender: T::AccountId,1974 new_owner: T::AccountId,1975 ) -> DispatchResult {1976 ensure!(1977 <NftItemList<T>>::contains_key(collection_id, item_id),1978 Error::<T>::TokenNotFound1979 );19801981 let mut item = <NftItemList<T>>::get(collection_id, item_id);19821983 ensure!(1984 sender == item.owner,1985 Error::<T>::MustBeTokenOwner1986 );19871988 // update balance1989 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1990 .checked_sub(1)1991 .ok_or(Error::<T>::NumOverflow)?;1992 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19931994 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1995 .checked_add(1)1996 .ok_or(Error::<T>::NumOverflow)?;1997 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19981999 // change owner2000 let old_owner = item.owner.clone();2001 item.owner = new_owner.clone();2002 <NftItemList<T>>::insert(collection_id, item_id, item);20032004 // update index collection2005 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20062007 Ok(())2008 }2009 2010 fn item_exists(2011 collection_id: CollectionId,2012 item_id: TokenId,2013 mode: &CollectionMode2014 ) -> DispatchResult {2015 match mode {2016 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2017 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2018 _ => ()2019 };2020 2021 Ok(())2022 }20232024 fn set_re_fungible_variable_data(2025 collection_id: CollectionId,2026 item_id: TokenId,2027 data: Vec<u8>2028 ) -> DispatchResult {2029 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20302031 item.variable_data = data;20322033 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20342035 Ok(())2036 }20372038 fn set_nft_variable_data(2039 collection_id: CollectionId,2040 item_id: TokenId,2041 data: Vec<u8>2042 ) -> DispatchResult {2043 let mut item = <NftItemList<T>>::get(collection_id, item_id);2044 2045 item.variable_data = data;20462047 <NftItemList<T>>::insert(collection_id, item_id, item);2048 2049 Ok(())2050 }20512052 fn init_collection(item: &CollectionType<T::AccountId>) {2053 // check params2054 assert!(2055 item.decimal_points <= MAX_DECIMAL_POINTS,2056 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2057 );2058 assert!(2059 item.name.len() <= 64,2060 "Collection name can not be longer than 63 char"2061 );2062 assert!(2063 item.name.len() <= 256,2064 "Collection description can not be longer than 255 char"2065 );2066 assert!(2067 item.token_prefix.len() <= 16,2068 "Token prefix can not be longer than 15 char"2069 );20702071 // Generate next collection ID2072 let next_id = CreatedCollectionCount::get()2073 .checked_add(1)2074 .unwrap();20752076 CreatedCollectionCount::put(next_id);2077 }20782079 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2080 let current_index = <ItemListIndex>::get(collection_id)2081 .checked_add(1)2082 .unwrap();20832084 let item_owner = item.owner.clone();2085 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20862087 <ItemListIndex>::insert(collection_id, current_index);20882089 // Update balance2090 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2091 .checked_add(1)2092 .unwrap();2093 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2094 }20952096 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2097 let current_index = <ItemListIndex>::get(collection_id)2098 .checked_add(1)2099 .unwrap();21002101 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();21022103 <ItemListIndex>::insert(collection_id, current_index);21042105 // Update balance2106 let new_balance = <Balance<T>>::get(collection_id, owner)2107 .checked_add(item.value)2108 .unwrap();2109 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2110 }21112112 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2113 let current_index = <ItemListIndex>::get(collection_id)2114 .checked_add(1)2115 .unwrap();21162117 let value = item.owner.first().unwrap().fraction;2118 let owner = item.owner.first().unwrap().owner.clone();21192120 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();21212122 <ItemListIndex>::insert(collection_id, current_index);21232124 // Update balance2125 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2126 .checked_add(value)2127 .unwrap();2128 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2129 }21302131 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21322133 // add to account limit2134 if <AccountItemCount<T>>::contains_key(owner.clone()) {21352136 // bound Owned tokens by a single address2137 let count = <AccountItemCount<T>>::get(owner.clone());2138 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21392140 <AccountItemCount<T>>::insert(owner.clone(), count2141 .checked_add(1)2142 .ok_or(Error::<T>::NumOverflow)?);2143 }2144 else {2145 <AccountItemCount<T>>::insert(owner.clone(), 1);2146 }21472148 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2149 if list_exists {2150 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2151 let item_contains = list.contains(&item_index.clone());21522153 if !item_contains {2154 list.push(item_index.clone());2155 }21562157 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2158 } else {2159 let mut itm = Vec::new();2160 itm.push(item_index.clone());2161 <AddressTokens<T>>::insert(collection_id, owner, itm);2162 2163 }21642165 Ok(())2166 }21672168 fn remove_token_index(2169 collection_id: CollectionId,2170 item_index: TokenId,2171 owner: T::AccountId,2172 ) -> DispatchResult {21732174 // update counter2175 <AccountItemCount<T>>::insert(owner.clone(), 2176 <AccountItemCount<T>>::get(owner.clone())2177 .checked_sub(1)2178 .ok_or(Error::<T>::NumOverflow)?);217921802181 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2182 if list_exists {2183 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2184 let item_contains = list.contains(&item_index.clone());21852186 if item_contains {2187 list.retain(|&item| item != item_index);2188 <AddressTokens<T>>::insert(collection_id, owner, list);2189 }2190 }21912192 Ok(())2193 }21942195 fn move_token_index(2196 collection_id: CollectionId,2197 item_index: TokenId,2198 old_owner: T::AccountId,2199 new_owner: T::AccountId,2200 ) -> DispatchResult {2201 Self::remove_token_index(collection_id, item_index, old_owner)?;2202 Self::add_token_index(collection_id, item_index, new_owner)?;22032204 Ok(())2205 }2206 2207 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2208 if <ContractOwner<T>>::contains_key(contract.clone()) {2209 let owner = <ContractOwner<T>>::get(contract);2210 ensure!(account == owner, Error::<T>::NoPermission);2211 } else {2212 fail!(Error::<T>::NoPermission);2213 }22142215 Ok(())2216 }2217}22182219////////////////////////////////////////////////////////////////////////////////////////////////////2220// Economic models2221// #region22222223/// Fee multiplier.2224pub type Multiplier = FixedU128;22252226type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2227 <T as system::Trait>::AccountId,2228>>::Balance;2229type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2230 <T as system::Trait>::AccountId,2231>>::NegativeImbalance;22322233/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2234/// in the queue.2235#[derive(Encode, Decode, Clone, Eq, PartialEq)]2236pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2237 #[codec(compact)] BalanceOf<T>2238);22392240impl<T: Trait + Send + Sync> sp_std::fmt::Debug2241 for ChargeTransactionPayment<T>2242{2243 #[cfg(feature = "std")]2244 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2245 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2246 }2247 #[cfg(not(feature = "std"))]2248 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2249 Ok(())2250 }2251}22522253impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2254where2255 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2256 BalanceOf<T>: Send + Sync + FixedPointOperand,2257{2258 /// utility constructor. Used only in client/factory code.2259 pub fn from(fee: BalanceOf<T>) -> Self {2260 Self(fee)2261 }22622263 pub fn traditional_fee(2264 len: usize,2265 info: &DispatchInfoOf<T::Call>,2266 tip: BalanceOf<T>,2267 ) -> BalanceOf<T>2268 where2269 T::Call: Dispatchable<Info = DispatchInfo>,2270 {2271 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2272 }22732274 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2275 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2276 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2277 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2278 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2279 }22802281 fn withdraw_fee(2282 &self,2283 who: &T::AccountId,2284 call: &T::Call,2285 info: &DispatchInfoOf<T::Call>,2286 len: usize,2287 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2288 let tip = self.0;22892290 // Set fee based on call type. Creating collection costs 1 Unique.2291 // All other transactions have traditional fees so far2292 // let fee = match call.is_sub_type() {2293 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2294 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2295 // // _ => <BalanceOf<T>>::from(100)2296 // };2297 let fee = Self::traditional_fee(len, info, tip);22982299 // Only mess with balances if fee is not zero.2300 if fee.is_zero() {2301 return Ok((fee, None));2302 }23032304 // Determine who is paying transaction fee based on ecnomic model2305 // Parse call to extract collection ID and access collection sponsor2306 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2307 Some(Call::create_item(collection_id, _owner, _properties)) => {23082309 // check free create limit2310 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2311 (<Collection<T>>::get(collection_id).sponsor_confirmed)2312 {2313 <Collection<T>>::get(collection_id).sponsor2314 } else {2315 T::AccountId::default()2316 }2317 }2318 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2319 2320 let mut sponsor_transfer = false;2321 if <Collection<T>>::get(collection_id).sponsor_confirmed {23222323 let collection_limits = <Collection<T>>::get(collection_id).limits;2324 let collection_mode = <Collection<T>>::get(collection_id).mode;2325 2326 // sponsor timeout2327 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2328 sponsor_transfer = match collection_mode {2329 CollectionMode::NFT => {2330 2331 // get correct limit2332 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2333 collection_limits.sponsor_transfer_timeout2334 } else {2335 ChainLimit::get().nft_sponsor_transfer_timeout2336 };2337 2338 let mut sponsored = true;2339 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2340 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2341 let limit_time = last_tx_block + limit.into();2342 if block_number <= limit_time {2343 sponsored = false;2344 }2345 }2346 if sponsored {2347 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2348 }23492350 sponsored2351 }2352 CollectionMode::Fungible(_) => {2353 2354 // get correct limit2355 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2356 collection_limits.sponsor_transfer_timeout2357 } else {2358 ChainLimit::get().fungible_sponsor_transfer_timeout2359 };2360 2361 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2362 let mut sponsored = true;2363 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2364 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2365 let limit_time = last_tx_block + limit.into();2366 if block_number <= limit_time {2367 sponsored = false;2368 }2369 }2370 if sponsored {2371 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2372 }23732374 sponsored2375 }2376 CollectionMode::ReFungible(_) => {2377 2378 // get correct limit2379 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2380 collection_limits.sponsor_transfer_timeout2381 } else {2382 ChainLimit::get().refungible_sponsor_transfer_timeout2383 };2384 2385 let mut sponsored = true;2386 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2387 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2388 let limit_time = last_tx_block + limit.into();2389 if block_number <= limit_time {2390 sponsored = false;2391 }2392 }2393 if sponsored {2394 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2395 }23962397 sponsored2398 }2399 _ => {2400 false2401 },2402 };2403 }24042405 if !sponsor_transfer {2406 T::AccountId::default()2407 } else {2408 <Collection<T>>::get(collection_id).sponsor2409 }2410 }24112412 _ => T::AccountId::default(),2413 };24142415 // Sponsor smart contracts2416 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24172418 // On instantiation: set the contract owner2419 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24202421 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2422 code_hash,2423 &data,2424 &who,2425 );2426 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24272428 T::AccountId::default()2429 },24302431 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2432 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24332434 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24352436 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2437 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2438 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2439 2440 if !owned_contract && white_list_enabled {2441 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2442 return Err(InvalidTransaction::Call.into());2443 }2444 }24452446 let mut sponsor_transfer = false;2447 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2448 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2449 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2450 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2451 let limit_time = last_tx_block + rate_limit;24522453 if block_number >= limit_time {2454 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2455 sponsor_transfer = true;2456 }2457 } else {2458 sponsor_transfer = false;2459 }2460 2461 2462 let mut sp = T::AccountId::default();2463 if sponsor_transfer {2464 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2465 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2466 sp = called_contract;2467 }2468 }2469 }24702471 sp2472 },24732474 _ => sponsor,2475 };24762477 let mut who_pays_fee: T::AccountId = sponsor.clone();2478 if sponsor == T::AccountId::default() {2479 who_pays_fee = who.clone();2480 }24812482 match <T as transaction_payment::Trait>::Currency::withdraw(2483 &who_pays_fee,2484 fee,2485 if tip.is_zero() {2486 WithdrawReason::TransactionPayment.into()2487 } else {2488 WithdrawReason::TransactionPayment | WithdrawReason::Tip2489 },2490 ExistenceRequirement::KeepAlive,2491 ) {2492 Ok(imbalance) => Ok((fee, Some(imbalance))),2493 Err(_) => Err(InvalidTransaction::Payment.into()),2494 }2495 }2496}249724982499impl<T: Trait + Send + Sync> SignedExtension2500 for ChargeTransactionPayment<T>2501where2502 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2503 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2504{2505 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2506 type AccountId = T::AccountId;2507 type Call = T::Call;2508 type AdditionalSigned = ();2509 type Pre = (2510 BalanceOf<T>,2511 Self::AccountId,2512 Option<NegativeImbalanceOf<T>>,2513 BalanceOf<T>,2514 );2515 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2516 Ok(())2517 }25182519 fn validate(2520 &self,2521 who: &Self::AccountId,2522 call: &Self::Call,2523 info: &DispatchInfoOf<Self::Call>,2524 len: usize,2525 ) -> TransactionValidity {2526 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2527 Ok(ValidTransaction {2528 priority: Self::get_priority(len, info, fee),2529 ..Default::default()2530 })2531 }25322533 fn pre_dispatch(2534 self,2535 who: &Self::AccountId,2536 call: &Self::Call,2537 info: &DispatchInfoOf<Self::Call>,2538 len: usize,2539 ) -> Result<Self::Pre, TransactionValidityError> {2540 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2541 Ok((self.0, who.clone(), imbalance, fee))2542 }25432544 fn post_dispatch(2545 pre: Self::Pre,2546 info: &DispatchInfoOf<Self::Call>,2547 post_info: &PostDispatchInfoOf<Self::Call>,2548 len: usize,2549 _result: &DispatchResult,2550 ) -> Result<(), TransactionValidityError> {2551 let (tip, who, imbalance, fee) = pre;2552 if let Some(payed) = imbalance {2553 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2554 len as u32, info, post_info, tip,2555 );2556 let refund = fee.saturating_sub(actual_fee);2557 let actual_payment =2558 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2559 &who, refund,2560 ) {2561 Ok(refund_imbalance) => {2562 // The refund cannot be larger than the up front payed max weight.2563 // `PostDispatchInfo::calc_unspent` guards against such a case.2564 match payed.offset(refund_imbalance) {2565 Ok(actual_payment) => actual_payment,2566 Err(_) => return Err(InvalidTransaction::Payment.into()),2567 }2568 }2569 // We do not recreate the account using the refund. The up front payment2570 // is gone in that case.2571 Err(_) => payed,2572 };2573 let imbalances = actual_payment.split(tip);2574 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2575 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2576 );2577 }2578 Ok(())2579 }2580}25812582// #endregion1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, WithdrawReason,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial,29 },30 IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69 Invalid,70 NFT,71 // decimal points72 Fungible(DecimalPoints),73 // decimal points74 ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78 fn into(self) -> u8 {79 match self {80 CollectionMode::Invalid => 0,81 CollectionMode::NFT => 1,82 CollectionMode::Fungible(_) => 2,83 CollectionMode::ReFungible(_) => 3,84 }85 }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91 Normal,92 WhiteList,93}94impl Default for AccessMode {95 fn default() -> Self {96 Self::Normal97 }98}99100impl Default for CollectionMode {101 fn default() -> Self {102 Self::Invalid103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub owner: AccountId,149 pub const_data: Vec<u8>,150 pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType {156 pub value: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct ReFungibleItemType<AccountId> {162 pub owner: Vec<Ownership<AccountId>>,163 pub const_data: Vec<u8>,164 pub variable_data: Vec<u8>,165}166167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169// pub struct VestingItem<AccountId, Moment> {170// pub sender: AccountId,171// pub recipient: AccountId,172// pub collection_id: CollectionId,173// pub item_id: TokenId,174// pub amount: u64,175// pub vesting_date: Moment,176// }177178#[derive(Encode, Decode, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct CollectionLimits {181 pub account_token_ownership_limit: u32,182 pub sponsored_data_size: u32,183 pub token_limit: u32,184185 // Timeouts for item types in passed blocks186 pub sponsor_transfer_timeout: u32,187}188189impl Default for CollectionLimits {190 fn default() -> CollectionLimits {191 CollectionLimits { 192 account_token_ownership_limit: 10_000_000, 193 token_limit: u32::max_value(),194 sponsored_data_size: u32::max_value(), 195 sponsor_transfer_timeout: 14400 }196 }197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]201pub struct ChainLimits {202 pub collection_numbers_limit: u32,203 pub account_token_ownership_limit: u32,204 pub collections_admins_limit: u64,205 pub custom_data_limit: u32,206207 // Timeouts for item types in passed blocks208 pub nft_sponsor_transfer_timeout: u32,209 pub fungible_sponsor_transfer_timeout: u32,210 pub refungible_sponsor_transfer_timeout: u32,211}212213pub trait WeightInfo {214 fn create_collection() -> Weight;215 fn destroy_collection() -> Weight;216 fn add_to_white_list() -> Weight;217 fn remove_from_white_list() -> Weight;218 fn set_public_access_mode() -> Weight;219 fn set_mint_permission() -> Weight;220 fn change_collection_owner() -> Weight;221 fn add_collection_admin() -> Weight;222 fn remove_collection_admin() -> Weight;223 fn set_collection_sponsor() -> Weight;224 fn confirm_sponsorship() -> Weight;225 fn remove_collection_sponsor() -> Weight;226 fn create_item(s: usize) -> Weight;227 fn burn_item() -> Weight;228 fn transfer() -> Weight;229 fn approve() -> Weight;230 fn transfer_from() -> Weight;231 fn set_offchain_schema() -> Weight;232 fn set_const_on_chain_schema() -> Weight;233 fn set_variable_on_chain_schema() -> Weight;234 fn set_variable_meta_data() -> Weight;235 fn enable_contract_sponsoring() -> Weight;236 fn set_schema_version() -> Weight;237 fn set_chain_limits() -> Weight;238 fn set_contract_sponsoring_rate_limit() -> Weight;239 fn toggle_contract_white_list() -> Weight;240 fn add_to_contract_white_list() -> Weight;241 fn remove_from_contract_white_list() -> Weight;242 fn set_collection_limits() -> Weight;243}244245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]247pub struct CreateNftData {248 pub const_data: Vec<u8>,249 pub variable_data: Vec<u8>,250}251252#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]253#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]254pub struct CreateFungibleData {255 pub value: u128,256}257258#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub struct CreateReFungibleData {261 pub const_data: Vec<u8>,262 pub variable_data: Vec<u8>,263}264265#[derive(Encode, Decode, Debug, Clone, PartialEq)]266#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]267pub enum CreateItemData {268 NFT(CreateNftData),269 Fungible(CreateFungibleData),270 ReFungible(CreateReFungibleData),271}272273impl CreateItemData {274 pub fn len(&self) -> usize {275 let len = match self {276 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),277 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),278 _ => 0279 };280 281 return len;282 }283}284285impl From<CreateNftData> for CreateItemData {286 fn from(item: CreateNftData) -> Self {287 CreateItemData::NFT(item)288 }289}290291impl From<CreateReFungibleData> for CreateItemData {292 fn from(item: CreateReFungibleData) -> Self {293 CreateItemData::ReFungible(item)294 }295}296297impl From<CreateFungibleData> for CreateItemData {298 fn from(item: CreateFungibleData) -> Self {299 CreateItemData::Fungible(item)300 }301}302303304decl_error! {305 /// Error for non-fungible-token module.306 pub enum Error for Module<T: Trait> {307 /// Total collections bound exceeded.308 TotalCollectionsLimitExceeded,309 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.310 CollectionDecimalPointLimitExceeded, 311 /// Collection name can not be longer than 63 char.312 CollectionNameLimitExceeded, 313 /// Collection description can not be longer than 255 char.314 CollectionDescriptionLimitExceeded, 315 /// Token prefix can not be longer than 15 char.316 CollectionTokenPrefixLimitExceeded,317 /// This collection does not exist.318 CollectionNotFound,319 /// Item not exists.320 TokenNotFound,321 /// Arithmetic calculation overflow.322 NumOverflow, 323 /// Account already has admin role.324 AlreadyAdmin, 325 /// You do not own this collection.326 NoPermission,327 /// This address is not set as sponsor, use setCollectionSponsor first.328 ConfirmUnsetSponsorFail,329 /// Collection is not in mint mode.330 PublicMintingNotAllowed,331 /// Sender parameter and item owner must be equal.332 MustBeTokenOwner,333 /// Item balance not enough.334 TokenValueTooLow,335 /// Size of item is too large.336 NftSizeLimitExceeded,337 /// No approve found338 ApproveNotFound,339 /// Requested value more than approved.340 TokenValueNotEnough,341 /// Only approved addresses can call this method.342 ApproveRequired,343 /// Address is not in white list.344 AddresNotInWhiteList,345 /// Number of collection admins bound exceeded.346 CollectionAdminsLimitExceeded,347 /// Owned tokens by a single address bound exceeded.348 AddressOwnershipLimitExceeded,349 /// Length of items properties must be greater than 0.350 EmptyArgument,351 /// const_data exceeded data limit.352 TokenConstDataLimitExceeded,353 /// variable_data exceeded data limit.354 TokenVariableDataLimitExceeded,355 /// Not NFT item data used to mint in NFT collection.356 NotNftDataUsedToMintNftCollectionToken,357 /// Not Fungible item data used to mint in Fungible collection.358 NotFungibleDataUsedToMintFungibleCollectionToken,359 /// Not Re Fungible item data used to mint in Re Fungible collection.360 NotReFungibleDataUsedToMintReFungibleCollectionToken,361 /// Unexpected collection type.362 UnexpectedCollectionType,363 /// Can't store metadata in fungible tokens.364 CantStoreMetadataInFungibleTokens,365 /// Collection token limit exceeded366 CollectionTokenLimitExceeded,367 /// Account token limit exceeded per collection368 AccountTokenLimitExceeded,369 /// Collection limit bounds per collection exceeded370 CollectionLimitBoundsExceeded371 }372}373374pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {375 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;376377 /// Weight information for extrinsics in this pallet.378 type WeightInfo: WeightInfo;379}380381#[cfg(feature = "runtime-benchmarks")]382mod benchmarking;383384// #endregion385386decl_storage! {387 trait Store for Module<T: Trait> as Nft {388389 // Private members390 NextCollectionID: CollectionId;391 CreatedCollectionCount: u32;392 ChainVersion: u64;393 ItemListIndex: map hasher(identity) CollectionId => TokenId;394395 // Chain limits struct396 pub ChainLimit get(fn chain_limit) config(): ChainLimits;397398 // Bound counters399 CollectionCount: u32;400 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;401402 // Basic collections403 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;404 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;405 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;406407 /// Balance owner per collection map408 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;409410 /// second parameter: item id + owner account id + spender account id411 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;412413 /// Item collections414 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;415 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;416 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;417418 /// Index list419 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;420421 /// Tokens transfer baskets422 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;423 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;424 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;425426 // Contract Sponsorship and Ownership427 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;428 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;429 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;430 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;431 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 432 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 433 }434 add_extra_genesis {435 build(|config: &GenesisConfig<T>| {436 // Modification of storage437 for (_num, _c) in &config.collection {438 <Module<T>>::init_collection(_c);439 }440441 for (_num, _c, _i) in &config.nft_item_id {442 <Module<T>>::init_nft_token(*_c, _i);443 }444445 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {446 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);447 }448449 for (_num, _c, _i) in &config.refungible_item_id {450 <Module<T>>::init_refungible_token(*_c, _i);451 }452 })453 }454}455456decl_event!(457 pub enum Event<T>458 where459 AccountId = <T as system::Trait>::AccountId,460 {461 /// New collection was created462 /// 463 /// # Arguments464 /// 465 /// * collection_id: Globally unique identifier of newly created collection.466 /// 467 /// * mode: [CollectionMode] converted into u8.468 /// 469 /// * account_id: Collection owner.470 Created(CollectionId, u8, AccountId),471472 /// New item was created.473 /// 474 /// # Arguments475 /// 476 /// * collection_id: Id of the collection where item was created.477 /// 478 /// * item_id: Id of an item. Unique within the collection.479 ItemCreated(CollectionId, TokenId),480481 /// Collection item was burned.482 /// 483 /// # Arguments484 /// 485 /// collection_id.486 /// 487 /// item_id: Identifier of burned NFT.488 ItemDestroyed(CollectionId, TokenId),489 }490);491492decl_module! {493 pub struct Module<T: Trait> for enum Call where origin: T::Origin {494495 fn deposit_event() = default;496 type Error = Error<T>;497498 fn on_initialize(now: T::BlockNumber) -> Weight {499500 if ChainVersion::get() < 2501 {502 let value = NextCollectionID::get();503 CreatedCollectionCount::put(value);504 ChainVersion::put(2);505 }506507 0508 }509510 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.511 /// 512 /// # Permissions513 /// 514 /// * Anyone.515 /// 516 /// # Arguments517 /// 518 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.519 /// 520 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.521 /// 522 /// * token_prefix: UTF-8 string with token prefix.523 /// 524 /// * mode: [CollectionMode] collection type and type dependent data.525 // returns collection ID526 #[weight = T::WeightInfo::create_collection()]527 pub fn create_collection(origin,528 collection_name: Vec<u16>,529 collection_description: Vec<u16>,530 token_prefix: Vec<u8>,531 mode: CollectionMode) -> DispatchResult {532533 // Anyone can create a collection534 let who = ensure_signed(origin)?;535536 let decimal_points = match mode {537 CollectionMode::Fungible(points) => points,538 CollectionMode::ReFungible(points) => points,539 _ => 0540 };541542 // bound Total number of collections543 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);544545 // check params546 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);547 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);548 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);549 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);550551 // Generate next collection ID552 let next_id = CreatedCollectionCount::get()553 .checked_add(1)554 .ok_or(Error::<T>::NumOverflow)?;555556 // bound counter557 let total = CollectionCount::get()558 .checked_add(1)559 .ok_or(Error::<T>::NumOverflow)?;560561 CreatedCollectionCount::put(next_id);562 CollectionCount::put(total);563564 // Create new collection565 let new_collection = CollectionType {566 owner: who.clone(),567 name: collection_name,568 mode: mode.clone(),569 mint_mode: false,570 access: AccessMode::Normal,571 description: collection_description,572 decimal_points: decimal_points,573 token_prefix: token_prefix,574 offchain_schema: Vec::new(),575 schema_version: SchemaVersion::ImageURL,576 sponsor: T::AccountId::default(),577 sponsor_confirmed: false,578 variable_on_chain_schema: Vec::new(),579 const_on_chain_schema: Vec::new(),580 limits: CollectionLimits::default(),581 };582583 // Add new collection to map584 <Collection<T>>::insert(next_id, new_collection);585586 // call event587 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));588589 Ok(())590 }591592 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.593 /// 594 /// # Permissions595 /// 596 /// * Collection Owner.597 /// 598 /// # Arguments599 /// 600 /// * collection_id: collection to destroy.601 #[weight = T::WeightInfo::destroy_collection()]602 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {603604 let sender = ensure_signed(origin)?;605 Self::check_owner_permissions(collection_id, sender)?;606607 <AddressTokens<T>>::remove_prefix(collection_id);608 <Allowances<T>>::remove_prefix(collection_id);609 <Balance<T>>::remove_prefix(collection_id);610 <ItemListIndex>::remove(collection_id);611 <AdminList<T>>::remove(collection_id);612 <Collection<T>>::remove(collection_id);613 <WhiteList<T>>::remove_prefix(collection_id);614615 <NftItemList<T>>::remove_prefix(collection_id);616 <FungibleItemList<T>>::remove_prefix(collection_id);617 <ReFungibleItemList<T>>::remove_prefix(collection_id);618619 <NftTransferBasket<T>>::remove_prefix(collection_id);620 <FungibleTransferBasket<T>>::remove_prefix(collection_id);621 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);622623 if CollectionCount::get() > 0624 {625 // bound couter626 let total = CollectionCount::get()627 .checked_sub(1)628 .ok_or(Error::<T>::NumOverflow)?;629630 CollectionCount::put(total);631 }632633 Ok(())634 }635636 /// Add an address to white list.637 /// 638 /// # Permissions639 /// 640 /// * Collection Owner641 /// * Collection Admin642 /// 643 /// # Arguments644 /// 645 /// * collection_id.646 /// 647 /// * address.648 #[weight = T::WeightInfo::add_to_white_list()]649 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{650651 let sender = ensure_signed(origin)?;652 Self::check_owner_or_admin_permissions(collection_id, sender)?;653654 <WhiteList<T>>::insert(collection_id, address, true);655 656 Ok(())657 }658659 /// Remove an address from white list.660 /// 661 /// # Permissions662 /// 663 /// * Collection Owner664 /// * Collection Admin665 /// 666 /// # Arguments667 /// 668 /// * collection_id.669 /// 670 /// * address.671 #[weight = T::WeightInfo::remove_from_white_list()]672 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{673674 let sender = ensure_signed(origin)?;675 Self::check_owner_or_admin_permissions(collection_id, sender)?;676677 <WhiteList<T>>::remove(collection_id, address);678679 Ok(())680 }681682 /// Toggle between normal and white list access for the methods with access for `Anyone`.683 /// 684 /// # Permissions685 /// 686 /// * Collection Owner.687 /// 688 /// # Arguments689 /// 690 /// * collection_id.691 /// 692 /// * mode: [AccessMode]693 #[weight = T::WeightInfo::set_public_access_mode()]694 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult695 {696 let sender = ensure_signed(origin)?;697698 Self::check_owner_permissions(collection_id, sender)?;699 let mut target_collection = <Collection<T>>::get(collection_id);700 target_collection.access = mode;701 <Collection<T>>::insert(collection_id, target_collection);702703 Ok(())704 }705706 /// Allows Anyone to create tokens if:707 /// * White List is enabled, and708 /// * Address is added to white list, and709 /// * This method was called with True parameter710 /// 711 /// # Permissions712 /// * Collection Owner713 ///714 /// # Arguments715 /// 716 /// * collection_id.717 /// 718 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.719 #[weight = T::WeightInfo::set_mint_permission()]720 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult721 {722 let sender = ensure_signed(origin)?;723724 Self::check_owner_permissions(collection_id, sender)?;725 let mut target_collection = <Collection<T>>::get(collection_id);726 target_collection.mint_mode = mint_permission;727 <Collection<T>>::insert(collection_id, target_collection);728729 Ok(())730 }731732 /// Change the owner of the collection.733 /// 734 /// # Permissions735 /// 736 /// * Collection Owner.737 /// 738 /// # Arguments739 /// 740 /// * collection_id.741 /// 742 /// * new_owner.743 #[weight = T::WeightInfo::change_collection_owner()]744 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {745746 let sender = ensure_signed(origin)?;747 Self::check_owner_permissions(collection_id, sender)?;748 let mut target_collection = <Collection<T>>::get(collection_id);749 target_collection.owner = new_owner;750 <Collection<T>>::insert(collection_id, target_collection);751752 Ok(())753 }754755 /// Adds an admin of the Collection.756 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 757 /// 758 /// # Permissions759 /// 760 /// * Collection Owner.761 /// * Collection Admin.762 /// 763 /// # Arguments764 /// 765 /// * collection_id: ID of the Collection to add admin for.766 /// 767 /// * new_admin_id: Address of new admin to add.768 #[weight = T::WeightInfo::add_collection_admin()]769 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {770771 let sender = ensure_signed(origin)?;772 Self::check_owner_or_admin_permissions(collection_id, sender)?;773 let mut admin_arr: Vec<T::AccountId> = Vec::new();774775 if <AdminList<T>>::contains_key(collection_id)776 {777 admin_arr = <AdminList<T>>::get(collection_id);778 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);779 }780781 // Number of collection admins782 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);783784 admin_arr.push(new_admin_id);785 <AdminList<T>>::insert(collection_id, admin_arr);786787 Ok(())788 }789790 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.791 ///792 /// # Permissions793 /// 794 /// * Collection Owner.795 /// * Collection Admin.796 /// 797 /// # Arguments798 /// 799 /// * collection_id: ID of the Collection to remove admin for.800 /// 801 /// * account_id: Address of admin to remove.802 #[weight = T::WeightInfo::remove_collection_admin()]803 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {804805 let sender = ensure_signed(origin)?;806 Self::check_owner_or_admin_permissions(collection_id, sender)?;807808 if <AdminList<T>>::contains_key(collection_id)809 {810 let mut admin_arr = <AdminList<T>>::get(collection_id);811 admin_arr.retain(|i| *i != account_id);812 <AdminList<T>>::insert(collection_id, admin_arr);813 }814815 Ok(())816 }817818 /// # Permissions819 /// 820 /// * Collection Owner821 /// 822 /// # Arguments823 /// 824 /// * collection_id.825 /// 826 /// * new_sponsor.827 #[weight = T::WeightInfo::set_collection_sponsor()]828 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {829830 let sender = ensure_signed(origin)?;831 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);832833 let mut target_collection = <Collection<T>>::get(collection_id);834 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);835836 target_collection.sponsor = new_sponsor;837 target_collection.sponsor_confirmed = false;838 <Collection<T>>::insert(collection_id, target_collection);839840 Ok(())841 }842843 /// # Permissions844 /// 845 /// * Sponsor.846 /// 847 /// # Arguments848 /// 849 /// * collection_id.850 #[weight = T::WeightInfo::confirm_sponsorship()]851 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {852853 let sender = ensure_signed(origin)?;854 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);855856 let mut target_collection = <Collection<T>>::get(collection_id);857 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);858859 target_collection.sponsor_confirmed = true;860 <Collection<T>>::insert(collection_id, target_collection);861862 Ok(())863 }864865 /// Switch back to pay-per-own-transaction model.866 ///867 /// # Permissions868 ///869 /// * Collection owner.870 /// 871 /// # Arguments872 /// 873 /// * collection_id.874 #[weight = T::WeightInfo::remove_collection_sponsor()]875 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {876877 let sender = ensure_signed(origin)?;878 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);879880 let mut target_collection = <Collection<T>>::get(collection_id);881 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);882883 target_collection.sponsor = T::AccountId::default();884 target_collection.sponsor_confirmed = false;885 <Collection<T>>::insert(collection_id, target_collection);886887 Ok(())888 }889890 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.891 /// 892 /// # Permissions893 /// 894 /// * Collection Owner.895 /// * Collection Admin.896 /// * Anyone if897 /// * White List is enabled, and898 /// * Address is added to white list, and899 /// * MintPermission is enabled (see SetMintPermission method)900 /// 901 /// # Arguments902 /// 903 /// * collection_id: ID of the collection.904 /// 905 /// * owner: Address, initial owner of the NFT.906 ///907 /// * data: Token data to store on chain.908 // #[weight =909 // (130_000_000 as Weight)910 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))911 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))912 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]913914 #[weight = T::WeightInfo::create_item(data.len())]915 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {916917 let sender = ensure_signed(origin)?;918919 Self::collection_exists(collection_id)?;920921 let target_collection = <Collection<T>>::get(collection_id);922923 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;924 Self::validate_create_item_args(&target_collection, &data)?;925 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;926927 Ok(())928 }929930 /// This method creates multiple instances of NFT Collection created with CreateCollection method.931 /// 932 /// # Permissions933 /// 934 /// * Collection Owner.935 /// * Collection Admin.936 /// * Anyone if937 /// * White List is enabled, and938 /// * Address is added to white list, and939 /// * MintPermission is enabled (see SetMintPermission method)940 /// 941 /// # Arguments942 /// 943 /// * collection_id: ID of the collection.944 /// 945 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].946 /// 947 /// * owner: Address, initial owner of the NFT.948 #[weight = T::WeightInfo::create_item(items_data.into_iter()949 .map(|data| { data.len() })950 .sum())]951 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {952953 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);954 let sender = ensure_signed(origin)?;955956 Self::collection_exists(collection_id)?;957 let target_collection = <Collection<T>>::get(collection_id);958959 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;960961 for data in &items_data {962 Self::validate_create_item_args(&target_collection, data)?;963 }964 for data in &items_data {965 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;966 }967968 Ok(())969 }970971 /// Destroys a concrete instance of NFT.972 /// 973 /// # Permissions974 /// 975 /// * Collection Owner.976 /// * Collection Admin.977 /// * Current NFT Owner.978 /// 979 /// # Arguments980 /// 981 /// * collection_id: ID of the collection.982 /// 983 /// * item_id: ID of NFT to burn.984 #[weight = T::WeightInfo::burn_item()]985 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {986987 let sender = ensure_signed(origin)?;988 Self::collection_exists(collection_id)?;989990 // Transfer permissions check991 let target_collection = <Collection<T>>::get(collection_id);992 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||993 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),994 Error::<T>::NoPermission);995996 if target_collection.access == AccessMode::WhiteList {997 Self::check_white_list(collection_id, &sender)?;998 }9991000 match target_collection.mode1001 {1002 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1003 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1004 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1005 _ => ()1006 };10071008 // call event1009 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10101011 Ok(())1012 }10131014 /// Change ownership of the token.1015 /// 1016 /// # Permissions1017 /// 1018 /// * Collection Owner1019 /// * Collection Admin1020 /// * Current NFT owner1021 ///1022 /// # Arguments1023 /// 1024 /// * recipient: Address of token recipient.1025 /// 1026 /// * collection_id.1027 /// 1028 /// * item_id: ID of the item1029 /// * Non-Fungible Mode: Required.1030 /// * Fungible Mode: Ignored.1031 /// * Re-Fungible Mode: Required.1032 /// 1033 /// * value: Amount to transfer.1034 /// * Non-Fungible Mode: Ignored1035 /// * Fungible Mode: Must specify transferred amount1036 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1037 #[weight = T::WeightInfo::transfer()]1038 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10391040 let sender = ensure_signed(origin)?;1041 let target_collection = <Collection<T>>::get(collection_id);10421043 // Limits check1044 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10451046 // Transfer permissions check1047 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1048 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1049 Error::<T>::NoPermission);10501051 if target_collection.access == AccessMode::WhiteList {1052 Self::check_white_list(collection_id, &sender)?;1053 Self::check_white_list(collection_id, &recipient)?;1054 }10551056 match target_collection.mode1057 {1058 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1059 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1060 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1061 _ => ()1062 };10631064 Ok(())1065 }10661067 /// Set, change, or remove approved address to transfer the ownership of the NFT.1068 /// 1069 /// # Permissions1070 /// 1071 /// * Collection Owner1072 /// * Collection Admin1073 /// * Current NFT owner1074 /// 1075 /// # Arguments1076 /// 1077 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1078 /// 1079 /// * collection_id.1080 /// 1081 /// * item_id: ID of the item.1082 #[weight = T::WeightInfo::approve()]1083 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10841085 let sender = ensure_signed(origin)?;10861087 // Transfer permissions check1088 let target_collection = <Collection<T>>::get(collection_id);1089 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1090 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1091 Error::<T>::NoPermission);10921093 if target_collection.access == AccessMode::WhiteList {1094 Self::check_white_list(collection_id, &sender)?;1095 Self::check_white_list(collection_id, &spender)?;1096 }10971098 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1099 let mut allowance: u128 = amount;1100 if allowance_exists {1101 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1102 }1103 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11041105 Ok(())1106 }1107 1108 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1109 /// 1110 /// # Permissions1111 /// * Collection Owner1112 /// * Collection Admin1113 /// * Current NFT owner1114 /// * Address approved by current NFT owner1115 /// 1116 /// # Arguments1117 /// 1118 /// * from: Address that owns token.1119 /// 1120 /// * recipient: Address of token recipient.1121 /// 1122 /// * collection_id.1123 /// 1124 /// * item_id: ID of the item.1125 /// 1126 /// * value: Amount to transfer.1127 #[weight = T::WeightInfo::transfer_from()]1128 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11291130 let sender = ensure_signed(origin)?;1131 let mut appoved_transfer = false;11321133 // Check approval1134 let mut approval: u128 = 0;1135 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1136 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1137 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1138 appoved_transfer = true;1139 }11401141 let target_collection = <Collection<T>>::get(collection_id);11421143 // Limits check1144 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11451146 // Transfer permissions check 1147 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1148 Error::<T>::NoPermission);11491150 if target_collection.access == AccessMode::WhiteList {1151 Self::check_white_list(collection_id, &sender)?;1152 Self::check_white_list(collection_id, &recipient)?;1153 }11541155 // Reduce approval by transferred amount or remove if remaining approval drops to 01156 if approval - value > 0 {1157 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1158 }1159 else {1160 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1161 }11621163 match target_collection.mode1164 {1165 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1166 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1167 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1168 _ => ()1169 };11701171 Ok(())1172 }11731174 #[weight = 0]1175 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11761177 // let no_perm_mes = "You do not have permissions to modify this collection";1178 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1179 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1180 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11811182 // // on_nft_received call11831184 // Self::transfer(origin, collection_id, item_id, new_owner)?;11851186 Ok(())1187 }11881189 /// Set off-chain data schema.1190 /// 1191 /// # Permissions1192 /// 1193 /// * Collection Owner1194 /// * Collection Admin1195 /// 1196 /// # Arguments1197 /// 1198 /// * collection_id.1199 /// 1200 /// * schema: String representing the offchain data schema.1201 #[weight = T::WeightInfo::set_variable_meta_data()]1202 pub fn set_variable_meta_data (1203 origin,1204 collection_id: CollectionId,1205 item_id: TokenId,1206 data: Vec<u8>1207 ) -> DispatchResult {1208 let sender = ensure_signed(origin)?;1209 1210 Self::collection_exists(collection_id)?;1211 1212 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12131214 // Modify permissions check1215 let target_collection = <Collection<T>>::get(collection_id);1216 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1217 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1218 Error::<T>::NoPermission);12191220 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12211222 match target_collection.mode1223 {1224 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1225 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1226 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1227 _ => fail!(Error::<T>::UnexpectedCollectionType)1228 };12291230 Ok(())1231 }1232 1233 /// Set schema standard1234 /// ImageURL1235 /// Unique1236 /// 1237 /// # Permissions1238 /// 1239 /// * Collection Owner1240 /// * Collection Admin1241 /// 1242 /// # Arguments1243 /// 1244 /// * collection_id.1245 /// 1246 /// * schema: SchemaVersion: enum1247 #[weight = T::WeightInfo::set_schema_version()]1248 pub fn set_schema_version(1249 origin,1250 collection_id: CollectionId,1251 version: SchemaVersion1252 ) -> DispatchResult {1253 let sender = ensure_signed(origin)?;1254 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1255 let mut target_collection = <Collection<T>>::get(collection_id);1256 target_collection.schema_version = version;1257 <Collection<T>>::insert(collection_id, target_collection);12581259 Ok(())1260 }12611262 /// Set off-chain data schema.1263 /// 1264 /// # Permissions1265 /// 1266 /// * Collection Owner1267 /// * Collection Admin1268 /// 1269 /// # Arguments1270 /// 1271 /// * collection_id.1272 /// 1273 /// * schema: String representing the offchain data schema.1274 #[weight = T::WeightInfo::set_offchain_schema()]1275 pub fn set_offchain_schema(1276 origin,1277 collection_id: CollectionId,1278 schema: Vec<u8>1279 ) -> DispatchResult {1280 let sender = ensure_signed(origin)?;1281 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12821283 let mut target_collection = <Collection<T>>::get(collection_id);1284 target_collection.offchain_schema = schema;1285 <Collection<T>>::insert(collection_id, target_collection);12861287 Ok(())1288 }12891290 /// Set const on-chain data schema.1291 /// 1292 /// # Permissions1293 /// 1294 /// * Collection Owner1295 /// * Collection Admin1296 /// 1297 /// # Arguments1298 /// 1299 /// * collection_id.1300 /// 1301 /// * schema: String representing the const on-chain data schema.1302 #[weight = T::WeightInfo::set_const_on_chain_schema()]1303 pub fn set_const_on_chain_schema (1304 origin,1305 collection_id: CollectionId,1306 schema: Vec<u8>1307 ) -> DispatchResult {1308 let sender = ensure_signed(origin)?;1309 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13101311 let mut target_collection = <Collection<T>>::get(collection_id);1312 target_collection.const_on_chain_schema = schema;1313 <Collection<T>>::insert(collection_id, target_collection);13141315 Ok(())1316 }13171318 /// Set variable on-chain data schema.1319 /// 1320 /// # Permissions1321 /// 1322 /// * Collection Owner1323 /// * Collection Admin1324 /// 1325 /// # Arguments1326 /// 1327 /// * collection_id.1328 /// 1329 /// * schema: String representing the variable on-chain data schema.1330 #[weight = T::WeightInfo::set_const_on_chain_schema()]1331 pub fn set_variable_on_chain_schema (1332 origin,1333 collection_id: CollectionId,1334 schema: Vec<u8>1335 ) -> DispatchResult {1336 let sender = ensure_signed(origin)?;1337 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13381339 let mut target_collection = <Collection<T>>::get(collection_id);1340 target_collection.variable_on_chain_schema = schema;1341 <Collection<T>>::insert(collection_id, target_collection);13421343 Ok(())1344 }13451346 // Sudo permissions function1347 #[weight = T::WeightInfo::set_chain_limits()]1348 pub fn set_chain_limits(1349 origin,1350 limits: ChainLimits1351 ) -> DispatchResult {13521353 #[cfg(not(feature = "runtime-benchmarks"))]1354 ensure_root(origin)?;13551356 <ChainLimit>::put(limits);1357 Ok(())1358 }13591360 /// Enable smart contract self-sponsoring.1361 /// 1362 /// # Permissions1363 /// 1364 /// * Contract Owner1365 /// 1366 /// # Arguments1367 /// 1368 /// * contract address1369 /// * enable flag1370 /// 1371 #[weight = T::WeightInfo::enable_contract_sponsoring()]1372 pub fn enable_contract_sponsoring(1373 origin,1374 contract_address: T::AccountId,1375 enable: bool1376 ) -> DispatchResult {13771378 let sender = ensure_signed(origin)?;13791380 #[cfg(feature = "runtime-benchmarks")]1381 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13821383 Self::ensure_contract_owned(sender, &contract_address)?;13841385 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1386 Ok(())1387 }13881389 /// Set the rate limit for contract sponsoring to specified number of blocks.1390 /// 1391 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1392 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1393 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1394 /// from contract endowment if there are at least B blocks between such transactions. 1395 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1396 /// 1397 /// # Permissions1398 /// 1399 /// * Contract Owner1400 /// 1401 /// # Arguments1402 /// 1403 /// -`contract_address`: Address of the contract to sponsor1404 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1405 /// 1406 #[weight = T::WeightInfo::set_contract_sponsoring_rate_limit()]1407 pub fn set_contract_sponsoring_rate_limit(1408 origin,1409 contract_address: T::AccountId,1410 rate_limit: T::BlockNumber1411 ) -> DispatchResult {1412 let sender = ensure_signed(origin)?;14131414 #[cfg(feature = "runtime-benchmarks")]1415 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14161417 Self::ensure_contract_owned(sender, &contract_address)?;1418 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1419 Ok(())1420 }14211422 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1423 /// 1424 /// # Permissions1425 /// 1426 /// * Address that deployed smart contract.1427 /// 1428 /// # Arguments1429 /// 1430 /// -`contract_address`: Address of the contract.1431 /// 1432 /// - `enable`: . 1433 #[weight = T::WeightInfo::toggle_contract_white_list()]1434 pub fn toggle_contract_white_list(1435 origin,1436 contract_address: T::AccountId,1437 enable: bool1438 ) -> DispatchResult {1439 let sender = ensure_signed(origin)?;14401441 #[cfg(feature = "runtime-benchmarks")]1442 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14431444 Self::ensure_contract_owned(sender, &contract_address)?;1445 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1446 Ok(())1447 }1448 1449 /// Add an address to smart contract white list.1450 /// 1451 /// # Permissions1452 /// 1453 /// * Address that deployed smart contract.1454 /// 1455 /// # Arguments1456 /// 1457 /// -`contract_address`: Address of the contract.1458 ///1459 /// -`account_address`: Address to add.1460 #[weight = T::WeightInfo::add_to_contract_white_list()]1461 pub fn add_to_contract_white_list(1462 origin,1463 contract_address: T::AccountId,1464 account_address: T::AccountId1465 ) -> DispatchResult {1466 let sender = ensure_signed(origin)?;14671468 #[cfg(feature = "runtime-benchmarks")]1469 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1470 1471 Self::ensure_contract_owned(sender, &contract_address)?; 1472 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1473 Ok(())1474 }14751476 /// Remove an address from smart contract white list.1477 /// 1478 /// # Permissions1479 /// 1480 /// * Address that deployed smart contract.1481 /// 1482 /// # Arguments1483 /// 1484 /// -`contract_address`: Address of the contract.1485 ///1486 /// -`account_address`: Address to remove.1487 #[weight = T::WeightInfo::remove_from_contract_white_list()]1488 pub fn remove_from_contract_white_list(1489 origin,1490 contract_address: T::AccountId,1491 account_address: T::AccountId1492 ) -> DispatchResult {1493 let sender = ensure_signed(origin)?;14941495 #[cfg(feature = "runtime-benchmarks")]1496 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14971498 Self::ensure_contract_owned(sender, &contract_address)?;1499 <ContractWhiteList<T>>::remove(contract_address, account_address);1500 Ok(())1501 }15021503 #[weight = T::WeightInfo::set_collection_limits()]1504 pub fn set_collection_limits(1505 origin,1506 collection_id: u32,1507 limits: CollectionLimits,1508 ) -> DispatchResult {1509 let sender = ensure_signed(origin)?;1510 Self::check_owner_permissions(collection_id, sender.clone())?;1511 let mut target_collection = <Collection<T>>::get(collection_id);1512 let chain_limits = ChainLimit::get();1513 let climits = target_collection.limits;15141515 // collection bounds1516 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1517 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1518 Error::<T>::CollectionLimitBoundsExceeded);15191520 // token_limit check prev1521 ensure!(climits.token_limit > limits.token_limit && 1522 limits.token_limit <= chain_limits.account_token_ownership_limit, 1523 Error::<T>::AccountTokenLimitExceeded);15241525 target_collection.limits = limits;1526 <Collection<T>>::insert(collection_id, target_collection);15271528 Ok(())1529 } 1530 }1531}15321533impl<T: Trait> Module<T> {15341535 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15361537 // check token limit and account token limit1538 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1539 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1540 1541 Ok(())1542 }15431544 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15451546 // check token limit and account token limit1547 let total_items: u32 = ItemListIndex::get(collection_id);1548 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1549 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1550 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15511552 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1553 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1554 Self::check_white_list(collection_id, owner)?;1555 Self::check_white_list(collection_id, sender)?;1556 }15571558 Ok(())1559 }15601561 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1562 match target_collection.mode1563 {1564 CollectionMode::NFT => {1565 if let CreateItemData::NFT(data) = data {1566 // check sizes1567 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1568 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1569 } else {1570 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1571 }1572 },1573 CollectionMode::Fungible(_) => {1574 if let CreateItemData::Fungible(_) = data {1575 } else {1576 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1577 }1578 },1579 CollectionMode::ReFungible(_) => {1580 if let CreateItemData::ReFungible(data) = data {15811582 // check sizes1583 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1584 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1585 } else {1586 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1587 }1588 },1589 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1590 };15911592 Ok(())1593 }15941595 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1596 match data1597 {1598 CreateItemData::NFT(data) => {1599 let item = NftItemType {1600 owner,1601 const_data: data.const_data,1602 variable_data: data.variable_data1603 };16041605 Self::add_nft_item(collection_id, item)?;1606 },1607 CreateItemData::Fungible(data) => {1608 Self::add_fungible_item(collection_id, &owner, data.value)?;1609 },1610 CreateItemData::ReFungible(data) => {1611 let mut owner_list = Vec::new();1612 let value = (10 as u128).pow(collection.decimal_points as u32);1613 owner_list.push(Ownership {owner: owner.clone(), fraction: value});16141615 let item = ReFungibleItemType {1616 owner: owner_list,1617 const_data: data.const_data,1618 variable_data: data.variable_data1619 };16201621 Self::add_refungible_item(collection_id, item)?;1622 }1623 };16241625 // call event1626 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16271628 Ok(())1629 }16301631 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16321633 // Does new owner already have an account?1634 let mut balance: u128 = 0;1635 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1636 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1637 } 16381639 // Mint 1640 let item = FungibleItemType {1641 value: balance + value1642 };1643 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16441645 // Update balance1646 let new_balance = <Balance<T>>::get(collection_id, owner)1647 .checked_add(value)1648 .ok_or(Error::<T>::NumOverflow)?;1649 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16501651 Ok(())1652 }16531654 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1655 let current_index = <ItemListIndex>::get(collection_id)1656 .checked_add(1)1657 .ok_or(Error::<T>::NumOverflow)?;1658 let itemcopy = item.clone();16591660 let value = item.owner.first().unwrap().fraction;1661 let owner = item.owner.first().unwrap().owner.clone();16621663 Self::add_token_index(collection_id, current_index, owner.clone())?;16641665 <ItemListIndex>::insert(collection_id, current_index);1666 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16671668 // Update balance1669 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1670 .checked_add(value)1671 .ok_or(Error::<T>::NumOverflow)?;1672 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16731674 Ok(())1675 }16761677 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1678 let current_index = <ItemListIndex>::get(collection_id)1679 .checked_add(1)1680 .ok_or(Error::<T>::NumOverflow)?;16811682 let item_owner = item.owner.clone();1683 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16841685 <ItemListIndex>::insert(collection_id, current_index);1686 <NftItemList<T>>::insert(collection_id, current_index, item);16871688 // Update balance1689 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1690 .checked_add(1)1691 .ok_or(Error::<T>::NumOverflow)?;1692 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16931694 Ok(())1695 }16961697 fn burn_refungible_item(1698 collection_id: CollectionId,1699 item_id: TokenId,1700 owner: T::AccountId,1701 ) -> DispatchResult {1702 ensure!(1703 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1704 Error::<T>::TokenNotFound1705 );1706 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1707 let item = collection1708 .owner1709 .iter()1710 .filter(|&i| i.owner == owner)1711 .next()1712 .unwrap();1713 Self::remove_token_index(collection_id, item_id, owner.clone())?;17141715 // update balance1716 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1717 .checked_sub(item.fraction)1718 .ok_or(Error::<T>::NumOverflow)?;1719 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17201721 <ReFungibleItemList<T>>::remove(collection_id, item_id);17221723 Ok(())1724 }17251726 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1727 ensure!(1728 <NftItemList<T>>::contains_key(collection_id, item_id),1729 Error::<T>::TokenNotFound1730 );1731 let item = <NftItemList<T>>::get(collection_id, item_id);1732 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17331734 // update balance1735 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1736 .checked_sub(1)1737 .ok_or(Error::<T>::NumOverflow)?;1738 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1739 <NftItemList<T>>::remove(collection_id, item_id);17401741 Ok(())1742 }17431744 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1745 ensure!(1746 <FungibleItemList<T>>::contains_key(collection_id, owner),1747 Error::<T>::TokenNotFound1748 );1749 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1750 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17511752 // update balance1753 let new_balance = <Balance<T>>::get(collection_id, owner)1754 .checked_sub(value)1755 .ok_or(Error::<T>::NumOverflow)?;1756 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17571758 if balance.value - value > 0 {1759 balance.value -= value;1760 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1761 }1762 else {1763 <FungibleItemList<T>>::remove(collection_id, owner);1764 }17651766 Ok(())1767 }17681769 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1770 ensure!(1771 <Collection<T>>::contains_key(collection_id),1772 Error::<T>::CollectionNotFound1773 );1774 Ok(())1775 }17761777 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1778 Self::collection_exists(collection_id)?;17791780 let target_collection = <Collection<T>>::get(collection_id);1781 ensure!(1782 subject == target_collection.owner,1783 Error::<T>::NoPermission1784 );17851786 Ok(())1787 }17881789 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1790 let target_collection = <Collection<T>>::get(collection_id);1791 let mut result: bool = subject == target_collection.owner;1792 let exists = <AdminList<T>>::contains_key(collection_id);17931794 if !result & exists {1795 if <AdminList<T>>::get(collection_id).contains(&subject) {1796 result = true1797 }1798 }17991800 result1801 }18021803 fn check_owner_or_admin_permissions(1804 collection_id: CollectionId,1805 subject: T::AccountId,1806 ) -> DispatchResult {1807 Self::collection_exists(collection_id)?;1808 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18091810 ensure!(1811 result,1812 Error::<T>::NoPermission1813 );1814 Ok(())1815 }18161817 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1818 let target_collection = <Collection<T>>::get(collection_id);18191820 match target_collection.mode {1821 CollectionMode::NFT => {1822 <NftItemList<T>>::get(collection_id, item_id).owner == subject1823 }1824 CollectionMode::Fungible(_) => {1825 <FungibleItemList<T>>::contains_key(collection_id, &subject)1826 }1827 CollectionMode::ReFungible(_) => {1828 <ReFungibleItemList<T>>::get(collection_id, item_id)1829 .owner1830 .iter()1831 .any(|i| i.owner == subject)1832 }1833 CollectionMode::Invalid => false,1834 }1835 }18361837 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1838 let mes = Error::<T>::AddresNotInWhiteList;1839 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18401841 Ok(())1842 }18431844 fn transfer_fungible(1845 collection_id: CollectionId,1846 value: u128,1847 owner: &T::AccountId,1848 recipient: &T::AccountId,1849 ) -> DispatchResult {1850 ensure!(1851 <FungibleItemList<T>>::contains_key(collection_id, owner),1852 Error::<T>::TokenNotFound1853 );18541855 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1856 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18571858 // Send balance to recipient (updates balanceOf of recipient)1859 Self::add_fungible_item(collection_id, recipient, value)?;18601861 // update balanceOf of sender1862 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18631864 // Reduce or remove sender1865 if balance.value == value {1866 <FungibleItemList<T>>::remove(collection_id, owner);1867 }1868 else {1869 balance.value -= value;1870 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1871 }18721873 Ok(())1874 }18751876 fn transfer_refungible(1877 collection_id: CollectionId,1878 item_id: TokenId,1879 value: u128,1880 owner: T::AccountId,1881 new_owner: T::AccountId,1882 ) -> DispatchResult {1883 ensure!(1884 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1885 Error::<T>::TokenNotFound1886 );18871888 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1889 let item = full_item1890 .owner1891 .iter()1892 .filter(|i| i.owner == owner)1893 .next()1894 .ok_or(Error::<T>::NumOverflow)?;1895 let amount = item.fraction;18961897 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18981899 // update balance1900 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1901 .checked_sub(value)1902 .ok_or(Error::<T>::NumOverflow)?;1903 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19041905 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1906 .checked_add(value)1907 .ok_or(Error::<T>::NumOverflow)?;1908 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19091910 let old_owner = item.owner.clone();1911 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19121913 // transfer1914 if amount == value && !new_owner_has_account {1915 // change owner1916 // new owner do not have account1917 let mut new_full_item = full_item.clone();1918 new_full_item1919 .owner1920 .iter_mut()1921 .find(|i| i.owner == owner)1922 .unwrap()1923 .owner = new_owner.clone();1924 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19251926 // update index collection1927 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1928 } else {1929 let mut new_full_item = full_item.clone();1930 new_full_item1931 .owner1932 .iter_mut()1933 .find(|i| i.owner == owner)1934 .unwrap()1935 .fraction -= value;19361937 // separate amount1938 if new_owner_has_account {1939 // new owner has account1940 new_full_item1941 .owner1942 .iter_mut()1943 .find(|i| i.owner == new_owner)1944 .unwrap()1945 .fraction += value;1946 } else {1947 // new owner do not have account1948 new_full_item.owner.push(Ownership {1949 owner: new_owner.clone(),1950 fraction: value,1951 });1952 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1953 }19541955 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1956 }19571958 Ok(())1959 }19601961 fn transfer_nft(1962 collection_id: CollectionId,1963 item_id: TokenId,1964 sender: T::AccountId,1965 new_owner: T::AccountId,1966 ) -> DispatchResult {1967 ensure!(1968 <NftItemList<T>>::contains_key(collection_id, item_id),1969 Error::<T>::TokenNotFound1970 );19711972 let mut item = <NftItemList<T>>::get(collection_id, item_id);19731974 ensure!(1975 sender == item.owner,1976 Error::<T>::MustBeTokenOwner1977 );19781979 // update balance1980 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1981 .checked_sub(1)1982 .ok_or(Error::<T>::NumOverflow)?;1983 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19841985 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1986 .checked_add(1)1987 .ok_or(Error::<T>::NumOverflow)?;1988 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19891990 // change owner1991 let old_owner = item.owner.clone();1992 item.owner = new_owner.clone();1993 <NftItemList<T>>::insert(collection_id, item_id, item);19941995 // update index collection1996 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19971998 Ok(())1999 }2000 2001 fn item_exists(2002 collection_id: CollectionId,2003 item_id: TokenId,2004 mode: &CollectionMode2005 ) -> DispatchResult {2006 match mode {2007 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2008 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2009 _ => ()2010 };2011 2012 Ok(())2013 }20142015 fn set_re_fungible_variable_data(2016 collection_id: CollectionId,2017 item_id: TokenId,2018 data: Vec<u8>2019 ) -> DispatchResult {2020 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20212022 item.variable_data = data;20232024 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20252026 Ok(())2027 }20282029 fn set_nft_variable_data(2030 collection_id: CollectionId,2031 item_id: TokenId,2032 data: Vec<u8>2033 ) -> DispatchResult {2034 let mut item = <NftItemList<T>>::get(collection_id, item_id);2035 2036 item.variable_data = data;20372038 <NftItemList<T>>::insert(collection_id, item_id, item);2039 2040 Ok(())2041 }20422043 fn init_collection(item: &CollectionType<T::AccountId>) {2044 // check params2045 assert!(2046 item.decimal_points <= MAX_DECIMAL_POINTS,2047 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2048 );2049 assert!(2050 item.name.len() <= 64,2051 "Collection name can not be longer than 63 char"2052 );2053 assert!(2054 item.name.len() <= 256,2055 "Collection description can not be longer than 255 char"2056 );2057 assert!(2058 item.token_prefix.len() <= 16,2059 "Token prefix can not be longer than 15 char"2060 );20612062 // Generate next collection ID2063 let next_id = CreatedCollectionCount::get()2064 .checked_add(1)2065 .unwrap();20662067 CreatedCollectionCount::put(next_id);2068 }20692070 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2071 let current_index = <ItemListIndex>::get(collection_id)2072 .checked_add(1)2073 .unwrap();20742075 let item_owner = item.owner.clone();2076 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20772078 <ItemListIndex>::insert(collection_id, current_index);20792080 // Update balance2081 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2082 .checked_add(1)2083 .unwrap();2084 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2085 }20862087 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2088 let current_index = <ItemListIndex>::get(collection_id)2089 .checked_add(1)2090 .unwrap();20912092 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();20932094 <ItemListIndex>::insert(collection_id, current_index);20952096 // Update balance2097 let new_balance = <Balance<T>>::get(collection_id, owner)2098 .checked_add(item.value)2099 .unwrap();2100 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2101 }21022103 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2104 let current_index = <ItemListIndex>::get(collection_id)2105 .checked_add(1)2106 .unwrap();21072108 let value = item.owner.first().unwrap().fraction;2109 let owner = item.owner.first().unwrap().owner.clone();21102111 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();21122113 <ItemListIndex>::insert(collection_id, current_index);21142115 // Update balance2116 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2117 .checked_add(value)2118 .unwrap();2119 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2120 }21212122 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21232124 // add to account limit2125 if <AccountItemCount<T>>::contains_key(owner.clone()) {21262127 // bound Owned tokens by a single address2128 let count = <AccountItemCount<T>>::get(owner.clone());2129 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21302131 <AccountItemCount<T>>::insert(owner.clone(), count2132 .checked_add(1)2133 .ok_or(Error::<T>::NumOverflow)?);2134 }2135 else {2136 <AccountItemCount<T>>::insert(owner.clone(), 1);2137 }21382139 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2140 if list_exists {2141 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2142 let item_contains = list.contains(&item_index.clone());21432144 if !item_contains {2145 list.push(item_index.clone());2146 }21472148 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2149 } else {2150 let mut itm = Vec::new();2151 itm.push(item_index.clone());2152 <AddressTokens<T>>::insert(collection_id, owner, itm);2153 2154 }21552156 Ok(())2157 }21582159 fn remove_token_index(2160 collection_id: CollectionId,2161 item_index: TokenId,2162 owner: T::AccountId,2163 ) -> DispatchResult {21642165 // update counter2166 <AccountItemCount<T>>::insert(owner.clone(), 2167 <AccountItemCount<T>>::get(owner.clone())2168 .checked_sub(1)2169 .ok_or(Error::<T>::NumOverflow)?);217021712172 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2173 if list_exists {2174 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2175 let item_contains = list.contains(&item_index.clone());21762177 if item_contains {2178 list.retain(|&item| item != item_index);2179 <AddressTokens<T>>::insert(collection_id, owner, list);2180 }2181 }21822183 Ok(())2184 }21852186 fn move_token_index(2187 collection_id: CollectionId,2188 item_index: TokenId,2189 old_owner: T::AccountId,2190 new_owner: T::AccountId,2191 ) -> DispatchResult {2192 Self::remove_token_index(collection_id, item_index, old_owner)?;2193 Self::add_token_index(collection_id, item_index, new_owner)?;21942195 Ok(())2196 }2197 2198 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2199 if <ContractOwner<T>>::contains_key(contract.clone()) {2200 let owner = <ContractOwner<T>>::get(contract);2201 ensure!(account == owner, Error::<T>::NoPermission);2202 } else {2203 fail!(Error::<T>::NoPermission);2204 }22052206 Ok(())2207 }2208}22092210////////////////////////////////////////////////////////////////////////////////////////////////////2211// Economic models2212// #region22132214/// Fee multiplier.2215pub type Multiplier = FixedU128;22162217type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2218 <T as system::Trait>::AccountId,2219>>::Balance;2220type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2221 <T as system::Trait>::AccountId,2222>>::NegativeImbalance;22232224/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2225/// in the queue.2226#[derive(Encode, Decode, Clone, Eq, PartialEq)]2227pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2228 #[codec(compact)] BalanceOf<T>2229);22302231impl<T: Trait + Send + Sync> sp_std::fmt::Debug2232 for ChargeTransactionPayment<T>2233{2234 #[cfg(feature = "std")]2235 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2236 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2237 }2238 #[cfg(not(feature = "std"))]2239 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2240 Ok(())2241 }2242}22432244impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2245where2246 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2247 BalanceOf<T>: Send + Sync + FixedPointOperand,2248{2249 /// utility constructor. Used only in client/factory code.2250 pub fn from(fee: BalanceOf<T>) -> Self {2251 Self(fee)2252 }22532254 pub fn traditional_fee(2255 len: usize,2256 info: &DispatchInfoOf<T::Call>,2257 tip: BalanceOf<T>,2258 ) -> BalanceOf<T>2259 where2260 T::Call: Dispatchable<Info = DispatchInfo>,2261 {2262 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2263 }22642265 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2266 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2267 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2268 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2269 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2270 }22712272 fn withdraw_fee(2273 &self,2274 who: &T::AccountId,2275 call: &T::Call,2276 info: &DispatchInfoOf<T::Call>,2277 len: usize,2278 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2279 let tip = self.0;22802281 // Set fee based on call type. Creating collection costs 1 Unique.2282 // All other transactions have traditional fees so far2283 // let fee = match call.is_sub_type() {2284 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2285 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2286 // // _ => <BalanceOf<T>>::from(100)2287 // };2288 let fee = Self::traditional_fee(len, info, tip);22892290 // Only mess with balances if fee is not zero.2291 if fee.is_zero() {2292 return Ok((fee, None));2293 }22942295 // Determine who is paying transaction fee based on ecnomic model2296 // Parse call to extract collection ID and access collection sponsor2297 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2298 Some(Call::create_item(collection_id, _owner, _properties)) => {22992300 // check free create limit2301 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2302 (<Collection<T>>::get(collection_id).sponsor_confirmed)2303 {2304 <Collection<T>>::get(collection_id).sponsor2305 } else {2306 T::AccountId::default()2307 }2308 }2309 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2310 2311 let mut sponsor_transfer = false;2312 if <Collection<T>>::get(collection_id).sponsor_confirmed {23132314 let collection_limits = <Collection<T>>::get(collection_id).limits;2315 let collection_mode = <Collection<T>>::get(collection_id).mode;2316 2317 // sponsor timeout2318 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2319 sponsor_transfer = match collection_mode {2320 CollectionMode::NFT => {2321 2322 // get correct limit2323 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2324 collection_limits.sponsor_transfer_timeout2325 } else {2326 ChainLimit::get().nft_sponsor_transfer_timeout2327 };2328 2329 let mut sponsored = true;2330 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2331 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2332 let limit_time = last_tx_block + limit.into();2333 if block_number <= limit_time {2334 sponsored = false;2335 }2336 }2337 if sponsored {2338 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2339 }23402341 sponsored2342 }2343 CollectionMode::Fungible(_) => {2344 2345 // get correct limit2346 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2347 collection_limits.sponsor_transfer_timeout2348 } else {2349 ChainLimit::get().fungible_sponsor_transfer_timeout2350 };2351 2352 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2353 let mut sponsored = true;2354 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2355 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2356 let limit_time = last_tx_block + limit.into();2357 if block_number <= limit_time {2358 sponsored = false;2359 }2360 }2361 if sponsored {2362 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2363 }23642365 sponsored2366 }2367 CollectionMode::ReFungible(_) => {2368 2369 // get correct limit2370 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2371 collection_limits.sponsor_transfer_timeout2372 } else {2373 ChainLimit::get().refungible_sponsor_transfer_timeout2374 };2375 2376 let mut sponsored = true;2377 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2378 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2379 let limit_time = last_tx_block + limit.into();2380 if block_number <= limit_time {2381 sponsored = false;2382 }2383 }2384 if sponsored {2385 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2386 }23872388 sponsored2389 }2390 _ => {2391 false2392 },2393 };2394 }23952396 if !sponsor_transfer {2397 T::AccountId::default()2398 } else {2399 <Collection<T>>::get(collection_id).sponsor2400 }2401 }24022403 _ => T::AccountId::default(),2404 };24052406 // Sponsor smart contracts2407 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24082409 // On instantiation: set the contract owner2410 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24112412 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2413 code_hash,2414 &data,2415 &who,2416 );2417 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24182419 T::AccountId::default()2420 },24212422 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2423 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24242425 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24262427 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2428 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2429 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2430 2431 if !owned_contract && white_list_enabled {2432 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2433 return Err(InvalidTransaction::Call.into());2434 }2435 }24362437 let mut sponsor_transfer = false;2438 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2439 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2440 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2441 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2442 let limit_time = last_tx_block + rate_limit;24432444 if block_number >= limit_time {2445 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2446 sponsor_transfer = true;2447 }2448 } else {2449 sponsor_transfer = false;2450 }2451 2452 2453 let mut sp = T::AccountId::default();2454 if sponsor_transfer {2455 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2456 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2457 sp = called_contract;2458 }2459 }2460 }24612462 sp2463 },24642465 _ => sponsor,2466 };24672468 let mut who_pays_fee: T::AccountId = sponsor.clone();2469 if sponsor == T::AccountId::default() {2470 who_pays_fee = who.clone();2471 }24722473 match <T as transaction_payment::Trait>::Currency::withdraw(2474 &who_pays_fee,2475 fee,2476 if tip.is_zero() {2477 WithdrawReason::TransactionPayment.into()2478 } else {2479 WithdrawReason::TransactionPayment | WithdrawReason::Tip2480 },2481 ExistenceRequirement::KeepAlive,2482 ) {2483 Ok(imbalance) => Ok((fee, Some(imbalance))),2484 Err(_) => Err(InvalidTransaction::Payment.into()),2485 }2486 }2487}248824892490impl<T: Trait + Send + Sync> SignedExtension2491 for ChargeTransactionPayment<T>2492where2493 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2494 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2495{2496 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2497 type AccountId = T::AccountId;2498 type Call = T::Call;2499 type AdditionalSigned = ();2500 type Pre = (2501 BalanceOf<T>,2502 Self::AccountId,2503 Option<NegativeImbalanceOf<T>>,2504 BalanceOf<T>,2505 );2506 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2507 Ok(())2508 }25092510 fn validate(2511 &self,2512 who: &Self::AccountId,2513 call: &Self::Call,2514 info: &DispatchInfoOf<Self::Call>,2515 len: usize,2516 ) -> TransactionValidity {2517 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2518 Ok(ValidTransaction {2519 priority: Self::get_priority(len, info, fee),2520 ..Default::default()2521 })2522 }25232524 fn pre_dispatch(2525 self,2526 who: &Self::AccountId,2527 call: &Self::Call,2528 info: &DispatchInfoOf<Self::Call>,2529 len: usize,2530 ) -> Result<Self::Pre, TransactionValidityError> {2531 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2532 Ok((self.0, who.clone(), imbalance, fee))2533 }25342535 fn post_dispatch(2536 pre: Self::Pre,2537 info: &DispatchInfoOf<Self::Call>,2538 post_info: &PostDispatchInfoOf<Self::Call>,2539 len: usize,2540 _result: &DispatchResult,2541 ) -> Result<(), TransactionValidityError> {2542 let (tip, who, imbalance, fee) = pre;2543 if let Some(payed) = imbalance {2544 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2545 len as u32, info, post_info, tip,2546 );2547 let refund = fee.saturating_sub(actual_fee);2548 let actual_payment =2549 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2550 &who, refund,2551 ) {2552 Ok(refund_imbalance) => {2553 // The refund cannot be larger than the up front payed max weight.2554 // `PostDispatchInfo::calc_unspent` guards against such a case.2555 match payed.offset(refund_imbalance) {2556 Ok(actual_payment) => actual_payment,2557 Err(_) => return Err(InvalidTransaction::Payment.into()),2558 }2559 }2560 // We do not recreate the account using the refund. The up front payment2561 // is gone in that case.2562 Err(_) => payed,2563 };2564 let imbalances = actual_payment.split(tip);2565 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2566 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2567 );2568 }2569 Ok(())2570 }2571}25722573// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -49,9 +49,9 @@
mode.clone()
));
- let saved_col_name: Vec<u16> = "Test1\0\0".encode_utf16().collect::<Vec<u16>>();
- let saved_description: Vec<u16> = "TestDescription1\0\0".encode_utf16().collect::<Vec<u16>>();
- let saved_prefix: Vec<u8> = b"token_prefix1\0\0".to_vec();
+ let saved_col_name: Vec<u16> = "Test1\0".encode_utf16().collect::<Vec<u16>>();
+ let saved_description: Vec<u16> = "TestDescription1\0".encode_utf16().collect::<Vec<u16>>();
+ let saved_prefix: Vec<u8> = b"token_prefix1\0".to_vec();
assert_eq!(TemplateModule::collection(id).owner, owner);
assert_eq!(TemplateModule::collection(id).name, saved_col_name);
assert_eq!(TemplateModule::collection(id).mode, *mode);