difftreelog
Merge branch 'develop' into feature/NFTPAR-250
in: master
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 Into<u8> for CollectionMode {78 fn into(self) -> u8 {79 match self {80 CollectionMode::Invalid => 0,81 CollectionMode::NFT => 1,82 CollectionMode::Fungible(_) => 2,83 CollectionMode::ReFungible(_) => 3,84 }85 }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91 Normal,92 WhiteList,93}94impl Default for AccessMode {95 fn default() -> Self {96 Self::Normal97 }98}99100impl Default for CollectionMode {101 fn default() -> Self {102 Self::Invalid103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub owner: AccountId,149 pub const_data: Vec<u8>,150 pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType {156 pub value: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct ReFungibleItemType<AccountId> {162 pub owner: Vec<Ownership<AccountId>>,163 pub const_data: Vec<u8>,164 pub variable_data: Vec<u8>,165}166167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169// pub struct VestingItem<AccountId, Moment> {170// pub sender: AccountId,171// pub recipient: AccountId,172// pub collection_id: CollectionId,173// pub item_id: TokenId,174// pub amount: u64,175// pub vesting_date: Moment,176// }177178#[derive(Encode, Decode, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct CollectionLimits {181 pub account_token_ownership_limit: u32,182 pub sponsored_data_size: u32,183 pub token_limit: u32,184185 // Timeouts for item types in passed blocks186 pub sponsor_transfer_timeout: u32,187}188189impl Default for CollectionLimits {190 fn default() -> CollectionLimits {191 CollectionLimits { 192 account_token_ownership_limit: 10_000_000, 193 token_limit: u32::max_value(),194 sponsored_data_size: u32::max_value(), 195 sponsor_transfer_timeout: 14400 }196 }197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]201pub struct ChainLimits {202 pub collection_numbers_limit: u32,203 pub account_token_ownership_limit: u32,204 pub collections_admins_limit: u64,205 pub custom_data_limit: u32,206207 // Timeouts for item types in passed blocks208 pub nft_sponsor_transfer_timeout: u32,209 pub fungible_sponsor_transfer_timeout: u32,210 pub refungible_sponsor_transfer_timeout: u32,211}212213pub trait WeightInfo {214 fn create_collection() -> Weight;215 fn destroy_collection() -> Weight;216 fn add_to_white_list() -> Weight;217 fn remove_from_white_list() -> Weight;218 fn set_public_access_mode() -> Weight;219 fn set_mint_permission() -> Weight;220 fn change_collection_owner() -> Weight;221 fn add_collection_admin() -> Weight;222 fn remove_collection_admin() -> Weight;223 fn set_collection_sponsor() -> Weight;224 fn confirm_sponsorship() -> Weight;225 fn remove_collection_sponsor() -> Weight;226 fn create_item(s: usize) -> Weight;227 fn burn_item() -> Weight;228 fn transfer() -> Weight;229 fn approve() -> Weight;230 fn transfer_from() -> Weight;231 fn set_offchain_schema() -> Weight;232 fn set_const_on_chain_schema() -> Weight;233 fn set_variable_on_chain_schema() -> Weight;234 fn set_variable_meta_data() -> Weight;235 fn enable_contract_sponsoring() -> Weight;236}237238#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]239#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]240pub struct CreateNftData {241 pub const_data: Vec<u8>,242 pub variable_data: Vec<u8>,243}244245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]247pub struct CreateFungibleData {248 pub value: u128,249}250251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]252#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]253pub struct CreateReFungibleData {254 pub const_data: Vec<u8>,255 pub variable_data: Vec<u8>,256}257258#[derive(Encode, Decode, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub enum CreateItemData {261 NFT(CreateNftData),262 Fungible(CreateFungibleData),263 ReFungible(CreateReFungibleData),264}265266impl CreateItemData {267 pub fn len(&self) -> usize {268 let len = match self {269 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),270 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),271 _ => 0272 };273 274 return len;275 }276}277278impl From<CreateNftData> for CreateItemData {279 fn from(item: CreateNftData) -> Self {280 CreateItemData::NFT(item)281 }282}283284impl From<CreateReFungibleData> for CreateItemData {285 fn from(item: CreateReFungibleData) -> Self {286 CreateItemData::ReFungible(item)287 }288}289290impl From<CreateFungibleData> for CreateItemData {291 fn from(item: CreateFungibleData) -> Self {292 CreateItemData::Fungible(item)293 }294}295296297decl_error! {298 /// Error for non-fungible-token module.299 pub enum Error for Module<T: Trait> {300 /// Total collections bound exceeded.301 TotalCollectionsLimitExceeded,302 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.303 CollectionDecimalPointLimitExceeded, 304 /// Collection name can not be longer than 63 char.305 CollectionNameLimitExceeded, 306 /// Collection description can not be longer than 255 char.307 CollectionDescriptionLimitExceeded, 308 /// Token prefix can not be longer than 15 char.309 CollectionTokenPrefixLimitExceeded,310 /// This collection does not exist.311 CollectionNotFound,312 /// Item not exists.313 TokenNotFound,314 /// Arithmetic calculation overflow.315 NumOverflow, 316 /// Account already has admin role.317 AlreadyAdmin, 318 /// You do not own this collection.319 NoPermission,320 /// This address is not set as sponsor, use setCollectionSponsor first.321 ConfirmUnsetSponsorFail,322 /// Collection is not in mint mode.323 PublicMintingNotAllowed,324 /// Sender parameter and item owner must be equal.325 MustBeTokenOwner,326 /// Item balance not enough.327 TokenValueTooLow,328 /// Size of item is too large.329 NftSizeLimitExceeded,330 /// No approve found331 ApproveNotFound,332 /// Requested value more than approved.333 TokenValueNotEnough,334 /// Only approved addresses can call this method.335 ApproveRequired,336 /// Address is not in white list.337 AddresNotInWhiteList,338 /// Number of collection admins bound exceeded.339 CollectionAdminsLimitExceeded,340 /// Owned tokens by a single address bound exceeded.341 AddressOwnershipLimitExceeded,342 /// Length of items properties must be greater than 0.343 EmptyArgument,344 /// const_data exceeded data limit.345 TokenConstDataLimitExceeded,346 /// variable_data exceeded data limit.347 TokenVariableDataLimitExceeded,348 /// Not NFT item data used to mint in NFT collection.349 NotNftDataUsedToMintNftCollectionToken,350 /// Not Fungible item data used to mint in Fungible collection.351 NotFungibleDataUsedToMintFungibleCollectionToken,352 /// Not Re Fungible item data used to mint in Re Fungible collection.353 NotReFungibleDataUsedToMintReFungibleCollectionToken,354 /// Unexpected collection type.355 UnexpectedCollectionType,356 /// Can't store metadata in fungible tokens.357 CantStoreMetadataInFungibleTokens,358 /// Collection token limit exceeded359 CollectionTokenLimitExceeded,360 /// Account token limit exceeded per collection361 AccountTokenLimitExceeded,362 /// Collection limit bounds per collection exceeded363 CollectionLimitBoundsExceeded364 }365}366367pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {368 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;369370 /// Weight information for extrinsics in this pallet.371 type WeightInfo: WeightInfo;372}373374#[cfg(feature = "runtime-benchmarks")]375mod benchmarking;376377// #endregion378379decl_storage! {380 trait Store for Module<T: Trait> as Nft {381382 // Private members383 NextCollectionID: CollectionId;384 CreatedCollectionCount: u32;385 ChainVersion: u64;386 ItemListIndex: map hasher(identity) CollectionId => TokenId;387388 // Chain limits struct389 pub ChainLimit get(fn chain_limit) config(): ChainLimits;390391 // Bound counters392 CollectionCount: u32;393 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;394395 // Basic collections396 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;397 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;398 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;399400 /// Balance owner per collection map401 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;402403 /// second parameter: item id + owner account id + spender account id404 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;405406 /// Item collections407 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;408 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;409 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;410411 /// Index list412 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;413414 /// Tokens transfer baskets415 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;416 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;417 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;418419 // Contract Sponsorship and Ownership420 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;421 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;422 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;423 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;424 }425 add_extra_genesis {426 build(|config: &GenesisConfig<T>| {427 // Modification of storage428 for (_num, _c) in &config.collection {429 <Module<T>>::init_collection(_c);430 }431432 for (_num, _c, _i) in &config.nft_item_id {433 <Module<T>>::init_nft_token(*_c, _i);434 }435436 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {437 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);438 }439440 for (_num, _c, _i) in &config.refungible_item_id {441 <Module<T>>::init_refungible_token(*_c, _i);442 }443 })444 }445}446447decl_event!(448 pub enum Event<T>449 where450 AccountId = <T as system::Trait>::AccountId,451 {452 /// New collection was created453 /// 454 /// # Arguments455 /// 456 /// * collection_id: Globally unique identifier of newly created collection.457 /// 458 /// * mode: [CollectionMode] converted into u8.459 /// 460 /// * account_id: Collection owner.461 Created(CollectionId, u8, AccountId),462463 /// New item was created.464 /// 465 /// # Arguments466 /// 467 /// * collection_id: Id of the collection where item was created.468 /// 469 /// * item_id: Id of an item. Unique within the collection.470 ItemCreated(CollectionId, TokenId),471472 /// Collection item was burned.473 /// 474 /// # Arguments475 /// 476 /// collection_id.477 /// 478 /// item_id: Identifier of burned NFT.479 ItemDestroyed(CollectionId, TokenId),480 }481);482483decl_module! {484 pub struct Module<T: Trait> for enum Call where origin: T::Origin {485486 fn deposit_event() = default;487 type Error = Error<T>;488489 fn on_initialize(now: T::BlockNumber) -> Weight {490491 if ChainVersion::get() < 2492 {493 let value = NextCollectionID::get();494 CreatedCollectionCount::put(value);495 ChainVersion::put(2);496 }497498 0499 }500501 /// 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.502 /// 503 /// # Permissions504 /// 505 /// * Anyone.506 /// 507 /// # Arguments508 /// 509 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.510 /// 511 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.512 /// 513 /// * token_prefix: UTF-8 string with token prefix.514 /// 515 /// * mode: [CollectionMode] collection type and type dependent data.516 // returns collection ID517 #[weight = T::WeightInfo::create_collection()]518 pub fn create_collection(origin,519 collection_name: Vec<u16>,520 collection_description: Vec<u16>,521 token_prefix: Vec<u8>,522 mode: CollectionMode) -> DispatchResult {523524 // Anyone can create a collection525 let who = ensure_signed(origin)?;526527 let decimal_points = match mode {528 CollectionMode::Fungible(points) => points,529 CollectionMode::ReFungible(points) => points,530 _ => 0531 };532533 // bound Total number of collections534 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);535536 // check params537 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);538539 let mut name = collection_name.to_vec();540 name.push(0);541 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);542543 let mut description = collection_description.to_vec();544 description.push(0);545 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);546547 let mut prefix = token_prefix.to_vec();548 prefix.push(0);549 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);550551 // Generate next collection ID552 let next_id = CreatedCollectionCount::get()553 .checked_add(1)554 .ok_or(Error::<T>::NumOverflow)?;555556 // bound counter557 let total = CollectionCount::get()558 .checked_add(1)559 .ok_or(Error::<T>::NumOverflow)?;560561 CreatedCollectionCount::put(next_id);562 CollectionCount::put(total);563564 // Create new collection565 let new_collection = CollectionType {566 owner: who.clone(),567 name: name,568 mode: mode.clone(),569 mint_mode: false,570 access: AccessMode::Normal,571 description: description,572 decimal_points: decimal_points,573 token_prefix: prefix,574 offchain_schema: Vec::new(),575 schema_version: SchemaVersion::ImageURL,576 sponsor: T::AccountId::default(),577 sponsor_confirmed: false,578 variable_on_chain_schema: Vec::new(),579 const_on_chain_schema: Vec::new(),580 limits: CollectionLimits::default(),581 };582583 // Add new collection to map584 <Collection<T>>::insert(next_id, new_collection);585586 // call event587 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));588589 Ok(())590 }591592 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.593 /// 594 /// # Permissions595 /// 596 /// * Collection Owner.597 /// 598 /// # Arguments599 /// 600 /// * collection_id: collection to destroy.601 #[weight = T::WeightInfo::destroy_collection()]602 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {603604 let sender = ensure_signed(origin)?;605 Self::check_owner_permissions(collection_id, sender)?;606607 <AddressTokens<T>>::remove_prefix(collection_id);608 <Allowances<T>>::remove_prefix(collection_id);609 <Balance<T>>::remove_prefix(collection_id);610 <ItemListIndex>::remove(collection_id);611 <AdminList<T>>::remove(collection_id);612 <Collection<T>>::remove(collection_id);613 <WhiteList<T>>::remove(collection_id);614615 <NftItemList<T>>::remove_prefix(collection_id);616 <FungibleItemList<T>>::remove_prefix(collection_id);617 <ReFungibleItemList<T>>::remove_prefix(collection_id);618619 <NftTransferBasket<T>>::remove_prefix(collection_id);620 <FungibleTransferBasket<T>>::remove_prefix(collection_id);621 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);622623 if CollectionCount::get() > 0624 {625 // bound couter626 let total = CollectionCount::get()627 .checked_sub(1)628 .ok_or(Error::<T>::NumOverflow)?;629630 CollectionCount::put(total);631 }632633 Ok(())634 }635636 /// Add an address to white list.637 /// 638 /// # Permissions639 /// 640 /// * Collection Owner641 /// * Collection Admin642 /// 643 /// # Arguments644 /// 645 /// * collection_id.646 /// 647 /// * address.648 #[weight = T::WeightInfo::add_to_white_list()]649 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{650651 let sender = ensure_signed(origin)?;652 Self::check_owner_or_admin_permissions(collection_id, sender)?;653654 let mut white_list_collection: Vec<T::AccountId>;655 if <WhiteList<T>>::contains_key(collection_id) {656 white_list_collection = <WhiteList<T>>::get(collection_id);657 if !white_list_collection.contains(&address.clone())658 {659 white_list_collection.push(address.clone());660 }661 }662 else {663 white_list_collection = Vec::new();664 white_list_collection.push(address.clone());665 }666667 <WhiteList<T>>::insert(collection_id, white_list_collection);668 Ok(())669 }670671 /// Remove an address from white list.672 /// 673 /// # Permissions674 /// 675 /// * Collection Owner676 /// * Collection Admin677 /// 678 /// # Arguments679 /// 680 /// * collection_id.681 /// 682 /// * address.683 #[weight = T::WeightInfo::remove_from_white_list()]684 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{685686 let sender = ensure_signed(origin)?;687 Self::check_owner_or_admin_permissions(collection_id, sender)?;688689 if <WhiteList<T>>::contains_key(collection_id) {690 let mut white_list_collection = <WhiteList<T>>::get(collection_id);691 if white_list_collection.contains(&address.clone())692 {693 white_list_collection.retain(|i| *i != address.clone());694 <WhiteList<T>>::insert(collection_id, white_list_collection);695 }696 }697698 Ok(())699 }700701 /// Toggle between normal and white list access for the methods with access for `Anyone`.702 /// 703 /// # Permissions704 /// 705 /// * Collection Owner.706 /// 707 /// # Arguments708 /// 709 /// * collection_id.710 /// 711 /// * mode: [AccessMode]712 #[weight = T::WeightInfo::set_public_access_mode()]713 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult714 {715 let sender = ensure_signed(origin)?;716717 Self::check_owner_permissions(collection_id, sender)?;718 let mut target_collection = <Collection<T>>::get(collection_id);719 target_collection.access = mode;720 <Collection<T>>::insert(collection_id, target_collection);721722 Ok(())723 }724725 /// Allows Anyone to create tokens if:726 /// * White List is enabled, and727 /// * Address is added to white list, and728 /// * This method was called with True parameter729 /// 730 /// # Permissions731 /// * Collection Owner732 ///733 /// # Arguments734 /// 735 /// * collection_id.736 /// 737 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.738 #[weight = T::WeightInfo::set_mint_permission()]739 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult740 {741 let sender = ensure_signed(origin)?;742743 Self::check_owner_permissions(collection_id, sender)?;744 let mut target_collection = <Collection<T>>::get(collection_id);745 target_collection.mint_mode = mint_permission;746 <Collection<T>>::insert(collection_id, target_collection);747748 Ok(())749 }750751 /// Change the owner of the collection.752 /// 753 /// # Permissions754 /// 755 /// * Collection Owner.756 /// 757 /// # Arguments758 /// 759 /// * collection_id.760 /// 761 /// * new_owner.762 #[weight = T::WeightInfo::change_collection_owner()]763 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {764765 let sender = ensure_signed(origin)?;766 Self::check_owner_permissions(collection_id, sender)?;767 let mut target_collection = <Collection<T>>::get(collection_id);768 target_collection.owner = new_owner;769 <Collection<T>>::insert(collection_id, target_collection);770771 Ok(())772 }773774 /// Adds an admin of the Collection.775 /// 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. 776 /// 777 /// # Permissions778 /// 779 /// * Collection Owner.780 /// * Collection Admin.781 /// 782 /// # Arguments783 /// 784 /// * collection_id: ID of the Collection to add admin for.785 /// 786 /// * new_admin_id: Address of new admin to add.787 #[weight = T::WeightInfo::add_collection_admin()]788 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {789790 let sender = ensure_signed(origin)?;791 Self::check_owner_or_admin_permissions(collection_id, sender)?;792 let mut admin_arr: Vec<T::AccountId> = Vec::new();793794 if <AdminList<T>>::contains_key(collection_id)795 {796 admin_arr = <AdminList<T>>::get(collection_id);797 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);798 }799800 // Number of collection admins801 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);802803 admin_arr.push(new_admin_id);804 <AdminList<T>>::insert(collection_id, admin_arr);805806 Ok(())807 }808809 /// 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.810 ///811 /// # Permissions812 /// 813 /// * Collection Owner.814 /// * Collection Admin.815 /// 816 /// # Arguments817 /// 818 /// * collection_id: ID of the Collection to remove admin for.819 /// 820 /// * account_id: Address of admin to remove.821 #[weight = T::WeightInfo::remove_collection_admin()]822 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {823824 let sender = ensure_signed(origin)?;825 Self::check_owner_or_admin_permissions(collection_id, sender)?;826827 if <AdminList<T>>::contains_key(collection_id)828 {829 let mut admin_arr = <AdminList<T>>::get(collection_id);830 admin_arr.retain(|i| *i != account_id);831 <AdminList<T>>::insert(collection_id, admin_arr);832 }833834 Ok(())835 }836837 /// # Permissions838 /// 839 /// * Collection Owner840 /// 841 /// # Arguments842 /// 843 /// * collection_id.844 /// 845 /// * new_sponsor.846 #[weight = T::WeightInfo::set_collection_sponsor()]847 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {848849 let sender = ensure_signed(origin)?;850 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);851852 let mut target_collection = <Collection<T>>::get(collection_id);853 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);854855 target_collection.sponsor = new_sponsor;856 target_collection.sponsor_confirmed = false;857 <Collection<T>>::insert(collection_id, target_collection);858859 Ok(())860 }861862 /// # Permissions863 /// 864 /// * Sponsor.865 /// 866 /// # Arguments867 /// 868 /// * collection_id.869 #[weight = T::WeightInfo::confirm_sponsorship()]870 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {871872 let sender = ensure_signed(origin)?;873 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);874875 let mut target_collection = <Collection<T>>::get(collection_id);876 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);877878 target_collection.sponsor_confirmed = true;879 <Collection<T>>::insert(collection_id, target_collection);880881 Ok(())882 }883884 /// Switch back to pay-per-own-transaction model.885 ///886 /// # Permissions887 ///888 /// * Collection owner.889 /// 890 /// # Arguments891 /// 892 /// * collection_id.893 #[weight = T::WeightInfo::remove_collection_sponsor()]894 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {895896 let sender = ensure_signed(origin)?;897 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);898899 let mut target_collection = <Collection<T>>::get(collection_id);900 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);901902 target_collection.sponsor = T::AccountId::default();903 target_collection.sponsor_confirmed = false;904 <Collection<T>>::insert(collection_id, target_collection);905906 Ok(())907 }908909 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.910 /// 911 /// # Permissions912 /// 913 /// * Collection Owner.914 /// * Collection Admin.915 /// * Anyone if916 /// * White List is enabled, and917 /// * Address is added to white list, and918 /// * MintPermission is enabled (see SetMintPermission method)919 /// 920 /// # Arguments921 /// 922 /// * collection_id: ID of the collection.923 /// 924 /// * owner: Address, initial owner of the NFT.925 ///926 /// * data: Token data to store on chain.927 // #[weight =928 // (130_000_000 as Weight)929 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))930 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))931 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]932933 #[weight = T::WeightInfo::create_item(data.len())]934 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {935936 let sender = ensure_signed(origin)?;937938 Self::collection_exists(collection_id)?;939940 let target_collection = <Collection<T>>::get(collection_id);941942 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;943 Self::validate_create_item_args(&target_collection, &data)?;944 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;945946 Ok(())947 }948949 /// This method creates multiple instances of NFT Collection created with CreateCollection method.950 /// 951 /// # Permissions952 /// 953 /// * Collection Owner.954 /// * Collection Admin.955 /// * Anyone if956 /// * White List is enabled, and957 /// * Address is added to white list, and958 /// * MintPermission is enabled (see SetMintPermission method)959 /// 960 /// # Arguments961 /// 962 /// * collection_id: ID of the collection.963 /// 964 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].965 /// 966 /// * owner: Address, initial owner of the NFT.967 #[weight = T::WeightInfo::create_item(items_data.into_iter()968 .map(|data| { data.len() })969 .sum())]970 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {971972 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);973 let sender = ensure_signed(origin)?;974975 Self::collection_exists(collection_id)?;976 let target_collection = <Collection<T>>::get(collection_id);977978 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;979980 for data in &items_data {981 Self::validate_create_item_args(&target_collection, data)?;982 }983 for data in &items_data {984 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;985 }986987 Ok(())988 }989990 /// Destroys a concrete instance of NFT.991 /// 992 /// # Permissions993 /// 994 /// * Collection Owner.995 /// * Collection Admin.996 /// * Current NFT Owner.997 /// 998 /// # Arguments999 /// 1000 /// * collection_id: ID of the collection.1001 /// 1002 /// * item_id: ID of NFT to burn.1003 #[weight = T::WeightInfo::burn_item()]1004 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10051006 let sender = ensure_signed(origin)?;1007 Self::collection_exists(collection_id)?;10081009 // Transfer permissions check1010 let target_collection = <Collection<T>>::get(collection_id);1011 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1012 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1013 Error::<T>::NoPermission);10141015 if target_collection.access == AccessMode::WhiteList {1016 Self::check_white_list(collection_id, &sender)?;1017 }10181019 match target_collection.mode1020 {1021 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1022 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1023 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1024 _ => ()1025 };10261027 // call event1028 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10291030 Ok(())1031 }10321033 /// Change ownership of the token.1034 /// 1035 /// # Permissions1036 /// 1037 /// * Collection Owner1038 /// * Collection Admin1039 /// * Current NFT owner1040 ///1041 /// # Arguments1042 /// 1043 /// * recipient: Address of token recipient.1044 /// 1045 /// * collection_id.1046 /// 1047 /// * item_id: ID of the item1048 /// * Non-Fungible Mode: Required.1049 /// * Fungible Mode: Ignored.1050 /// * Re-Fungible Mode: Required.1051 /// 1052 /// * value: Amount to transfer.1053 /// * Non-Fungible Mode: Ignored1054 /// * Fungible Mode: Must specify transferred amount1055 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1056 #[weight = T::WeightInfo::transfer()]1057 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10581059 let sender = ensure_signed(origin)?;1060 let target_collection = <Collection<T>>::get(collection_id);10611062 // Limits check1063 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10641065 // Transfer permissions check1066 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1067 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1068 Error::<T>::NoPermission);10691070 if target_collection.access == AccessMode::WhiteList {1071 Self::check_white_list(collection_id, &sender)?;1072 Self::check_white_list(collection_id, &recipient)?;1073 }10741075 match target_collection.mode1076 {1077 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1078 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1079 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1080 _ => ()1081 };10821083 Ok(())1084 }10851086 /// Set, change, or remove approved address to transfer the ownership of the NFT.1087 /// 1088 /// # Permissions1089 /// 1090 /// * Collection Owner1091 /// * Collection Admin1092 /// * Current NFT owner1093 /// 1094 /// # Arguments1095 /// 1096 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1097 /// 1098 /// * collection_id.1099 /// 1100 /// * item_id: ID of the item.1101 #[weight = T::WeightInfo::approve()]1102 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {11031104 let sender = ensure_signed(origin)?;11051106 // Transfer permissions check1107 let target_collection = <Collection<T>>::get(collection_id);1108 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1109 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1110 Error::<T>::NoPermission);11111112 if target_collection.access == AccessMode::WhiteList {1113 Self::check_white_list(collection_id, &sender)?;1114 Self::check_white_list(collection_id, &spender)?;1115 }11161117 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1118 let mut allowance: u128 = amount;1119 if allowance_exists {1120 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1121 }1122 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11231124 Ok(())1125 }1126 1127 /// 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.1128 /// 1129 /// # Permissions1130 /// * Collection Owner1131 /// * Collection Admin1132 /// * Current NFT owner1133 /// * Address approved by current NFT owner1134 /// 1135 /// # Arguments1136 /// 1137 /// * from: Address that owns token.1138 /// 1139 /// * recipient: Address of token recipient.1140 /// 1141 /// * collection_id.1142 /// 1143 /// * item_id: ID of the item.1144 /// 1145 /// * value: Amount to transfer.1146 #[weight = T::WeightInfo::transfer_from()]1147 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11481149 let sender = ensure_signed(origin)?;1150 let mut appoved_transfer = false;11511152 // Check approval1153 let mut approval: u128 = 0;1154 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1155 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1156 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1157 appoved_transfer = true;1158 }11591160 let target_collection = <Collection<T>>::get(collection_id);11611162 // Limits check1163 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11641165 // Transfer permissions check 1166 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1167 Error::<T>::NoPermission);11681169 if target_collection.access == AccessMode::WhiteList {1170 Self::check_white_list(collection_id, &sender)?;1171 Self::check_white_list(collection_id, &recipient)?;1172 }11731174 // Reduce approval by transferred amount or remove if remaining approval drops to 01175 if approval - value > 0 {1176 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1177 }1178 else {1179 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1180 }11811182 match target_collection.mode1183 {1184 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1185 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1186 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1187 _ => ()1188 };11891190 Ok(())1191 }11921193 #[weight = 0]1194 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11951196 // let no_perm_mes = "You do not have permissions to modify this collection";1197 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1198 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1199 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12001201 // // on_nft_received call12021203 // Self::transfer(origin, collection_id, item_id, new_owner)?;12041205 Ok(())1206 }12071208 /// Set off-chain data schema.1209 /// 1210 /// # Permissions1211 /// 1212 /// * Collection Owner1213 /// * Collection Admin1214 /// 1215 /// # Arguments1216 /// 1217 /// * collection_id.1218 /// 1219 /// * schema: String representing the offchain data schema.1220 #[weight = T::WeightInfo::set_variable_meta_data()]1221 pub fn set_variable_meta_data (1222 origin,1223 collection_id: CollectionId,1224 item_id: TokenId,1225 data: Vec<u8>1226 ) -> DispatchResult {1227 let sender = ensure_signed(origin)?;1228 1229 Self::collection_exists(collection_id)?;1230 1231 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12321233 // Modify permissions check1234 let target_collection = <Collection<T>>::get(collection_id);1235 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1236 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1237 Error::<T>::NoPermission);12381239 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12401241 match target_collection.mode1242 {1243 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1244 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1245 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1246 _ => fail!(Error::<T>::UnexpectedCollectionType)1247 };12481249 Ok(())1250 }1251 1252 /// Set schema standard1253 /// ImageURL1254 /// Unique1255 /// 1256 /// # Permissions1257 /// 1258 /// * Collection Owner1259 /// * Collection Admin1260 /// 1261 /// # Arguments1262 /// 1263 /// * collection_id.1264 /// 1265 /// * schema: SchemaVersion: enum1266 #[weight = 0]1267 pub fn set_schema_version(1268 origin,1269 collection_id: CollectionId,1270 version: SchemaVersion1271 ) -> DispatchResult {1272 let sender = ensure_signed(origin)?;1273 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1274 let mut target_collection = <Collection<T>>::get(collection_id);1275 target_collection.schema_version = version;1276 <Collection<T>>::insert(collection_id, target_collection);12771278 Ok(())1279 }12801281 /// Set off-chain data schema.1282 /// 1283 /// # Permissions1284 /// 1285 /// * Collection Owner1286 /// * Collection Admin1287 /// 1288 /// # Arguments1289 /// 1290 /// * collection_id.1291 /// 1292 /// * schema: String representing the offchain data schema.1293 #[weight = T::WeightInfo::set_offchain_schema()]1294 pub fn set_offchain_schema(1295 origin,1296 collection_id: CollectionId,1297 schema: Vec<u8>1298 ) -> DispatchResult {1299 let sender = ensure_signed(origin)?;1300 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13011302 let mut target_collection = <Collection<T>>::get(collection_id);1303 target_collection.offchain_schema = schema;1304 <Collection<T>>::insert(collection_id, target_collection);13051306 Ok(())1307 }13081309 /// Set const on-chain data schema.1310 /// 1311 /// # Permissions1312 /// 1313 /// * Collection Owner1314 /// * Collection Admin1315 /// 1316 /// # Arguments1317 /// 1318 /// * collection_id.1319 /// 1320 /// * schema: String representing the const on-chain data schema.1321 #[weight = T::WeightInfo::set_const_on_chain_schema()]1322 pub fn set_const_on_chain_schema (1323 origin,1324 collection_id: CollectionId,1325 schema: Vec<u8>1326 ) -> DispatchResult {1327 let sender = ensure_signed(origin)?;1328 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13291330 let mut target_collection = <Collection<T>>::get(collection_id);1331 target_collection.const_on_chain_schema = schema;1332 <Collection<T>>::insert(collection_id, target_collection);13331334 Ok(())1335 }13361337 /// Set variable on-chain data schema.1338 /// 1339 /// # Permissions1340 /// 1341 /// * Collection Owner1342 /// * Collection Admin1343 /// 1344 /// # Arguments1345 /// 1346 /// * collection_id.1347 /// 1348 /// * schema: String representing the variable on-chain data schema.1349 #[weight = T::WeightInfo::set_const_on_chain_schema()]1350 pub fn set_variable_on_chain_schema (1351 origin,1352 collection_id: CollectionId,1353 schema: Vec<u8>1354 ) -> DispatchResult {1355 let sender = ensure_signed(origin)?;1356 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;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 = 0]1367 pub fn set_chain_limits(1368 origin,1369 limits: ChainLimits1370 ) -> DispatchResult {1371 ensure_root(origin)?;1372 <ChainLimit>::put(limits);1373 Ok(())1374 }13751376 /// Enable smart contract self-sponsoring.1377 /// 1378 /// # Permissions1379 /// 1380 /// * Contract Owner1381 /// 1382 /// # Arguments1383 /// 1384 /// * contract address1385 /// * enable flag1386 /// 1387 #[weight = T::WeightInfo::enable_contract_sponsoring()]1388 pub fn enable_contract_sponsoring(1389 origin,1390 contract_address: T::AccountId,1391 enable: bool1392 ) -> DispatchResult {13931394 let sender = ensure_signed(origin)?;13951396 #[cfg(feature = "runtime-benchmarks")]1397 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13981399 let mut is_owner = false;1400 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1401 let owner = <ContractOwner<T>>::get(&contract_address);1402 is_owner = sender == owner;1403 }1404 ensure!(is_owner, Error::<T>::NoPermission);14051406 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1407 Ok(())1408 }14091410 /// Set the rate limit for contract sponsoring to specified number of blocks.1411 /// 1412 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1413 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1414 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1415 /// from contract endowment if there are at least B blocks between such transactions. 1416 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1417 /// 1418 /// # Permissions1419 /// 1420 /// * Contract Owner1421 /// 1422 /// # Arguments1423 /// 1424 /// -`contract_address`: Address of the contract to sponsor1425 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1426 /// 1427 #[weight = 0]1428 pub fn set_contract_sponsoring_rate_limit(1429 origin,1430 contract_address: T::AccountId,1431 rate_limit: T::BlockNumber1432 ) -> DispatchResult {1433 let sender = ensure_signed(origin)?;1434 let mut is_owner = false;1435 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1436 let owner = <ContractOwner<T>>::get(&contract_address);1437 is_owner = sender == owner;1438 }1439 ensure!(is_owner, Error::<T>::NoPermission);14401441 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1442 Ok(())1443 }14441445 #[weight = 0]1446 pub fn set_collection_limits(1447 origin,1448 collection_id: u32,1449 limits: CollectionLimits,1450 ) -> DispatchResult {1451 let sender = ensure_signed(origin)?;1452 Self::check_owner_permissions(collection_id, sender.clone())?;1453 let mut target_collection = <Collection<T>>::get(collection_id);1454 let chain_limits = ChainLimit::get();1455 let climits = target_collection.limits;14561457 // collection bounds1458 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1459 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1460 Error::<T>::CollectionLimitBoundsExceeded);14611462 // token_limit check prev1463 ensure!(climits.token_limit > limits.token_limit && 1464 limits.token_limit <= chain_limits.account_token_ownership_limit, 1465 Error::<T>::AccountTokenLimitExceeded);14661467 target_collection.limits = limits;1468 <Collection<T>>::insert(collection_id, target_collection);14691470 Ok(())1471 } 1472 }1473}14741475impl<T: Trait> Module<T> {14761477 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {14781479 // check token limit and account token limit1480 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1481 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1482 1483 Ok(())1484 }14851486 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {14871488 // check token limit and account token limit1489 let total_items: u32 = ItemListIndex::get(collection_id);1490 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1491 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1492 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);14931494 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1495 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1496 Self::check_white_list(collection_id, owner)?;1497 Self::check_white_list(collection_id, sender)?;1498 }14991500 Ok(())1501 }15021503 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1504 match target_collection.mode1505 {1506 CollectionMode::NFT => {1507 if let CreateItemData::NFT(data) = data {1508 // check sizes1509 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1510 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1511 } else {1512 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1513 }1514 },1515 CollectionMode::Fungible(_) => {1516 if let CreateItemData::Fungible(_) = data {1517 } else {1518 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1519 }1520 },1521 CollectionMode::ReFungible(_) => {1522 if let CreateItemData::ReFungible(data) = data {15231524 // check sizes1525 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1526 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1527 } else {1528 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1529 }1530 },1531 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1532 };15331534 Ok(())1535 }15361537 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1538 match data1539 {1540 CreateItemData::NFT(data) => {1541 let item = NftItemType {1542 owner,1543 const_data: data.const_data,1544 variable_data: data.variable_data1545 };15461547 Self::add_nft_item(collection_id, item)?;1548 },1549 CreateItemData::Fungible(data) => {1550 Self::add_fungible_item(collection_id, &owner, data.value)?;1551 },1552 CreateItemData::ReFungible(data) => {1553 let mut owner_list = Vec::new();1554 let value = (10 as u128).pow(collection.decimal_points as u32);1555 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15561557 let item = ReFungibleItemType {1558 owner: owner_list,1559 const_data: data.const_data,1560 variable_data: data.variable_data1561 };15621563 Self::add_refungible_item(collection_id, item)?;1564 }1565 };15661567 // call event1568 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));15691570 Ok(())1571 }15721573 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {15741575 // Does new owner already have an account?1576 let mut balance: u128 = 0;1577 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1578 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1579 } 15801581 // Mint 1582 let item = FungibleItemType {1583 value: balance + value1584 };1585 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);15861587 // Update balance1588 let new_balance = <Balance<T>>::get(collection_id, owner)1589 .checked_add(value)1590 .ok_or(Error::<T>::NumOverflow)?;1591 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);15921593 Ok(())1594 }15951596 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1597 let current_index = <ItemListIndex>::get(collection_id)1598 .checked_add(1)1599 .ok_or(Error::<T>::NumOverflow)?;1600 let itemcopy = item.clone();16011602 let value = item.owner.first().unwrap().fraction;1603 let owner = item.owner.first().unwrap().owner.clone();16041605 Self::add_token_index(collection_id, current_index, owner.clone())?;16061607 <ItemListIndex>::insert(collection_id, current_index);1608 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16091610 // Update balance1611 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1612 .checked_add(value)1613 .ok_or(Error::<T>::NumOverflow)?;1614 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16151616 Ok(())1617 }16181619 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1620 let current_index = <ItemListIndex>::get(collection_id)1621 .checked_add(1)1622 .ok_or(Error::<T>::NumOverflow)?;16231624 let item_owner = item.owner.clone();1625 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16261627 <ItemListIndex>::insert(collection_id, current_index);1628 <NftItemList<T>>::insert(collection_id, current_index, item);16291630 // Update balance1631 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1632 .checked_add(1)1633 .ok_or(Error::<T>::NumOverflow)?;1634 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16351636 Ok(())1637 }16381639 fn burn_refungible_item(1640 collection_id: CollectionId,1641 item_id: TokenId,1642 owner: T::AccountId,1643 ) -> DispatchResult {1644 ensure!(1645 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1646 Error::<T>::TokenNotFound1647 );1648 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1649 let item = collection1650 .owner1651 .iter()1652 .filter(|&i| i.owner == owner)1653 .next()1654 .unwrap();1655 Self::remove_token_index(collection_id, item_id, owner.clone())?;16561657 // update balance1658 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1659 .checked_sub(item.fraction)1660 .ok_or(Error::<T>::NumOverflow)?;1661 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16621663 <ReFungibleItemList<T>>::remove(collection_id, item_id);16641665 Ok(())1666 }16671668 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1669 ensure!(1670 <NftItemList<T>>::contains_key(collection_id, item_id),1671 Error::<T>::TokenNotFound1672 );1673 let item = <NftItemList<T>>::get(collection_id, item_id);1674 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16751676 // update balance1677 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1678 .checked_sub(1)1679 .ok_or(Error::<T>::NumOverflow)?;1680 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1681 <NftItemList<T>>::remove(collection_id, item_id);16821683 Ok(())1684 }16851686 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1687 ensure!(1688 <FungibleItemList<T>>::contains_key(collection_id, owner),1689 Error::<T>::TokenNotFound1690 );1691 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1692 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);16931694 // update balance1695 let new_balance = <Balance<T>>::get(collection_id, owner)1696 .checked_sub(value)1697 .ok_or(Error::<T>::NumOverflow)?;1698 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16991700 if balance.value - value > 0 {1701 balance.value -= value;1702 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1703 }1704 else {1705 <FungibleItemList<T>>::remove(collection_id, owner);1706 }17071708 Ok(())1709 }17101711 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1712 ensure!(1713 <Collection<T>>::contains_key(collection_id),1714 Error::<T>::CollectionNotFound1715 );1716 Ok(())1717 }17181719 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1720 Self::collection_exists(collection_id)?;17211722 let target_collection = <Collection<T>>::get(collection_id);1723 ensure!(1724 subject == target_collection.owner,1725 Error::<T>::NoPermission1726 );17271728 Ok(())1729 }17301731 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1732 let target_collection = <Collection<T>>::get(collection_id);1733 let mut result: bool = subject == target_collection.owner;1734 let exists = <AdminList<T>>::contains_key(collection_id);17351736 if !result & exists {1737 if <AdminList<T>>::get(collection_id).contains(&subject) {1738 result = true1739 }1740 }17411742 result1743 }17441745 fn check_owner_or_admin_permissions(1746 collection_id: CollectionId,1747 subject: T::AccountId,1748 ) -> DispatchResult {1749 Self::collection_exists(collection_id)?;1750 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17511752 ensure!(1753 result,1754 Error::<T>::NoPermission1755 );1756 Ok(())1757 }17581759 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1760 let target_collection = <Collection<T>>::get(collection_id);17611762 match target_collection.mode {1763 CollectionMode::NFT => {1764 <NftItemList<T>>::get(collection_id, item_id).owner == subject1765 }1766 CollectionMode::Fungible(_) => {1767 <FungibleItemList<T>>::contains_key(collection_id, &subject)1768 }1769 CollectionMode::ReFungible(_) => {1770 <ReFungibleItemList<T>>::get(collection_id, item_id)1771 .owner1772 .iter()1773 .any(|i| i.owner == subject)1774 }1775 CollectionMode::Invalid => false,1776 }1777 }17781779 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1780 let mes = Error::<T>::AddresNotInWhiteList;1781 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1782 let wl = <WhiteList<T>>::get(collection_id);1783 ensure!(wl.contains(address), mes);17841785 Ok(())1786 }17871788 fn transfer_fungible(1789 collection_id: CollectionId,1790 value: u128,1791 owner: &T::AccountId,1792 recipient: &T::AccountId,1793 ) -> DispatchResult {1794 ensure!(1795 <FungibleItemList<T>>::contains_key(collection_id, owner),1796 Error::<T>::TokenNotFound1797 );17981799 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1800 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18011802 // Send balance to recipient (updates balanceOf of recipient)1803 Self::add_fungible_item(collection_id, recipient, value)?;18041805 // update balanceOf of sender1806 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18071808 // Reduce or remove sender1809 if balance.value == value {1810 <FungibleItemList<T>>::remove(collection_id, owner);1811 }1812 else {1813 balance.value -= value;1814 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1815 }18161817 Ok(())1818 }18191820 fn transfer_refungible(1821 collection_id: CollectionId,1822 item_id: TokenId,1823 value: u128,1824 owner: T::AccountId,1825 new_owner: T::AccountId,1826 ) -> DispatchResult {1827 ensure!(1828 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1829 Error::<T>::TokenNotFound1830 );18311832 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1833 let item = full_item1834 .owner1835 .iter()1836 .filter(|i| i.owner == owner)1837 .next()1838 .ok_or(Error::<T>::NumOverflow)?;1839 let amount = item.fraction;18401841 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18421843 // update balance1844 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1845 .checked_sub(value)1846 .ok_or(Error::<T>::NumOverflow)?;1847 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18481849 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1850 .checked_add(value)1851 .ok_or(Error::<T>::NumOverflow)?;1852 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);18531854 let old_owner = item.owner.clone();1855 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);18561857 // transfer1858 if amount == value && !new_owner_has_account {1859 // change owner1860 // new owner do not have account1861 let mut new_full_item = full_item.clone();1862 new_full_item1863 .owner1864 .iter_mut()1865 .find(|i| i.owner == owner)1866 .unwrap()1867 .owner = new_owner.clone();1868 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18691870 // update index collection1871 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1872 } else {1873 let mut new_full_item = full_item.clone();1874 new_full_item1875 .owner1876 .iter_mut()1877 .find(|i| i.owner == owner)1878 .unwrap()1879 .fraction -= value;18801881 // separate amount1882 if new_owner_has_account {1883 // new owner has account1884 new_full_item1885 .owner1886 .iter_mut()1887 .find(|i| i.owner == new_owner)1888 .unwrap()1889 .fraction += value;1890 } else {1891 // new owner do not have account1892 new_full_item.owner.push(Ownership {1893 owner: new_owner.clone(),1894 fraction: value,1895 });1896 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1897 }18981899 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1900 }19011902 Ok(())1903 }19041905 fn transfer_nft(1906 collection_id: CollectionId,1907 item_id: TokenId,1908 sender: T::AccountId,1909 new_owner: T::AccountId,1910 ) -> DispatchResult {1911 ensure!(1912 <NftItemList<T>>::contains_key(collection_id, item_id),1913 Error::<T>::TokenNotFound1914 );19151916 let mut item = <NftItemList<T>>::get(collection_id, item_id);19171918 ensure!(1919 sender == item.owner,1920 Error::<T>::MustBeTokenOwner1921 );19221923 // update balance1924 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1925 .checked_sub(1)1926 .ok_or(Error::<T>::NumOverflow)?;1927 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19281929 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1930 .checked_add(1)1931 .ok_or(Error::<T>::NumOverflow)?;1932 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19331934 // change owner1935 let old_owner = item.owner.clone();1936 item.owner = new_owner.clone();1937 <NftItemList<T>>::insert(collection_id, item_id, item);19381939 // update index collection1940 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19411942 Ok(())1943 }1944 1945 fn item_exists(1946 collection_id: CollectionId,1947 item_id: TokenId,1948 mode: &CollectionMode1949 ) -> DispatchResult {1950 match mode {1951 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1952 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1953 _ => ()1954 };1955 1956 Ok(())1957 }19581959 fn set_re_fungible_variable_data(1960 collection_id: CollectionId,1961 item_id: TokenId,1962 data: Vec<u8>1963 ) -> DispatchResult {1964 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19651966 item.variable_data = data;19671968 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);19691970 Ok(())1971 }19721973 fn set_nft_variable_data(1974 collection_id: CollectionId,1975 item_id: TokenId,1976 data: Vec<u8>1977 ) -> DispatchResult {1978 let mut item = <NftItemList<T>>::get(collection_id, item_id);1979 1980 item.variable_data = data;19811982 <NftItemList<T>>::insert(collection_id, item_id, item);1983 1984 Ok(())1985 }19861987 fn init_collection(item: &CollectionType<T::AccountId>) {1988 // check params1989 assert!(1990 item.decimal_points <= MAX_DECIMAL_POINTS,1991 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"1992 );1993 assert!(1994 item.name.len() <= 64,1995 "Collection name can not be longer than 63 char"1996 );1997 assert!(1998 item.name.len() <= 256,1999 "Collection description can not be longer than 255 char"2000 );2001 assert!(2002 item.token_prefix.len() <= 16,2003 "Token prefix can not be longer than 15 char"2004 );20052006 // Generate next collection ID2007 let next_id = CreatedCollectionCount::get()2008 .checked_add(1)2009 .unwrap();20102011 CreatedCollectionCount::put(next_id);2012 }20132014 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2015 let current_index = <ItemListIndex>::get(collection_id)2016 .checked_add(1)2017 .unwrap();20182019 let item_owner = item.owner.clone();2020 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20212022 <ItemListIndex>::insert(collection_id, current_index);20232024 // Update balance2025 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2026 .checked_add(1)2027 .unwrap();2028 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2029 }20302031 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2032 let current_index = <ItemListIndex>::get(collection_id)2033 .checked_add(1)2034 .unwrap();20352036 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();20372038 <ItemListIndex>::insert(collection_id, current_index);20392040 // Update balance2041 let new_balance = <Balance<T>>::get(collection_id, owner)2042 .checked_add(item.value)2043 .unwrap();2044 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2045 }20462047 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2048 let current_index = <ItemListIndex>::get(collection_id)2049 .checked_add(1)2050 .unwrap();20512052 let value = item.owner.first().unwrap().fraction;2053 let owner = item.owner.first().unwrap().owner.clone();20542055 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();20562057 <ItemListIndex>::insert(collection_id, current_index);20582059 // Update balance2060 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2061 .checked_add(value)2062 .unwrap();2063 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2064 }20652066 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {20672068 // add to account limit2069 if <AccountItemCount<T>>::contains_key(owner.clone()) {20702071 // bound Owned tokens by a single address2072 let count = <AccountItemCount<T>>::get(owner.clone());2073 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20742075 <AccountItemCount<T>>::insert(owner.clone(), count2076 .checked_add(1)2077 .ok_or(Error::<T>::NumOverflow)?);2078 }2079 else {2080 <AccountItemCount<T>>::insert(owner.clone(), 1);2081 }20822083 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2084 if list_exists {2085 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2086 let item_contains = list.contains(&item_index.clone());20872088 if !item_contains {2089 list.push(item_index.clone());2090 }20912092 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2093 } else {2094 let mut itm = Vec::new();2095 itm.push(item_index.clone());2096 <AddressTokens<T>>::insert(collection_id, owner, itm);2097 2098 }20992100 Ok(())2101 }21022103 fn remove_token_index(2104 collection_id: CollectionId,2105 item_index: TokenId,2106 owner: T::AccountId,2107 ) -> DispatchResult {21082109 // update counter2110 <AccountItemCount<T>>::insert(owner.clone(), 2111 <AccountItemCount<T>>::get(owner.clone())2112 .checked_sub(1)2113 .ok_or(Error::<T>::NumOverflow)?);211421152116 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2117 if list_exists {2118 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2119 let item_contains = list.contains(&item_index.clone());21202121 if item_contains {2122 list.retain(|&item| item != item_index);2123 <AddressTokens<T>>::insert(collection_id, owner, list);2124 }2125 }21262127 Ok(())2128 }21292130 fn move_token_index(2131 collection_id: CollectionId,2132 item_index: TokenId,2133 old_owner: T::AccountId,2134 new_owner: T::AccountId,2135 ) -> DispatchResult {2136 Self::remove_token_index(collection_id, item_index, old_owner)?;2137 Self::add_token_index(collection_id, item_index, new_owner)?;21382139 Ok(())2140 }2141}21422143////////////////////////////////////////////////////////////////////////////////////////////////////2144// Economic models2145// #region21462147/// Fee multiplier.2148pub type Multiplier = FixedU128;21492150type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2151 <T as system::Trait>::AccountId,2152>>::Balance;2153type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2154 <T as system::Trait>::AccountId,2155>>::NegativeImbalance;21562157/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2158/// in the queue.2159#[derive(Encode, Decode, Clone, Eq, PartialEq)]2160pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2161 #[codec(compact)] BalanceOf<T>2162);21632164impl<T: Trait + Send + Sync> sp_std::fmt::Debug2165 for ChargeTransactionPayment<T>2166{2167 #[cfg(feature = "std")]2168 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2169 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2170 }2171 #[cfg(not(feature = "std"))]2172 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2173 Ok(())2174 }2175}21762177impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2178where2179 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2180 BalanceOf<T>: Send + Sync + FixedPointOperand,2181{2182 /// utility constructor. Used only in client/factory code.2183 pub fn from(fee: BalanceOf<T>) -> Self {2184 Self(fee)2185 }21862187 pub fn traditional_fee(2188 len: usize,2189 info: &DispatchInfoOf<T::Call>,2190 tip: BalanceOf<T>,2191 ) -> BalanceOf<T>2192 where2193 T::Call: Dispatchable<Info = DispatchInfo>,2194 {2195 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2196 }21972198 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2199 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2200 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2201 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2202 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2203 }22042205 fn withdraw_fee(2206 &self,2207 who: &T::AccountId,2208 call: &T::Call,2209 info: &DispatchInfoOf<T::Call>,2210 len: usize,2211 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2212 let tip = self.0;22132214 // Set fee based on call type. Creating collection costs 1 Unique.2215 // All other transactions have traditional fees so far2216 // let fee = match call.is_sub_type() {2217 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2218 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2219 // // _ => <BalanceOf<T>>::from(100)2220 // };2221 let fee = Self::traditional_fee(len, info, tip);22222223 // Only mess with balances if fee is not zero.2224 if fee.is_zero() {2225 return Ok((fee, None));2226 }22272228 // Determine who is paying transaction fee based on ecnomic model2229 // Parse call to extract collection ID and access collection sponsor2230 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2231 Some(Call::create_item(collection_id, _owner, _properties)) => {22322233 // check free create limit2234 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2235 (<Collection<T>>::get(collection_id).sponsor_confirmed)2236 {2237 <Collection<T>>::get(collection_id).sponsor2238 } else {2239 T::AccountId::default()2240 }2241 }2242 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2243 2244 let mut sponsor_transfer = false;2245 if <Collection<T>>::get(collection_id).sponsor_confirmed {22462247 let collection_limits = <Collection<T>>::get(collection_id).limits;2248 let collection_mode = <Collection<T>>::get(collection_id).mode;2249 2250 // sponsor timeout2251 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2252 sponsor_transfer = match collection_mode {2253 CollectionMode::NFT => {2254 2255 // get correct limit2256 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2257 collection_limits.sponsor_transfer_timeout2258 } else {2259 ChainLimit::get().nft_sponsor_transfer_timeout2260 };2261 2262 let mut sponsored = true;2263 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2264 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2265 let limit_time = last_tx_block + limit.into();2266 if block_number <= limit_time {2267 sponsored = false;2268 }2269 }2270 if sponsored {2271 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2272 }22732274 sponsored2275 }2276 CollectionMode::Fungible(_) => {2277 2278 // get correct limit2279 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2280 collection_limits.sponsor_transfer_timeout2281 } else {2282 ChainLimit::get().fungible_sponsor_transfer_timeout2283 };2284 2285 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2286 let mut sponsored = true;2287 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2288 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2289 let limit_time = last_tx_block + limit.into();2290 if block_number <= limit_time {2291 sponsored = false;2292 }2293 }2294 if sponsored {2295 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2296 }22972298 sponsored2299 }2300 CollectionMode::ReFungible(_) => {2301 2302 // get correct limit2303 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2304 collection_limits.sponsor_transfer_timeout2305 } else {2306 ChainLimit::get().refungible_sponsor_transfer_timeout2307 };2308 2309 let mut sponsored = true;2310 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2311 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2312 let limit_time = last_tx_block + limit.into();2313 if block_number <= limit_time {2314 sponsored = false;2315 }2316 }2317 if sponsored {2318 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2319 }23202321 sponsored2322 }2323 _ => {2324 false2325 },2326 };2327 }23282329 if !sponsor_transfer {2330 T::AccountId::default()2331 } else {2332 <Collection<T>>::get(collection_id).sponsor2333 }2334 }23352336 _ => T::AccountId::default(),2337 };23382339 // Sponsor smart contracts2340 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {23412342 // On instantiation: set the contract owner2343 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {23442345 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2346 code_hash,2347 &data,2348 &who,2349 );2350 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());23512352 T::AccountId::default()2353 },23542355 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2356 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {23572358 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());23592360 let mut sponsor_transfer = false;2361 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2362 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2363 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2364 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2365 let limit_time = last_tx_block + rate_limit;23662367 if block_number >= limit_time {2368 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2369 sponsor_transfer = true;2370 }2371 } else {2372 sponsor_transfer = false;2373 }2374 2375 2376 let mut sp = T::AccountId::default();2377 if sponsor_transfer {2378 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2379 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2380 sp = called_contract;2381 }2382 }2383 }23842385 sp2386 },23872388 _ => sponsor,2389 };23902391 let mut who_pays_fee: T::AccountId = sponsor.clone();2392 if sponsor == T::AccountId::default() {2393 who_pays_fee = who.clone();2394 }23952396 match <T as transaction_payment::Trait>::Currency::withdraw(2397 &who_pays_fee,2398 fee,2399 if tip.is_zero() {2400 WithdrawReason::TransactionPayment.into()2401 } else {2402 WithdrawReason::TransactionPayment | WithdrawReason::Tip2403 },2404 ExistenceRequirement::KeepAlive,2405 ) {2406 Ok(imbalance) => Ok((fee, Some(imbalance))),2407 Err(_) => Err(InvalidTransaction::Payment.into()),2408 }2409 }2410}241124122413impl<T: Trait + Send + Sync> SignedExtension2414 for ChargeTransactionPayment<T>2415where2416 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2417 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2418{2419 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2420 type AccountId = T::AccountId;2421 type Call = T::Call;2422 type AdditionalSigned = ();2423 type Pre = (2424 BalanceOf<T>,2425 Self::AccountId,2426 Option<NegativeImbalanceOf<T>>,2427 BalanceOf<T>,2428 );2429 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2430 Ok(())2431 }24322433 fn validate(2434 &self,2435 who: &Self::AccountId,2436 call: &Self::Call,2437 info: &DispatchInfoOf<Self::Call>,2438 len: usize,2439 ) -> TransactionValidity {2440 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2441 Ok(ValidTransaction {2442 priority: Self::get_priority(len, info, fee),2443 ..Default::default()2444 })2445 }24462447 fn pre_dispatch(2448 self,2449 who: &Self::AccountId,2450 call: &Self::Call,2451 info: &DispatchInfoOf<Self::Call>,2452 len: usize,2453 ) -> Result<Self::Pre, TransactionValidityError> {2454 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2455 Ok((self.0, who.clone(), imbalance, fee))2456 }24572458 fn post_dispatch(2459 pre: Self::Pre,2460 info: &DispatchInfoOf<Self::Call>,2461 post_info: &PostDispatchInfoOf<Self::Call>,2462 len: usize,2463 _result: &DispatchResult,2464 ) -> Result<(), TransactionValidityError> {2465 let (tip, who, imbalance, fee) = pre;2466 if let Some(payed) = imbalance {2467 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2468 len as u32, info, post_info, tip,2469 );2470 let refund = fee.saturating_sub(actual_fee);2471 let actual_payment =2472 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2473 &who, refund,2474 ) {2475 Ok(refund_imbalance) => {2476 // The refund cannot be larger than the up front payed max weight.2477 // `PostDispatchInfo::calc_unspent` guards against such a case.2478 match payed.offset(refund_imbalance) {2479 Ok(actual_payment) => actual_payment,2480 Err(_) => return Err(InvalidTransaction::Payment.into()),2481 }2482 }2483 // We do not recreate the account using the refund. The up front payment2484 // is gone in that case.2485 Err(_) => payed,2486 };2487 let imbalances = actual_payment.split(tip);2488 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2489 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2490 );2491 }2492 Ok(())2493 }2494}24952496// #endregion1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, WithdrawReason,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial,29 },30 IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69 Invalid,70 NFT,71 // decimal points72 Fungible(DecimalPoints),73 // decimal points74 ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78 fn into(self) -> u8 {79 match self {80 CollectionMode::Invalid => 0,81 CollectionMode::NFT => 1,82 CollectionMode::Fungible(_) => 2,83 CollectionMode::ReFungible(_) => 3,84 }85 }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91 Normal,92 WhiteList,93}94impl Default for AccessMode {95 fn default() -> Self {96 Self::Normal97 }98}99100impl Default for CollectionMode {101 fn default() -> Self {102 Self::Invalid103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub owner: AccountId,149 pub const_data: Vec<u8>,150 pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType {156 pub value: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct ReFungibleItemType<AccountId> {162 pub owner: Vec<Ownership<AccountId>>,163 pub const_data: Vec<u8>,164 pub variable_data: Vec<u8>,165}166167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169// pub struct VestingItem<AccountId, Moment> {170// pub sender: AccountId,171// pub recipient: AccountId,172// pub collection_id: CollectionId,173// pub item_id: TokenId,174// pub amount: u64,175// pub vesting_date: Moment,176// }177178#[derive(Encode, Decode, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct CollectionLimits {181 pub account_token_ownership_limit: u32,182 pub sponsored_data_size: u32,183 pub token_limit: u32,184185 // Timeouts for item types in passed blocks186 pub sponsor_transfer_timeout: u32,187}188189impl Default for CollectionLimits {190 fn default() -> CollectionLimits {191 CollectionLimits { 192 account_token_ownership_limit: 10_000_000, 193 token_limit: u32::max_value(),194 sponsored_data_size: u32::max_value(), 195 sponsor_transfer_timeout: 14400 }196 }197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]201pub struct ChainLimits {202 pub collection_numbers_limit: u32,203 pub account_token_ownership_limit: u32,204 pub collections_admins_limit: u64,205 pub custom_data_limit: u32,206207 // Timeouts for item types in passed blocks208 pub nft_sponsor_transfer_timeout: u32,209 pub fungible_sponsor_transfer_timeout: u32,210 pub refungible_sponsor_transfer_timeout: u32,211}212213pub trait WeightInfo {214 fn create_collection() -> Weight;215 fn destroy_collection() -> Weight;216 fn add_to_white_list() -> Weight;217 fn remove_from_white_list() -> Weight;218 fn set_public_access_mode() -> Weight;219 fn set_mint_permission() -> Weight;220 fn change_collection_owner() -> Weight;221 fn add_collection_admin() -> Weight;222 fn remove_collection_admin() -> Weight;223 fn set_collection_sponsor() -> Weight;224 fn confirm_sponsorship() -> Weight;225 fn remove_collection_sponsor() -> Weight;226 fn create_item(s: usize) -> Weight;227 fn burn_item() -> Weight;228 fn transfer() -> Weight;229 fn approve() -> Weight;230 fn transfer_from() -> Weight;231 fn set_offchain_schema() -> Weight;232 fn set_const_on_chain_schema() -> Weight;233 fn set_variable_on_chain_schema() -> Weight;234 fn set_variable_meta_data() -> Weight;235 fn enable_contract_sponsoring() -> Weight;236}237238#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]239#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]240pub struct CreateNftData {241 pub const_data: Vec<u8>,242 pub variable_data: Vec<u8>,243}244245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]247pub struct CreateFungibleData {248 pub value: u128,249}250251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]252#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]253pub struct CreateReFungibleData {254 pub const_data: Vec<u8>,255 pub variable_data: Vec<u8>,256}257258#[derive(Encode, Decode, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub enum CreateItemData {261 NFT(CreateNftData),262 Fungible(CreateFungibleData),263 ReFungible(CreateReFungibleData),264}265266impl CreateItemData {267 pub fn len(&self) -> usize {268 let len = match self {269 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),270 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),271 _ => 0272 };273 274 return len;275 }276}277278impl From<CreateNftData> for CreateItemData {279 fn from(item: CreateNftData) -> Self {280 CreateItemData::NFT(item)281 }282}283284impl From<CreateReFungibleData> for CreateItemData {285 fn from(item: CreateReFungibleData) -> Self {286 CreateItemData::ReFungible(item)287 }288}289290impl From<CreateFungibleData> for CreateItemData {291 fn from(item: CreateFungibleData) -> Self {292 CreateItemData::Fungible(item)293 }294}295296297decl_error! {298 /// Error for non-fungible-token module.299 pub enum Error for Module<T: Trait> {300 /// Total collections bound exceeded.301 TotalCollectionsLimitExceeded,302 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.303 CollectionDecimalPointLimitExceeded, 304 /// Collection name can not be longer than 63 char.305 CollectionNameLimitExceeded, 306 /// Collection description can not be longer than 255 char.307 CollectionDescriptionLimitExceeded, 308 /// Token prefix can not be longer than 15 char.309 CollectionTokenPrefixLimitExceeded,310 /// This collection does not exist.311 CollectionNotFound,312 /// Item not exists.313 TokenNotFound,314 /// Arithmetic calculation overflow.315 NumOverflow, 316 /// Account already has admin role.317 AlreadyAdmin, 318 /// You do not own this collection.319 NoPermission,320 /// This address is not set as sponsor, use setCollectionSponsor first.321 ConfirmUnsetSponsorFail,322 /// Collection is not in mint mode.323 PublicMintingNotAllowed,324 /// Sender parameter and item owner must be equal.325 MustBeTokenOwner,326 /// Item balance not enough.327 TokenValueTooLow,328 /// Size of item is too large.329 NftSizeLimitExceeded,330 /// No approve found331 ApproveNotFound,332 /// Requested value more than approved.333 TokenValueNotEnough,334 /// Only approved addresses can call this method.335 ApproveRequired,336 /// Address is not in white list.337 AddresNotInWhiteList,338 /// Number of collection admins bound exceeded.339 CollectionAdminsLimitExceeded,340 /// Owned tokens by a single address bound exceeded.341 AddressOwnershipLimitExceeded,342 /// Length of items properties must be greater than 0.343 EmptyArgument,344 /// const_data exceeded data limit.345 TokenConstDataLimitExceeded,346 /// variable_data exceeded data limit.347 TokenVariableDataLimitExceeded,348 /// Not NFT item data used to mint in NFT collection.349 NotNftDataUsedToMintNftCollectionToken,350 /// Not Fungible item data used to mint in Fungible collection.351 NotFungibleDataUsedToMintFungibleCollectionToken,352 /// Not Re Fungible item data used to mint in Re Fungible collection.353 NotReFungibleDataUsedToMintReFungibleCollectionToken,354 /// Unexpected collection type.355 UnexpectedCollectionType,356 /// Can't store metadata in fungible tokens.357 CantStoreMetadataInFungibleTokens,358 /// Collection token limit exceeded359 CollectionTokenLimitExceeded,360 /// Account token limit exceeded per collection361 AccountTokenLimitExceeded,362 /// Collection limit bounds per collection exceeded363 CollectionLimitBoundsExceeded364 }365}366367pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {368 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;369370 /// Weight information for extrinsics in this pallet.371 type WeightInfo: WeightInfo;372}373374#[cfg(feature = "runtime-benchmarks")]375mod benchmarking;376377// #endregion378379decl_storage! {380 trait Store for Module<T: Trait> as Nft {381382 // Private members383 NextCollectionID: CollectionId;384 CreatedCollectionCount: u32;385 ChainVersion: u64;386 ItemListIndex: map hasher(identity) CollectionId => TokenId;387388 // Chain limits struct389 pub ChainLimit get(fn chain_limit) config(): ChainLimits;390391 // Bound counters392 CollectionCount: u32;393 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;394395 // Basic collections396 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;397 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;398 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;399400 /// Balance owner per collection map401 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;402403 /// second parameter: item id + owner account id + spender account id404 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;405406 /// Item collections407 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;408 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;409 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;410411 /// Index list412 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;413414 /// Tokens transfer baskets415 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;416 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;417 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;418419 // Contract Sponsorship and Ownership420 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;421 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;422 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;423 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;424 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 425 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 426 }427 add_extra_genesis {428 build(|config: &GenesisConfig<T>| {429 // Modification of storage430 for (_num, _c) in &config.collection {431 <Module<T>>::init_collection(_c);432 }433434 for (_num, _c, _i) in &config.nft_item_id {435 <Module<T>>::init_nft_token(*_c, _i);436 }437438 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {439 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);440 }441442 for (_num, _c, _i) in &config.refungible_item_id {443 <Module<T>>::init_refungible_token(*_c, _i);444 }445 })446 }447}448449decl_event!(450 pub enum Event<T>451 where452 AccountId = <T as system::Trait>::AccountId,453 {454 /// New collection was created455 /// 456 /// # Arguments457 /// 458 /// * collection_id: Globally unique identifier of newly created collection.459 /// 460 /// * mode: [CollectionMode] converted into u8.461 /// 462 /// * account_id: Collection owner.463 Created(CollectionId, u8, AccountId),464465 /// New item was created.466 /// 467 /// # Arguments468 /// 469 /// * collection_id: Id of the collection where item was created.470 /// 471 /// * item_id: Id of an item. Unique within the collection.472 ItemCreated(CollectionId, TokenId),473474 /// Collection item was burned.475 /// 476 /// # Arguments477 /// 478 /// collection_id.479 /// 480 /// item_id: Identifier of burned NFT.481 ItemDestroyed(CollectionId, TokenId),482 }483);484485decl_module! {486 pub struct Module<T: Trait> for enum Call where origin: T::Origin {487488 fn deposit_event() = default;489 type Error = Error<T>;490491 fn on_initialize(now: T::BlockNumber) -> Weight {492493 if ChainVersion::get() < 2494 {495 let value = NextCollectionID::get();496 CreatedCollectionCount::put(value);497 ChainVersion::put(2);498 }499500 0501 }502503 /// 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.504 /// 505 /// # Permissions506 /// 507 /// * Anyone.508 /// 509 /// # Arguments510 /// 511 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.512 /// 513 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.514 /// 515 /// * token_prefix: UTF-8 string with token prefix.516 /// 517 /// * mode: [CollectionMode] collection type and type dependent data.518 // returns collection ID519 #[weight = T::WeightInfo::create_collection()]520 pub fn create_collection(origin,521 collection_name: Vec<u16>,522 collection_description: Vec<u16>,523 token_prefix: Vec<u8>,524 mode: CollectionMode) -> DispatchResult {525526 // Anyone can create a collection527 let who = ensure_signed(origin)?;528529 let decimal_points = match mode {530 CollectionMode::Fungible(points) => points,531 CollectionMode::ReFungible(points) => points,532 _ => 0533 };534535 // bound Total number of collections536 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);537538 // check params539 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);540541 let mut name = collection_name.to_vec();542 name.push(0);543 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);544545 let mut description = collection_description.to_vec();546 description.push(0);547 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);548549 let mut prefix = token_prefix.to_vec();550 prefix.push(0);551 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);552553 // Generate next collection ID554 let next_id = CreatedCollectionCount::get()555 .checked_add(1)556 .ok_or(Error::<T>::NumOverflow)?;557558 // bound counter559 let total = CollectionCount::get()560 .checked_add(1)561 .ok_or(Error::<T>::NumOverflow)?;562563 CreatedCollectionCount::put(next_id);564 CollectionCount::put(total);565566 // Create new collection567 let new_collection = CollectionType {568 owner: who.clone(),569 name: name,570 mode: mode.clone(),571 mint_mode: false,572 access: AccessMode::Normal,573 description: description,574 decimal_points: decimal_points,575 token_prefix: prefix,576 offchain_schema: Vec::new(),577 schema_version: SchemaVersion::ImageURL,578 sponsor: T::AccountId::default(),579 sponsor_confirmed: false,580 variable_on_chain_schema: Vec::new(),581 const_on_chain_schema: Vec::new(),582 limits: CollectionLimits::default(),583 };584585 // Add new collection to map586 <Collection<T>>::insert(next_id, new_collection);587588 // call event589 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));590591 Ok(())592 }593594 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.595 /// 596 /// # Permissions597 /// 598 /// * Collection Owner.599 /// 600 /// # Arguments601 /// 602 /// * collection_id: collection to destroy.603 #[weight = T::WeightInfo::destroy_collection()]604 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {605606 let sender = ensure_signed(origin)?;607 Self::check_owner_permissions(collection_id, sender)?;608609 <AddressTokens<T>>::remove_prefix(collection_id);610 <Allowances<T>>::remove_prefix(collection_id);611 <Balance<T>>::remove_prefix(collection_id);612 <ItemListIndex>::remove(collection_id);613 <AdminList<T>>::remove(collection_id);614 <Collection<T>>::remove(collection_id);615 <WhiteList<T>>::remove_prefix(collection_id);616617 <NftItemList<T>>::remove_prefix(collection_id);618 <FungibleItemList<T>>::remove_prefix(collection_id);619 <ReFungibleItemList<T>>::remove_prefix(collection_id);620621 <NftTransferBasket<T>>::remove_prefix(collection_id);622 <FungibleTransferBasket<T>>::remove_prefix(collection_id);623 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);624625 if CollectionCount::get() > 0626 {627 // bound couter628 let total = CollectionCount::get()629 .checked_sub(1)630 .ok_or(Error::<T>::NumOverflow)?;631632 CollectionCount::put(total);633 }634635 Ok(())636 }637638 /// Add an address to white list.639 /// 640 /// # Permissions641 /// 642 /// * Collection Owner643 /// * Collection Admin644 /// 645 /// # Arguments646 /// 647 /// * collection_id.648 /// 649 /// * address.650 #[weight = T::WeightInfo::add_to_white_list()]651 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{652653 let sender = ensure_signed(origin)?;654 Self::check_owner_or_admin_permissions(collection_id, sender)?;655656 <WhiteList<T>>::insert(collection_id, address, true);657 658 Ok(())659 }660661 /// Remove an address from white list.662 /// 663 /// # Permissions664 /// 665 /// * Collection Owner666 /// * Collection Admin667 /// 668 /// # Arguments669 /// 670 /// * collection_id.671 /// 672 /// * address.673 #[weight = T::WeightInfo::remove_from_white_list()]674 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{675676 let sender = ensure_signed(origin)?;677 Self::check_owner_or_admin_permissions(collection_id, sender)?;678679 <WhiteList<T>>::remove(collection_id, address);680681 Ok(())682 }683684 /// Toggle between normal and white list access for the methods with access for `Anyone`.685 /// 686 /// # Permissions687 /// 688 /// * Collection Owner.689 /// 690 /// # Arguments691 /// 692 /// * collection_id.693 /// 694 /// * mode: [AccessMode]695 #[weight = T::WeightInfo::set_public_access_mode()]696 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult697 {698 let sender = ensure_signed(origin)?;699700 Self::check_owner_permissions(collection_id, sender)?;701 let mut target_collection = <Collection<T>>::get(collection_id);702 target_collection.access = mode;703 <Collection<T>>::insert(collection_id, target_collection);704705 Ok(())706 }707708 /// Allows Anyone to create tokens if:709 /// * White List is enabled, and710 /// * Address is added to white list, and711 /// * This method was called with True parameter712 /// 713 /// # Permissions714 /// * Collection Owner715 ///716 /// # Arguments717 /// 718 /// * collection_id.719 /// 720 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.721 #[weight = T::WeightInfo::set_mint_permission()]722 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult723 {724 let sender = ensure_signed(origin)?;725726 Self::check_owner_permissions(collection_id, sender)?;727 let mut target_collection = <Collection<T>>::get(collection_id);728 target_collection.mint_mode = mint_permission;729 <Collection<T>>::insert(collection_id, target_collection);730731 Ok(())732 }733734 /// Change the owner of the collection.735 /// 736 /// # Permissions737 /// 738 /// * Collection Owner.739 /// 740 /// # Arguments741 /// 742 /// * collection_id.743 /// 744 /// * new_owner.745 #[weight = T::WeightInfo::change_collection_owner()]746 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {747748 let sender = ensure_signed(origin)?;749 Self::check_owner_permissions(collection_id, sender)?;750 let mut target_collection = <Collection<T>>::get(collection_id);751 target_collection.owner = new_owner;752 <Collection<T>>::insert(collection_id, target_collection);753754 Ok(())755 }756757 /// Adds an admin of the Collection.758 /// 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. 759 /// 760 /// # Permissions761 /// 762 /// * Collection Owner.763 /// * Collection Admin.764 /// 765 /// # Arguments766 /// 767 /// * collection_id: ID of the Collection to add admin for.768 /// 769 /// * new_admin_id: Address of new admin to add.770 #[weight = T::WeightInfo::add_collection_admin()]771 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {772773 let sender = ensure_signed(origin)?;774 Self::check_owner_or_admin_permissions(collection_id, sender)?;775 let mut admin_arr: Vec<T::AccountId> = Vec::new();776777 if <AdminList<T>>::contains_key(collection_id)778 {779 admin_arr = <AdminList<T>>::get(collection_id);780 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);781 }782783 // Number of collection admins784 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);785786 admin_arr.push(new_admin_id);787 <AdminList<T>>::insert(collection_id, admin_arr);788789 Ok(())790 }791792 /// 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.793 ///794 /// # Permissions795 /// 796 /// * Collection Owner.797 /// * Collection Admin.798 /// 799 /// # Arguments800 /// 801 /// * collection_id: ID of the Collection to remove admin for.802 /// 803 /// * account_id: Address of admin to remove.804 #[weight = T::WeightInfo::remove_collection_admin()]805 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {806807 let sender = ensure_signed(origin)?;808 Self::check_owner_or_admin_permissions(collection_id, sender)?;809810 if <AdminList<T>>::contains_key(collection_id)811 {812 let mut admin_arr = <AdminList<T>>::get(collection_id);813 admin_arr.retain(|i| *i != account_id);814 <AdminList<T>>::insert(collection_id, admin_arr);815 }816817 Ok(())818 }819820 /// # Permissions821 /// 822 /// * Collection Owner823 /// 824 /// # Arguments825 /// 826 /// * collection_id.827 /// 828 /// * new_sponsor.829 #[weight = T::WeightInfo::set_collection_sponsor()]830 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {831832 let sender = ensure_signed(origin)?;833 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);834835 let mut target_collection = <Collection<T>>::get(collection_id);836 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);837838 target_collection.sponsor = new_sponsor;839 target_collection.sponsor_confirmed = false;840 <Collection<T>>::insert(collection_id, target_collection);841842 Ok(())843 }844845 /// # Permissions846 /// 847 /// * Sponsor.848 /// 849 /// # Arguments850 /// 851 /// * collection_id.852 #[weight = T::WeightInfo::confirm_sponsorship()]853 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {854855 let sender = ensure_signed(origin)?;856 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);857858 let mut target_collection = <Collection<T>>::get(collection_id);859 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);860861 target_collection.sponsor_confirmed = true;862 <Collection<T>>::insert(collection_id, target_collection);863864 Ok(())865 }866867 /// Switch back to pay-per-own-transaction model.868 ///869 /// # Permissions870 ///871 /// * Collection owner.872 /// 873 /// # Arguments874 /// 875 /// * collection_id.876 #[weight = T::WeightInfo::remove_collection_sponsor()]877 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {878879 let sender = ensure_signed(origin)?;880 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);881882 let mut target_collection = <Collection<T>>::get(collection_id);883 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);884885 target_collection.sponsor = T::AccountId::default();886 target_collection.sponsor_confirmed = false;887 <Collection<T>>::insert(collection_id, target_collection);888889 Ok(())890 }891892 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.893 /// 894 /// # Permissions895 /// 896 /// * Collection Owner.897 /// * Collection Admin.898 /// * Anyone if899 /// * White List is enabled, and900 /// * Address is added to white list, and901 /// * MintPermission is enabled (see SetMintPermission method)902 /// 903 /// # Arguments904 /// 905 /// * collection_id: ID of the collection.906 /// 907 /// * owner: Address, initial owner of the NFT.908 ///909 /// * data: Token data to store on chain.910 // #[weight =911 // (130_000_000 as Weight)912 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))913 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))914 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]915916 #[weight = T::WeightInfo::create_item(data.len())]917 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {918919 let sender = ensure_signed(origin)?;920921 Self::collection_exists(collection_id)?;922923 let target_collection = <Collection<T>>::get(collection_id);924925 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;926 Self::validate_create_item_args(&target_collection, &data)?;927 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;928929 Ok(())930 }931932 /// This method creates multiple instances of NFT Collection created with CreateCollection method.933 /// 934 /// # Permissions935 /// 936 /// * Collection Owner.937 /// * Collection Admin.938 /// * Anyone if939 /// * White List is enabled, and940 /// * Address is added to white list, and941 /// * MintPermission is enabled (see SetMintPermission method)942 /// 943 /// # Arguments944 /// 945 /// * collection_id: ID of the collection.946 /// 947 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].948 /// 949 /// * owner: Address, initial owner of the NFT.950 #[weight = T::WeightInfo::create_item(items_data.into_iter()951 .map(|data| { data.len() })952 .sum())]953 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {954955 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);956 let sender = ensure_signed(origin)?;957958 Self::collection_exists(collection_id)?;959 let target_collection = <Collection<T>>::get(collection_id);960961 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;962963 for data in &items_data {964 Self::validate_create_item_args(&target_collection, data)?;965 }966 for data in &items_data {967 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;968 }969970 Ok(())971 }972973 /// Destroys a concrete instance of NFT.974 /// 975 /// # Permissions976 /// 977 /// * Collection Owner.978 /// * Collection Admin.979 /// * Current NFT Owner.980 /// 981 /// # Arguments982 /// 983 /// * collection_id: ID of the collection.984 /// 985 /// * item_id: ID of NFT to burn.986 #[weight = T::WeightInfo::burn_item()]987 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {988989 let sender = ensure_signed(origin)?;990 Self::collection_exists(collection_id)?;991992 // Transfer permissions check993 let target_collection = <Collection<T>>::get(collection_id);994 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||995 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),996 Error::<T>::NoPermission);997998 if target_collection.access == AccessMode::WhiteList {999 Self::check_white_list(collection_id, &sender)?;1000 }10011002 match target_collection.mode1003 {1004 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1005 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1006 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1007 _ => ()1008 };10091010 // call event1011 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10121013 Ok(())1014 }10151016 /// Change ownership of the token.1017 /// 1018 /// # Permissions1019 /// 1020 /// * Collection Owner1021 /// * Collection Admin1022 /// * Current NFT owner1023 ///1024 /// # Arguments1025 /// 1026 /// * recipient: Address of token recipient.1027 /// 1028 /// * collection_id.1029 /// 1030 /// * item_id: ID of the item1031 /// * Non-Fungible Mode: Required.1032 /// * Fungible Mode: Ignored.1033 /// * Re-Fungible Mode: Required.1034 /// 1035 /// * value: Amount to transfer.1036 /// * Non-Fungible Mode: Ignored1037 /// * Fungible Mode: Must specify transferred amount1038 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1039 #[weight = T::WeightInfo::transfer()]1040 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10411042 let sender = ensure_signed(origin)?;1043 let target_collection = <Collection<T>>::get(collection_id);10441045 // Limits check1046 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10471048 // Transfer permissions check1049 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1050 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1051 Error::<T>::NoPermission);10521053 if target_collection.access == AccessMode::WhiteList {1054 Self::check_white_list(collection_id, &sender)?;1055 Self::check_white_list(collection_id, &recipient)?;1056 }10571058 match target_collection.mode1059 {1060 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1061 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1062 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1063 _ => ()1064 };10651066 Ok(())1067 }10681069 /// Set, change, or remove approved address to transfer the ownership of the NFT.1070 /// 1071 /// # Permissions1072 /// 1073 /// * Collection Owner1074 /// * Collection Admin1075 /// * Current NFT owner1076 /// 1077 /// # Arguments1078 /// 1079 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1080 /// 1081 /// * collection_id.1082 /// 1083 /// * item_id: ID of the item.1084 #[weight = T::WeightInfo::approve()]1085 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10861087 let sender = ensure_signed(origin)?;10881089 // Transfer permissions check1090 let target_collection = <Collection<T>>::get(collection_id);1091 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1092 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1093 Error::<T>::NoPermission);10941095 if target_collection.access == AccessMode::WhiteList {1096 Self::check_white_list(collection_id, &sender)?;1097 Self::check_white_list(collection_id, &spender)?;1098 }10991100 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1101 let mut allowance: u128 = amount;1102 if allowance_exists {1103 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1104 }1105 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11061107 Ok(())1108 }1109 1110 /// 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.1111 /// 1112 /// # Permissions1113 /// * Collection Owner1114 /// * Collection Admin1115 /// * Current NFT owner1116 /// * Address approved by current NFT owner1117 /// 1118 /// # Arguments1119 /// 1120 /// * from: Address that owns token.1121 /// 1122 /// * recipient: Address of token recipient.1123 /// 1124 /// * collection_id.1125 /// 1126 /// * item_id: ID of the item.1127 /// 1128 /// * value: Amount to transfer.1129 #[weight = T::WeightInfo::transfer_from()]1130 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11311132 let sender = ensure_signed(origin)?;1133 let mut appoved_transfer = false;11341135 // Check approval1136 let mut approval: u128 = 0;1137 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1138 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1139 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1140 appoved_transfer = true;1141 }11421143 let target_collection = <Collection<T>>::get(collection_id);11441145 // Limits check1146 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11471148 // Transfer permissions check 1149 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1150 Error::<T>::NoPermission);11511152 if target_collection.access == AccessMode::WhiteList {1153 Self::check_white_list(collection_id, &sender)?;1154 Self::check_white_list(collection_id, &recipient)?;1155 }11561157 // Reduce approval by transferred amount or remove if remaining approval drops to 01158 if approval - value > 0 {1159 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1160 }1161 else {1162 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1163 }11641165 match target_collection.mode1166 {1167 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1168 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1169 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1170 _ => ()1171 };11721173 Ok(())1174 }11751176 #[weight = 0]1177 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11781179 // let no_perm_mes = "You do not have permissions to modify this collection";1180 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1181 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1182 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11831184 // // on_nft_received call11851186 // Self::transfer(origin, collection_id, item_id, new_owner)?;11871188 Ok(())1189 }11901191 /// Set off-chain data schema.1192 /// 1193 /// # Permissions1194 /// 1195 /// * Collection Owner1196 /// * Collection Admin1197 /// 1198 /// # Arguments1199 /// 1200 /// * collection_id.1201 /// 1202 /// * schema: String representing the offchain data schema.1203 #[weight = T::WeightInfo::set_variable_meta_data()]1204 pub fn set_variable_meta_data (1205 origin,1206 collection_id: CollectionId,1207 item_id: TokenId,1208 data: Vec<u8>1209 ) -> DispatchResult {1210 let sender = ensure_signed(origin)?;1211 1212 Self::collection_exists(collection_id)?;1213 1214 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12151216 // Modify permissions check1217 let target_collection = <Collection<T>>::get(collection_id);1218 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1219 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1220 Error::<T>::NoPermission);12211222 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12231224 match target_collection.mode1225 {1226 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1227 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1228 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1229 _ => fail!(Error::<T>::UnexpectedCollectionType)1230 };12311232 Ok(())1233 }1234 1235 /// Set schema standard1236 /// ImageURL1237 /// Unique1238 /// 1239 /// # Permissions1240 /// 1241 /// * Collection Owner1242 /// * Collection Admin1243 /// 1244 /// # Arguments1245 /// 1246 /// * collection_id.1247 /// 1248 /// * schema: SchemaVersion: enum1249 #[weight = 0]1250 pub fn set_schema_version(1251 origin,1252 collection_id: CollectionId,1253 version: SchemaVersion1254 ) -> DispatchResult {1255 let sender = ensure_signed(origin)?;1256 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1257 let mut target_collection = <Collection<T>>::get(collection_id);1258 target_collection.schema_version = version;1259 <Collection<T>>::insert(collection_id, target_collection);12601261 Ok(())1262 }12631264 /// Set off-chain data schema.1265 /// 1266 /// # Permissions1267 /// 1268 /// * Collection Owner1269 /// * Collection Admin1270 /// 1271 /// # Arguments1272 /// 1273 /// * collection_id.1274 /// 1275 /// * schema: String representing the offchain data schema.1276 #[weight = T::WeightInfo::set_offchain_schema()]1277 pub fn set_offchain_schema(1278 origin,1279 collection_id: CollectionId,1280 schema: Vec<u8>1281 ) -> DispatchResult {1282 let sender = ensure_signed(origin)?;1283 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12841285 let mut target_collection = <Collection<T>>::get(collection_id);1286 target_collection.offchain_schema = schema;1287 <Collection<T>>::insert(collection_id, target_collection);12881289 Ok(())1290 }12911292 /// Set const on-chain data schema.1293 /// 1294 /// # Permissions1295 /// 1296 /// * Collection Owner1297 /// * Collection Admin1298 /// 1299 /// # Arguments1300 /// 1301 /// * collection_id.1302 /// 1303 /// * schema: String representing the const on-chain data schema.1304 #[weight = T::WeightInfo::set_const_on_chain_schema()]1305 pub fn set_const_on_chain_schema (1306 origin,1307 collection_id: CollectionId,1308 schema: Vec<u8>1309 ) -> DispatchResult {1310 let sender = ensure_signed(origin)?;1311 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13121313 let mut target_collection = <Collection<T>>::get(collection_id);1314 target_collection.const_on_chain_schema = schema;1315 <Collection<T>>::insert(collection_id, target_collection);13161317 Ok(())1318 }13191320 /// Set variable on-chain data schema.1321 /// 1322 /// # Permissions1323 /// 1324 /// * Collection Owner1325 /// * Collection Admin1326 /// 1327 /// # Arguments1328 /// 1329 /// * collection_id.1330 /// 1331 /// * schema: String representing the variable on-chain data schema.1332 #[weight = T::WeightInfo::set_const_on_chain_schema()]1333 pub fn set_variable_on_chain_schema (1334 origin,1335 collection_id: CollectionId,1336 schema: Vec<u8>1337 ) -> DispatchResult {1338 let sender = ensure_signed(origin)?;1339 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13401341 let mut target_collection = <Collection<T>>::get(collection_id);1342 target_collection.variable_on_chain_schema = schema;1343 <Collection<T>>::insert(collection_id, target_collection);13441345 Ok(())1346 }13471348 // Sudo permissions function1349 #[weight = 0]1350 pub fn set_chain_limits(1351 origin,1352 limits: ChainLimits1353 ) -> DispatchResult {1354 ensure_root(origin)?;1355 <ChainLimit>::put(limits);1356 Ok(())1357 }13581359 /// Enable smart contract self-sponsoring.1360 /// 1361 /// # Permissions1362 /// 1363 /// * Contract Owner1364 /// 1365 /// # Arguments1366 /// 1367 /// * contract address1368 /// * enable flag1369 /// 1370 #[weight = T::WeightInfo::enable_contract_sponsoring()]1371 pub fn enable_contract_sponsoring(1372 origin,1373 contract_address: T::AccountId,1374 enable: bool1375 ) -> DispatchResult {13761377 let sender = ensure_signed(origin)?;13781379 #[cfg(feature = "runtime-benchmarks")]1380 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13811382 Self::ensure_contract_owned(sender, &contract_address)?;13831384 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1385 Ok(())1386 }13871388 /// Set the rate limit for contract sponsoring to specified number of blocks.1389 /// 1390 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1391 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1392 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1393 /// from contract endowment if there are at least B blocks between such transactions. 1394 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1395 /// 1396 /// # Permissions1397 /// 1398 /// * Contract Owner1399 /// 1400 /// # Arguments1401 /// 1402 /// -`contract_address`: Address of the contract to sponsor1403 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1404 /// 1405 #[weight = 0]1406 pub fn set_contract_sponsoring_rate_limit(1407 origin,1408 contract_address: T::AccountId,1409 rate_limit: T::BlockNumber1410 ) -> DispatchResult {1411 let sender = ensure_signed(origin)?;1412 Self::ensure_contract_owned(sender, &contract_address)?;14131414 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1415 Ok(())1416 }14171418 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1419 /// 1420 /// # Permissions1421 /// 1422 /// * Address that deployed smart contract.1423 /// 1424 /// # Arguments1425 /// 1426 /// -`contract_address`: Address of the contract.1427 /// 1428 /// - `enable`: . 1429 #[weight = 0]1430 pub fn toggle_contract_white_list(1431 origin,1432 contract_address: T::AccountId,1433 enable: bool1434 ) -> DispatchResult {1435 let sender = ensure_signed(origin)?;1436 Self::ensure_contract_owned(sender, &contract_address)?;14371438 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1439 Ok(())1440 }1441 1442 /// Add an address to smart contract white list.1443 /// 1444 /// # Permissions1445 /// 1446 /// * Address that deployed smart contract.1447 /// 1448 /// # Arguments1449 /// 1450 /// -`contract_address`: Address of the contract.1451 ///1452 /// -`account_address`: Address to add.1453 #[weight = 0]1454 pub fn add_to_contract_white_list(1455 origin,1456 contract_address: T::AccountId,1457 account_address: T::AccountId1458 ) -> DispatchResult {1459 let sender = ensure_signed(origin)?;1460 Self::ensure_contract_owned(sender, &contract_address)?;1461 1462 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1463 Ok(())1464 }14651466 /// Remove an address from 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 remove.1477 #[weight = 0]1478 pub fn remove_from_contract_white_list(1479 origin,1480 contract_address: T::AccountId,1481 account_address: T::AccountId1482 ) -> DispatchResult {1483 let sender = ensure_signed(origin)?;1484 Self::ensure_contract_owned(sender, &contract_address)?;1485 1486 <ContractWhiteList<T>>::remove(contract_address, account_address);1487 Ok(())1488 }14891490 #[weight = 0]1491 pub fn set_collection_limits(1492 origin,1493 collection_id: u32,1494 limits: CollectionLimits,1495 ) -> DispatchResult {1496 let sender = ensure_signed(origin)?;1497 Self::check_owner_permissions(collection_id, sender.clone())?;1498 let mut target_collection = <Collection<T>>::get(collection_id);1499 let chain_limits = ChainLimit::get();1500 let climits = target_collection.limits;15011502 // collection bounds1503 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1504 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1505 Error::<T>::CollectionLimitBoundsExceeded);15061507 // token_limit check prev1508 ensure!(climits.token_limit > limits.token_limit && 1509 limits.token_limit <= chain_limits.account_token_ownership_limit, 1510 Error::<T>::AccountTokenLimitExceeded);15111512 target_collection.limits = limits;1513 <Collection<T>>::insert(collection_id, target_collection);15141515 Ok(())1516 } 1517 }1518}15191520impl<T: Trait> Module<T> {15211522 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15231524 // check token limit and account token limit1525 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1526 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1527 1528 Ok(())1529 }15301531 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15321533 // check token limit and account token limit1534 let total_items: u32 = ItemListIndex::get(collection_id);1535 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1536 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1537 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15381539 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1540 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1541 Self::check_white_list(collection_id, owner)?;1542 Self::check_white_list(collection_id, sender)?;1543 }15441545 Ok(())1546 }15471548 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1549 match target_collection.mode1550 {1551 CollectionMode::NFT => {1552 if let CreateItemData::NFT(data) = data {1553 // check sizes1554 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1555 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1556 } else {1557 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1558 }1559 },1560 CollectionMode::Fungible(_) => {1561 if let CreateItemData::Fungible(_) = data {1562 } else {1563 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1564 }1565 },1566 CollectionMode::ReFungible(_) => {1567 if let CreateItemData::ReFungible(data) = data {15681569 // check sizes1570 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1571 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1572 } else {1573 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1574 }1575 },1576 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1577 };15781579 Ok(())1580 }15811582 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1583 match data1584 {1585 CreateItemData::NFT(data) => {1586 let item = NftItemType {1587 owner,1588 const_data: data.const_data,1589 variable_data: data.variable_data1590 };15911592 Self::add_nft_item(collection_id, item)?;1593 },1594 CreateItemData::Fungible(data) => {1595 Self::add_fungible_item(collection_id, &owner, data.value)?;1596 },1597 CreateItemData::ReFungible(data) => {1598 let mut owner_list = Vec::new();1599 let value = (10 as u128).pow(collection.decimal_points as u32);1600 owner_list.push(Ownership {owner: owner.clone(), fraction: value});16011602 let item = ReFungibleItemType {1603 owner: owner_list,1604 const_data: data.const_data,1605 variable_data: data.variable_data1606 };16071608 Self::add_refungible_item(collection_id, item)?;1609 }1610 };16111612 // call event1613 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16141615 Ok(())1616 }16171618 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16191620 // Does new owner already have an account?1621 let mut balance: u128 = 0;1622 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1623 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1624 } 16251626 // Mint 1627 let item = FungibleItemType {1628 value: balance + value1629 };1630 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16311632 // Update balance1633 let new_balance = <Balance<T>>::get(collection_id, owner)1634 .checked_add(value)1635 .ok_or(Error::<T>::NumOverflow)?;1636 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16371638 Ok(())1639 }16401641 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1642 let current_index = <ItemListIndex>::get(collection_id)1643 .checked_add(1)1644 .ok_or(Error::<T>::NumOverflow)?;1645 let itemcopy = item.clone();16461647 let value = item.owner.first().unwrap().fraction;1648 let owner = item.owner.first().unwrap().owner.clone();16491650 Self::add_token_index(collection_id, current_index, owner.clone())?;16511652 <ItemListIndex>::insert(collection_id, current_index);1653 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16541655 // Update balance1656 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1657 .checked_add(value)1658 .ok_or(Error::<T>::NumOverflow)?;1659 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16601661 Ok(())1662 }16631664 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1665 let current_index = <ItemListIndex>::get(collection_id)1666 .checked_add(1)1667 .ok_or(Error::<T>::NumOverflow)?;16681669 let item_owner = item.owner.clone();1670 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16711672 <ItemListIndex>::insert(collection_id, current_index);1673 <NftItemList<T>>::insert(collection_id, current_index, item);16741675 // Update balance1676 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1677 .checked_add(1)1678 .ok_or(Error::<T>::NumOverflow)?;1679 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16801681 Ok(())1682 }16831684 fn burn_refungible_item(1685 collection_id: CollectionId,1686 item_id: TokenId,1687 owner: T::AccountId,1688 ) -> DispatchResult {1689 ensure!(1690 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1691 Error::<T>::TokenNotFound1692 );1693 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1694 let item = collection1695 .owner1696 .iter()1697 .filter(|&i| i.owner == owner)1698 .next()1699 .unwrap();1700 Self::remove_token_index(collection_id, item_id, owner.clone())?;17011702 // update balance1703 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1704 .checked_sub(item.fraction)1705 .ok_or(Error::<T>::NumOverflow)?;1706 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17071708 <ReFungibleItemList<T>>::remove(collection_id, item_id);17091710 Ok(())1711 }17121713 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1714 ensure!(1715 <NftItemList<T>>::contains_key(collection_id, item_id),1716 Error::<T>::TokenNotFound1717 );1718 let item = <NftItemList<T>>::get(collection_id, item_id);1719 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17201721 // update balance1722 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1723 .checked_sub(1)1724 .ok_or(Error::<T>::NumOverflow)?;1725 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1726 <NftItemList<T>>::remove(collection_id, item_id);17271728 Ok(())1729 }17301731 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1732 ensure!(1733 <FungibleItemList<T>>::contains_key(collection_id, owner),1734 Error::<T>::TokenNotFound1735 );1736 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1737 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17381739 // update balance1740 let new_balance = <Balance<T>>::get(collection_id, owner)1741 .checked_sub(value)1742 .ok_or(Error::<T>::NumOverflow)?;1743 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17441745 if balance.value - value > 0 {1746 balance.value -= value;1747 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1748 }1749 else {1750 <FungibleItemList<T>>::remove(collection_id, owner);1751 }17521753 Ok(())1754 }17551756 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1757 ensure!(1758 <Collection<T>>::contains_key(collection_id),1759 Error::<T>::CollectionNotFound1760 );1761 Ok(())1762 }17631764 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1765 Self::collection_exists(collection_id)?;17661767 let target_collection = <Collection<T>>::get(collection_id);1768 ensure!(1769 subject == target_collection.owner,1770 Error::<T>::NoPermission1771 );17721773 Ok(())1774 }17751776 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1777 let target_collection = <Collection<T>>::get(collection_id);1778 let mut result: bool = subject == target_collection.owner;1779 let exists = <AdminList<T>>::contains_key(collection_id);17801781 if !result & exists {1782 if <AdminList<T>>::get(collection_id).contains(&subject) {1783 result = true1784 }1785 }17861787 result1788 }17891790 fn check_owner_or_admin_permissions(1791 collection_id: CollectionId,1792 subject: T::AccountId,1793 ) -> DispatchResult {1794 Self::collection_exists(collection_id)?;1795 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17961797 ensure!(1798 result,1799 Error::<T>::NoPermission1800 );1801 Ok(())1802 }18031804 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1805 let target_collection = <Collection<T>>::get(collection_id);18061807 match target_collection.mode {1808 CollectionMode::NFT => {1809 <NftItemList<T>>::get(collection_id, item_id).owner == subject1810 }1811 CollectionMode::Fungible(_) => {1812 <FungibleItemList<T>>::contains_key(collection_id, &subject)1813 }1814 CollectionMode::ReFungible(_) => {1815 <ReFungibleItemList<T>>::get(collection_id, item_id)1816 .owner1817 .iter()1818 .any(|i| i.owner == subject)1819 }1820 CollectionMode::Invalid => false,1821 }1822 }18231824 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1825 let mes = Error::<T>::AddresNotInWhiteList;1826 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18271828 Ok(())1829 }18301831 fn transfer_fungible(1832 collection_id: CollectionId,1833 value: u128,1834 owner: &T::AccountId,1835 recipient: &T::AccountId,1836 ) -> DispatchResult {1837 ensure!(1838 <FungibleItemList<T>>::contains_key(collection_id, owner),1839 Error::<T>::TokenNotFound1840 );18411842 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1843 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18441845 // Send balance to recipient (updates balanceOf of recipient)1846 Self::add_fungible_item(collection_id, recipient, value)?;18471848 // update balanceOf of sender1849 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18501851 // Reduce or remove sender1852 if balance.value == value {1853 <FungibleItemList<T>>::remove(collection_id, owner);1854 }1855 else {1856 balance.value -= value;1857 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1858 }18591860 Ok(())1861 }18621863 fn transfer_refungible(1864 collection_id: CollectionId,1865 item_id: TokenId,1866 value: u128,1867 owner: T::AccountId,1868 new_owner: T::AccountId,1869 ) -> DispatchResult {1870 ensure!(1871 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1872 Error::<T>::TokenNotFound1873 );18741875 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1876 let item = full_item1877 .owner1878 .iter()1879 .filter(|i| i.owner == owner)1880 .next()1881 .ok_or(Error::<T>::NumOverflow)?;1882 let amount = item.fraction;18831884 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18851886 // update balance1887 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1888 .checked_sub(value)1889 .ok_or(Error::<T>::NumOverflow)?;1890 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18911892 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1893 .checked_add(value)1894 .ok_or(Error::<T>::NumOverflow)?;1895 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);18961897 let old_owner = item.owner.clone();1898 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);18991900 // transfer1901 if amount == value && !new_owner_has_account {1902 // change owner1903 // new owner do not have account1904 let mut new_full_item = full_item.clone();1905 new_full_item1906 .owner1907 .iter_mut()1908 .find(|i| i.owner == owner)1909 .unwrap()1910 .owner = new_owner.clone();1911 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19121913 // update index collection1914 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1915 } else {1916 let mut new_full_item = full_item.clone();1917 new_full_item1918 .owner1919 .iter_mut()1920 .find(|i| i.owner == owner)1921 .unwrap()1922 .fraction -= value;19231924 // separate amount1925 if new_owner_has_account {1926 // new owner has account1927 new_full_item1928 .owner1929 .iter_mut()1930 .find(|i| i.owner == new_owner)1931 .unwrap()1932 .fraction += value;1933 } else {1934 // new owner do not have account1935 new_full_item.owner.push(Ownership {1936 owner: new_owner.clone(),1937 fraction: value,1938 });1939 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1940 }19411942 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1943 }19441945 Ok(())1946 }19471948 fn transfer_nft(1949 collection_id: CollectionId,1950 item_id: TokenId,1951 sender: T::AccountId,1952 new_owner: T::AccountId,1953 ) -> DispatchResult {1954 ensure!(1955 <NftItemList<T>>::contains_key(collection_id, item_id),1956 Error::<T>::TokenNotFound1957 );19581959 let mut item = <NftItemList<T>>::get(collection_id, item_id);19601961 ensure!(1962 sender == item.owner,1963 Error::<T>::MustBeTokenOwner1964 );19651966 // update balance1967 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1968 .checked_sub(1)1969 .ok_or(Error::<T>::NumOverflow)?;1970 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19711972 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1973 .checked_add(1)1974 .ok_or(Error::<T>::NumOverflow)?;1975 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19761977 // change owner1978 let old_owner = item.owner.clone();1979 item.owner = new_owner.clone();1980 <NftItemList<T>>::insert(collection_id, item_id, item);19811982 // update index collection1983 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19841985 Ok(())1986 }1987 1988 fn item_exists(1989 collection_id: CollectionId,1990 item_id: TokenId,1991 mode: &CollectionMode1992 ) -> DispatchResult {1993 match mode {1994 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1995 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1996 _ => ()1997 };1998 1999 Ok(())2000 }20012002 fn set_re_fungible_variable_data(2003 collection_id: CollectionId,2004 item_id: TokenId,2005 data: Vec<u8>2006 ) -> DispatchResult {2007 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20082009 item.variable_data = data;20102011 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20122013 Ok(())2014 }20152016 fn set_nft_variable_data(2017 collection_id: CollectionId,2018 item_id: TokenId,2019 data: Vec<u8>2020 ) -> DispatchResult {2021 let mut item = <NftItemList<T>>::get(collection_id, item_id);2022 2023 item.variable_data = data;20242025 <NftItemList<T>>::insert(collection_id, item_id, item);2026 2027 Ok(())2028 }20292030 fn init_collection(item: &CollectionType<T::AccountId>) {2031 // check params2032 assert!(2033 item.decimal_points <= MAX_DECIMAL_POINTS,2034 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2035 );2036 assert!(2037 item.name.len() <= 64,2038 "Collection name can not be longer than 63 char"2039 );2040 assert!(2041 item.name.len() <= 256,2042 "Collection description can not be longer than 255 char"2043 );2044 assert!(2045 item.token_prefix.len() <= 16,2046 "Token prefix can not be longer than 15 char"2047 );20482049 // Generate next collection ID2050 let next_id = CreatedCollectionCount::get()2051 .checked_add(1)2052 .unwrap();20532054 CreatedCollectionCount::put(next_id);2055 }20562057 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2058 let current_index = <ItemListIndex>::get(collection_id)2059 .checked_add(1)2060 .unwrap();20612062 let item_owner = item.owner.clone();2063 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20642065 <ItemListIndex>::insert(collection_id, current_index);20662067 // Update balance2068 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2069 .checked_add(1)2070 .unwrap();2071 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2072 }20732074 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2075 let current_index = <ItemListIndex>::get(collection_id)2076 .checked_add(1)2077 .unwrap();20782079 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();20802081 <ItemListIndex>::insert(collection_id, current_index);20822083 // Update balance2084 let new_balance = <Balance<T>>::get(collection_id, owner)2085 .checked_add(item.value)2086 .unwrap();2087 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2088 }20892090 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2091 let current_index = <ItemListIndex>::get(collection_id)2092 .checked_add(1)2093 .unwrap();20942095 let value = item.owner.first().unwrap().fraction;2096 let owner = item.owner.first().unwrap().owner.clone();20972098 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();20992100 <ItemListIndex>::insert(collection_id, current_index);21012102 // Update balance2103 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2104 .checked_add(value)2105 .unwrap();2106 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2107 }21082109 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21102111 // add to account limit2112 if <AccountItemCount<T>>::contains_key(owner.clone()) {21132114 // bound Owned tokens by a single address2115 let count = <AccountItemCount<T>>::get(owner.clone());2116 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21172118 <AccountItemCount<T>>::insert(owner.clone(), count2119 .checked_add(1)2120 .ok_or(Error::<T>::NumOverflow)?);2121 }2122 else {2123 <AccountItemCount<T>>::insert(owner.clone(), 1);2124 }21252126 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2127 if list_exists {2128 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2129 let item_contains = list.contains(&item_index.clone());21302131 if !item_contains {2132 list.push(item_index.clone());2133 }21342135 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2136 } else {2137 let mut itm = Vec::new();2138 itm.push(item_index.clone());2139 <AddressTokens<T>>::insert(collection_id, owner, itm);2140 2141 }21422143 Ok(())2144 }21452146 fn remove_token_index(2147 collection_id: CollectionId,2148 item_index: TokenId,2149 owner: T::AccountId,2150 ) -> DispatchResult {21512152 // update counter2153 <AccountItemCount<T>>::insert(owner.clone(), 2154 <AccountItemCount<T>>::get(owner.clone())2155 .checked_sub(1)2156 .ok_or(Error::<T>::NumOverflow)?);215721582159 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2160 if list_exists {2161 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2162 let item_contains = list.contains(&item_index.clone());21632164 if item_contains {2165 list.retain(|&item| item != item_index);2166 <AddressTokens<T>>::insert(collection_id, owner, list);2167 }2168 }21692170 Ok(())2171 }21722173 fn move_token_index(2174 collection_id: CollectionId,2175 item_index: TokenId,2176 old_owner: T::AccountId,2177 new_owner: T::AccountId,2178 ) -> DispatchResult {2179 Self::remove_token_index(collection_id, item_index, old_owner)?;2180 Self::add_token_index(collection_id, item_index, new_owner)?;21812182 Ok(())2183 }2184 2185 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2186 if <ContractOwner<T>>::contains_key(contract.clone()) {2187 let owner = <ContractOwner<T>>::get(contract);2188 ensure!(account == owner, Error::<T>::NoPermission);2189 } else {2190 fail!(Error::<T>::NoPermission);2191 }21922193 Ok(())2194 }2195}21962197////////////////////////////////////////////////////////////////////////////////////////////////////2198// Economic models2199// #region22002201/// Fee multiplier.2202pub type Multiplier = FixedU128;22032204type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2205 <T as system::Trait>::AccountId,2206>>::Balance;2207type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2208 <T as system::Trait>::AccountId,2209>>::NegativeImbalance;22102211/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2212/// in the queue.2213#[derive(Encode, Decode, Clone, Eq, PartialEq)]2214pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2215 #[codec(compact)] BalanceOf<T>2216);22172218impl<T: Trait + Send + Sync> sp_std::fmt::Debug2219 for ChargeTransactionPayment<T>2220{2221 #[cfg(feature = "std")]2222 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2223 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2224 }2225 #[cfg(not(feature = "std"))]2226 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2227 Ok(())2228 }2229}22302231impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2232where2233 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2234 BalanceOf<T>: Send + Sync + FixedPointOperand,2235{2236 /// utility constructor. Used only in client/factory code.2237 pub fn from(fee: BalanceOf<T>) -> Self {2238 Self(fee)2239 }22402241 pub fn traditional_fee(2242 len: usize,2243 info: &DispatchInfoOf<T::Call>,2244 tip: BalanceOf<T>,2245 ) -> BalanceOf<T>2246 where2247 T::Call: Dispatchable<Info = DispatchInfo>,2248 {2249 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2250 }22512252 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2253 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2254 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2255 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2256 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2257 }22582259 fn withdraw_fee(2260 &self,2261 who: &T::AccountId,2262 call: &T::Call,2263 info: &DispatchInfoOf<T::Call>,2264 len: usize,2265 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2266 let tip = self.0;22672268 // Set fee based on call type. Creating collection costs 1 Unique.2269 // All other transactions have traditional fees so far2270 // let fee = match call.is_sub_type() {2271 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2272 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2273 // // _ => <BalanceOf<T>>::from(100)2274 // };2275 let fee = Self::traditional_fee(len, info, tip);22762277 // Only mess with balances if fee is not zero.2278 if fee.is_zero() {2279 return Ok((fee, None));2280 }22812282 // Determine who is paying transaction fee based on ecnomic model2283 // Parse call to extract collection ID and access collection sponsor2284 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2285 Some(Call::create_item(collection_id, _owner, _properties)) => {22862287 // check free create limit2288 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2289 (<Collection<T>>::get(collection_id).sponsor_confirmed)2290 {2291 <Collection<T>>::get(collection_id).sponsor2292 } else {2293 T::AccountId::default()2294 }2295 }2296 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2297 2298 let mut sponsor_transfer = false;2299 if <Collection<T>>::get(collection_id).sponsor_confirmed {23002301 let collection_limits = <Collection<T>>::get(collection_id).limits;2302 let collection_mode = <Collection<T>>::get(collection_id).mode;2303 2304 // sponsor timeout2305 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2306 sponsor_transfer = match collection_mode {2307 CollectionMode::NFT => {2308 2309 // get correct limit2310 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2311 collection_limits.sponsor_transfer_timeout2312 } else {2313 ChainLimit::get().nft_sponsor_transfer_timeout2314 };2315 2316 let mut sponsored = true;2317 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2318 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2319 let limit_time = last_tx_block + limit.into();2320 if block_number <= limit_time {2321 sponsored = false;2322 }2323 }2324 if sponsored {2325 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2326 }23272328 sponsored2329 }2330 CollectionMode::Fungible(_) => {2331 2332 // get correct limit2333 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2334 collection_limits.sponsor_transfer_timeout2335 } else {2336 ChainLimit::get().fungible_sponsor_transfer_timeout2337 };2338 2339 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2340 let mut sponsored = true;2341 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2342 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2343 let limit_time = last_tx_block + limit.into();2344 if block_number <= limit_time {2345 sponsored = false;2346 }2347 }2348 if sponsored {2349 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2350 }23512352 sponsored2353 }2354 CollectionMode::ReFungible(_) => {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().refungible_sponsor_transfer_timeout2361 };2362 2363 let mut sponsored = true;2364 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2365 let last_tx_block = <ReFungibleTransferBasket<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 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2373 }23742375 sponsored2376 }2377 _ => {2378 false2379 },2380 };2381 }23822383 if !sponsor_transfer {2384 T::AccountId::default()2385 } else {2386 <Collection<T>>::get(collection_id).sponsor2387 }2388 }23892390 _ => T::AccountId::default(),2391 };23922393 // Sponsor smart contracts2394 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {23952396 // On instantiation: set the contract owner2397 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {23982399 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2400 code_hash,2401 &data,2402 &who,2403 );2404 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24052406 T::AccountId::default()2407 },24082409 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2410 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24112412 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24132414 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2415 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2416 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2417 2418 if !owned_contract && white_list_enabled {2419 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2420 return Err(InvalidTransaction::Call.into());2421 }2422 }24232424 let mut sponsor_transfer = false;2425 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2426 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2427 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2428 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2429 let limit_time = last_tx_block + rate_limit;24302431 if block_number >= limit_time {2432 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2433 sponsor_transfer = true;2434 }2435 } else {2436 sponsor_transfer = false;2437 }2438 2439 2440 let mut sp = T::AccountId::default();2441 if sponsor_transfer {2442 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2443 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2444 sp = called_contract;2445 }2446 }2447 }24482449 sp2450 },24512452 _ => sponsor,2453 };24542455 let mut who_pays_fee: T::AccountId = sponsor.clone();2456 if sponsor == T::AccountId::default() {2457 who_pays_fee = who.clone();2458 }24592460 match <T as transaction_payment::Trait>::Currency::withdraw(2461 &who_pays_fee,2462 fee,2463 if tip.is_zero() {2464 WithdrawReason::TransactionPayment.into()2465 } else {2466 WithdrawReason::TransactionPayment | WithdrawReason::Tip2467 },2468 ExistenceRequirement::KeepAlive,2469 ) {2470 Ok(imbalance) => Ok((fee, Some(imbalance))),2471 Err(_) => Err(InvalidTransaction::Payment.into()),2472 }2473 }2474}247524762477impl<T: Trait + Send + Sync> SignedExtension2478 for ChargeTransactionPayment<T>2479where2480 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2481 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2482{2483 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2484 type AccountId = T::AccountId;2485 type Call = T::Call;2486 type AdditionalSigned = ();2487 type Pre = (2488 BalanceOf<T>,2489 Self::AccountId,2490 Option<NegativeImbalanceOf<T>>,2491 BalanceOf<T>,2492 );2493 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2494 Ok(())2495 }24962497 fn validate(2498 &self,2499 who: &Self::AccountId,2500 call: &Self::Call,2501 info: &DispatchInfoOf<Self::Call>,2502 len: usize,2503 ) -> TransactionValidity {2504 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2505 Ok(ValidTransaction {2506 priority: Self::get_priority(len, info, fee),2507 ..Default::default()2508 })2509 }25102511 fn pre_dispatch(2512 self,2513 who: &Self::AccountId,2514 call: &Self::Call,2515 info: &DispatchInfoOf<Self::Call>,2516 len: usize,2517 ) -> Result<Self::Pre, TransactionValidityError> {2518 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2519 Ok((self.0, who.clone(), imbalance, fee))2520 }25212522 fn post_dispatch(2523 pre: Self::Pre,2524 info: &DispatchInfoOf<Self::Call>,2525 post_info: &PostDispatchInfoOf<Self::Call>,2526 len: usize,2527 _result: &DispatchResult,2528 ) -> Result<(), TransactionValidityError> {2529 let (tip, who, imbalance, fee) = pre;2530 if let Some(payed) = imbalance {2531 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2532 len as u32, info, post_info, tip,2533 );2534 let refund = fee.saturating_sub(actual_fee);2535 let actual_payment =2536 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2537 &who, refund,2538 ) {2539 Ok(refund_imbalance) => {2540 // The refund cannot be larger than the up front payed max weight.2541 // `PostDispatchInfo::calc_unspent` guards against such a case.2542 match payed.offset(refund_imbalance) {2543 Ok(actual_payment) => actual_payment,2544 Err(_) => return Err(InvalidTransaction::Payment.into()),2545 }2546 }2547 // We do not recreate the account using the refund. The up front payment2548 // is gone in that case.2549 Err(_) => payed,2550 };2551 let imbalances = actual_payment.split(tip);2552 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2553 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2554 );2555 }2556 Ok(())2557 }2558}25592560// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -960,7 +960,7 @@
let origin1 = Origin::signed(1);
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2));
- assert_eq!(TemplateModule::white_list(collection_id)[0], 2);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), true);
});
}
@@ -975,7 +975,7 @@
assert_ok!(TemplateModule::add_collection_admin(origin1.clone(), collection_id, 2));
assert_ok!(TemplateModule::add_to_white_list(origin2.clone(), collection_id, 3));
- assert_eq!(TemplateModule::white_list(collection_id)[0], 3);
+ assert_eq!(TemplateModule::white_list(collection_id, 3), true);
});
}
@@ -1035,8 +1035,7 @@
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2));
assert_ok!(TemplateModule::add_to_white_list(origin1.clone(), collection_id, 2));
- assert_eq!(TemplateModule::white_list(collection_id)[0], 2);
- assert_eq!(TemplateModule::white_list(collection_id).len(), 1);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), true);
});
}
@@ -1054,7 +1053,7 @@
collection_id,
2
));
- assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), false);
});
}
@@ -1075,7 +1074,7 @@
collection_id,
3
));
- assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+ assert_eq!(TemplateModule::white_list(collection_id, 3), false);
});
}
@@ -1093,7 +1092,7 @@
TemplateModule::remove_from_white_list(origin2.clone(), collection_id, 2),
Error::<Test>::NoPermission
);
- assert_eq!(TemplateModule::white_list(collection_id)[0], 2);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), true);
});
}
@@ -1125,7 +1124,7 @@
TemplateModule::remove_from_white_list(origin2.clone(), collection_id, 2),
Error::<Test>::CollectionNotFound
);
- assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), false);
});
}
@@ -1149,7 +1148,7 @@
collection_id,
2
));
- assert_eq!(TemplateModule::white_list(collection_id).len(), 0);
+ assert_eq!(TemplateModule::white_list(collection_id, 2), false);
});
}
tests/src/contracts.test.tsdiffbeforeafterboth--- a/tests/src/contracts.test.ts
+++ b/tests/src/contracts.test.ts
@@ -1,16 +1,15 @@
-//
-// This file is subject to the terms and conditions defined in
-// file 'LICENSE', which is part of this source code package.
-//
-
-import { ApiPromise } from "@polkadot/api";
-import { expect } from "chai";
-import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
+import chai from "chai";
+import chaiAsPromised from 'chai-as-promised';
+import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
import fs from "fs";
-import { Abi, BlueprintPromise, CodePromise } from "@polkadot/api-contract";
+import { Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract } from "@polkadot/api-contract";
import { IKeyringPair } from "@polkadot/types/types";
-import { Keyring } from "@polkadot/api";
+import { ApiPromise, Keyring } from "@polkadot/api";
import { ApiTypes, SubmittableExtrinsic } from "@polkadot/api/types";
+import privateKey from "./substrate/privateKey";
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
import { BigNumber } from 'bignumber.js';
import { findUnusedAddress } from './util/helpers'
@@ -18,8 +17,8 @@
const gasLimit = 3000n * 1000000n;
const endowment = `1000000000000000`;
-function deployBlueprint(alice: IKeyringPair, code: CodePromise): Promise<BlueprintPromise> {
- return new Promise<BlueprintPromise>(async (resolve, reject) => {
+function deployBlueprint(alice: IKeyringPair, code: CodePromise): Promise<Blueprint> {
+ return new Promise<Blueprint>(async (resolve, reject) => {
const unsub = await code
.createBlueprint()
.signAndSend(alice, (result) => {
@@ -32,7 +31,7 @@
});
}
-function deployContract(alice: IKeyringPair, blueprint: BlueprintPromise) : Promise<any> {
+function deployContract(alice: IKeyringPair, blueprint: Blueprint) : Promise<any> {
return new Promise<any>(async (resolve, reject) => {
const initValue = true;
@@ -62,39 +61,112 @@
return deployer;
}
-describe('Contracts smoke test', () => {
+async function deployFlipper(api: ApiPromise): Promise<[Contract, IKeyringPair]> {
+ const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8'));
+ const abi = new Abi(metadata);
+
+ const deployer = await prepareDeployer(api);
+
+ const wasm = fs.readFileSync('./src/flipper/flipper.wasm');
+
+ const code = new CodePromise(api, abi, wasm);
+
+ const blueprint = await deployBlueprint(deployer, code);
+ const contract = (await deployContract(deployer, blueprint))['contract'] as Contract;
+
+ const initialGetResponse = await getFlipValue(contract, deployer);
+ expect(initialGetResponse).to.be.true;
+
+ return [contract, deployer];
+}
+
+async function getFlipValue(contract: Contract, deployer: IKeyringPair) {
+ const result = await contract.query.get(deployer.address, value, gasLimit);
+
+ if(!result.result.isSuccess) {
+ throw `Failed to get flipper value`;
+ }
+ return (result.result.asSuccess.data[0] == 0x00) ? false : true;
+}
+
+describe('Contracts', () => {
it(`Can deploy smart contract Flipper, instantiate it and call it's get and flip messages.`, async () => {
await usingApi(async api => {
- const deployer = await prepareDeployer(api);
+ const [contract, deployer] = await deployFlipper(api);
+ const initialGetResponse = await getFlipValue(contract, deployer);
+
+ const bob = privateKey("//Bob");
+ const flip = contract.exec('flip', value, gasLimit);
+ await submitTransactionAsync(bob, flip);
+
+ const afterFlipGetResponse = await getFlipValue(contract, deployer);
+ expect(afterFlipGetResponse).not.to.be.eq(initialGetResponse, 'Flipping should change value.');
+ });
+ });
+
+ it(`Whitelisted account can call contract.`, async () => {
+ await usingApi(async api => {
+ const bob = privateKey("//Bob");
- const wasm = fs.readFileSync('./src/flipper/flipper.wasm');
-
- const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8'));
- const abi = new Abi(metadata);
+ const [contract, deployer] = await deployFlipper(api);
+ const consoleError = console.error;
+ console.error = (...data: any[]) => {
+ };
- const code = new CodePromise(api, abi, wasm);
+ let expectedFlipValue = await getFlipValue(contract, deployer);
- const blueprint = await deployBlueprint(deployer, code);
- const contract = (await deployContract(deployer, blueprint))['contract'];
+ const flip = contract.exec('flip', value, gasLimit);
+ await submitTransactionAsync(bob, flip);
+ expectedFlipValue = !expectedFlipValue;
+ const afterFlip = await getFlipValue(contract,deployer);
+ expect(afterFlip).to.be.eq(expectedFlipValue, `Anyone can call new contract.`);
- const getFlipValue = async () => {
- const result = await contract.query.get(deployer.address, value, gasLimit);
+ const deployerCanFlip = async () => {
+ expectedFlipValue = !expectedFlipValue;
+ const deployerFlip = contract.exec('flip', value, gasLimit);
+ await submitTransactionAsync(deployer, deployerFlip);
+ const aliceFlip1Response = await getFlipValue(contract, deployer);
+ expect(aliceFlip1Response).to.be.eq(expectedFlipValue, `Deployer always can flip.`);
+ };
+ await deployerCanFlip();
- if(!result.result.isSuccess) {
- throw `Failed to get flipper value`;
- }
- return (result.result.asSuccess.data[0] == 0x00) ? false : true;
- }
+ const enableWhiteListTx = api.tx.nft.toggleContractWhiteList(contract.address, true);
+ const enableResult = await submitTransactionAsync(deployer, enableWhiteListTx);
+ const flipWithEnabledWhiteList = contract.exec('flip', value, gasLimit);
+ await expect(submitTransactionExpectFailAsync(bob, flipWithEnabledWhiteList)).to.be.rejected;
+ const flipValueAfterEnableWhiteList = await getFlipValue(contract, deployer);
+ expect(flipValueAfterEnableWhiteList).to.be.eq(expectedFlipValue, `Enabling whitelist doesn't make it possible to call contract for everyone.`);
- const initialGetResponse = await getFlipValue();
- expect(initialGetResponse).to.be.true;
+ await deployerCanFlip();
- const flip = contract.exec('flip', value, gasLimit);
- await submitTransactionAsync(deployer, flip);
+ const addBobToWhiteListTx = api.tx.nft.addToContractWhiteList(contract.address, bob.address);
+ const addBobResult = await submitTransactionAsync(deployer, addBobToWhiteListTx);
+ const flipWithWhitelistedBob = contract.exec('flip', value, gasLimit);
+ await submitTransactionAsync(bob, flipWithWhitelistedBob);
+ expectedFlipValue = !expectedFlipValue;
+ const flipAfterWhiteListed = await getFlipValue(contract,deployer);
+ expect(flipAfterWhiteListed).to.be.eq(expectedFlipValue, `Bob was whitelisted, now he can flip.`);
- const afterFlipGetResponse = await getFlipValue();
+ await deployerCanFlip();
- expect(afterFlipGetResponse).to.be.false;
+ const removeBobFromWhiteListTx = api.tx.nft.removeFromContractWhiteList(contract.address, bob.address);
+ const removeBobResult = await submitTransactionAsync(deployer, removeBobFromWhiteListTx);
+ const bobRemoved = contract.exec('flip', value, gasLimit);
+ await expect(submitTransactionExpectFailAsync(bob, bobRemoved)).to.be.rejected;
+ const afterBobRemoved = await getFlipValue(contract, deployer);
+ expect(afterBobRemoved).to.be.eq(expectedFlipValue, `Bob can't call contract, now when he is removeed from white list.`);
+
+ await deployerCanFlip();
+
+ const disableWhiteListTx = api.tx.nft.toggleContractWhiteList(contract.address, false);
+ const disableWhiteListResult = await submitTransactionAsync(deployer, disableWhiteListTx);
+ const whiteListDisabledFlip = contract.exec('flip', value, gasLimit);
+ await submitTransactionAsync(bob, whiteListDisabledFlip);
+ expectedFlipValue = !expectedFlipValue;
+ const afterWhiteListDisabled = await getFlipValue(contract,deployer);
+ expect(afterWhiteListDisabled).to.be.eq(expectedFlipValue, `Anyone can call contract with disabled whitelist.`);
+
+ console.error = consoleError;
});
});
tests/src/substrate/substrate-api.tsdiffbeforeafterboth--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -56,3 +56,25 @@
}
});
}
+
+export function submitTransactionExpectFailAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
+ return new Promise(async function(resolve, reject) {
+ try {
+ await transaction.signAndSend(sender, ({ events = [], status }) => {
+ if (status.isReady) {
+ // nothing to do
+ // console.log(`Current tx status is Ready`);
+ } else if (status.isBroadcast) {
+ // nothing to do
+ // console.log(`Current tx status is Broadcast`);
+ } else if (status.isInBlock || status.isFinalized) {
+ resolve(events);
+ } else {
+ reject("Transaction failed");
+ }
+ });
+ } catch (e) {
+ reject(e);
+ }
+ });
+}
\ No newline at end of file