difftreelog
Merge pull request #70 from usetech-llc/fix/NFTPAR-290_291
in: master
Fix/nftpar 290 291
4 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 Default for CollectionMode {78 fn default() -> Self {79 Self::Invalid80 }81}8283impl Into<u8> for CollectionMode {84 fn into(self) -> u8 {85 match self {86 CollectionMode::Invalid => 0,87 CollectionMode::NFT => 1,88 CollectionMode::Fungible(_) => 2,89 CollectionMode::ReFungible(_) => 3,90 }91 }92}9394#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]95#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]96pub enum AccessMode {97 Normal,98 WhiteList,99}100impl Default for AccessMode {101 fn default() -> Self {102 Self::Normal103 }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,211212 // Schema limits213 pub offchain_schema_limit: u32,214 pub variable_on_chain_schema_limit: u32,215 pub const_on_chain_schema_limit: u32,216}217218pub trait WeightInfo {219 fn create_collection() -> Weight;220 fn destroy_collection() -> Weight;221 fn add_to_white_list() -> Weight;222 fn remove_from_white_list() -> Weight;223 fn set_public_access_mode() -> Weight;224 fn set_mint_permission() -> Weight;225 fn change_collection_owner() -> Weight;226 fn add_collection_admin() -> Weight;227 fn remove_collection_admin() -> Weight;228 fn set_collection_sponsor() -> Weight;229 fn confirm_sponsorship() -> Weight;230 fn remove_collection_sponsor() -> Weight;231 fn create_item(s: usize) -> Weight;232 fn burn_item() -> Weight;233 fn transfer() -> Weight;234 fn approve() -> Weight;235 fn transfer_from() -> Weight;236 fn set_offchain_schema() -> Weight;237 fn set_const_on_chain_schema() -> Weight;238 fn set_variable_on_chain_schema() -> Weight;239 fn set_variable_meta_data() -> Weight;240 fn enable_contract_sponsoring() -> Weight;241 fn set_schema_version() -> Weight;242 fn set_chain_limits() -> Weight;243 fn set_contract_sponsoring_rate_limit() -> Weight;244 fn toggle_contract_white_list() -> Weight;245 fn add_to_contract_white_list() -> Weight;246 fn remove_from_contract_white_list() -> Weight;247 fn set_collection_limits() -> Weight;248}249250#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]251#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]252pub struct CreateNftData {253 pub const_data: Vec<u8>,254 pub variable_data: Vec<u8>,255}256257#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]258#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]259pub struct CreateFungibleData {260 pub value: u128,261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateReFungibleData {266 pub const_data: Vec<u8>,267 pub variable_data: Vec<u8>,268}269270#[derive(Encode, Decode, Debug, Clone, PartialEq)]271#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]272pub enum CreateItemData {273 NFT(CreateNftData),274 Fungible(CreateFungibleData),275 ReFungible(CreateReFungibleData),276}277278impl CreateItemData {279 pub fn len(&self) -> usize {280 let len = match self {281 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),282 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),283 _ => 0284 };285 286 return len;287 }288}289290impl From<CreateNftData> for CreateItemData {291 fn from(item: CreateNftData) -> Self {292 CreateItemData::NFT(item)293 }294}295296impl From<CreateReFungibleData> for CreateItemData {297 fn from(item: CreateReFungibleData) -> Self {298 CreateItemData::ReFungible(item)299 }300}301302impl From<CreateFungibleData> for CreateItemData {303 fn from(item: CreateFungibleData) -> Self {304 CreateItemData::Fungible(item)305 }306}307308309decl_error! {310 /// Error for non-fungible-token module.311 pub enum Error for Module<T: Trait> {312 /// Total collections bound exceeded.313 TotalCollectionsLimitExceeded,314 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.315 CollectionDecimalPointLimitExceeded, 316 /// Collection name can not be longer than 63 char.317 CollectionNameLimitExceeded, 318 /// Collection description can not be longer than 255 char.319 CollectionDescriptionLimitExceeded, 320 /// Token prefix can not be longer than 15 char.321 CollectionTokenPrefixLimitExceeded,322 /// This collection does not exist.323 CollectionNotFound,324 /// Item not exists.325 TokenNotFound,326 /// Admin not found327 AdminNotFound,328 /// Arithmetic calculation overflow.329 NumOverflow, 330 /// Account already has admin role.331 AlreadyAdmin, 332 /// You do not own this collection.333 NoPermission,334 /// This address is not set as sponsor, use setCollectionSponsor first.335 ConfirmUnsetSponsorFail,336 /// Collection is not in mint mode.337 PublicMintingNotAllowed,338 /// Sender parameter and item owner must be equal.339 MustBeTokenOwner,340 /// Item balance not enough.341 TokenValueTooLow,342 /// Size of item is too large.343 NftSizeLimitExceeded,344 /// No approve found345 ApproveNotFound,346 /// Requested value more than approved.347 TokenValueNotEnough,348 /// Only approved addresses can call this method.349 ApproveRequired,350 /// Address is not in white list.351 AddresNotInWhiteList,352 /// Number of collection admins bound exceeded.353 CollectionAdminsLimitExceeded,354 /// Owned tokens by a single address bound exceeded.355 AddressOwnershipLimitExceeded,356 /// Length of items properties must be greater than 0.357 EmptyArgument,358 /// const_data exceeded data limit.359 TokenConstDataLimitExceeded,360 /// variable_data exceeded data limit.361 TokenVariableDataLimitExceeded,362 /// Not NFT item data used to mint in NFT collection.363 NotNftDataUsedToMintNftCollectionToken,364 /// Not Fungible item data used to mint in Fungible collection.365 NotFungibleDataUsedToMintFungibleCollectionToken,366 /// Not Re Fungible item data used to mint in Re Fungible collection.367 NotReFungibleDataUsedToMintReFungibleCollectionToken,368 /// Unexpected collection type.369 UnexpectedCollectionType,370 /// Can't store metadata in fungible tokens.371 CantStoreMetadataInFungibleTokens,372 /// Collection token limit exceeded373 CollectionTokenLimitExceeded,374 /// Account token limit exceeded per collection375 AccountTokenLimitExceeded,376 /// Collection limit bounds per collection exceeded377 CollectionLimitBoundsExceeded,378 /// Schema data size limit bound exceeded379 SchemaDataLimitExceeded380 }381}382383pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {384 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;385386 /// Weight information for extrinsics in this pallet.387 type WeightInfo: WeightInfo;388}389390#[cfg(feature = "runtime-benchmarks")]391mod benchmarking;392393// #endregion394395decl_storage! {396 trait Store for Module<T: Trait> as Nft {397398 // Private members399 NextCollectionID: CollectionId;400 CreatedCollectionCount: u32;401 ChainVersion: u64;402 ItemListIndex: map hasher(identity) CollectionId => TokenId;403404 // Chain limits struct405 pub ChainLimit get(fn chain_limit) config(): ChainLimits;406407 // Bound counters408 CollectionCount: u32;409 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;410411 // Basic collections412 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;413 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;414 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;415416 /// Balance owner per collection map417 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;418419 /// second parameter: item id + owner account id + spender account id420 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;421422 /// Item collections423 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;424 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;425 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;426427 /// Index list428 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;429430 /// Tokens transfer baskets431 pub CreateItemBasket get(fn create_item_basket): map hasher(twox_64_concat) (CollectionId, T::AccountId) => T::BlockNumber;432 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;434 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;435436 // Contract Sponsorship and Ownership437 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;438 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;439 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;440 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;441 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 442 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 443 }444 add_extra_genesis {445 build(|config: &GenesisConfig<T>| {446 // Modification of storage447 for (_num, _c) in &config.collection {448 <Module<T>>::init_collection(_c);449 }450451 for (_num, _c, _i) in &config.nft_item_id {452 <Module<T>>::init_nft_token(*_c, _i);453 }454455 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {456 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);457 }458459 for (_num, _c, _i) in &config.refungible_item_id {460 <Module<T>>::init_refungible_token(*_c, _i);461 }462 })463 }464}465466decl_event!(467 pub enum Event<T>468 where469 AccountId = <T as system::Trait>::AccountId,470 {471 /// New collection was created472 /// 473 /// # Arguments474 /// 475 /// * collection_id: Globally unique identifier of newly created collection.476 /// 477 /// * mode: [CollectionMode] converted into u8.478 /// 479 /// * account_id: Collection owner.480 Created(CollectionId, u8, AccountId),481482 /// New item was created.483 /// 484 /// # Arguments485 /// 486 /// * collection_id: Id of the collection where item was created.487 /// 488 /// * item_id: Id of an item. Unique within the collection.489 ItemCreated(CollectionId, TokenId),490491 /// Collection item was burned.492 /// 493 /// # Arguments494 /// 495 /// collection_id.496 /// 497 /// item_id: Identifier of burned NFT.498 ItemDestroyed(CollectionId, TokenId),499 }500);501502decl_module! {503 pub struct Module<T: Trait> for enum Call where origin: T::Origin {504505 fn deposit_event() = default;506 type Error = Error<T>;507508 fn on_initialize(now: T::BlockNumber) -> Weight {509510 if ChainVersion::get() < 2511 {512 let value = NextCollectionID::get();513 CreatedCollectionCount::put(value);514 ChainVersion::put(2);515 }516517 0518 }519520 /// 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.521 /// 522 /// # Permissions523 /// 524 /// * Anyone.525 /// 526 /// # Arguments527 /// 528 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.529 /// 530 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.531 /// 532 /// * token_prefix: UTF-8 string with token prefix.533 /// 534 /// * mode: [CollectionMode] collection type and type dependent data.535 // returns collection ID536 #[weight = T::WeightInfo::create_collection()]537 pub fn create_collection(origin,538 collection_name: Vec<u16>,539 collection_description: Vec<u16>,540 token_prefix: Vec<u8>,541 mode: CollectionMode) -> DispatchResult {542543 // Anyone can create a collection544 let who = ensure_signed(origin)?;545546 let decimal_points = match mode {547 CollectionMode::Fungible(points) => points,548 CollectionMode::ReFungible(points) => points,549 _ => 0550 };551552 // bound Total number of collections553 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);554555 // check params556 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);557 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);558 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);559 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);560561 // Generate next collection ID562 let next_id = CreatedCollectionCount::get()563 .checked_add(1)564 .ok_or(Error::<T>::NumOverflow)?;565566 // bound counter567 let total = CollectionCount::get()568 .checked_add(1)569 .ok_or(Error::<T>::NumOverflow)?;570571 CreatedCollectionCount::put(next_id);572 CollectionCount::put(total);573574 // Create new collection575 let new_collection = CollectionType {576 owner: who.clone(),577 name: collection_name,578 mode: mode.clone(),579 mint_mode: false,580 access: AccessMode::Normal,581 description: collection_description,582 decimal_points: decimal_points,583 token_prefix: token_prefix,584 offchain_schema: Vec::new(),585 schema_version: SchemaVersion::ImageURL,586 sponsor: T::AccountId::default(),587 sponsor_confirmed: false,588 variable_on_chain_schema: Vec::new(),589 const_on_chain_schema: Vec::new(),590 limits: CollectionLimits::default(),591 };592593 // Add new collection to map594 <Collection<T>>::insert(next_id, new_collection);595596 // call event597 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));598599 Ok(())600 }601602 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.603 /// 604 /// # Permissions605 /// 606 /// * Collection Owner.607 /// 608 /// # Arguments609 /// 610 /// * collection_id: collection to destroy.611 #[weight = T::WeightInfo::destroy_collection()]612 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {613614 let sender = ensure_signed(origin)?;615 Self::check_owner_permissions(collection_id, sender)?;616617 <AddressTokens<T>>::remove_prefix(collection_id);618 <Allowances<T>>::remove_prefix(collection_id);619 <Balance<T>>::remove_prefix(collection_id);620 <ItemListIndex>::remove(collection_id);621 <AdminList<T>>::remove(collection_id);622 <Collection<T>>::remove(collection_id);623 <WhiteList<T>>::remove_prefix(collection_id);624625 <NftItemList<T>>::remove_prefix(collection_id);626 <FungibleItemList<T>>::remove_prefix(collection_id);627 <ReFungibleItemList<T>>::remove_prefix(collection_id);628629 <NftTransferBasket<T>>::remove_prefix(collection_id);630 <FungibleTransferBasket<T>>::remove_prefix(collection_id);631 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);632633 if CollectionCount::get() > 0634 {635 // bound couter636 let total = CollectionCount::get()637 .checked_sub(1)638 .ok_or(Error::<T>::NumOverflow)?;639640 CollectionCount::put(total);641 }642643 Ok(())644 }645646 /// Add an address to white list.647 /// 648 /// # Permissions649 /// 650 /// * Collection Owner651 /// * Collection Admin652 /// 653 /// # Arguments654 /// 655 /// * collection_id.656 /// 657 /// * address.658 #[weight = T::WeightInfo::add_to_white_list()]659 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{660661 let sender = ensure_signed(origin)?;662 Self::check_owner_or_admin_permissions(collection_id, sender)?;663664 <WhiteList<T>>::insert(collection_id, address, true);665 666 Ok(())667 }668669 /// Remove an address from white list.670 /// 671 /// # Permissions672 /// 673 /// * Collection Owner674 /// * Collection Admin675 /// 676 /// # Arguments677 /// 678 /// * collection_id.679 /// 680 /// * address.681 #[weight = T::WeightInfo::remove_from_white_list()]682 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{683684 let sender = ensure_signed(origin)?;685 Self::check_owner_or_admin_permissions(collection_id, sender)?;686687 <WhiteList<T>>::remove(collection_id, address);688689 Ok(())690 }691692 /// Toggle between normal and white list access for the methods with access for `Anyone`.693 /// 694 /// # Permissions695 /// 696 /// * Collection Owner.697 /// 698 /// # Arguments699 /// 700 /// * collection_id.701 /// 702 /// * mode: [AccessMode]703 #[weight = T::WeightInfo::set_public_access_mode()]704 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult705 {706 let sender = ensure_signed(origin)?;707708 Self::check_owner_permissions(collection_id, sender)?;709 let mut target_collection = <Collection<T>>::get(collection_id);710 target_collection.access = mode;711 <Collection<T>>::insert(collection_id, target_collection);712713 Ok(())714 }715716 /// Allows Anyone to create tokens if:717 /// * White List is enabled, and718 /// * Address is added to white list, and719 /// * This method was called with True parameter720 /// 721 /// # Permissions722 /// * Collection Owner723 ///724 /// # Arguments725 /// 726 /// * collection_id.727 /// 728 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.729 #[weight = T::WeightInfo::set_mint_permission()]730 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult731 {732 let sender = ensure_signed(origin)?;733734 Self::check_owner_permissions(collection_id, sender)?;735 let mut target_collection = <Collection<T>>::get(collection_id);736 target_collection.mint_mode = mint_permission;737 <Collection<T>>::insert(collection_id, target_collection);738739 Ok(())740 }741742 /// Change the owner of the collection.743 /// 744 /// # Permissions745 /// 746 /// * Collection Owner.747 /// 748 /// # Arguments749 /// 750 /// * collection_id.751 /// 752 /// * new_owner.753 #[weight = T::WeightInfo::change_collection_owner()]754 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {755756 let sender = ensure_signed(origin)?;757 Self::check_owner_permissions(collection_id, sender)?;758 let mut target_collection = <Collection<T>>::get(collection_id);759 target_collection.owner = new_owner;760 <Collection<T>>::insert(collection_id, target_collection);761762 Ok(())763 }764765 /// Adds an admin of the Collection.766 /// 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. 767 /// 768 /// # Permissions769 /// 770 /// * Collection Owner.771 /// * Collection Admin.772 /// 773 /// # Arguments774 /// 775 /// * collection_id: ID of the Collection to add admin for.776 /// 777 /// * new_admin_id: Address of new admin to add.778 #[weight = T::WeightInfo::add_collection_admin()]779 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {780781 let sender = ensure_signed(origin)?;782 Self::check_owner_or_admin_permissions(collection_id, sender)?;783 let mut admin_arr: Vec<T::AccountId> = Vec::new();784785 if <AdminList<T>>::contains_key(collection_id)786 {787 admin_arr = <AdminList<T>>::get(collection_id);788 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);789 }790791 // Number of collection admins792 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);793794 admin_arr.push(new_admin_id);795 <AdminList<T>>::insert(collection_id, admin_arr);796797 Ok(())798 }799800 /// 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.801 ///802 /// # Permissions803 /// 804 /// * Collection Owner.805 /// * Collection Admin.806 /// 807 /// # Arguments808 /// 809 /// * collection_id: ID of the Collection to remove admin for.810 /// 811 /// * account_id: Address of admin to remove.812 #[weight = T::WeightInfo::remove_collection_admin()]813 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {814815 let sender = ensure_signed(origin)?;816 Self::check_owner_or_admin_permissions(collection_id, sender)?;817 ensure!(<AdminList<T>>::contains_key(collection_id), Error::<T>::AdminNotFound);818819 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);822823 Ok(())824 }825826 /// # Permissions827 /// 828 /// * Collection Owner829 /// 830 /// # Arguments831 /// 832 /// * collection_id.833 /// 834 /// * new_sponsor.835 #[weight = T::WeightInfo::set_collection_sponsor()]836 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {837838 let sender = ensure_signed(origin)?;839 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);840841 let mut target_collection = <Collection<T>>::get(collection_id);842 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);843844 target_collection.sponsor = new_sponsor;845 target_collection.sponsor_confirmed = false;846 <Collection<T>>::insert(collection_id, target_collection);847848 Ok(())849 }850851 /// # Permissions852 /// 853 /// * Sponsor.854 /// 855 /// # Arguments856 /// 857 /// * collection_id.858 #[weight = T::WeightInfo::confirm_sponsorship()]859 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {860861 let sender = ensure_signed(origin)?;862 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);863864 let mut target_collection = <Collection<T>>::get(collection_id);865 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);866867 target_collection.sponsor_confirmed = true;868 <Collection<T>>::insert(collection_id, target_collection);869870 Ok(())871 }872873 /// Switch back to pay-per-own-transaction model.874 ///875 /// # Permissions876 ///877 /// * Collection owner.878 /// 879 /// # Arguments880 /// 881 /// * collection_id.882 #[weight = T::WeightInfo::remove_collection_sponsor()]883 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {884885 let sender = ensure_signed(origin)?;886 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);887888 let mut target_collection = <Collection<T>>::get(collection_id);889 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);890891 target_collection.sponsor = T::AccountId::default();892 target_collection.sponsor_confirmed = false;893 <Collection<T>>::insert(collection_id, target_collection);894895 Ok(())896 }897898 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.899 /// 900 /// # Permissions901 /// 902 /// * Collection Owner.903 /// * Collection Admin.904 /// * Anyone if905 /// * White List is enabled, and906 /// * Address is added to white list, and907 /// * MintPermission is enabled (see SetMintPermission method)908 /// 909 /// # Arguments910 /// 911 /// * collection_id: ID of the collection.912 /// 913 /// * owner: Address, initial owner of the NFT.914 ///915 /// * data: Token data to store on chain.916 // #[weight =917 // (130_000_000 as Weight)918 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))919 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))920 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]921922 #[weight = T::WeightInfo::create_item(data.len())]923 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {924925 let sender = ensure_signed(origin)?;926927 Self::collection_exists(collection_id)?;928929 let target_collection = <Collection<T>>::get(collection_id);930931 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;932 Self::validate_create_item_args(&target_collection, &data)?;933 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;934935 Ok(())936 }937938 /// This method creates multiple instances of NFT Collection created with CreateCollection method.939 /// 940 /// # Permissions941 /// 942 /// * Collection Owner.943 /// * Collection Admin.944 /// * Anyone if945 /// * White List is enabled, and946 /// * Address is added to white list, and947 /// * MintPermission is enabled (see SetMintPermission method)948 /// 949 /// # Arguments950 /// 951 /// * collection_id: ID of the collection.952 /// 953 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].954 /// 955 /// * owner: Address, initial owner of the NFT.956 #[weight = T::WeightInfo::create_item(items_data.into_iter()957 .map(|data| { data.len() })958 .sum())]959 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {960961 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);962 let sender = ensure_signed(origin)?;963964 Self::collection_exists(collection_id)?;965 let target_collection = <Collection<T>>::get(collection_id);966967 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;968969 for data in &items_data {970 Self::validate_create_item_args(&target_collection, data)?;971 }972 for data in &items_data {973 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;974 }975976 Ok(())977 }978979 /// Destroys a concrete instance of NFT.980 /// 981 /// # Permissions982 /// 983 /// * Collection Owner.984 /// * Collection Admin.985 /// * Current NFT Owner.986 /// 987 /// # Arguments988 /// 989 /// * collection_id: ID of the collection.990 /// 991 /// * item_id: ID of NFT to burn.992 #[weight = T::WeightInfo::burn_item()]993 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {994995 let sender = ensure_signed(origin)?;996 Self::collection_exists(collection_id)?;997998 // Transfer permissions check999 let target_collection = <Collection<T>>::get(collection_id);1000 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1001 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1002 Error::<T>::NoPermission);10031004 if target_collection.access == AccessMode::WhiteList {1005 Self::check_white_list(collection_id, &sender)?;1006 }10071008 match target_collection.mode1009 {1010 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1011 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1012 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1013 _ => ()1014 };10151016 // call event1017 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10181019 Ok(())1020 }10211022 /// Change ownership of the token.1023 /// 1024 /// # Permissions1025 /// 1026 /// * Collection Owner1027 /// * Collection Admin1028 /// * Current NFT owner1029 ///1030 /// # Arguments1031 /// 1032 /// * recipient: Address of token recipient.1033 /// 1034 /// * collection_id.1035 /// 1036 /// * item_id: ID of the item1037 /// * Non-Fungible Mode: Required.1038 /// * Fungible Mode: Ignored.1039 /// * Re-Fungible Mode: Required.1040 /// 1041 /// * value: Amount to transfer.1042 /// * Non-Fungible Mode: Ignored1043 /// * Fungible Mode: Must specify transferred amount1044 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1045 #[weight = T::WeightInfo::transfer()]1046 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10471048 let sender = ensure_signed(origin)?;1049 let target_collection = <Collection<T>>::get(collection_id);10501051 // Limits check1052 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10531054 // Transfer permissions check1055 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1056 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1057 Error::<T>::NoPermission);10581059 if target_collection.access == AccessMode::WhiteList {1060 Self::check_white_list(collection_id, &sender)?;1061 Self::check_white_list(collection_id, &recipient)?;1062 }10631064 match target_collection.mode1065 {1066 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1067 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1068 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1069 _ => ()1070 };10711072 Ok(())1073 }10741075 /// Set, change, or remove approved address to transfer the ownership of the NFT.1076 /// 1077 /// # Permissions1078 /// 1079 /// * Collection Owner1080 /// * Collection Admin1081 /// * Current NFT owner1082 /// 1083 /// # Arguments1084 /// 1085 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1086 /// 1087 /// * collection_id.1088 /// 1089 /// * item_id: ID of the item.1090 #[weight = T::WeightInfo::approve()]1091 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10921093 let sender = ensure_signed(origin)?;10941095 // Transfer permissions check1096 let target_collection = <Collection<T>>::get(collection_id);1097 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1098 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1099 Error::<T>::NoPermission);11001101 if target_collection.access == AccessMode::WhiteList {1102 Self::check_white_list(collection_id, &sender)?;1103 Self::check_white_list(collection_id, &spender)?;1104 }11051106 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1107 let mut allowance: u128 = amount;1108 if allowance_exists {1109 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1110 }1111 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11121113 Ok(())1114 }1115 1116 /// 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.1117 /// 1118 /// # Permissions1119 /// * Collection Owner1120 /// * Collection Admin1121 /// * Current NFT owner1122 /// * Address approved by current NFT owner1123 /// 1124 /// # Arguments1125 /// 1126 /// * from: Address that owns token.1127 /// 1128 /// * recipient: Address of token recipient.1129 /// 1130 /// * collection_id.1131 /// 1132 /// * item_id: ID of the item.1133 /// 1134 /// * value: Amount to transfer.1135 #[weight = T::WeightInfo::transfer_from()]1136 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11371138 let sender = ensure_signed(origin)?;1139 let mut appoved_transfer = false;11401141 // Check approval1142 let mut approval: u128 = 0;1143 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1144 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1145 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1146 appoved_transfer = true;1147 }11481149 let target_collection = <Collection<T>>::get(collection_id);11501151 // Limits check1152 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11531154 // Transfer permissions check 1155 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1156 Error::<T>::NoPermission);11571158 if target_collection.access == AccessMode::WhiteList {1159 Self::check_white_list(collection_id, &sender)?;1160 Self::check_white_list(collection_id, &recipient)?;1161 }11621163 // Reduce approval by transferred amount or remove if remaining approval drops to 01164 if approval.checked_sub(value).unwrap_or(0) > 0 {1165 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1166 }1167 else {1168 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1169 }11701171 match target_collection.mode1172 {1173 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1174 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1175 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1176 _ => ()1177 };11781179 Ok(())1180 }11811182 #[weight = 0]1183 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11841185 // let no_perm_mes = "You do not have permissions to modify this collection";1186 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1187 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1188 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11891190 // // on_nft_received call11911192 // Self::transfer(origin, collection_id, item_id, new_owner)?;11931194 Ok(())1195 }11961197 /// Set off-chain data schema.1198 /// 1199 /// # Permissions1200 /// 1201 /// * Collection Owner1202 /// * Collection Admin1203 /// 1204 /// # Arguments1205 /// 1206 /// * collection_id.1207 /// 1208 /// * schema: String representing the offchain data schema.1209 #[weight = T::WeightInfo::set_variable_meta_data()]1210 pub fn set_variable_meta_data (1211 origin,1212 collection_id: CollectionId,1213 item_id: TokenId,1214 data: Vec<u8>1215 ) -> DispatchResult {1216 let sender = ensure_signed(origin)?;1217 1218 Self::collection_exists(collection_id)?;1219 1220 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12211222 // Modify permissions check1223 let target_collection = <Collection<T>>::get(collection_id);1224 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1225 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1226 Error::<T>::NoPermission);12271228 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12291230 match target_collection.mode1231 {1232 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1233 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1234 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1235 _ => fail!(Error::<T>::UnexpectedCollectionType)1236 };12371238 Ok(())1239 }1240 1241 /// Set schema standard1242 /// ImageURL1243 /// Unique1244 /// 1245 /// # Permissions1246 /// 1247 /// * Collection Owner1248 /// * Collection Admin1249 /// 1250 /// # Arguments1251 /// 1252 /// * collection_id.1253 /// 1254 /// * schema: SchemaVersion: enum1255 #[weight = T::WeightInfo::set_schema_version()]1256 pub fn set_schema_version(1257 origin,1258 collection_id: CollectionId,1259 version: SchemaVersion1260 ) -> DispatchResult {1261 let sender = ensure_signed(origin)?;1262 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1263 let mut target_collection = <Collection<T>>::get(collection_id);1264 target_collection.schema_version = version;1265 <Collection<T>>::insert(collection_id, target_collection);12661267 Ok(())1268 }12691270 /// Set off-chain data schema.1271 /// 1272 /// # Permissions1273 /// 1274 /// * Collection Owner1275 /// * Collection Admin1276 /// 1277 /// # Arguments1278 /// 1279 /// * collection_id.1280 /// 1281 /// * schema: String representing the offchain data schema.1282 #[weight = T::WeightInfo::set_offchain_schema()]1283 pub fn set_offchain_schema(1284 origin,1285 collection_id: CollectionId,1286 schema: Vec<u8>1287 ) -> DispatchResult {1288 let sender = ensure_signed(origin)?;1289 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12901291 // check schema limit1292 ensure!(schema.len() as u32 > ChainLimit::get().offchain_schema_limit, "");12931294 let mut target_collection = <Collection<T>>::get(collection_id);1295 target_collection.offchain_schema = schema;1296 <Collection<T>>::insert(collection_id, target_collection);12971298 Ok(())1299 }13001301 /// Set const on-chain data schema.1302 /// 1303 /// # Permissions1304 /// 1305 /// * Collection Owner1306 /// * Collection Admin1307 /// 1308 /// # Arguments1309 /// 1310 /// * collection_id.1311 /// 1312 /// * schema: String representing the const on-chain data schema.1313 #[weight = T::WeightInfo::set_const_on_chain_schema()]1314 pub fn set_const_on_chain_schema (1315 origin,1316 collection_id: CollectionId,1317 schema: Vec<u8>1318 ) -> DispatchResult {1319 let sender = ensure_signed(origin)?;1320 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13211322 // check schema limit1323 ensure!(schema.len() as u32 > ChainLimit::get().const_on_chain_schema_limit, "");13241325 let mut target_collection = <Collection<T>>::get(collection_id);1326 target_collection.const_on_chain_schema = schema;1327 <Collection<T>>::insert(collection_id, target_collection);13281329 Ok(())1330 }13311332 /// Set variable on-chain data schema.1333 /// 1334 /// # Permissions1335 /// 1336 /// * Collection Owner1337 /// * Collection Admin1338 /// 1339 /// # Arguments1340 /// 1341 /// * collection_id.1342 /// 1343 /// * schema: String representing the variable on-chain data schema.1344 #[weight = T::WeightInfo::set_const_on_chain_schema()]1345 pub fn set_variable_on_chain_schema (1346 origin,1347 collection_id: CollectionId,1348 schema: Vec<u8>1349 ) -> DispatchResult {1350 let sender = ensure_signed(origin)?;1351 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13521353 // check schema limit1354 ensure!(schema.len() as u32 > ChainLimit::get().variable_on_chain_schema_limit, "");13551356 let mut target_collection = <Collection<T>>::get(collection_id);1357 target_collection.variable_on_chain_schema = schema;1358 <Collection<T>>::insert(collection_id, target_collection);13591360 Ok(())1361 }13621363 // Sudo permissions function1364 #[weight = T::WeightInfo::set_chain_limits()]1365 pub fn set_chain_limits(1366 origin,1367 limits: ChainLimits1368 ) -> DispatchResult {13691370 #[cfg(not(feature = "runtime-benchmarks"))]1371 ensure_root(origin)?;13721373 <ChainLimit>::put(limits);1374 Ok(())1375 }13761377 /// Enable smart contract self-sponsoring.1378 /// 1379 /// # Permissions1380 /// 1381 /// * Contract Owner1382 /// 1383 /// # Arguments1384 /// 1385 /// * contract address1386 /// * enable flag1387 /// 1388 #[weight = T::WeightInfo::enable_contract_sponsoring()]1389 pub fn enable_contract_sponsoring(1390 origin,1391 contract_address: T::AccountId,1392 enable: bool1393 ) -> DispatchResult {13941395 let sender = ensure_signed(origin)?;13961397 #[cfg(feature = "runtime-benchmarks")]1398 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13991400 Self::ensure_contract_owned(sender, &contract_address)?;14011402 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1403 Ok(())1404 }14051406 /// Set the rate limit for contract sponsoring to specified number of blocks.1407 /// 1408 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1409 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1410 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1411 /// from contract endowment if there are at least B blocks between such transactions. 1412 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1413 /// 1414 /// # Permissions1415 /// 1416 /// * Contract Owner1417 /// 1418 /// # Arguments1419 /// 1420 /// -`contract_address`: Address of the contract to sponsor1421 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1422 /// 1423 #[weight = T::WeightInfo::set_contract_sponsoring_rate_limit()]1424 pub fn set_contract_sponsoring_rate_limit(1425 origin,1426 contract_address: T::AccountId,1427 rate_limit: T::BlockNumber1428 ) -> DispatchResult {1429 let sender = ensure_signed(origin)?;14301431 #[cfg(feature = "runtime-benchmarks")]1432 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14331434 Self::ensure_contract_owned(sender, &contract_address)?;1435 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1436 Ok(())1437 }14381439 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1440 /// 1441 /// # Permissions1442 /// 1443 /// * Address that deployed smart contract.1444 /// 1445 /// # Arguments1446 /// 1447 /// -`contract_address`: Address of the contract.1448 /// 1449 /// - `enable`: . 1450 #[weight = T::WeightInfo::toggle_contract_white_list()]1451 pub fn toggle_contract_white_list(1452 origin,1453 contract_address: T::AccountId,1454 enable: bool1455 ) -> DispatchResult {1456 let sender = ensure_signed(origin)?;14571458 #[cfg(feature = "runtime-benchmarks")]1459 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14601461 Self::ensure_contract_owned(sender, &contract_address)?;1462 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1463 Ok(())1464 }1465 1466 /// Add an address to smart contract white list.1467 /// 1468 /// # Permissions1469 /// 1470 /// * Address that deployed smart contract.1471 /// 1472 /// # Arguments1473 /// 1474 /// -`contract_address`: Address of the contract.1475 ///1476 /// -`account_address`: Address to add.1477 #[weight = T::WeightInfo::add_to_contract_white_list()]1478 pub fn add_to_contract_white_list(1479 origin,1480 contract_address: T::AccountId,1481 account_address: T::AccountId1482 ) -> DispatchResult {1483 let sender = ensure_signed(origin)?;14841485 #[cfg(feature = "runtime-benchmarks")]1486 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1487 1488 Self::ensure_contract_owned(sender, &contract_address)?; 1489 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1490 Ok(())1491 }14921493 /// Remove an address from smart contract white list.1494 /// 1495 /// # Permissions1496 /// 1497 /// * Address that deployed smart contract.1498 /// 1499 /// # Arguments1500 /// 1501 /// -`contract_address`: Address of the contract.1502 ///1503 /// -`account_address`: Address to remove.1504 #[weight = T::WeightInfo::remove_from_contract_white_list()]1505 pub fn remove_from_contract_white_list(1506 origin,1507 contract_address: T::AccountId,1508 account_address: T::AccountId1509 ) -> DispatchResult {1510 let sender = ensure_signed(origin)?;15111512 #[cfg(feature = "runtime-benchmarks")]1513 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15141515 Self::ensure_contract_owned(sender, &contract_address)?;1516 <ContractWhiteList<T>>::remove(contract_address, account_address);1517 Ok(())1518 }15191520 #[weight = T::WeightInfo::set_collection_limits()]1521 pub fn set_collection_limits(1522 origin,1523 collection_id: u32,1524 limits: CollectionLimits,1525 ) -> DispatchResult {1526 let sender = ensure_signed(origin)?;1527 Self::check_owner_permissions(collection_id, sender.clone())?;1528 let mut target_collection = <Collection<T>>::get(collection_id);1529 let chain_limits = ChainLimit::get();1530 let climits = target_collection.limits;15311532 // collection bounds1533 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1534 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1535 Error::<T>::CollectionLimitBoundsExceeded);15361537 // token_limit check prev1538 ensure!(climits.token_limit > limits.token_limit && 1539 limits.token_limit <= chain_limits.account_token_ownership_limit, 1540 Error::<T>::AccountTokenLimitExceeded);15411542 target_collection.limits = limits;1543 <Collection<T>>::insert(collection_id, target_collection);15441545 Ok(())1546 } 1547 }1548}15491550impl<T: Trait> Module<T> {15511552 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15531554 // check token limit and account token limit1555 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1556 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1557 1558 Ok(())1559 }15601561 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15621563 // check token limit and account token limit1564 let total_items: u32 = ItemListIndex::get(collection_id);1565 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1566 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1567 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15681569 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1570 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1571 Self::check_white_list(collection_id, owner)?;1572 Self::check_white_list(collection_id, sender)?;1573 }15741575 Ok(())1576 }15771578 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1579 match target_collection.mode1580 {1581 CollectionMode::NFT => {1582 if let CreateItemData::NFT(data) = data {1583 // check sizes1584 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1585 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1586 } else {1587 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1588 }1589 },1590 CollectionMode::Fungible(_) => {1591 if let CreateItemData::Fungible(_) = data {1592 } else {1593 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1594 }1595 },1596 CollectionMode::ReFungible(_) => {1597 if let CreateItemData::ReFungible(data) = data {15981599 // check sizes1600 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1601 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1602 } else {1603 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1604 }1605 },1606 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1607 };16081609 Ok(())1610 }16111612 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1613 match data1614 {1615 CreateItemData::NFT(data) => {1616 let item = NftItemType {1617 owner,1618 const_data: data.const_data,1619 variable_data: data.variable_data1620 };16211622 Self::add_nft_item(collection_id, item)?;1623 },1624 CreateItemData::Fungible(data) => {1625 Self::add_fungible_item(collection_id, &owner, data.value)?;1626 },1627 CreateItemData::ReFungible(data) => {1628 let mut owner_list = Vec::new();1629 let value = (10 as u128).pow(collection.decimal_points as u32);1630 owner_list.push(Ownership {owner: owner.clone(), fraction: value});16311632 let item = ReFungibleItemType {1633 owner: owner_list,1634 const_data: data.const_data,1635 variable_data: data.variable_data1636 };16371638 Self::add_refungible_item(collection_id, item)?;1639 }1640 };16411642 // call event1643 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16441645 Ok(())1646 }16471648 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16491650 // Does new owner already have an account?1651 let mut balance: u128 = 0;1652 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1653 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1654 } 16551656 // Mint 1657 let item = FungibleItemType {1658 value: balance + value1659 };1660 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16611662 // Update balance1663 let new_balance = <Balance<T>>::get(collection_id, owner)1664 .checked_add(value)1665 .ok_or(Error::<T>::NumOverflow)?;1666 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16671668 Ok(())1669 }16701671 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1672 let current_index = <ItemListIndex>::get(collection_id)1673 .checked_add(1)1674 .ok_or(Error::<T>::NumOverflow)?;1675 let itemcopy = item.clone();16761677 let value = item.owner.first().unwrap().fraction;1678 let owner = item.owner.first().unwrap().owner.clone();16791680 Self::add_token_index(collection_id, current_index, owner.clone())?;16811682 <ItemListIndex>::insert(collection_id, current_index);1683 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16841685 // Update balance1686 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1687 .checked_add(value)1688 .ok_or(Error::<T>::NumOverflow)?;1689 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16901691 Ok(())1692 }16931694 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1695 let current_index = <ItemListIndex>::get(collection_id)1696 .checked_add(1)1697 .ok_or(Error::<T>::NumOverflow)?;16981699 let item_owner = item.owner.clone();1700 Self::add_token_index(collection_id, current_index, item.owner.clone())?;17011702 <ItemListIndex>::insert(collection_id, current_index);1703 <NftItemList<T>>::insert(collection_id, current_index, item);17041705 // Update balance1706 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1707 .checked_add(1)1708 .ok_or(Error::<T>::NumOverflow)?;1709 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17101711 Ok(())1712 }17131714 fn burn_refungible_item(1715 collection_id: CollectionId,1716 item_id: TokenId,1717 owner: T::AccountId,1718 ) -> DispatchResult {1719 ensure!(1720 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1721 Error::<T>::TokenNotFound1722 );1723 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1724 let item = collection1725 .owner1726 .iter()1727 .filter(|&i| i.owner == owner)1728 .next()1729 .unwrap();1730 Self::remove_token_index(collection_id, item_id, owner.clone())?;17311732 // update balance1733 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1734 .checked_sub(item.fraction)1735 .ok_or(Error::<T>::NumOverflow)?;1736 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17371738 <ReFungibleItemList<T>>::remove(collection_id, item_id);17391740 Ok(())1741 }17421743 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1744 ensure!(1745 <NftItemList<T>>::contains_key(collection_id, item_id),1746 Error::<T>::TokenNotFound1747 );1748 let item = <NftItemList<T>>::get(collection_id, item_id);1749 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17501751 // update balance1752 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1753 .checked_sub(1)1754 .ok_or(Error::<T>::NumOverflow)?;1755 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1756 <NftItemList<T>>::remove(collection_id, item_id);17571758 Ok(())1759 }17601761 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1762 ensure!(1763 <FungibleItemList<T>>::contains_key(collection_id, owner),1764 Error::<T>::TokenNotFound1765 );1766 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1767 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17681769 // update balance1770 let new_balance = <Balance<T>>::get(collection_id, owner)1771 .checked_sub(value)1772 .ok_or(Error::<T>::NumOverflow)?;1773 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17741775 if balance.value - value > 0 {1776 balance.value -= value;1777 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1778 }1779 else {1780 <FungibleItemList<T>>::remove(collection_id, owner);1781 }17821783 Ok(())1784 }17851786 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1787 ensure!(1788 <Collection<T>>::contains_key(collection_id),1789 Error::<T>::CollectionNotFound1790 );1791 Ok(())1792 }17931794 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1795 Self::collection_exists(collection_id)?;17961797 let target_collection = <Collection<T>>::get(collection_id);1798 ensure!(1799 subject == target_collection.owner,1800 Error::<T>::NoPermission1801 );18021803 Ok(())1804 }18051806 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1807 let target_collection = <Collection<T>>::get(collection_id);1808 let mut result: bool = subject == target_collection.owner;1809 let exists = <AdminList<T>>::contains_key(collection_id);18101811 if !result & exists {1812 if <AdminList<T>>::get(collection_id).contains(&subject) {1813 result = true1814 }1815 }18161817 result1818 }18191820 fn check_owner_or_admin_permissions(1821 collection_id: CollectionId,1822 subject: T::AccountId,1823 ) -> DispatchResult {1824 Self::collection_exists(collection_id)?;1825 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18261827 ensure!(1828 result,1829 Error::<T>::NoPermission1830 );1831 Ok(())1832 }18331834 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1835 let target_collection = <Collection<T>>::get(collection_id);18361837 match target_collection.mode {1838 CollectionMode::NFT => {1839 <NftItemList<T>>::get(collection_id, item_id).owner == subject1840 }1841 CollectionMode::Fungible(_) => {1842 <FungibleItemList<T>>::contains_key(collection_id, &subject)1843 }1844 CollectionMode::ReFungible(_) => {1845 <ReFungibleItemList<T>>::get(collection_id, item_id)1846 .owner1847 .iter()1848 .any(|i| i.owner == subject)1849 }1850 CollectionMode::Invalid => false,1851 }1852 }18531854 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1855 let mes = Error::<T>::AddresNotInWhiteList;1856 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18571858 Ok(())1859 }18601861 fn transfer_fungible(1862 collection_id: CollectionId,1863 value: u128,1864 owner: &T::AccountId,1865 recipient: &T::AccountId,1866 ) -> DispatchResult {1867 ensure!(1868 <FungibleItemList<T>>::contains_key(collection_id, owner),1869 Error::<T>::TokenNotFound1870 );18711872 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1873 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18741875 // Send balance to recipient (updates balanceOf of recipient)1876 Self::add_fungible_item(collection_id, recipient, value)?;18771878 // update balanceOf of sender1879 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18801881 // Reduce or remove sender1882 if balance.value == value {1883 <FungibleItemList<T>>::remove(collection_id, owner);1884 }1885 else {1886 balance.value -= value;1887 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1888 }18891890 Ok(())1891 }18921893 fn transfer_refungible(1894 collection_id: CollectionId,1895 item_id: TokenId,1896 value: u128,1897 owner: T::AccountId,1898 new_owner: T::AccountId,1899 ) -> DispatchResult {1900 ensure!(1901 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1902 Error::<T>::TokenNotFound1903 );19041905 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1906 let item = full_item1907 .owner1908 .iter()1909 .filter(|i| i.owner == owner)1910 .next()1911 .ok_or(Error::<T>::NumOverflow)?;1912 let amount = item.fraction;19131914 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19151916 // update balance1917 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1918 .checked_sub(value)1919 .ok_or(Error::<T>::NumOverflow)?;1920 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19211922 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1923 .checked_add(value)1924 .ok_or(Error::<T>::NumOverflow)?;1925 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19261927 let old_owner = item.owner.clone();1928 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19291930 // transfer1931 if amount == value && !new_owner_has_account {1932 // change owner1933 // new owner do not have account1934 let mut new_full_item = full_item.clone();1935 new_full_item1936 .owner1937 .iter_mut()1938 .find(|i| i.owner == owner)1939 .unwrap()1940 .owner = new_owner.clone();1941 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19421943 // update index collection1944 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1945 } else {1946 let mut new_full_item = full_item.clone();1947 new_full_item1948 .owner1949 .iter_mut()1950 .find(|i| i.owner == owner)1951 .unwrap()1952 .fraction -= value;19531954 // separate amount1955 if new_owner_has_account {1956 // new owner has account1957 new_full_item1958 .owner1959 .iter_mut()1960 .find(|i| i.owner == new_owner)1961 .unwrap()1962 .fraction += value;1963 } else {1964 // new owner do not have account1965 new_full_item.owner.push(Ownership {1966 owner: new_owner.clone(),1967 fraction: value,1968 });1969 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1970 }19711972 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1973 }19741975 Ok(())1976 }19771978 fn transfer_nft(1979 collection_id: CollectionId,1980 item_id: TokenId,1981 sender: T::AccountId,1982 new_owner: T::AccountId,1983 ) -> DispatchResult {1984 ensure!(1985 <NftItemList<T>>::contains_key(collection_id, item_id),1986 Error::<T>::TokenNotFound1987 );19881989 let mut item = <NftItemList<T>>::get(collection_id, item_id);19901991 ensure!(1992 sender == item.owner,1993 Error::<T>::MustBeTokenOwner1994 );19951996 // update balance1997 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1998 .checked_sub(1)1999 .ok_or(Error::<T>::NumOverflow)?;2000 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20012002 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2003 .checked_add(1)2004 .ok_or(Error::<T>::NumOverflow)?;2005 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20062007 // change owner2008 let old_owner = item.owner.clone();2009 item.owner = new_owner.clone();2010 <NftItemList<T>>::insert(collection_id, item_id, item);20112012 // update index collection2013 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20142015 Ok(())2016 }2017 2018 fn item_exists(2019 collection_id: CollectionId,2020 item_id: TokenId,2021 mode: &CollectionMode2022 ) -> DispatchResult {2023 match mode {2024 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2025 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2026 _ => ()2027 };2028 2029 Ok(())2030 }20312032 fn set_re_fungible_variable_data(2033 collection_id: CollectionId,2034 item_id: TokenId,2035 data: Vec<u8>2036 ) -> DispatchResult {2037 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20382039 item.variable_data = data;20402041 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20422043 Ok(())2044 }20452046 fn set_nft_variable_data(2047 collection_id: CollectionId,2048 item_id: TokenId,2049 data: Vec<u8>2050 ) -> DispatchResult {2051 let mut item = <NftItemList<T>>::get(collection_id, item_id);2052 2053 item.variable_data = data;20542055 <NftItemList<T>>::insert(collection_id, item_id, item);2056 2057 Ok(())2058 }20592060 fn init_collection(item: &CollectionType<T::AccountId>) {2061 // check params2062 assert!(2063 item.decimal_points <= MAX_DECIMAL_POINTS,2064 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2065 );2066 assert!(2067 item.name.len() <= 64,2068 "Collection name can not be longer than 63 char"2069 );2070 assert!(2071 item.name.len() <= 256,2072 "Collection description can not be longer than 255 char"2073 );2074 assert!(2075 item.token_prefix.len() <= 16,2076 "Token prefix can not be longer than 15 char"2077 );20782079 // Generate next collection ID2080 let next_id = CreatedCollectionCount::get()2081 .checked_add(1)2082 .unwrap();20832084 CreatedCollectionCount::put(next_id);2085 }20862087 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2088 let current_index = <ItemListIndex>::get(collection_id)2089 .checked_add(1)2090 .unwrap();20912092 let item_owner = item.owner.clone();2093 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20942095 <ItemListIndex>::insert(collection_id, current_index);20962097 // Update balance2098 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2099 .checked_add(1)2100 .unwrap();2101 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2102 }21032104 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2105 let current_index = <ItemListIndex>::get(collection_id)2106 .checked_add(1)2107 .unwrap();21082109 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();21102111 <ItemListIndex>::insert(collection_id, current_index);21122113 // Update balance2114 let new_balance = <Balance<T>>::get(collection_id, owner)2115 .checked_add(item.value)2116 .unwrap();2117 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2118 }21192120 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2121 let current_index = <ItemListIndex>::get(collection_id)2122 .checked_add(1)2123 .unwrap();21242125 let value = item.owner.first().unwrap().fraction;2126 let owner = item.owner.first().unwrap().owner.clone();21272128 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();21292130 <ItemListIndex>::insert(collection_id, current_index);21312132 // Update balance2133 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2134 .checked_add(value)2135 .unwrap();2136 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2137 }21382139 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21402141 // add to account limit2142 if <AccountItemCount<T>>::contains_key(owner.clone()) {21432144 // bound Owned tokens by a single address2145 let count = <AccountItemCount<T>>::get(owner.clone());2146 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21472148 <AccountItemCount<T>>::insert(owner.clone(), count2149 .checked_add(1)2150 .ok_or(Error::<T>::NumOverflow)?);2151 }2152 else {2153 <AccountItemCount<T>>::insert(owner.clone(), 1);2154 }21552156 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2157 if list_exists {2158 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2159 let item_contains = list.contains(&item_index.clone());21602161 if !item_contains {2162 list.push(item_index.clone());2163 }21642165 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2166 } else {2167 let mut itm = Vec::new();2168 itm.push(item_index.clone());2169 <AddressTokens<T>>::insert(collection_id, owner, itm);2170 2171 }21722173 Ok(())2174 }21752176 fn remove_token_index(2177 collection_id: CollectionId,2178 item_index: TokenId,2179 owner: T::AccountId,2180 ) -> DispatchResult {21812182 // update counter2183 <AccountItemCount<T>>::insert(owner.clone(), 2184 <AccountItemCount<T>>::get(owner.clone())2185 .checked_sub(1)2186 .ok_or(Error::<T>::NumOverflow)?);218721882189 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2190 if list_exists {2191 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2192 let item_contains = list.contains(&item_index.clone());21932194 if item_contains {2195 list.retain(|&item| item != item_index);2196 <AddressTokens<T>>::insert(collection_id, owner, list);2197 }2198 }21992200 Ok(())2201 }22022203 fn move_token_index(2204 collection_id: CollectionId,2205 item_index: TokenId,2206 old_owner: T::AccountId,2207 new_owner: T::AccountId,2208 ) -> DispatchResult {2209 Self::remove_token_index(collection_id, item_index, old_owner)?;2210 Self::add_token_index(collection_id, item_index, new_owner)?;22112212 Ok(())2213 }2214 2215 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2216 if <ContractOwner<T>>::contains_key(contract.clone()) {2217 let owner = <ContractOwner<T>>::get(contract);2218 ensure!(account == owner, Error::<T>::NoPermission);2219 } else {2220 fail!(Error::<T>::NoPermission);2221 }22222223 Ok(())2224 }2225}22262227////////////////////////////////////////////////////////////////////////////////////////////////////2228// Economic models2229// #region22302231/// Fee multiplier.2232pub type Multiplier = FixedU128;22332234type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2235 <T as system::Trait>::AccountId,2236>>::Balance;2237type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2238 <T as system::Trait>::AccountId,2239>>::NegativeImbalance;22402241/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2242/// in the queue.2243#[derive(Encode, Decode, Clone, Eq, PartialEq)]2244pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2245 #[codec(compact)] BalanceOf<T>2246);22472248impl<T: Trait + Send + Sync> sp_std::fmt::Debug2249 for ChargeTransactionPayment<T>2250{2251 #[cfg(feature = "std")]2252 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2253 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2254 }2255 #[cfg(not(feature = "std"))]2256 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2257 Ok(())2258 }2259}22602261impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2262where2263 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2264 BalanceOf<T>: Send + Sync + FixedPointOperand,2265{2266 /// utility constructor. Used only in client/factory code.2267 pub fn from(fee: BalanceOf<T>) -> Self {2268 Self(fee)2269 }22702271 pub fn traditional_fee(2272 len: usize,2273 info: &DispatchInfoOf<T::Call>,2274 tip: BalanceOf<T>,2275 ) -> BalanceOf<T>2276 where2277 T::Call: Dispatchable<Info = DispatchInfo>,2278 {2279 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2280 }22812282 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2283 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2284 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2285 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2286 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2287 }22882289 fn withdraw_fee(2290 &self,2291 who: &T::AccountId,2292 call: &T::Call,2293 info: &DispatchInfoOf<T::Call>,2294 len: usize,2295 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2296 let tip = self.0;22972298 // Set fee based on call type. Creating collection costs 1 Unique.2299 // All other transactions have traditional fees so far2300 // let fee = match call.is_sub_type() {2301 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2302 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2303 // // _ => <BalanceOf<T>>::from(100)2304 // };2305 let fee = Self::traditional_fee(len, info, tip);23062307 // Only mess with balances if fee is not zero.2308 if fee.is_zero() {2309 return Ok((fee, None));2310 }23112312 // Determine who is paying transaction fee based on ecnomic model2313 // Parse call to extract collection ID and access collection sponsor2314 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2315 Some(Call::create_item(collection_id, _owner, _properties)) => {23162317 // sponsor timeout2318 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;23192320 let limit = <Collection<T>>::get(collection_id).limits.sponsor_transfer_timeout;2321 let mut sponsored = true;2322 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2323 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2324 let limit_time = last_tx_block + limit.into();2325 if block_number <= limit_time {2326 sponsored = false;2327 }2328 }2329 if sponsored {2330 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2331 }23322333 // check free create limit2334 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2335 (<Collection<T>>::get(collection_id).sponsor_confirmed) &&2336 (sponsored)2337 {2338 <Collection<T>>::get(collection_id).sponsor2339 } else {2340 T::AccountId::default()2341 }2342 }2343 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2344 2345 let mut sponsor_transfer = false;2346 if <Collection<T>>::get(collection_id).sponsor_confirmed {23472348 let collection_limits = <Collection<T>>::get(collection_id).limits;2349 let collection_mode = <Collection<T>>::get(collection_id).mode;2350 2351 // sponsor timeout2352 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2353 sponsor_transfer = match collection_mode {2354 CollectionMode::NFT => {2355 2356 // get correct limit2357 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2358 collection_limits.sponsor_transfer_timeout2359 } else {2360 ChainLimit::get().nft_sponsor_transfer_timeout2361 };2362 2363 let mut sponsored = true;2364 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2365 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2366 let limit_time = last_tx_block + limit.into();2367 if block_number <= limit_time {2368 sponsored = false;2369 }2370 }2371 if sponsored {2372 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2373 }23742375 sponsored2376 }2377 CollectionMode::Fungible(_) => {2378 2379 // get correct limit2380 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2381 collection_limits.sponsor_transfer_timeout2382 } else {2383 ChainLimit::get().fungible_sponsor_transfer_timeout2384 };2385 2386 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2387 let mut sponsored = true;2388 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2389 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2390 let limit_time = last_tx_block + limit.into();2391 if block_number <= limit_time {2392 sponsored = false;2393 }2394 }2395 if sponsored {2396 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2397 }23982399 sponsored2400 }2401 CollectionMode::ReFungible(_) => {2402 2403 // get correct limit2404 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2405 collection_limits.sponsor_transfer_timeout2406 } else {2407 ChainLimit::get().refungible_sponsor_transfer_timeout2408 };2409 2410 let mut sponsored = true;2411 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2412 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2413 let limit_time = last_tx_block + limit.into();2414 if block_number <= limit_time {2415 sponsored = false;2416 }2417 }2418 if sponsored {2419 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2420 }24212422 sponsored2423 }2424 _ => {2425 false2426 },2427 };2428 }24292430 if !sponsor_transfer {2431 T::AccountId::default()2432 } else {2433 <Collection<T>>::get(collection_id).sponsor2434 }2435 }24362437 _ => T::AccountId::default(),2438 };24392440 // Sponsor smart contracts2441 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24422443 // On instantiation: set the contract owner2444 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24452446 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2447 code_hash,2448 &data,2449 &who,2450 );2451 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24522453 T::AccountId::default()2454 },24552456 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2457 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24582459 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24602461 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2462 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2463 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2464 2465 if !owned_contract && white_list_enabled {2466 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2467 return Err(InvalidTransaction::Call.into());2468 }2469 }24702471 let mut sponsor_transfer = false;2472 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2473 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2474 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2475 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2476 let limit_time = last_tx_block + rate_limit;24772478 if block_number >= limit_time {2479 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2480 sponsor_transfer = true;2481 }2482 } else {2483 sponsor_transfer = false;2484 }2485 2486 2487 let mut sp = T::AccountId::default();2488 if sponsor_transfer {2489 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2490 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2491 sp = called_contract;2492 }2493 }2494 }24952496 sp2497 },24982499 _ => sponsor,2500 };25012502 let mut who_pays_fee: T::AccountId = sponsor.clone();2503 if sponsor == T::AccountId::default() {2504 who_pays_fee = who.clone();2505 }25062507 match <T as transaction_payment::Trait>::Currency::withdraw(2508 &who_pays_fee,2509 fee,2510 if tip.is_zero() {2511 WithdrawReason::TransactionPayment.into()2512 } else {2513 WithdrawReason::TransactionPayment | WithdrawReason::Tip2514 },2515 ExistenceRequirement::KeepAlive,2516 ) {2517 Ok(imbalance) => Ok((fee, Some(imbalance))),2518 Err(_) => Err(InvalidTransaction::Payment.into()),2519 }2520 }2521}252225232524impl<T: Trait + Send + Sync> SignedExtension2525 for ChargeTransactionPayment<T>2526where2527 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2528 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2529{2530 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2531 type AccountId = T::AccountId;2532 type Call = T::Call;2533 type AdditionalSigned = ();2534 type Pre = (2535 BalanceOf<T>,2536 Self::AccountId,2537 Option<NegativeImbalanceOf<T>>,2538 BalanceOf<T>,2539 );2540 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2541 Ok(())2542 }25432544 fn validate(2545 &self,2546 who: &Self::AccountId,2547 call: &Self::Call,2548 info: &DispatchInfoOf<Self::Call>,2549 len: usize,2550 ) -> TransactionValidity {2551 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2552 Ok(ValidTransaction {2553 priority: Self::get_priority(len, info, fee),2554 ..Default::default()2555 })2556 }25572558 fn pre_dispatch(2559 self,2560 who: &Self::AccountId,2561 call: &Self::Call,2562 info: &DispatchInfoOf<Self::Call>,2563 len: usize,2564 ) -> Result<Self::Pre, TransactionValidityError> {2565 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2566 Ok((self.0, who.clone(), imbalance, fee))2567 }25682569 fn post_dispatch(2570 pre: Self::Pre,2571 info: &DispatchInfoOf<Self::Call>,2572 post_info: &PostDispatchInfoOf<Self::Call>,2573 len: usize,2574 _result: &DispatchResult,2575 ) -> Result<(), TransactionValidityError> {2576 let (tip, who, imbalance, fee) = pre;2577 if let Some(payed) = imbalance {2578 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2579 len as u32, info, post_info, tip,2580 );2581 let refund = fee.saturating_sub(actual_fee);2582 let actual_payment =2583 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2584 &who, refund,2585 ) {2586 Ok(refund_imbalance) => {2587 // The refund cannot be larger than the up front payed max weight.2588 // `PostDispatchInfo::calc_unspent` guards against such a case.2589 match payed.offset(refund_imbalance) {2590 Ok(actual_payment) => actual_payment,2591 Err(_) => return Err(InvalidTransaction::Payment.into()),2592 }2593 }2594 // We do not recreate the account using the refund. The up front payment2595 // is gone in that case.2596 Err(_) => payed,2597 };2598 let imbalances = actual_payment.split(tip);2599 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2600 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2601 );2602 }2603 Ok(())2604 }2605}26062607// #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 Default for CollectionMode {78 fn default() -> Self {79 Self::Invalid80 }81}8283impl Into<u8> for CollectionMode {84 fn into(self) -> u8 {85 match self {86 CollectionMode::Invalid => 0,87 CollectionMode::NFT => 1,88 CollectionMode::Fungible(_) => 2,89 CollectionMode::ReFungible(_) => 3,90 }91 }92}9394#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]95#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]96pub enum AccessMode {97 Normal,98 WhiteList,99}100impl Default for AccessMode {101 fn default() -> Self {102 Self::Normal103 }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,211212 // Schema limits213 pub offchain_schema_limit: u32,214 pub variable_on_chain_schema_limit: u32,215 pub const_on_chain_schema_limit: u32,216}217218pub trait WeightInfo {219 fn create_collection() -> Weight;220 fn destroy_collection() -> Weight;221 fn add_to_white_list() -> Weight;222 fn remove_from_white_list() -> Weight;223 fn set_public_access_mode() -> Weight;224 fn set_mint_permission() -> Weight;225 fn change_collection_owner() -> Weight;226 fn add_collection_admin() -> Weight;227 fn remove_collection_admin() -> Weight;228 fn set_collection_sponsor() -> Weight;229 fn confirm_sponsorship() -> Weight;230 fn remove_collection_sponsor() -> Weight;231 fn create_item(s: usize) -> Weight;232 fn burn_item() -> Weight;233 fn transfer() -> Weight;234 fn approve() -> Weight;235 fn transfer_from() -> Weight;236 fn set_offchain_schema() -> Weight;237 fn set_const_on_chain_schema() -> Weight;238 fn set_variable_on_chain_schema() -> Weight;239 fn set_variable_meta_data() -> Weight;240 fn enable_contract_sponsoring() -> Weight;241 fn set_schema_version() -> Weight;242 fn set_chain_limits() -> Weight;243 fn set_contract_sponsoring_rate_limit() -> Weight;244 fn toggle_contract_white_list() -> Weight;245 fn add_to_contract_white_list() -> Weight;246 fn remove_from_contract_white_list() -> Weight;247 fn set_collection_limits() -> Weight;248}249250#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]251#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]252pub struct CreateNftData {253 pub const_data: Vec<u8>,254 pub variable_data: Vec<u8>,255}256257#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]258#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]259pub struct CreateFungibleData {260 pub value: u128,261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateReFungibleData {266 pub const_data: Vec<u8>,267 pub variable_data: Vec<u8>,268}269270#[derive(Encode, Decode, Debug, Clone, PartialEq)]271#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]272pub enum CreateItemData {273 NFT(CreateNftData),274 Fungible(CreateFungibleData),275 ReFungible(CreateReFungibleData),276}277278impl CreateItemData {279 pub fn len(&self) -> usize {280 let len = match self {281 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),282 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),283 _ => 0284 };285 286 return len;287 }288}289290impl From<CreateNftData> for CreateItemData {291 fn from(item: CreateNftData) -> Self {292 CreateItemData::NFT(item)293 }294}295296impl From<CreateReFungibleData> for CreateItemData {297 fn from(item: CreateReFungibleData) -> Self {298 CreateItemData::ReFungible(item)299 }300}301302impl From<CreateFungibleData> for CreateItemData {303 fn from(item: CreateFungibleData) -> Self {304 CreateItemData::Fungible(item)305 }306}307308309decl_error! {310 /// Error for non-fungible-token module.311 pub enum Error for Module<T: Trait> {312 /// Total collections bound exceeded.313 TotalCollectionsLimitExceeded,314 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.315 CollectionDecimalPointLimitExceeded, 316 /// Collection name can not be longer than 63 char.317 CollectionNameLimitExceeded, 318 /// Collection description can not be longer than 255 char.319 CollectionDescriptionLimitExceeded, 320 /// Token prefix can not be longer than 15 char.321 CollectionTokenPrefixLimitExceeded,322 /// This collection does not exist.323 CollectionNotFound,324 /// Item not exists.325 TokenNotFound,326 /// Admin not found327 AdminNotFound,328 /// Arithmetic calculation overflow.329 NumOverflow, 330 /// Account already has admin role.331 AlreadyAdmin, 332 /// You do not own this collection.333 NoPermission,334 /// This address is not set as sponsor, use setCollectionSponsor first.335 ConfirmUnsetSponsorFail,336 /// Collection is not in mint mode.337 PublicMintingNotAllowed,338 /// Sender parameter and item owner must be equal.339 MustBeTokenOwner,340 /// Item balance not enough.341 TokenValueTooLow,342 /// Size of item is too large.343 NftSizeLimitExceeded,344 /// No approve found345 ApproveNotFound,346 /// Requested value more than approved.347 TokenValueNotEnough,348 /// Only approved addresses can call this method.349 ApproveRequired,350 /// Address is not in white list.351 AddresNotInWhiteList,352 /// Number of collection admins bound exceeded.353 CollectionAdminsLimitExceeded,354 /// Owned tokens by a single address bound exceeded.355 AddressOwnershipLimitExceeded,356 /// Length of items properties must be greater than 0.357 EmptyArgument,358 /// const_data exceeded data limit.359 TokenConstDataLimitExceeded,360 /// variable_data exceeded data limit.361 TokenVariableDataLimitExceeded,362 /// Not NFT item data used to mint in NFT collection.363 NotNftDataUsedToMintNftCollectionToken,364 /// Not Fungible item data used to mint in Fungible collection.365 NotFungibleDataUsedToMintFungibleCollectionToken,366 /// Not Re Fungible item data used to mint in Re Fungible collection.367 NotReFungibleDataUsedToMintReFungibleCollectionToken,368 /// Unexpected collection type.369 UnexpectedCollectionType,370 /// Can't store metadata in fungible tokens.371 CantStoreMetadataInFungibleTokens,372 /// Collection token limit exceeded373 CollectionTokenLimitExceeded,374 /// Account token limit exceeded per collection375 AccountTokenLimitExceeded,376 /// Collection limit bounds per collection exceeded377 CollectionLimitBoundsExceeded,378 /// Schema data size limit bound exceeded379 SchemaDataLimitExceeded380 }381}382383pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {384 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;385386 /// Weight information for extrinsics in this pallet.387 type WeightInfo: WeightInfo;388}389390#[cfg(feature = "runtime-benchmarks")]391mod benchmarking;392393// #endregion394395decl_storage! {396 trait Store for Module<T: Trait> as Nft {397398 // Private members399 NextCollectionID: CollectionId;400 CreatedCollectionCount: u32;401 ChainVersion: u64;402 ItemListIndex: map hasher(identity) CollectionId => TokenId;403404 // Chain limits struct405 pub ChainLimit get(fn chain_limit) config(): ChainLimits;406407 // Bound counters408 CollectionCount: u32;409 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;410411 // Basic collections412 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;413 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;414 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;415416 /// Balance owner per collection map417 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;418419 /// second parameter: item id + owner account id + spender account id420 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;421422 /// Item collections423 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;424 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;425 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;426427 /// Index list428 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;429430 /// Tokens transfer baskets431 pub CreateItemBasket get(fn create_item_basket): map hasher(twox_64_concat) (CollectionId, T::AccountId) => T::BlockNumber;432 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;434 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;435436 // Contract Sponsorship and Ownership437 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;438 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;439 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;440 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;441 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 442 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 443 }444 add_extra_genesis {445 build(|config: &GenesisConfig<T>| {446 // Modification of storage447 for (_num, _c) in &config.collection {448 <Module<T>>::init_collection(_c);449 }450451 for (_num, _c, _i) in &config.nft_item_id {452 <Module<T>>::init_nft_token(*_c, _i);453 }454455 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {456 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);457 }458459 for (_num, _c, _i) in &config.refungible_item_id {460 <Module<T>>::init_refungible_token(*_c, _i);461 }462 })463 }464}465466decl_event!(467 pub enum Event<T>468 where469 AccountId = <T as system::Trait>::AccountId,470 {471 /// New collection was created472 /// 473 /// # Arguments474 /// 475 /// * collection_id: Globally unique identifier of newly created collection.476 /// 477 /// * mode: [CollectionMode] converted into u8.478 /// 479 /// * account_id: Collection owner.480 Created(CollectionId, u8, AccountId),481482 /// New item was created.483 /// 484 /// # Arguments485 /// 486 /// * collection_id: Id of the collection where item was created.487 /// 488 /// * item_id: Id of an item. Unique within the collection.489 ItemCreated(CollectionId, TokenId),490491 /// Collection item was burned.492 /// 493 /// # Arguments494 /// 495 /// collection_id.496 /// 497 /// item_id: Identifier of burned NFT.498 ItemDestroyed(CollectionId, TokenId),499 }500);501502decl_module! {503 pub struct Module<T: Trait> for enum Call where origin: T::Origin {504505 fn deposit_event() = default;506 type Error = Error<T>;507508 fn on_initialize(now: T::BlockNumber) -> Weight {509510 if ChainVersion::get() < 2511 {512 let value = NextCollectionID::get();513 CreatedCollectionCount::put(value);514 ChainVersion::put(2);515 }516517 0518 }519520 /// 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.521 /// 522 /// # Permissions523 /// 524 /// * Anyone.525 /// 526 /// # Arguments527 /// 528 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.529 /// 530 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.531 /// 532 /// * token_prefix: UTF-8 string with token prefix.533 /// 534 /// * mode: [CollectionMode] collection type and type dependent data.535 // returns collection ID536 #[weight = T::WeightInfo::create_collection()]537 pub fn create_collection(origin,538 collection_name: Vec<u16>,539 collection_description: Vec<u16>,540 token_prefix: Vec<u8>,541 mode: CollectionMode) -> DispatchResult {542543 // Anyone can create a collection544 let who = ensure_signed(origin)?;545546 let decimal_points = match mode {547 CollectionMode::Fungible(points) => points,548 CollectionMode::ReFungible(points) => points,549 _ => 0550 };551552 // bound Total number of collections553 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);554555 // check params556 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);557 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);558 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);559 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);560561 // Generate next collection ID562 let next_id = CreatedCollectionCount::get()563 .checked_add(1)564 .ok_or(Error::<T>::NumOverflow)?;565566 // bound counter567 let total = CollectionCount::get()568 .checked_add(1)569 .ok_or(Error::<T>::NumOverflow)?;570571 CreatedCollectionCount::put(next_id);572 CollectionCount::put(total);573574 // Create new collection575 let new_collection = CollectionType {576 owner: who.clone(),577 name: collection_name,578 mode: mode.clone(),579 mint_mode: false,580 access: AccessMode::Normal,581 description: collection_description,582 decimal_points: decimal_points,583 token_prefix: token_prefix,584 offchain_schema: Vec::new(),585 schema_version: SchemaVersion::ImageURL,586 sponsor: T::AccountId::default(),587 sponsor_confirmed: false,588 variable_on_chain_schema: Vec::new(),589 const_on_chain_schema: Vec::new(),590 limits: CollectionLimits::default(),591 };592593 // Add new collection to map594 <Collection<T>>::insert(next_id, new_collection);595596 // call event597 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));598599 Ok(())600 }601602 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.603 /// 604 /// # Permissions605 /// 606 /// * Collection Owner.607 /// 608 /// # Arguments609 /// 610 /// * collection_id: collection to destroy.611 #[weight = T::WeightInfo::destroy_collection()]612 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {613614 let sender = ensure_signed(origin)?;615 Self::check_owner_permissions(collection_id, sender)?;616617 <AddressTokens<T>>::remove_prefix(collection_id);618 <Allowances<T>>::remove_prefix(collection_id);619 <Balance<T>>::remove_prefix(collection_id);620 <ItemListIndex>::remove(collection_id);621 <AdminList<T>>::remove(collection_id);622 <Collection<T>>::remove(collection_id);623 <WhiteList<T>>::remove_prefix(collection_id);624625 <NftItemList<T>>::remove_prefix(collection_id);626 <FungibleItemList<T>>::remove_prefix(collection_id);627 <ReFungibleItemList<T>>::remove_prefix(collection_id);628629 <NftTransferBasket<T>>::remove_prefix(collection_id);630 <FungibleTransferBasket<T>>::remove_prefix(collection_id);631 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);632633 if CollectionCount::get() > 0634 {635 // bound couter636 let total = CollectionCount::get()637 .checked_sub(1)638 .ok_or(Error::<T>::NumOverflow)?;639640 CollectionCount::put(total);641 }642643 Ok(())644 }645646 /// Add an address to white list.647 /// 648 /// # Permissions649 /// 650 /// * Collection Owner651 /// * Collection Admin652 /// 653 /// # Arguments654 /// 655 /// * collection_id.656 /// 657 /// * address.658 #[weight = T::WeightInfo::add_to_white_list()]659 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{660661 let sender = ensure_signed(origin)?;662 Self::check_owner_or_admin_permissions(collection_id, sender)?;663664 <WhiteList<T>>::insert(collection_id, address, true);665 666 Ok(())667 }668669 /// Remove an address from white list.670 /// 671 /// # Permissions672 /// 673 /// * Collection Owner674 /// * Collection Admin675 /// 676 /// # Arguments677 /// 678 /// * collection_id.679 /// 680 /// * address.681 #[weight = T::WeightInfo::remove_from_white_list()]682 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{683684 let sender = ensure_signed(origin)?;685 Self::check_owner_or_admin_permissions(collection_id, sender)?;686687 <WhiteList<T>>::remove(collection_id, address);688689 Ok(())690 }691692 /// Toggle between normal and white list access for the methods with access for `Anyone`.693 /// 694 /// # Permissions695 /// 696 /// * Collection Owner.697 /// 698 /// # Arguments699 /// 700 /// * collection_id.701 /// 702 /// * mode: [AccessMode]703 #[weight = T::WeightInfo::set_public_access_mode()]704 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult705 {706 let sender = ensure_signed(origin)?;707708 Self::check_owner_permissions(collection_id, sender)?;709 let mut target_collection = <Collection<T>>::get(collection_id);710 target_collection.access = mode;711 <Collection<T>>::insert(collection_id, target_collection);712713 Ok(())714 }715716 /// Allows Anyone to create tokens if:717 /// * White List is enabled, and718 /// * Address is added to white list, and719 /// * This method was called with True parameter720 /// 721 /// # Permissions722 /// * Collection Owner723 ///724 /// # Arguments725 /// 726 /// * collection_id.727 /// 728 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.729 #[weight = T::WeightInfo::set_mint_permission()]730 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult731 {732 let sender = ensure_signed(origin)?;733734 Self::check_owner_permissions(collection_id, sender)?;735 let mut target_collection = <Collection<T>>::get(collection_id);736 target_collection.mint_mode = mint_permission;737 <Collection<T>>::insert(collection_id, target_collection);738739 Ok(())740 }741742 /// Change the owner of the collection.743 /// 744 /// # Permissions745 /// 746 /// * Collection Owner.747 /// 748 /// # Arguments749 /// 750 /// * collection_id.751 /// 752 /// * new_owner.753 #[weight = T::WeightInfo::change_collection_owner()]754 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {755756 let sender = ensure_signed(origin)?;757 Self::check_owner_permissions(collection_id, sender)?;758 let mut target_collection = <Collection<T>>::get(collection_id);759 target_collection.owner = new_owner;760 <Collection<T>>::insert(collection_id, target_collection);761762 Ok(())763 }764765 /// Adds an admin of the Collection.766 /// 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. 767 /// 768 /// # Permissions769 /// 770 /// * Collection Owner.771 /// * Collection Admin.772 /// 773 /// # Arguments774 /// 775 /// * collection_id: ID of the Collection to add admin for.776 /// 777 /// * new_admin_id: Address of new admin to add.778 #[weight = T::WeightInfo::add_collection_admin()]779 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {780781 let sender = ensure_signed(origin)?;782 Self::check_owner_or_admin_permissions(collection_id, sender)?;783 let mut admin_arr: Vec<T::AccountId> = Vec::new();784785 if <AdminList<T>>::contains_key(collection_id)786 {787 admin_arr = <AdminList<T>>::get(collection_id);788 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);789 }790791 // Number of collection admins792 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);793794 admin_arr.push(new_admin_id);795 <AdminList<T>>::insert(collection_id, admin_arr);796797 Ok(())798 }799800 /// 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.801 ///802 /// # Permissions803 /// 804 /// * Collection Owner.805 /// * Collection Admin.806 /// 807 /// # Arguments808 /// 809 /// * collection_id: ID of the Collection to remove admin for.810 /// 811 /// * account_id: Address of admin to remove.812 #[weight = T::WeightInfo::remove_collection_admin()]813 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {814815 let sender = ensure_signed(origin)?;816 Self::check_owner_or_admin_permissions(collection_id, sender)?;817 ensure!(<AdminList<T>>::contains_key(collection_id), Error::<T>::AdminNotFound);818819 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);822823 Ok(())824 }825826 /// # Permissions827 /// 828 /// * Collection Owner829 /// 830 /// # Arguments831 /// 832 /// * collection_id.833 /// 834 /// * new_sponsor.835 #[weight = T::WeightInfo::set_collection_sponsor()]836 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {837838 let sender = ensure_signed(origin)?;839 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);840841 let mut target_collection = <Collection<T>>::get(collection_id);842 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);843844 target_collection.sponsor = new_sponsor;845 target_collection.sponsor_confirmed = false;846 <Collection<T>>::insert(collection_id, target_collection);847848 Ok(())849 }850851 /// # Permissions852 /// 853 /// * Sponsor.854 /// 855 /// # Arguments856 /// 857 /// * collection_id.858 #[weight = T::WeightInfo::confirm_sponsorship()]859 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {860861 let sender = ensure_signed(origin)?;862 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);863864 let mut target_collection = <Collection<T>>::get(collection_id);865 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);866867 target_collection.sponsor_confirmed = true;868 <Collection<T>>::insert(collection_id, target_collection);869870 Ok(())871 }872873 /// Switch back to pay-per-own-transaction model.874 ///875 /// # Permissions876 ///877 /// * Collection owner.878 /// 879 /// # Arguments880 /// 881 /// * collection_id.882 #[weight = T::WeightInfo::remove_collection_sponsor()]883 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {884885 let sender = ensure_signed(origin)?;886 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);887888 let mut target_collection = <Collection<T>>::get(collection_id);889 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);890891 target_collection.sponsor = T::AccountId::default();892 target_collection.sponsor_confirmed = false;893 <Collection<T>>::insert(collection_id, target_collection);894895 Ok(())896 }897898 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.899 /// 900 /// # Permissions901 /// 902 /// * Collection Owner.903 /// * Collection Admin.904 /// * Anyone if905 /// * White List is enabled, and906 /// * Address is added to white list, and907 /// * MintPermission is enabled (see SetMintPermission method)908 /// 909 /// # Arguments910 /// 911 /// * collection_id: ID of the collection.912 /// 913 /// * owner: Address, initial owner of the NFT.914 ///915 /// * data: Token data to store on chain.916 // #[weight =917 // (130_000_000 as Weight)918 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))919 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))920 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]921922 #[weight = T::WeightInfo::create_item(data.len())]923 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {924925 let sender = ensure_signed(origin)?;926927 Self::collection_exists(collection_id)?;928929 let target_collection = <Collection<T>>::get(collection_id);930931 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;932 Self::validate_create_item_args(&target_collection, &data)?;933 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;934935 Ok(())936 }937938 /// This method creates multiple instances of NFT Collection created with CreateCollection method.939 /// 940 /// # Permissions941 /// 942 /// * Collection Owner.943 /// * Collection Admin.944 /// * Anyone if945 /// * White List is enabled, and946 /// * Address is added to white list, and947 /// * MintPermission is enabled (see SetMintPermission method)948 /// 949 /// # Arguments950 /// 951 /// * collection_id: ID of the collection.952 /// 953 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].954 /// 955 /// * owner: Address, initial owner of the NFT.956 #[weight = T::WeightInfo::create_item(items_data.into_iter()957 .map(|data| { data.len() })958 .sum())]959 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {960961 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);962 let sender = ensure_signed(origin)?;963964 Self::collection_exists(collection_id)?;965 let target_collection = <Collection<T>>::get(collection_id);966967 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;968969 for data in &items_data {970 Self::validate_create_item_args(&target_collection, data)?;971 }972 for data in &items_data {973 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;974 }975976 Ok(())977 }978979 /// Destroys a concrete instance of NFT.980 /// 981 /// # Permissions982 /// 983 /// * Collection Owner.984 /// * Collection Admin.985 /// * Current NFT Owner.986 /// 987 /// # Arguments988 /// 989 /// * collection_id: ID of the collection.990 /// 991 /// * item_id: ID of NFT to burn.992 #[weight = T::WeightInfo::burn_item()]993 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {994995 let sender = ensure_signed(origin)?;996 Self::collection_exists(collection_id)?;997998 // Transfer permissions check999 let target_collection = <Collection<T>>::get(collection_id);1000 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1001 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1002 Error::<T>::NoPermission);10031004 if target_collection.access == AccessMode::WhiteList {1005 Self::check_white_list(collection_id, &sender)?;1006 }10071008 match target_collection.mode1009 {1010 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1011 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1012 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, &sender)?,1013 _ => ()1014 };10151016 // call event1017 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10181019 Ok(())1020 }10211022 /// Change ownership of the token.1023 /// 1024 /// # Permissions1025 /// 1026 /// * Collection Owner1027 /// * Collection Admin1028 /// * Current NFT owner1029 ///1030 /// # Arguments1031 /// 1032 /// * recipient: Address of token recipient.1033 /// 1034 /// * collection_id.1035 /// 1036 /// * item_id: ID of the item1037 /// * Non-Fungible Mode: Required.1038 /// * Fungible Mode: Ignored.1039 /// * Re-Fungible Mode: Required.1040 /// 1041 /// * value: Amount to transfer.1042 /// * Non-Fungible Mode: Ignored1043 /// * Fungible Mode: Must specify transferred amount1044 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1045 #[weight = T::WeightInfo::transfer()]1046 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10471048 let sender = ensure_signed(origin)?;1049 let target_collection = <Collection<T>>::get(collection_id);10501051 // Limits check1052 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10531054 // Transfer permissions check1055 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1056 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1057 Error::<T>::NoPermission);10581059 if target_collection.access == AccessMode::WhiteList {1060 Self::check_white_list(collection_id, &sender)?;1061 Self::check_white_list(collection_id, &recipient)?;1062 }10631064 match target_collection.mode1065 {1066 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1067 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1068 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1069 _ => ()1070 };10711072 Ok(())1073 }10741075 /// Set, change, or remove approved address to transfer the ownership of the NFT.1076 /// 1077 /// # Permissions1078 /// 1079 /// * Collection Owner1080 /// * Collection Admin1081 /// * Current NFT owner1082 /// 1083 /// # Arguments1084 /// 1085 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1086 /// 1087 /// * collection_id.1088 /// 1089 /// * item_id: ID of the item.1090 #[weight = T::WeightInfo::approve()]1091 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10921093 let sender = ensure_signed(origin)?;10941095 Self::collection_exists(collection_id)?;1096 Self::token_exists(collection_id, item_id, &sender)?;10971098 // Transfer permissions check1099 let target_collection = <Collection<T>>::get(collection_id);1100 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1101 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1102 Error::<T>::NoPermission);11031104 if target_collection.access == AccessMode::WhiteList {1105 Self::check_white_list(collection_id, &sender)?;1106 Self::check_white_list(collection_id, &spender)?;1107 }11081109 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1110 let mut allowance: u128 = amount;1111 if allowance_exists {1112 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1113 }1114 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11151116 Ok(())1117 }1118 1119 /// 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.1120 /// 1121 /// # Permissions1122 /// * Collection Owner1123 /// * Collection Admin1124 /// * Current NFT owner1125 /// * Address approved by current NFT owner1126 /// 1127 /// # Arguments1128 /// 1129 /// * from: Address that owns token.1130 /// 1131 /// * recipient: Address of token recipient.1132 /// 1133 /// * collection_id.1134 /// 1135 /// * item_id: ID of the item.1136 /// 1137 /// * value: Amount to transfer.1138 #[weight = T::WeightInfo::transfer_from()]1139 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11401141 let sender = ensure_signed(origin)?;1142 let mut appoved_transfer = false;11431144 // Check approval1145 let mut approval: u128 = 0;1146 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1147 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1148 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1149 appoved_transfer = true;1150 }11511152 let target_collection = <Collection<T>>::get(collection_id);11531154 // Limits check1155 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11561157 // Transfer permissions check 1158 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1159 Error::<T>::NoPermission);11601161 if target_collection.access == AccessMode::WhiteList {1162 Self::check_white_list(collection_id, &sender)?;1163 Self::check_white_list(collection_id, &recipient)?;1164 }11651166 // Reduce approval by transferred amount or remove if remaining approval drops to 01167 if approval.checked_sub(value).unwrap_or(0) > 0 {1168 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1169 }1170 else {1171 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1172 }11731174 match target_collection.mode1175 {1176 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1177 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1178 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1179 _ => ()1180 };11811182 Ok(())1183 }11841185 #[weight = 0]1186 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11871188 // let no_perm_mes = "You do not have permissions to modify this collection";1189 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1190 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1191 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11921193 // // on_nft_received call11941195 // Self::transfer(origin, collection_id, item_id, new_owner)?;11961197 Ok(())1198 }11991200 /// Set off-chain data schema.1201 /// 1202 /// # Permissions1203 /// 1204 /// * Collection Owner1205 /// * Collection Admin1206 /// 1207 /// # Arguments1208 /// 1209 /// * collection_id.1210 /// 1211 /// * schema: String representing the offchain data schema.1212 #[weight = T::WeightInfo::set_variable_meta_data()]1213 pub fn set_variable_meta_data (1214 origin,1215 collection_id: CollectionId,1216 item_id: TokenId,1217 data: Vec<u8>1218 ) -> DispatchResult {1219 let sender = ensure_signed(origin)?;1220 1221 Self::collection_exists(collection_id)?;1222 Self::token_exists(collection_id, item_id, &sender)?;12231224 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12251226 // Modify permissions check1227 let target_collection = <Collection<T>>::get(collection_id);1228 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1229 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1230 Error::<T>::NoPermission);12311232 match target_collection.mode1233 {1234 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1235 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1236 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1237 _ => fail!(Error::<T>::UnexpectedCollectionType)1238 };12391240 Ok(())1241 }1242 1243 /// Set schema standard1244 /// ImageURL1245 /// Unique1246 /// 1247 /// # Permissions1248 /// 1249 /// * Collection Owner1250 /// * Collection Admin1251 /// 1252 /// # Arguments1253 /// 1254 /// * collection_id.1255 /// 1256 /// * schema: SchemaVersion: enum1257 #[weight = T::WeightInfo::set_schema_version()]1258 pub fn set_schema_version(1259 origin,1260 collection_id: CollectionId,1261 version: SchemaVersion1262 ) -> DispatchResult {1263 let sender = ensure_signed(origin)?;1264 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1265 let mut target_collection = <Collection<T>>::get(collection_id);1266 target_collection.schema_version = version;1267 <Collection<T>>::insert(collection_id, target_collection);12681269 Ok(())1270 }12711272 /// Set off-chain data schema.1273 /// 1274 /// # Permissions1275 /// 1276 /// * Collection Owner1277 /// * Collection Admin1278 /// 1279 /// # Arguments1280 /// 1281 /// * collection_id.1282 /// 1283 /// * schema: String representing the offchain data schema.1284 #[weight = T::WeightInfo::set_offchain_schema()]1285 pub fn set_offchain_schema(1286 origin,1287 collection_id: CollectionId,1288 schema: Vec<u8>1289 ) -> DispatchResult {1290 let sender = ensure_signed(origin)?;1291 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12921293 // check schema limit1294 ensure!(schema.len() as u32 > ChainLimit::get().offchain_schema_limit, "");12951296 let mut target_collection = <Collection<T>>::get(collection_id);1297 target_collection.offchain_schema = schema;1298 <Collection<T>>::insert(collection_id, target_collection);12991300 Ok(())1301 }13021303 /// Set const on-chain data schema.1304 /// 1305 /// # Permissions1306 /// 1307 /// * Collection Owner1308 /// * Collection Admin1309 /// 1310 /// # Arguments1311 /// 1312 /// * collection_id.1313 /// 1314 /// * schema: String representing the const on-chain data schema.1315 #[weight = T::WeightInfo::set_const_on_chain_schema()]1316 pub fn set_const_on_chain_schema (1317 origin,1318 collection_id: CollectionId,1319 schema: Vec<u8>1320 ) -> DispatchResult {1321 let sender = ensure_signed(origin)?;1322 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13231324 // check schema limit1325 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");13261327 let mut target_collection = <Collection<T>>::get(collection_id);1328 target_collection.const_on_chain_schema = schema;1329 <Collection<T>>::insert(collection_id, target_collection);13301331 Ok(())1332 }13331334 /// Set variable on-chain data schema.1335 /// 1336 /// # Permissions1337 /// 1338 /// * Collection Owner1339 /// * Collection Admin1340 /// 1341 /// # Arguments1342 /// 1343 /// * collection_id.1344 /// 1345 /// * schema: String representing the variable on-chain data schema.1346 #[weight = T::WeightInfo::set_const_on_chain_schema()]1347 pub fn set_variable_on_chain_schema (1348 origin,1349 collection_id: CollectionId,1350 schema: Vec<u8>1351 ) -> DispatchResult {1352 let sender = ensure_signed(origin)?;1353 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13541355 // check schema limit1356 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");13571358 let mut target_collection = <Collection<T>>::get(collection_id);1359 target_collection.variable_on_chain_schema = schema;1360 <Collection<T>>::insert(collection_id, target_collection);13611362 Ok(())1363 }13641365 // Sudo permissions function1366 #[weight = T::WeightInfo::set_chain_limits()]1367 pub fn set_chain_limits(1368 origin,1369 limits: ChainLimits1370 ) -> DispatchResult {13711372 #[cfg(not(feature = "runtime-benchmarks"))]1373 ensure_root(origin)?;13741375 <ChainLimit>::put(limits);1376 Ok(())1377 }13781379 /// Enable smart contract self-sponsoring.1380 /// 1381 /// # Permissions1382 /// 1383 /// * Contract Owner1384 /// 1385 /// # Arguments1386 /// 1387 /// * contract address1388 /// * enable flag1389 /// 1390 #[weight = T::WeightInfo::enable_contract_sponsoring()]1391 pub fn enable_contract_sponsoring(1392 origin,1393 contract_address: T::AccountId,1394 enable: bool1395 ) -> DispatchResult {13961397 let sender = ensure_signed(origin)?;13981399 #[cfg(feature = "runtime-benchmarks")]1400 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14011402 Self::ensure_contract_owned(sender, &contract_address)?;14031404 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1405 Ok(())1406 }14071408 /// Set the rate limit for contract sponsoring to specified number of blocks.1409 /// 1410 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1411 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1412 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1413 /// from contract endowment if there are at least B blocks between such transactions. 1414 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1415 /// 1416 /// # Permissions1417 /// 1418 /// * Contract Owner1419 /// 1420 /// # Arguments1421 /// 1422 /// -`contract_address`: Address of the contract to sponsor1423 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1424 /// 1425 #[weight = T::WeightInfo::set_contract_sponsoring_rate_limit()]1426 pub fn set_contract_sponsoring_rate_limit(1427 origin,1428 contract_address: T::AccountId,1429 rate_limit: T::BlockNumber1430 ) -> DispatchResult {1431 let sender = ensure_signed(origin)?;14321433 #[cfg(feature = "runtime-benchmarks")]1434 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14351436 Self::ensure_contract_owned(sender, &contract_address)?;1437 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1438 Ok(())1439 }14401441 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1442 /// 1443 /// # Permissions1444 /// 1445 /// * Address that deployed smart contract.1446 /// 1447 /// # Arguments1448 /// 1449 /// -`contract_address`: Address of the contract.1450 /// 1451 /// - `enable`: . 1452 #[weight = T::WeightInfo::toggle_contract_white_list()]1453 pub fn toggle_contract_white_list(1454 origin,1455 contract_address: T::AccountId,1456 enable: bool1457 ) -> DispatchResult {1458 let sender = ensure_signed(origin)?;14591460 #[cfg(feature = "runtime-benchmarks")]1461 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14621463 Self::ensure_contract_owned(sender, &contract_address)?;1464 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1465 Ok(())1466 }1467 1468 /// Add an address to smart contract white list.1469 /// 1470 /// # Permissions1471 /// 1472 /// * Address that deployed smart contract.1473 /// 1474 /// # Arguments1475 /// 1476 /// -`contract_address`: Address of the contract.1477 ///1478 /// -`account_address`: Address to add.1479 #[weight = T::WeightInfo::add_to_contract_white_list()]1480 pub fn add_to_contract_white_list(1481 origin,1482 contract_address: T::AccountId,1483 account_address: T::AccountId1484 ) -> DispatchResult {1485 let sender = ensure_signed(origin)?;14861487 #[cfg(feature = "runtime-benchmarks")]1488 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1489 1490 Self::ensure_contract_owned(sender, &contract_address)?; 1491 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1492 Ok(())1493 }14941495 /// Remove an address from smart contract white list.1496 /// 1497 /// # Permissions1498 /// 1499 /// * Address that deployed smart contract.1500 /// 1501 /// # Arguments1502 /// 1503 /// -`contract_address`: Address of the contract.1504 ///1505 /// -`account_address`: Address to remove.1506 #[weight = T::WeightInfo::remove_from_contract_white_list()]1507 pub fn remove_from_contract_white_list(1508 origin,1509 contract_address: T::AccountId,1510 account_address: T::AccountId1511 ) -> DispatchResult {1512 let sender = ensure_signed(origin)?;15131514 #[cfg(feature = "runtime-benchmarks")]1515 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15161517 Self::ensure_contract_owned(sender, &contract_address)?;1518 <ContractWhiteList<T>>::remove(contract_address, account_address);1519 Ok(())1520 }15211522 #[weight = T::WeightInfo::set_collection_limits()]1523 pub fn set_collection_limits(1524 origin,1525 collection_id: u32,1526 limits: CollectionLimits,1527 ) -> DispatchResult {1528 let sender = ensure_signed(origin)?;1529 Self::check_owner_permissions(collection_id, sender.clone())?;1530 let mut target_collection = <Collection<T>>::get(collection_id);1531 let chain_limits = ChainLimit::get();1532 let climits = target_collection.limits;15331534 // collection bounds1535 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1536 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1537 Error::<T>::CollectionLimitBoundsExceeded);15381539 // token_limit check prev1540 ensure!(climits.token_limit > limits.token_limit && 1541 limits.token_limit <= chain_limits.account_token_ownership_limit, 1542 Error::<T>::AccountTokenLimitExceeded);15431544 target_collection.limits = limits;1545 <Collection<T>>::insert(collection_id, target_collection);15461547 Ok(())1548 } 1549 }1550}15511552impl<T: Trait> Module<T> {15531554 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15551556 // check token limit and account token limit1557 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1558 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1559 1560 Ok(())1561 }15621563 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15641565 // check token limit and account token limit1566 let total_items: u32 = ItemListIndex::get(collection_id);1567 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1568 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1569 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15701571 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1572 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1573 Self::check_white_list(collection_id, owner)?;1574 Self::check_white_list(collection_id, sender)?;1575 }15761577 Ok(())1578 }15791580 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1581 match target_collection.mode1582 {1583 CollectionMode::NFT => {1584 if let CreateItemData::NFT(data) = data {1585 // check sizes1586 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1587 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1588 } else {1589 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1590 }1591 },1592 CollectionMode::Fungible(_) => {1593 if let CreateItemData::Fungible(_) = data {1594 } else {1595 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1596 }1597 },1598 CollectionMode::ReFungible(_) => {1599 if let CreateItemData::ReFungible(data) = data {16001601 // check sizes1602 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1603 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1604 } else {1605 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1606 }1607 },1608 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1609 };16101611 Ok(())1612 }16131614 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1615 match data1616 {1617 CreateItemData::NFT(data) => {1618 let item = NftItemType {1619 owner,1620 const_data: data.const_data,1621 variable_data: data.variable_data1622 };16231624 Self::add_nft_item(collection_id, item)?;1625 },1626 CreateItemData::Fungible(data) => {1627 Self::add_fungible_item(collection_id, &owner, data.value)?;1628 },1629 CreateItemData::ReFungible(data) => {1630 let mut owner_list = Vec::new();1631 let value = (10 as u128).pow(collection.decimal_points as u32);1632 owner_list.push(Ownership {owner: owner.clone(), fraction: value});16331634 let item = ReFungibleItemType {1635 owner: owner_list,1636 const_data: data.const_data,1637 variable_data: data.variable_data1638 };16391640 Self::add_refungible_item(collection_id, item)?;1641 }1642 };16431644 // call event1645 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16461647 Ok(())1648 }16491650 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16511652 // Does new owner already have an account?1653 let mut balance: u128 = 0;1654 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1655 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1656 } 16571658 // Mint 1659 let item = FungibleItemType {1660 value: balance + value1661 };1662 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16631664 // Update balance1665 let new_balance = <Balance<T>>::get(collection_id, owner)1666 .checked_add(value)1667 .ok_or(Error::<T>::NumOverflow)?;1668 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16691670 Ok(())1671 }16721673 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1674 let current_index = <ItemListIndex>::get(collection_id)1675 .checked_add(1)1676 .ok_or(Error::<T>::NumOverflow)?;1677 let itemcopy = item.clone();16781679 let value = item.owner.first().unwrap().fraction;1680 let owner = item.owner.first().unwrap().owner.clone();16811682 Self::add_token_index(collection_id, current_index, &owner)?;16831684 <ItemListIndex>::insert(collection_id, current_index);1685 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16861687 // Update balance1688 let new_balance = <Balance<T>>::get(collection_id, &owner)1689 .checked_add(value)1690 .ok_or(Error::<T>::NumOverflow)?;1691 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16921693 Ok(())1694 }16951696 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1697 let current_index = <ItemListIndex>::get(collection_id)1698 .checked_add(1)1699 .ok_or(Error::<T>::NumOverflow)?;17001701 let item_owner = item.owner.clone();1702 Self::add_token_index(collection_id, current_index, &item.owner)?;17031704 <ItemListIndex>::insert(collection_id, current_index);1705 <NftItemList<T>>::insert(collection_id, current_index, item);17061707 // Update balance1708 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1709 .checked_add(1)1710 .ok_or(Error::<T>::NumOverflow)?;1711 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17121713 Ok(())1714 }17151716 fn burn_refungible_item(1717 collection_id: CollectionId,1718 item_id: TokenId,1719 owner: &T::AccountId,1720 ) -> DispatchResult {1721 ensure!(1722 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1723 Error::<T>::TokenNotFound1724 );1725 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id);1726 let rft_balance = token1727 .owner1728 .iter()1729 .filter(|&i| i.owner == *owner)1730 .next()1731 .unwrap();1732 Self::remove_token_index(collection_id, item_id, owner)?;17331734 // update balance1735 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1736 .checked_sub(rft_balance.fraction)1737 .ok_or(Error::<T>::NumOverflow)?;1738 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);17391740 // Re-create owners list with sender removed1741 let index = token1742 .owner1743 .iter()1744 .position(|i| i.owner == *owner)1745 .unwrap();1746 token.owner.remove(index);1747 let owner_count = token.owner.len();17481749 // Burn the token completely if this was the last (only) owner1750 if owner_count == 0 {1751 <ReFungibleItemList<T>>::remove(collection_id, item_id);1752 }1753 else {1754 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1755 }17561757 Ok(())1758 }17591760 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1761 ensure!(1762 <NftItemList<T>>::contains_key(collection_id, item_id),1763 Error::<T>::TokenNotFound1764 );1765 let item = <NftItemList<T>>::get(collection_id, item_id);1766 Self::remove_token_index(collection_id, item_id, &item.owner)?;17671768 // update balance1769 let new_balance = <Balance<T>>::get(collection_id, &item.owner)1770 .checked_sub(1)1771 .ok_or(Error::<T>::NumOverflow)?;1772 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1773 <NftItemList<T>>::remove(collection_id, item_id);17741775 Ok(())1776 }17771778 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1779 ensure!(1780 <FungibleItemList<T>>::contains_key(collection_id, owner),1781 Error::<T>::TokenNotFound1782 );1783 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1784 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17851786 // update balance1787 let new_balance = <Balance<T>>::get(collection_id, owner)1788 .checked_sub(value)1789 .ok_or(Error::<T>::NumOverflow)?;1790 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17911792 if balance.value - value > 0 {1793 balance.value -= value;1794 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1795 }1796 else {1797 <FungibleItemList<T>>::remove(collection_id, owner);1798 }17991800 Ok(())1801 }18021803 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1804 ensure!(1805 <Collection<T>>::contains_key(collection_id),1806 Error::<T>::CollectionNotFound1807 );1808 Ok(())1809 }18101811 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1812 Self::collection_exists(collection_id)?;18131814 let target_collection = <Collection<T>>::get(collection_id);1815 ensure!(1816 subject == target_collection.owner,1817 Error::<T>::NoPermission1818 );18191820 Ok(())1821 }18221823 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1824 let target_collection = <Collection<T>>::get(collection_id);1825 let mut result: bool = subject == target_collection.owner;1826 let exists = <AdminList<T>>::contains_key(collection_id);18271828 if !result & exists {1829 if <AdminList<T>>::get(collection_id).contains(&subject) {1830 result = true1831 }1832 }18331834 result1835 }18361837 fn check_owner_or_admin_permissions(1838 collection_id: CollectionId,1839 subject: T::AccountId,1840 ) -> DispatchResult {1841 Self::collection_exists(collection_id)?;1842 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18431844 ensure!(1845 result,1846 Error::<T>::NoPermission1847 );1848 Ok(())1849 }18501851 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1852 let target_collection = <Collection<T>>::get(collection_id);18531854 match target_collection.mode {1855 CollectionMode::NFT => {1856 <NftItemList<T>>::get(collection_id, item_id).owner == subject1857 }1858 CollectionMode::Fungible(_) => {1859 <FungibleItemList<T>>::contains_key(collection_id, &subject)1860 }1861 CollectionMode::ReFungible(_) => {1862 <ReFungibleItemList<T>>::get(collection_id, item_id)1863 .owner1864 .iter()1865 .any(|i| i.owner == subject)1866 }1867 CollectionMode::Invalid => false,1868 }1869 }18701871 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1872 let mes = Error::<T>::AddresNotInWhiteList;1873 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18741875 Ok(())1876 }18771878 /// Check if token exists. In case of Fungible, check if there is an entry for 1879 /// the owner in fungible balances double map1880 fn token_exists(1881 collection_id: CollectionId,1882 item_id: TokenId,1883 owner: &T::AccountId1884 ) -> DispatchResult {1885 let target_collection = <Collection<T>>::get(collection_id);1886 let exists = match target_collection.mode1887 {1888 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1889 CollectionMode::Fungible(_) => <FungibleItemList<T>>::contains_key(collection_id, owner),1890 CollectionMode::ReFungible(_) => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1891 _ => false1892 };18931894 ensure!(exists == true, Error::<T>::TokenNotFound);1895 Ok(())1896 }18971898 fn transfer_fungible(1899 collection_id: CollectionId,1900 value: u128,1901 owner: &T::AccountId,1902 recipient: &T::AccountId,1903 ) -> DispatchResult {1904 Self::token_exists(collection_id, 0, owner)?;19051906 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1907 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);19081909 // Send balance to recipient (updates balanceOf of recipient)1910 Self::add_fungible_item(collection_id, recipient, value)?;19111912 // update balanceOf of sender1913 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);19141915 // Reduce or remove sender1916 if balance.value == value {1917 <FungibleItemList<T>>::remove(collection_id, owner);1918 }1919 else {1920 balance.value -= value;1921 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1922 }19231924 Ok(())1925 }19261927 fn transfer_refungible(1928 collection_id: CollectionId,1929 item_id: TokenId,1930 value: u128,1931 owner: T::AccountId,1932 new_owner: T::AccountId,1933 ) -> DispatchResult {1934 Self::token_exists(collection_id, item_id, &owner)?;19351936 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1937 let item = full_item1938 .owner1939 .iter()1940 .filter(|i| i.owner == owner)1941 .next()1942 .ok_or(Error::<T>::NumOverflow)?;1943 let amount = item.fraction;19441945 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19461947 // update balance1948 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1949 .checked_sub(value)1950 .ok_or(Error::<T>::NumOverflow)?;1951 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19521953 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1954 .checked_add(value)1955 .ok_or(Error::<T>::NumOverflow)?;1956 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19571958 let old_owner = item.owner.clone();1959 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19601961 // transfer1962 if amount == value && !new_owner_has_account {1963 // change owner1964 // new owner do not have account1965 let mut new_full_item = full_item.clone();1966 new_full_item1967 .owner1968 .iter_mut()1969 .find(|i| i.owner == owner)1970 .unwrap()1971 .owner = new_owner.clone();1972 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19731974 // update index collection1975 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;1976 } else {1977 let mut new_full_item = full_item.clone();1978 new_full_item1979 .owner1980 .iter_mut()1981 .find(|i| i.owner == owner)1982 .unwrap()1983 .fraction -= value;19841985 // separate amount1986 if new_owner_has_account {1987 // new owner has account1988 new_full_item1989 .owner1990 .iter_mut()1991 .find(|i| i.owner == new_owner)1992 .unwrap()1993 .fraction += value;1994 } else {1995 // new owner do not have account1996 new_full_item.owner.push(Ownership {1997 owner: new_owner.clone(),1998 fraction: value,1999 });2000 Self::add_token_index(collection_id, item_id, &new_owner)?;2001 }20022003 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2004 }20052006 Ok(())2007 }20082009 fn transfer_nft(2010 collection_id: CollectionId,2011 item_id: TokenId,2012 sender: T::AccountId,2013 new_owner: T::AccountId,2014 ) -> DispatchResult {2015 Self::token_exists(collection_id, item_id, &sender)?;20162017 let mut item = <NftItemList<T>>::get(collection_id, item_id);20182019 ensure!(2020 sender == item.owner,2021 Error::<T>::MustBeTokenOwner2022 );20232024 // update balance2025 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2026 .checked_sub(1)2027 .ok_or(Error::<T>::NumOverflow)?;2028 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20292030 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2031 .checked_add(1)2032 .ok_or(Error::<T>::NumOverflow)?;2033 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20342035 // change owner2036 let old_owner = item.owner.clone();2037 item.owner = new_owner.clone();2038 <NftItemList<T>>::insert(collection_id, item_id, item);20392040 // update index collection2041 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20422043 Ok(())2044 }2045 2046 fn set_re_fungible_variable_data(2047 collection_id: CollectionId,2048 item_id: TokenId,2049 data: Vec<u8>2050 ) -> DispatchResult {2051 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20522053 item.variable_data = data;20542055 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20562057 Ok(())2058 }20592060 fn set_nft_variable_data(2061 collection_id: CollectionId,2062 item_id: TokenId,2063 data: Vec<u8>2064 ) -> DispatchResult {2065 let mut item = <NftItemList<T>>::get(collection_id, item_id);2066 2067 item.variable_data = data;20682069 <NftItemList<T>>::insert(collection_id, item_id, item);2070 2071 Ok(())2072 }20732074 fn init_collection(item: &CollectionType<T::AccountId>) {2075 // check params2076 assert!(2077 item.decimal_points <= MAX_DECIMAL_POINTS,2078 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2079 );2080 assert!(2081 item.name.len() <= 64,2082 "Collection name can not be longer than 63 char"2083 );2084 assert!(2085 item.name.len() <= 256,2086 "Collection description can not be longer than 255 char"2087 );2088 assert!(2089 item.token_prefix.len() <= 16,2090 "Token prefix can not be longer than 15 char"2091 );20922093 // Generate next collection ID2094 let next_id = CreatedCollectionCount::get()2095 .checked_add(1)2096 .unwrap();20972098 CreatedCollectionCount::put(next_id);2099 }21002101 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2102 let current_index = <ItemListIndex>::get(collection_id)2103 .checked_add(1)2104 .unwrap();21052106 let item_owner = item.owner.clone();2107 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();21082109 <ItemListIndex>::insert(collection_id, current_index);21102111 // Update balance2112 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2113 .checked_add(1)2114 .unwrap();2115 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2116 }21172118 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2119 let current_index = <ItemListIndex>::get(collection_id)2120 .checked_add(1)2121 .unwrap();21222123 Self::add_token_index(collection_id, current_index, owner).unwrap();21242125 <ItemListIndex>::insert(collection_id, current_index);21262127 // Update balance2128 let new_balance = <Balance<T>>::get(collection_id, owner)2129 .checked_add(item.value)2130 .unwrap();2131 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2132 }21332134 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2135 let current_index = <ItemListIndex>::get(collection_id)2136 .checked_add(1)2137 .unwrap();21382139 let value = item.owner.first().unwrap().fraction;2140 let owner = item.owner.first().unwrap().owner.clone();21412142 Self::add_token_index(collection_id, current_index, &owner).unwrap();21432144 <ItemListIndex>::insert(collection_id, current_index);21452146 // Update balance2147 let new_balance = <Balance<T>>::get(collection_id, &owner)2148 .checked_add(value)2149 .unwrap();2150 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2151 }21522153 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {21542155 // add to account limit2156 if <AccountItemCount<T>>::contains_key(owner) {21572158 // bound Owned tokens by a single address2159 let count = <AccountItemCount<T>>::get(owner);2160 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21612162 <AccountItemCount<T>>::insert(owner.clone(), count2163 .checked_add(1)2164 .ok_or(Error::<T>::NumOverflow)?);2165 }2166 else {2167 <AccountItemCount<T>>::insert(owner.clone(), 1);2168 }21692170 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2171 if list_exists {2172 let mut list = <AddressTokens<T>>::get(collection_id, owner);2173 let item_contains = list.contains(&item_index.clone());21742175 if !item_contains {2176 list.push(item_index.clone());2177 }21782179 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2180 } else {2181 let mut itm = Vec::new();2182 itm.push(item_index.clone());2183 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2184 }21852186 Ok(())2187 }21882189 fn remove_token_index(2190 collection_id: CollectionId,2191 item_index: TokenId,2192 owner: &T::AccountId,2193 ) -> DispatchResult {21942195 // update counter2196 <AccountItemCount<T>>::insert(owner.clone(), 2197 <AccountItemCount<T>>::get(owner)2198 .checked_sub(1)2199 .ok_or(Error::<T>::NumOverflow)?);220022012202 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2203 if list_exists {2204 let mut list = <AddressTokens<T>>::get(collection_id, owner);2205 let item_contains = list.contains(&item_index.clone());22062207 if item_contains {2208 list.retain(|&item| item != item_index);2209 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2210 }2211 }22122213 Ok(())2214 }22152216 fn move_token_index(2217 collection_id: CollectionId,2218 item_index: TokenId,2219 old_owner: &T::AccountId,2220 new_owner: &T::AccountId,2221 ) -> DispatchResult {2222 Self::remove_token_index(collection_id, item_index, old_owner)?;2223 Self::add_token_index(collection_id, item_index, new_owner)?;22242225 Ok(())2226 }2227 2228 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2229 if <ContractOwner<T>>::contains_key(contract.clone()) {2230 let owner = <ContractOwner<T>>::get(contract);2231 ensure!(account == owner, Error::<T>::NoPermission);2232 } else {2233 fail!(Error::<T>::NoPermission);2234 }22352236 Ok(())2237 }2238}22392240////////////////////////////////////////////////////////////////////////////////////////////////////2241// Economic models2242// #region22432244/// Fee multiplier.2245pub type Multiplier = FixedU128;22462247type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2248 <T as system::Trait>::AccountId,2249>>::Balance;2250type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2251 <T as system::Trait>::AccountId,2252>>::NegativeImbalance;22532254/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2255/// in the queue.2256#[derive(Encode, Decode, Clone, Eq, PartialEq)]2257pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2258 #[codec(compact)] BalanceOf<T>2259);22602261impl<T: Trait + Send + Sync> sp_std::fmt::Debug2262 for ChargeTransactionPayment<T>2263{2264 #[cfg(feature = "std")]2265 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2266 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2267 }2268 #[cfg(not(feature = "std"))]2269 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2270 Ok(())2271 }2272}22732274impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2275where2276 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2277 BalanceOf<T>: Send + Sync + FixedPointOperand,2278{2279 /// utility constructor. Used only in client/factory code.2280 pub fn from(fee: BalanceOf<T>) -> Self {2281 Self(fee)2282 }22832284 pub fn traditional_fee(2285 len: usize,2286 info: &DispatchInfoOf<T::Call>,2287 tip: BalanceOf<T>,2288 ) -> BalanceOf<T>2289 where2290 T::Call: Dispatchable<Info = DispatchInfo>,2291 {2292 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2293 }22942295 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2296 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2297 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2298 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2299 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2300 }23012302 fn withdraw_fee(2303 &self,2304 who: &T::AccountId,2305 call: &T::Call,2306 info: &DispatchInfoOf<T::Call>,2307 len: usize,2308 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2309 let tip = self.0;23102311 // Set fee based on call type. Creating collection costs 1 Unique.2312 // All other transactions have traditional fees so far2313 // let fee = match call.is_sub_type() {2314 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2315 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2316 // // _ => <BalanceOf<T>>::from(100)2317 // };2318 let fee = Self::traditional_fee(len, info, tip);23192320 // Only mess with balances if fee is not zero.2321 if fee.is_zero() {2322 return Ok((fee, None));2323 }23242325 // Determine who is paying transaction fee based on ecnomic model2326 // Parse call to extract collection ID and access collection sponsor2327 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2328 Some(Call::create_item(collection_id, _owner, _properties)) => {23292330 // sponsor timeout2331 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;23322333 let limit = <Collection<T>>::get(collection_id).limits.sponsor_transfer_timeout;2334 let mut sponsored = true;2335 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2336 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2337 let limit_time = last_tx_block + limit.into();2338 if block_number <= limit_time {2339 sponsored = false;2340 }2341 }2342 if sponsored {2343 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2344 }23452346 // check free create limit2347 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2348 (<Collection<T>>::get(collection_id).sponsor_confirmed) &&2349 (sponsored)2350 {2351 <Collection<T>>::get(collection_id).sponsor2352 } else {2353 T::AccountId::default()2354 }2355 }2356 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2357 2358 let mut sponsor_transfer = false;2359 if <Collection<T>>::get(collection_id).sponsor_confirmed {23602361 let collection_limits = <Collection<T>>::get(collection_id).limits;2362 let collection_mode = <Collection<T>>::get(collection_id).mode;2363 2364 // sponsor timeout2365 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2366 sponsor_transfer = match collection_mode {2367 CollectionMode::NFT => {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().nft_sponsor_transfer_timeout2374 };2375 2376 let mut sponsored = true;2377 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2378 let last_tx_block = <NftTransferBasket<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 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2386 }23872388 sponsored2389 }2390 CollectionMode::Fungible(_) => {2391 2392 // get correct limit2393 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2394 collection_limits.sponsor_transfer_timeout2395 } else {2396 ChainLimit::get().fungible_sponsor_transfer_timeout2397 };2398 2399 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2400 let mut sponsored = true;2401 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2402 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2403 let limit_time = last_tx_block + limit.into();2404 if block_number <= limit_time {2405 sponsored = false;2406 }2407 }2408 if sponsored {2409 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2410 }24112412 sponsored2413 }2414 CollectionMode::ReFungible(_) => {2415 2416 // get correct limit2417 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2418 collection_limits.sponsor_transfer_timeout2419 } else {2420 ChainLimit::get().refungible_sponsor_transfer_timeout2421 };2422 2423 let mut sponsored = true;2424 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2425 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2426 let limit_time = last_tx_block + limit.into();2427 if block_number <= limit_time {2428 sponsored = false;2429 }2430 }2431 if sponsored {2432 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2433 }24342435 sponsored2436 }2437 _ => {2438 false2439 },2440 };2441 }24422443 if !sponsor_transfer {2444 T::AccountId::default()2445 } else {2446 <Collection<T>>::get(collection_id).sponsor2447 }2448 }24492450 _ => T::AccountId::default(),2451 };24522453 // Sponsor smart contracts2454 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24552456 // On instantiation: set the contract owner2457 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24582459 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2460 code_hash,2461 &data,2462 &who,2463 );2464 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24652466 T::AccountId::default()2467 },24682469 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2470 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24712472 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24732474 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2475 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2476 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2477 2478 if !owned_contract && white_list_enabled {2479 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2480 return Err(InvalidTransaction::Call.into());2481 }2482 }24832484 let mut sponsor_transfer = false;2485 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2486 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2487 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2488 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2489 let limit_time = last_tx_block + rate_limit;24902491 if block_number >= limit_time {2492 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2493 sponsor_transfer = true;2494 }2495 } else {2496 sponsor_transfer = false;2497 }2498 2499 2500 let mut sp = T::AccountId::default();2501 if sponsor_transfer {2502 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2503 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2504 sp = called_contract;2505 }2506 }2507 }25082509 sp2510 },25112512 _ => sponsor,2513 };25142515 let mut who_pays_fee: T::AccountId = sponsor.clone();2516 if sponsor == T::AccountId::default() {2517 who_pays_fee = who.clone();2518 }25192520 match <T as transaction_payment::Trait>::Currency::withdraw(2521 &who_pays_fee,2522 fee,2523 if tip.is_zero() {2524 WithdrawReason::TransactionPayment.into()2525 } else {2526 WithdrawReason::TransactionPayment | WithdrawReason::Tip2527 },2528 ExistenceRequirement::KeepAlive,2529 ) {2530 Ok(imbalance) => Ok((fee, Some(imbalance))),2531 Err(_) => Err(InvalidTransaction::Payment.into()),2532 }2533 }2534}253525362537impl<T: Trait + Send + Sync> SignedExtension2538 for ChargeTransactionPayment<T>2539where2540 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2541 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2542{2543 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2544 type AccountId = T::AccountId;2545 type Call = T::Call;2546 type AdditionalSigned = ();2547 type Pre = (2548 BalanceOf<T>,2549 Self::AccountId,2550 Option<NegativeImbalanceOf<T>>,2551 BalanceOf<T>,2552 );2553 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2554 Ok(())2555 }25562557 fn validate(2558 &self,2559 who: &Self::AccountId,2560 call: &Self::Call,2561 info: &DispatchInfoOf<Self::Call>,2562 len: usize,2563 ) -> TransactionValidity {2564 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2565 Ok(ValidTransaction {2566 priority: Self::get_priority(len, info, fee),2567 ..Default::default()2568 })2569 }25702571 fn pre_dispatch(2572 self,2573 who: &Self::AccountId,2574 call: &Self::Call,2575 info: &DispatchInfoOf<Self::Call>,2576 len: usize,2577 ) -> Result<Self::Pre, TransactionValidityError> {2578 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2579 Ok((self.0, who.clone(), imbalance, fee))2580 }25812582 fn post_dispatch(2583 pre: Self::Pre,2584 info: &DispatchInfoOf<Self::Call>,2585 post_info: &PostDispatchInfoOf<Self::Call>,2586 len: usize,2587 _result: &DispatchResult,2588 ) -> Result<(), TransactionValidityError> {2589 let (tip, who, imbalance, fee) = pre;2590 if let Some(payed) = imbalance {2591 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2592 len as u32, info, post_info, tip,2593 );2594 let refund = fee.saturating_sub(actual_fee);2595 let actual_payment =2596 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2597 &who, refund,2598 ) {2599 Ok(refund_imbalance) => {2600 // The refund cannot be larger than the up front payed max weight.2601 // `PostDispatchInfo::calc_unspent` guards against such a case.2602 match payed.offset(refund_imbalance) {2603 Ok(actual_payment) => actual_payment,2604 Err(_) => return Err(InvalidTransaction::Payment.into()),2605 }2606 }2607 // We do not recreate the account using the refund. The up front payment2608 // is gone in that case.2609 Err(_) => payed,2610 };2611 let imbalances = actual_payment.split(tip);2612 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2613 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2614 );2615 }2616 Ok(())2617 }2618}26192620// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -12,14 +12,17 @@
}
fn default_limits() {
- assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
+ assert_ok!(TemplateModule::set_chain_limits(RawOrigin::Root.into(), ChainLimits {
collection_numbers_limit: default_collection_numbers_limit(),
account_token_ownership_limit: 10,
collections_admins_limit: 5,
custom_data_limit: 2048,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ const_on_chain_schema_limit: 1024,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
}));
}
@@ -1255,7 +1258,7 @@
});
}
-// If Public Access mode is set to WhiteList, oken transfers can’t be Approved by a non-whitelisted address (see Approve method).
+// If Public Access mode is set to WhiteList, token transfers can’t be Approved by a non-whitelisted address (see Approve method).
#[test]
fn white_list_test_6() {
new_test_ext().execute_with(|| {
@@ -1264,6 +1267,10 @@
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
let origin1 = Origin::signed(1);
+
+ let data = default_nft_data();
+ create_test_item(collection_id, &data.into());
+
assert_ok!(TemplateModule::set_public_access_mode(
origin1.clone(),
collection_id,
@@ -1638,7 +1645,10 @@
custom_data_limit: 2048,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ const_on_chain_schema_limit: 1024,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
}));
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
@@ -1667,7 +1677,10 @@
custom_data_limit: 2048,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ const_on_chain_schema_limit: 1024,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
}));
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
@@ -1690,7 +1703,10 @@
custom_data_limit: 2048,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ const_on_chain_schema_limit: 1024,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
}));
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
@@ -1713,7 +1729,10 @@
custom_data_limit: 2,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ const_on_chain_schema_limit: 1024,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
}));
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
@@ -1744,7 +1763,10 @@
custom_data_limit: 2,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ const_on_chain_schema_limit: 1024,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
}));
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
@@ -1775,7 +1797,10 @@
custom_data_limit: 2,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ const_on_chain_schema_limit: 1024,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
}));
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
@@ -1806,7 +1831,10 @@
custom_data_limit: 2,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ const_on_chain_schema_limit: 1024,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
}));
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
@@ -1923,7 +1951,10 @@
custom_data_limit: 10,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ const_on_chain_schema_limit: 1024,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
}));
let collection_id = create_test_collection(&CollectionMode::NFT, 1);
@@ -1948,7 +1979,10 @@
custom_data_limit: 10,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ const_on_chain_schema_limit: 1024,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
}));
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -29,7 +29,8 @@
"testCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/createCollection.test.ts",
"testToggleContractWhiteList": "mocha --timeout 9999999 -r ts-node/register ./**/toggleContractWhiteList.test.ts",
"testAddToContractWhiteList": "mocha --timeout 9999999 -r ts-node/register ./**/addToContractWhiteList.test.ts",
- "testTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/transfer.test.ts"
+ "testTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/transfer.test.ts",
+ "testBurnItem": "mocha --timeout 9999999 -r ts-node/register ./**/burnItem.test.ts"
},
"author": "",
"license": "Apache 2.0",
tests/src/addToWhiteList.test.tsdiffbeforeafterboth--- a/tests/src/addToWhiteList.test.ts
+++ b/tests/src/addToWhiteList.test.ts
@@ -18,7 +18,7 @@
let Alice: IKeyringPair;
let Bob: IKeyringPair;
-describe.only('Integration Test ext. addToWhiteList()', () => {
+describe('Integration Test ext. addToWhiteList()', () => {
before(async () => {
await usingApi(async (api) => {
@@ -41,7 +41,7 @@
});
});
-describe.only('Negative Integration Test ext. addToWhiteList()', () => {
+describe('Negative Integration Test ext. addToWhiteList()', () => {
it('White list an address in the collection that does not exist', async () => {
await usingApi(async (api) => {