difftreelog
Merge pull request #65 from usetech-llc/feature/NFTPAR-288
in: master
Feature/nftpar 288
3 files changed
node/src/chain_spec.rsdiffbeforeafterboth--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -209,7 +209,10 @@
custom_data_limit: 2048,
nft_sponsor_transfer_timeout: 15,
fungible_sponsor_transfer_timeout: 15,
- refungible_sponsor_transfer_timeout: 15,
+ refungible_sponsor_transfer_timeout: 15,
+ offchain_schema_limit: 1024,
+ variable_on_chain_schema_limit: 1024,
+ const_on_chain_schema_limit: 1024,
},
}),
pallet_contracts: Some(ContractsConfig {
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, WithdrawReason,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial,29 },30 IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69 Invalid,70 NFT,71 // decimal points72 Fungible(DecimalPoints),73 // decimal points74 ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78 fn into(self) -> u8 {79 match self {80 CollectionMode::Invalid => 0,81 CollectionMode::NFT => 1,82 CollectionMode::Fungible(_) => 2,83 CollectionMode::ReFungible(_) => 3,84 }85 }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91 Normal,92 WhiteList,93}94impl Default for AccessMode {95 fn default() -> Self {96 Self::Normal97 }98}99100impl Default for CollectionMode {101 fn default() -> Self {102 Self::Invalid103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub owner: AccountId,149 pub const_data: Vec<u8>,150 pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType {156 pub value: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct ReFungibleItemType<AccountId> {162 pub owner: Vec<Ownership<AccountId>>,163 pub const_data: Vec<u8>,164 pub variable_data: Vec<u8>,165}166167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169// pub struct VestingItem<AccountId, Moment> {170// pub sender: AccountId,171// pub recipient: AccountId,172// pub collection_id: CollectionId,173// pub item_id: TokenId,174// pub amount: u64,175// pub vesting_date: Moment,176// }177178#[derive(Encode, Decode, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct CollectionLimits {181 pub account_token_ownership_limit: u32,182 pub sponsored_data_size: u32,183 pub token_limit: u32,184185 // Timeouts for item types in passed blocks186 pub sponsor_transfer_timeout: u32,187}188189impl Default for CollectionLimits {190 fn default() -> CollectionLimits {191 CollectionLimits { 192 account_token_ownership_limit: 10_000_000, 193 token_limit: u32::max_value(),194 sponsored_data_size: u32::max_value(), 195 sponsor_transfer_timeout: 14400 }196 }197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]201pub struct ChainLimits {202 pub collection_numbers_limit: u32,203 pub account_token_ownership_limit: u32,204 pub collections_admins_limit: u64,205 pub custom_data_limit: u32,206207 // Timeouts for item types in passed blocks208 pub nft_sponsor_transfer_timeout: u32,209 pub fungible_sponsor_transfer_timeout: u32,210 pub refungible_sponsor_transfer_timeout: u32,211}212213pub trait WeightInfo {214 fn create_collection() -> Weight;215 fn destroy_collection() -> Weight;216 fn add_to_white_list() -> Weight;217 fn remove_from_white_list() -> Weight;218 fn set_public_access_mode() -> Weight;219 fn set_mint_permission() -> Weight;220 fn change_collection_owner() -> Weight;221 fn add_collection_admin() -> Weight;222 fn remove_collection_admin() -> Weight;223 fn set_collection_sponsor() -> Weight;224 fn confirm_sponsorship() -> Weight;225 fn remove_collection_sponsor() -> Weight;226 fn create_item(s: usize) -> Weight;227 fn burn_item() -> Weight;228 fn transfer() -> Weight;229 fn approve() -> Weight;230 fn transfer_from() -> Weight;231 fn set_offchain_schema() -> Weight;232 fn set_const_on_chain_schema() -> Weight;233 fn set_variable_on_chain_schema() -> Weight;234 fn set_variable_meta_data() -> Weight;235 fn enable_contract_sponsoring() -> Weight;236 fn set_schema_version() -> Weight;237 fn set_chain_limits() -> Weight;238 fn set_contract_sponsoring_rate_limit() -> Weight;239 fn toggle_contract_white_list() -> Weight;240 fn add_to_contract_white_list() -> Weight;241 fn remove_from_contract_white_list() -> Weight;242 fn set_collection_limits() -> Weight;243}244245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]247pub struct CreateNftData {248 pub const_data: Vec<u8>,249 pub variable_data: Vec<u8>,250}251252#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]253#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]254pub struct CreateFungibleData {255 pub value: u128,256}257258#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub struct CreateReFungibleData {261 pub const_data: Vec<u8>,262 pub variable_data: Vec<u8>,263}264265#[derive(Encode, Decode, Debug, Clone, PartialEq)]266#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]267pub enum CreateItemData {268 NFT(CreateNftData),269 Fungible(CreateFungibleData),270 ReFungible(CreateReFungibleData),271}272273impl CreateItemData {274 pub fn len(&self) -> usize {275 let len = match self {276 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),277 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),278 _ => 0279 };280 281 return len;282 }283}284285impl From<CreateNftData> for CreateItemData {286 fn from(item: CreateNftData) -> Self {287 CreateItemData::NFT(item)288 }289}290291impl From<CreateReFungibleData> for CreateItemData {292 fn from(item: CreateReFungibleData) -> Self {293 CreateItemData::ReFungible(item)294 }295}296297impl From<CreateFungibleData> for CreateItemData {298 fn from(item: CreateFungibleData) -> Self {299 CreateItemData::Fungible(item)300 }301}302303304decl_error! {305 /// Error for non-fungible-token module.306 pub enum Error for Module<T: Trait> {307 /// Total collections bound exceeded.308 TotalCollectionsLimitExceeded,309 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.310 CollectionDecimalPointLimitExceeded, 311 /// Collection name can not be longer than 63 char.312 CollectionNameLimitExceeded, 313 /// Collection description can not be longer than 255 char.314 CollectionDescriptionLimitExceeded, 315 /// Token prefix can not be longer than 15 char.316 CollectionTokenPrefixLimitExceeded,317 /// This collection does not exist.318 CollectionNotFound,319 /// Item not exists.320 TokenNotFound,321 /// Arithmetic calculation overflow.322 NumOverflow, 323 /// Account already has admin role.324 AlreadyAdmin, 325 /// You do not own this collection.326 NoPermission,327 /// This address is not set as sponsor, use setCollectionSponsor first.328 ConfirmUnsetSponsorFail,329 /// Collection is not in mint mode.330 PublicMintingNotAllowed,331 /// Sender parameter and item owner must be equal.332 MustBeTokenOwner,333 /// Item balance not enough.334 TokenValueTooLow,335 /// Size of item is too large.336 NftSizeLimitExceeded,337 /// No approve found338 ApproveNotFound,339 /// Requested value more than approved.340 TokenValueNotEnough,341 /// Only approved addresses can call this method.342 ApproveRequired,343 /// Address is not in white list.344 AddresNotInWhiteList,345 /// Number of collection admins bound exceeded.346 CollectionAdminsLimitExceeded,347 /// Owned tokens by a single address bound exceeded.348 AddressOwnershipLimitExceeded,349 /// Length of items properties must be greater than 0.350 EmptyArgument,351 /// const_data exceeded data limit.352 TokenConstDataLimitExceeded,353 /// variable_data exceeded data limit.354 TokenVariableDataLimitExceeded,355 /// Not NFT item data used to mint in NFT collection.356 NotNftDataUsedToMintNftCollectionToken,357 /// Not Fungible item data used to mint in Fungible collection.358 NotFungibleDataUsedToMintFungibleCollectionToken,359 /// Not Re Fungible item data used to mint in Re Fungible collection.360 NotReFungibleDataUsedToMintReFungibleCollectionToken,361 /// Unexpected collection type.362 UnexpectedCollectionType,363 /// Can't store metadata in fungible tokens.364 CantStoreMetadataInFungibleTokens,365 /// Collection token limit exceeded366 CollectionTokenLimitExceeded,367 /// Account token limit exceeded per collection368 AccountTokenLimitExceeded,369 /// Collection limit bounds per collection exceeded370 CollectionLimitBoundsExceeded371 }372}373374pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {375 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;376377 /// Weight information for extrinsics in this pallet.378 type WeightInfo: WeightInfo;379}380381#[cfg(feature = "runtime-benchmarks")]382mod benchmarking;383384// #endregion385386decl_storage! {387 trait Store for Module<T: Trait> as Nft {388389 // Private members390 NextCollectionID: CollectionId;391 CreatedCollectionCount: u32;392 ChainVersion: u64;393 ItemListIndex: map hasher(identity) CollectionId => TokenId;394395 // Chain limits struct396 pub ChainLimit get(fn chain_limit) config(): ChainLimits;397398 // Bound counters399 CollectionCount: u32;400 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;401402 // Basic collections403 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;404 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;405 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;406407 /// Balance owner per collection map408 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;409410 /// second parameter: item id + owner account id + spender account id411 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;412413 /// Item collections414 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;415 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;416 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;417418 /// Index list419 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;420421 /// Tokens transfer baskets422 pub CreateItemBasket get(fn create_item_basket): map hasher(twox_64_concat) (CollectionId, T::AccountId) => T::BlockNumber;423 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;424 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;425 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;426427 // Contract Sponsorship and Ownership428 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;429 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;430 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;431 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;432 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 433 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 434 }435 add_extra_genesis {436 build(|config: &GenesisConfig<T>| {437 // Modification of storage438 for (_num, _c) in &config.collection {439 <Module<T>>::init_collection(_c);440 }441442 for (_num, _c, _i) in &config.nft_item_id {443 <Module<T>>::init_nft_token(*_c, _i);444 }445446 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {447 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);448 }449450 for (_num, _c, _i) in &config.refungible_item_id {451 <Module<T>>::init_refungible_token(*_c, _i);452 }453 })454 }455}456457decl_event!(458 pub enum Event<T>459 where460 AccountId = <T as system::Trait>::AccountId,461 {462 /// New collection was created463 /// 464 /// # Arguments465 /// 466 /// * collection_id: Globally unique identifier of newly created collection.467 /// 468 /// * mode: [CollectionMode] converted into u8.469 /// 470 /// * account_id: Collection owner.471 Created(CollectionId, u8, AccountId),472473 /// New item was created.474 /// 475 /// # Arguments476 /// 477 /// * collection_id: Id of the collection where item was created.478 /// 479 /// * item_id: Id of an item. Unique within the collection.480 ItemCreated(CollectionId, TokenId),481482 /// Collection item was burned.483 /// 484 /// # Arguments485 /// 486 /// collection_id.487 /// 488 /// item_id: Identifier of burned NFT.489 ItemDestroyed(CollectionId, TokenId),490 }491);492493decl_module! {494 pub struct Module<T: Trait> for enum Call where origin: T::Origin {495496 fn deposit_event() = default;497 type Error = Error<T>;498499 fn on_initialize(now: T::BlockNumber) -> Weight {500501 if ChainVersion::get() < 2502 {503 let value = NextCollectionID::get();504 CreatedCollectionCount::put(value);505 ChainVersion::put(2);506 }507508 0509 }510511 /// 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.512 /// 513 /// # Permissions514 /// 515 /// * Anyone.516 /// 517 /// # Arguments518 /// 519 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.520 /// 521 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.522 /// 523 /// * token_prefix: UTF-8 string with token prefix.524 /// 525 /// * mode: [CollectionMode] collection type and type dependent data.526 // returns collection ID527 #[weight = T::WeightInfo::create_collection()]528 pub fn create_collection(origin,529 collection_name: Vec<u16>,530 collection_description: Vec<u16>,531 token_prefix: Vec<u8>,532 mode: CollectionMode) -> DispatchResult {533534 // Anyone can create a collection535 let who = ensure_signed(origin)?;536537 let decimal_points = match mode {538 CollectionMode::Fungible(points) => points,539 CollectionMode::ReFungible(points) => points,540 _ => 0541 };542543 // bound Total number of collections544 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);545546 // check params547 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);548 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);549 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);550 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);551552 // Generate next collection ID553 let next_id = CreatedCollectionCount::get()554 .checked_add(1)555 .ok_or(Error::<T>::NumOverflow)?;556557 // bound counter558 let total = CollectionCount::get()559 .checked_add(1)560 .ok_or(Error::<T>::NumOverflow)?;561562 CreatedCollectionCount::put(next_id);563 CollectionCount::put(total);564565 // Create new collection566 let new_collection = CollectionType {567 owner: who.clone(),568 name: collection_name,569 mode: mode.clone(),570 mint_mode: false,571 access: AccessMode::Normal,572 description: collection_description,573 decimal_points: decimal_points,574 token_prefix: token_prefix,575 offchain_schema: Vec::new(),576 schema_version: SchemaVersion::ImageURL,577 sponsor: T::AccountId::default(),578 sponsor_confirmed: false,579 variable_on_chain_schema: Vec::new(),580 const_on_chain_schema: Vec::new(),581 limits: CollectionLimits::default(),582 };583584 // Add new collection to map585 <Collection<T>>::insert(next_id, new_collection);586587 // call event588 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));589590 Ok(())591 }592593 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.594 /// 595 /// # Permissions596 /// 597 /// * Collection Owner.598 /// 599 /// # Arguments600 /// 601 /// * collection_id: collection to destroy.602 #[weight = T::WeightInfo::destroy_collection()]603 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {604605 let sender = ensure_signed(origin)?;606 Self::check_owner_permissions(collection_id, sender)?;607608 <AddressTokens<T>>::remove_prefix(collection_id);609 <Allowances<T>>::remove_prefix(collection_id);610 <Balance<T>>::remove_prefix(collection_id);611 <ItemListIndex>::remove(collection_id);612 <AdminList<T>>::remove(collection_id);613 <Collection<T>>::remove(collection_id);614 <WhiteList<T>>::remove_prefix(collection_id);615616 <NftItemList<T>>::remove_prefix(collection_id);617 <FungibleItemList<T>>::remove_prefix(collection_id);618 <ReFungibleItemList<T>>::remove_prefix(collection_id);619620 <NftTransferBasket<T>>::remove_prefix(collection_id);621 <FungibleTransferBasket<T>>::remove_prefix(collection_id);622 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);623624 if CollectionCount::get() > 0625 {626 // bound couter627 let total = CollectionCount::get()628 .checked_sub(1)629 .ok_or(Error::<T>::NumOverflow)?;630631 CollectionCount::put(total);632 }633634 Ok(())635 }636637 /// Add an address to white list.638 /// 639 /// # Permissions640 /// 641 /// * Collection Owner642 /// * Collection Admin643 /// 644 /// # Arguments645 /// 646 /// * collection_id.647 /// 648 /// * address.649 #[weight = T::WeightInfo::add_to_white_list()]650 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{651652 let sender = ensure_signed(origin)?;653 Self::check_owner_or_admin_permissions(collection_id, sender)?;654655 <WhiteList<T>>::insert(collection_id, address, true);656 657 Ok(())658 }659660 /// Remove an address from white list.661 /// 662 /// # Permissions663 /// 664 /// * Collection Owner665 /// * Collection Admin666 /// 667 /// # Arguments668 /// 669 /// * collection_id.670 /// 671 /// * address.672 #[weight = T::WeightInfo::remove_from_white_list()]673 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{674675 let sender = ensure_signed(origin)?;676 Self::check_owner_or_admin_permissions(collection_id, sender)?;677678 <WhiteList<T>>::remove(collection_id, address);679680 Ok(())681 }682683 /// Toggle between normal and white list access for the methods with access for `Anyone`.684 /// 685 /// # Permissions686 /// 687 /// * Collection Owner.688 /// 689 /// # Arguments690 /// 691 /// * collection_id.692 /// 693 /// * mode: [AccessMode]694 #[weight = T::WeightInfo::set_public_access_mode()]695 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult696 {697 let sender = ensure_signed(origin)?;698699 Self::check_owner_permissions(collection_id, sender)?;700 let mut target_collection = <Collection<T>>::get(collection_id);701 target_collection.access = mode;702 <Collection<T>>::insert(collection_id, target_collection);703704 Ok(())705 }706707 /// Allows Anyone to create tokens if:708 /// * White List is enabled, and709 /// * Address is added to white list, and710 /// * This method was called with True parameter711 /// 712 /// # Permissions713 /// * Collection Owner714 ///715 /// # Arguments716 /// 717 /// * collection_id.718 /// 719 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.720 #[weight = T::WeightInfo::set_mint_permission()]721 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult722 {723 let sender = ensure_signed(origin)?;724725 Self::check_owner_permissions(collection_id, sender)?;726 let mut target_collection = <Collection<T>>::get(collection_id);727 target_collection.mint_mode = mint_permission;728 <Collection<T>>::insert(collection_id, target_collection);729730 Ok(())731 }732733 /// Change the owner of the collection.734 /// 735 /// # Permissions736 /// 737 /// * Collection Owner.738 /// 739 /// # Arguments740 /// 741 /// * collection_id.742 /// 743 /// * new_owner.744 #[weight = T::WeightInfo::change_collection_owner()]745 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {746747 let sender = ensure_signed(origin)?;748 Self::check_owner_permissions(collection_id, sender)?;749 let mut target_collection = <Collection<T>>::get(collection_id);750 target_collection.owner = new_owner;751 <Collection<T>>::insert(collection_id, target_collection);752753 Ok(())754 }755756 /// Adds an admin of the Collection.757 /// 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. 758 /// 759 /// # Permissions760 /// 761 /// * Collection Owner.762 /// * Collection Admin.763 /// 764 /// # Arguments765 /// 766 /// * collection_id: ID of the Collection to add admin for.767 /// 768 /// * new_admin_id: Address of new admin to add.769 #[weight = T::WeightInfo::add_collection_admin()]770 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {771772 let sender = ensure_signed(origin)?;773 Self::check_owner_or_admin_permissions(collection_id, sender)?;774 let mut admin_arr: Vec<T::AccountId> = Vec::new();775776 if <AdminList<T>>::contains_key(collection_id)777 {778 admin_arr = <AdminList<T>>::get(collection_id);779 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);780 }781782 // Number of collection admins783 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);784785 admin_arr.push(new_admin_id);786 <AdminList<T>>::insert(collection_id, admin_arr);787788 Ok(())789 }790791 /// 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.792 ///793 /// # Permissions794 /// 795 /// * Collection Owner.796 /// * Collection Admin.797 /// 798 /// # Arguments799 /// 800 /// * collection_id: ID of the Collection to remove admin for.801 /// 802 /// * account_id: Address of admin to remove.803 #[weight = T::WeightInfo::remove_collection_admin()]804 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {805806 let sender = ensure_signed(origin)?;807 Self::check_owner_or_admin_permissions(collection_id, sender)?;808809 if <AdminList<T>>::contains_key(collection_id)810 {811 let mut admin_arr = <AdminList<T>>::get(collection_id);812 admin_arr.retain(|i| *i != account_id);813 <AdminList<T>>::insert(collection_id, admin_arr);814 }815816 Ok(())817 }818819 /// # Permissions820 /// 821 /// * Collection Owner822 /// 823 /// # Arguments824 /// 825 /// * collection_id.826 /// 827 /// * new_sponsor.828 #[weight = T::WeightInfo::set_collection_sponsor()]829 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {830831 let sender = ensure_signed(origin)?;832 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);833834 let mut target_collection = <Collection<T>>::get(collection_id);835 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);836837 target_collection.sponsor = new_sponsor;838 target_collection.sponsor_confirmed = false;839 <Collection<T>>::insert(collection_id, target_collection);840841 Ok(())842 }843844 /// # Permissions845 /// 846 /// * Sponsor.847 /// 848 /// # Arguments849 /// 850 /// * collection_id.851 #[weight = T::WeightInfo::confirm_sponsorship()]852 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {853854 let sender = ensure_signed(origin)?;855 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);856857 let mut target_collection = <Collection<T>>::get(collection_id);858 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);859860 target_collection.sponsor_confirmed = true;861 <Collection<T>>::insert(collection_id, target_collection);862863 Ok(())864 }865866 /// Switch back to pay-per-own-transaction model.867 ///868 /// # Permissions869 ///870 /// * Collection owner.871 /// 872 /// # Arguments873 /// 874 /// * collection_id.875 #[weight = T::WeightInfo::remove_collection_sponsor()]876 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {877878 let sender = ensure_signed(origin)?;879 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);880881 let mut target_collection = <Collection<T>>::get(collection_id);882 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);883884 target_collection.sponsor = T::AccountId::default();885 target_collection.sponsor_confirmed = false;886 <Collection<T>>::insert(collection_id, target_collection);887888 Ok(())889 }890891 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.892 /// 893 /// # Permissions894 /// 895 /// * Collection Owner.896 /// * Collection Admin.897 /// * Anyone if898 /// * White List is enabled, and899 /// * Address is added to white list, and900 /// * MintPermission is enabled (see SetMintPermission method)901 /// 902 /// # Arguments903 /// 904 /// * collection_id: ID of the collection.905 /// 906 /// * owner: Address, initial owner of the NFT.907 ///908 /// * data: Token data to store on chain.909 // #[weight =910 // (130_000_000 as Weight)911 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))912 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))913 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]914915 #[weight = T::WeightInfo::create_item(data.len())]916 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {917918 let sender = ensure_signed(origin)?;919920 Self::collection_exists(collection_id)?;921922 let target_collection = <Collection<T>>::get(collection_id);923924 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;925 Self::validate_create_item_args(&target_collection, &data)?;926 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;927928 Ok(())929 }930931 /// This method creates multiple instances of NFT Collection created with CreateCollection method.932 /// 933 /// # Permissions934 /// 935 /// * Collection Owner.936 /// * Collection Admin.937 /// * Anyone if938 /// * White List is enabled, and939 /// * Address is added to white list, and940 /// * MintPermission is enabled (see SetMintPermission method)941 /// 942 /// # Arguments943 /// 944 /// * collection_id: ID of the collection.945 /// 946 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].947 /// 948 /// * owner: Address, initial owner of the NFT.949 #[weight = T::WeightInfo::create_item(items_data.into_iter()950 .map(|data| { data.len() })951 .sum())]952 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {953954 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);955 let sender = ensure_signed(origin)?;956957 Self::collection_exists(collection_id)?;958 let target_collection = <Collection<T>>::get(collection_id);959960 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;961962 for data in &items_data {963 Self::validate_create_item_args(&target_collection, data)?;964 }965 for data in &items_data {966 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;967 }968969 Ok(())970 }971972 /// Destroys a concrete instance of NFT.973 /// 974 /// # Permissions975 /// 976 /// * Collection Owner.977 /// * Collection Admin.978 /// * Current NFT Owner.979 /// 980 /// # Arguments981 /// 982 /// * collection_id: ID of the collection.983 /// 984 /// * item_id: ID of NFT to burn.985 #[weight = T::WeightInfo::burn_item()]986 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {987988 let sender = ensure_signed(origin)?;989 Self::collection_exists(collection_id)?;990991 // Transfer permissions check992 let target_collection = <Collection<T>>::get(collection_id);993 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||994 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),995 Error::<T>::NoPermission);996997 if target_collection.access == AccessMode::WhiteList {998 Self::check_white_list(collection_id, &sender)?;999 }10001001 match target_collection.mode1002 {1003 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1004 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1005 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1006 _ => ()1007 };10081009 // call event1010 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10111012 Ok(())1013 }10141015 /// Change ownership of the token.1016 /// 1017 /// # Permissions1018 /// 1019 /// * Collection Owner1020 /// * Collection Admin1021 /// * Current NFT owner1022 ///1023 /// # Arguments1024 /// 1025 /// * recipient: Address of token recipient.1026 /// 1027 /// * collection_id.1028 /// 1029 /// * item_id: ID of the item1030 /// * Non-Fungible Mode: Required.1031 /// * Fungible Mode: Ignored.1032 /// * Re-Fungible Mode: Required.1033 /// 1034 /// * value: Amount to transfer.1035 /// * Non-Fungible Mode: Ignored1036 /// * Fungible Mode: Must specify transferred amount1037 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1038 #[weight = T::WeightInfo::transfer()]1039 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10401041 let sender = ensure_signed(origin)?;1042 let target_collection = <Collection<T>>::get(collection_id);10431044 // Limits check1045 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10461047 // Transfer permissions check1048 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1049 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1050 Error::<T>::NoPermission);10511052 if target_collection.access == AccessMode::WhiteList {1053 Self::check_white_list(collection_id, &sender)?;1054 Self::check_white_list(collection_id, &recipient)?;1055 }10561057 match target_collection.mode1058 {1059 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1060 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1061 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1062 _ => ()1063 };10641065 Ok(())1066 }10671068 /// Set, change, or remove approved address to transfer the ownership of the NFT.1069 /// 1070 /// # Permissions1071 /// 1072 /// * Collection Owner1073 /// * Collection Admin1074 /// * Current NFT owner1075 /// 1076 /// # Arguments1077 /// 1078 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1079 /// 1080 /// * collection_id.1081 /// 1082 /// * item_id: ID of the item.1083 #[weight = T::WeightInfo::approve()]1084 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10851086 let sender = ensure_signed(origin)?;10871088 // Transfer permissions check1089 let target_collection = <Collection<T>>::get(collection_id);1090 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1091 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1092 Error::<T>::NoPermission);10931094 if target_collection.access == AccessMode::WhiteList {1095 Self::check_white_list(collection_id, &sender)?;1096 Self::check_white_list(collection_id, &spender)?;1097 }10981099 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1100 let mut allowance: u128 = amount;1101 if allowance_exists {1102 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1103 }1104 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11051106 Ok(())1107 }1108 1109 /// 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.1110 /// 1111 /// # Permissions1112 /// * Collection Owner1113 /// * Collection Admin1114 /// * Current NFT owner1115 /// * Address approved by current NFT owner1116 /// 1117 /// # Arguments1118 /// 1119 /// * from: Address that owns token.1120 /// 1121 /// * recipient: Address of token recipient.1122 /// 1123 /// * collection_id.1124 /// 1125 /// * item_id: ID of the item.1126 /// 1127 /// * value: Amount to transfer.1128 #[weight = T::WeightInfo::transfer_from()]1129 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11301131 let sender = ensure_signed(origin)?;1132 let mut appoved_transfer = false;11331134 // Check approval1135 let mut approval: u128 = 0;1136 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1137 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1138 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1139 appoved_transfer = true;1140 }11411142 let target_collection = <Collection<T>>::get(collection_id);11431144 // Limits check1145 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11461147 // Transfer permissions check 1148 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1149 Error::<T>::NoPermission);11501151 if target_collection.access == AccessMode::WhiteList {1152 Self::check_white_list(collection_id, &sender)?;1153 Self::check_white_list(collection_id, &recipient)?;1154 }11551156 // Reduce approval by transferred amount or remove if remaining approval drops to 01157 if approval.checked_sub(value).unwrap_or(0) > 0 {1158 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1159 }1160 else {1161 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1162 }11631164 match target_collection.mode1165 {1166 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1167 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1168 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1169 _ => ()1170 };11711172 Ok(())1173 }11741175 #[weight = 0]1176 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11771178 // let no_perm_mes = "You do not have permissions to modify this collection";1179 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1180 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1181 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11821183 // // on_nft_received call11841185 // Self::transfer(origin, collection_id, item_id, new_owner)?;11861187 Ok(())1188 }11891190 /// Set off-chain data schema.1191 /// 1192 /// # Permissions1193 /// 1194 /// * Collection Owner1195 /// * Collection Admin1196 /// 1197 /// # Arguments1198 /// 1199 /// * collection_id.1200 /// 1201 /// * schema: String representing the offchain data schema.1202 #[weight = T::WeightInfo::set_variable_meta_data()]1203 pub fn set_variable_meta_data (1204 origin,1205 collection_id: CollectionId,1206 item_id: TokenId,1207 data: Vec<u8>1208 ) -> DispatchResult {1209 let sender = ensure_signed(origin)?;1210 1211 Self::collection_exists(collection_id)?;1212 1213 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12141215 // Modify permissions check1216 let target_collection = <Collection<T>>::get(collection_id);1217 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1218 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1219 Error::<T>::NoPermission);12201221 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12221223 match target_collection.mode1224 {1225 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1226 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1227 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1228 _ => fail!(Error::<T>::UnexpectedCollectionType)1229 };12301231 Ok(())1232 }1233 1234 /// Set schema standard1235 /// ImageURL1236 /// Unique1237 /// 1238 /// # Permissions1239 /// 1240 /// * Collection Owner1241 /// * Collection Admin1242 /// 1243 /// # Arguments1244 /// 1245 /// * collection_id.1246 /// 1247 /// * schema: SchemaVersion: enum1248 #[weight = T::WeightInfo::set_schema_version()]1249 pub fn set_schema_version(1250 origin,1251 collection_id: CollectionId,1252 version: SchemaVersion1253 ) -> DispatchResult {1254 let sender = ensure_signed(origin)?;1255 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1256 let mut target_collection = <Collection<T>>::get(collection_id);1257 target_collection.schema_version = version;1258 <Collection<T>>::insert(collection_id, target_collection);12591260 Ok(())1261 }12621263 /// Set off-chain data schema.1264 /// 1265 /// # Permissions1266 /// 1267 /// * Collection Owner1268 /// * Collection Admin1269 /// 1270 /// # Arguments1271 /// 1272 /// * collection_id.1273 /// 1274 /// * schema: String representing the offchain data schema.1275 #[weight = T::WeightInfo::set_offchain_schema()]1276 pub fn set_offchain_schema(1277 origin,1278 collection_id: CollectionId,1279 schema: Vec<u8>1280 ) -> DispatchResult {1281 let sender = ensure_signed(origin)?;1282 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12831284 let mut target_collection = <Collection<T>>::get(collection_id);1285 target_collection.offchain_schema = schema;1286 <Collection<T>>::insert(collection_id, target_collection);12871288 Ok(())1289 }12901291 /// Set const on-chain data schema.1292 /// 1293 /// # Permissions1294 /// 1295 /// * Collection Owner1296 /// * Collection Admin1297 /// 1298 /// # Arguments1299 /// 1300 /// * collection_id.1301 /// 1302 /// * schema: String representing the const on-chain data schema.1303 #[weight = T::WeightInfo::set_const_on_chain_schema()]1304 pub fn set_const_on_chain_schema (1305 origin,1306 collection_id: CollectionId,1307 schema: Vec<u8>1308 ) -> DispatchResult {1309 let sender = ensure_signed(origin)?;1310 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13111312 let mut target_collection = <Collection<T>>::get(collection_id);1313 target_collection.const_on_chain_schema = schema;1314 <Collection<T>>::insert(collection_id, target_collection);13151316 Ok(())1317 }13181319 /// Set variable on-chain data schema.1320 /// 1321 /// # Permissions1322 /// 1323 /// * Collection Owner1324 /// * Collection Admin1325 /// 1326 /// # Arguments1327 /// 1328 /// * collection_id.1329 /// 1330 /// * schema: String representing the variable on-chain data schema.1331 #[weight = T::WeightInfo::set_const_on_chain_schema()]1332 pub fn set_variable_on_chain_schema (1333 origin,1334 collection_id: CollectionId,1335 schema: Vec<u8>1336 ) -> DispatchResult {1337 let sender = ensure_signed(origin)?;1338 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13391340 let mut target_collection = <Collection<T>>::get(collection_id);1341 target_collection.variable_on_chain_schema = schema;1342 <Collection<T>>::insert(collection_id, target_collection);13431344 Ok(())1345 }13461347 // Sudo permissions function1348 #[weight = T::WeightInfo::set_chain_limits()]1349 pub fn set_chain_limits(1350 origin,1351 limits: ChainLimits1352 ) -> DispatchResult {13531354 #[cfg(not(feature = "runtime-benchmarks"))]1355 ensure_root(origin)?;13561357 <ChainLimit>::put(limits);1358 Ok(())1359 }13601361 /// Enable smart contract self-sponsoring.1362 /// 1363 /// # Permissions1364 /// 1365 /// * Contract Owner1366 /// 1367 /// # Arguments1368 /// 1369 /// * contract address1370 /// * enable flag1371 /// 1372 #[weight = T::WeightInfo::enable_contract_sponsoring()]1373 pub fn enable_contract_sponsoring(1374 origin,1375 contract_address: T::AccountId,1376 enable: bool1377 ) -> DispatchResult {13781379 let sender = ensure_signed(origin)?;13801381 #[cfg(feature = "runtime-benchmarks")]1382 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13831384 Self::ensure_contract_owned(sender, &contract_address)?;13851386 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1387 Ok(())1388 }13891390 /// Set the rate limit for contract sponsoring to specified number of blocks.1391 /// 1392 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1393 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1394 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1395 /// from contract endowment if there are at least B blocks between such transactions. 1396 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1397 /// 1398 /// # Permissions1399 /// 1400 /// * Contract Owner1401 /// 1402 /// # Arguments1403 /// 1404 /// -`contract_address`: Address of the contract to sponsor1405 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1406 /// 1407 #[weight = T::WeightInfo::set_contract_sponsoring_rate_limit()]1408 pub fn set_contract_sponsoring_rate_limit(1409 origin,1410 contract_address: T::AccountId,1411 rate_limit: T::BlockNumber1412 ) -> DispatchResult {1413 let sender = ensure_signed(origin)?;14141415 #[cfg(feature = "runtime-benchmarks")]1416 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14171418 Self::ensure_contract_owned(sender, &contract_address)?;1419 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1420 Ok(())1421 }14221423 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1424 /// 1425 /// # Permissions1426 /// 1427 /// * Address that deployed smart contract.1428 /// 1429 /// # Arguments1430 /// 1431 /// -`contract_address`: Address of the contract.1432 /// 1433 /// - `enable`: . 1434 #[weight = T::WeightInfo::toggle_contract_white_list()]1435 pub fn toggle_contract_white_list(1436 origin,1437 contract_address: T::AccountId,1438 enable: bool1439 ) -> DispatchResult {1440 let sender = ensure_signed(origin)?;14411442 #[cfg(feature = "runtime-benchmarks")]1443 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14441445 Self::ensure_contract_owned(sender, &contract_address)?;1446 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1447 Ok(())1448 }1449 1450 /// Add an address to smart contract white list.1451 /// 1452 /// # Permissions1453 /// 1454 /// * Address that deployed smart contract.1455 /// 1456 /// # Arguments1457 /// 1458 /// -`contract_address`: Address of the contract.1459 ///1460 /// -`account_address`: Address to add.1461 #[weight = T::WeightInfo::add_to_contract_white_list()]1462 pub fn add_to_contract_white_list(1463 origin,1464 contract_address: T::AccountId,1465 account_address: T::AccountId1466 ) -> DispatchResult {1467 let sender = ensure_signed(origin)?;14681469 #[cfg(feature = "runtime-benchmarks")]1470 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1471 1472 Self::ensure_contract_owned(sender, &contract_address)?; 1473 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1474 Ok(())1475 }14761477 /// Remove an address from smart contract white list.1478 /// 1479 /// # Permissions1480 /// 1481 /// * Address that deployed smart contract.1482 /// 1483 /// # Arguments1484 /// 1485 /// -`contract_address`: Address of the contract.1486 ///1487 /// -`account_address`: Address to remove.1488 #[weight = T::WeightInfo::remove_from_contract_white_list()]1489 pub fn remove_from_contract_white_list(1490 origin,1491 contract_address: T::AccountId,1492 account_address: T::AccountId1493 ) -> DispatchResult {1494 let sender = ensure_signed(origin)?;14951496 #[cfg(feature = "runtime-benchmarks")]1497 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14981499 Self::ensure_contract_owned(sender, &contract_address)?;1500 <ContractWhiteList<T>>::remove(contract_address, account_address);1501 Ok(())1502 }15031504 #[weight = T::WeightInfo::set_collection_limits()]1505 pub fn set_collection_limits(1506 origin,1507 collection_id: u32,1508 limits: CollectionLimits,1509 ) -> DispatchResult {1510 let sender = ensure_signed(origin)?;1511 Self::check_owner_permissions(collection_id, sender.clone())?;1512 let mut target_collection = <Collection<T>>::get(collection_id);1513 let chain_limits = ChainLimit::get();1514 let climits = target_collection.limits;15151516 // collection bounds1517 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1518 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1519 Error::<T>::CollectionLimitBoundsExceeded);15201521 // token_limit check prev1522 ensure!(climits.token_limit > limits.token_limit && 1523 limits.token_limit <= chain_limits.account_token_ownership_limit, 1524 Error::<T>::AccountTokenLimitExceeded);15251526 target_collection.limits = limits;1527 <Collection<T>>::insert(collection_id, target_collection);15281529 Ok(())1530 } 1531 }1532}15331534impl<T: Trait> Module<T> {15351536 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15371538 // check token limit and account token limit1539 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1540 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1541 1542 Ok(())1543 }15441545 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15461547 // check token limit and account token limit1548 let total_items: u32 = ItemListIndex::get(collection_id);1549 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1550 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1551 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15521553 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1554 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1555 Self::check_white_list(collection_id, owner)?;1556 Self::check_white_list(collection_id, sender)?;1557 }15581559 Ok(())1560 }15611562 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1563 match target_collection.mode1564 {1565 CollectionMode::NFT => {1566 if let CreateItemData::NFT(data) = data {1567 // check sizes1568 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1569 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1570 } else {1571 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1572 }1573 },1574 CollectionMode::Fungible(_) => {1575 if let CreateItemData::Fungible(_) = data {1576 } else {1577 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1578 }1579 },1580 CollectionMode::ReFungible(_) => {1581 if let CreateItemData::ReFungible(data) = data {15821583 // check sizes1584 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1585 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1586 } else {1587 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1588 }1589 },1590 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1591 };15921593 Ok(())1594 }15951596 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1597 match data1598 {1599 CreateItemData::NFT(data) => {1600 let item = NftItemType {1601 owner,1602 const_data: data.const_data,1603 variable_data: data.variable_data1604 };16051606 Self::add_nft_item(collection_id, item)?;1607 },1608 CreateItemData::Fungible(data) => {1609 Self::add_fungible_item(collection_id, &owner, data.value)?;1610 },1611 CreateItemData::ReFungible(data) => {1612 let mut owner_list = Vec::new();1613 let value = (10 as u128).pow(collection.decimal_points as u32);1614 owner_list.push(Ownership {owner: owner.clone(), fraction: value});16151616 let item = ReFungibleItemType {1617 owner: owner_list,1618 const_data: data.const_data,1619 variable_data: data.variable_data1620 };16211622 Self::add_refungible_item(collection_id, item)?;1623 }1624 };16251626 // call event1627 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16281629 Ok(())1630 }16311632 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16331634 // Does new owner already have an account?1635 let mut balance: u128 = 0;1636 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1637 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1638 } 16391640 // Mint 1641 let item = FungibleItemType {1642 value: balance + value1643 };1644 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16451646 // Update balance1647 let new_balance = <Balance<T>>::get(collection_id, owner)1648 .checked_add(value)1649 .ok_or(Error::<T>::NumOverflow)?;1650 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16511652 Ok(())1653 }16541655 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1656 let current_index = <ItemListIndex>::get(collection_id)1657 .checked_add(1)1658 .ok_or(Error::<T>::NumOverflow)?;1659 let itemcopy = item.clone();16601661 let value = item.owner.first().unwrap().fraction;1662 let owner = item.owner.first().unwrap().owner.clone();16631664 Self::add_token_index(collection_id, current_index, owner.clone())?;16651666 <ItemListIndex>::insert(collection_id, current_index);1667 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16681669 // Update balance1670 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1671 .checked_add(value)1672 .ok_or(Error::<T>::NumOverflow)?;1673 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16741675 Ok(())1676 }16771678 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1679 let current_index = <ItemListIndex>::get(collection_id)1680 .checked_add(1)1681 .ok_or(Error::<T>::NumOverflow)?;16821683 let item_owner = item.owner.clone();1684 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16851686 <ItemListIndex>::insert(collection_id, current_index);1687 <NftItemList<T>>::insert(collection_id, current_index, item);16881689 // Update balance1690 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1691 .checked_add(1)1692 .ok_or(Error::<T>::NumOverflow)?;1693 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16941695 Ok(())1696 }16971698 fn burn_refungible_item(1699 collection_id: CollectionId,1700 item_id: TokenId,1701 owner: T::AccountId,1702 ) -> DispatchResult {1703 ensure!(1704 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1705 Error::<T>::TokenNotFound1706 );1707 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1708 let item = collection1709 .owner1710 .iter()1711 .filter(|&i| i.owner == owner)1712 .next()1713 .unwrap();1714 Self::remove_token_index(collection_id, item_id, owner.clone())?;17151716 // update balance1717 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1718 .checked_sub(item.fraction)1719 .ok_or(Error::<T>::NumOverflow)?;1720 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17211722 <ReFungibleItemList<T>>::remove(collection_id, item_id);17231724 Ok(())1725 }17261727 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1728 ensure!(1729 <NftItemList<T>>::contains_key(collection_id, item_id),1730 Error::<T>::TokenNotFound1731 );1732 let item = <NftItemList<T>>::get(collection_id, item_id);1733 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17341735 // update balance1736 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1737 .checked_sub(1)1738 .ok_or(Error::<T>::NumOverflow)?;1739 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1740 <NftItemList<T>>::remove(collection_id, item_id);17411742 Ok(())1743 }17441745 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1746 ensure!(1747 <FungibleItemList<T>>::contains_key(collection_id, owner),1748 Error::<T>::TokenNotFound1749 );1750 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1751 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17521753 // update balance1754 let new_balance = <Balance<T>>::get(collection_id, owner)1755 .checked_sub(value)1756 .ok_or(Error::<T>::NumOverflow)?;1757 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17581759 if balance.value - value > 0 {1760 balance.value -= value;1761 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1762 }1763 else {1764 <FungibleItemList<T>>::remove(collection_id, owner);1765 }17661767 Ok(())1768 }17691770 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1771 ensure!(1772 <Collection<T>>::contains_key(collection_id),1773 Error::<T>::CollectionNotFound1774 );1775 Ok(())1776 }17771778 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1779 Self::collection_exists(collection_id)?;17801781 let target_collection = <Collection<T>>::get(collection_id);1782 ensure!(1783 subject == target_collection.owner,1784 Error::<T>::NoPermission1785 );17861787 Ok(())1788 }17891790 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1791 let target_collection = <Collection<T>>::get(collection_id);1792 let mut result: bool = subject == target_collection.owner;1793 let exists = <AdminList<T>>::contains_key(collection_id);17941795 if !result & exists {1796 if <AdminList<T>>::get(collection_id).contains(&subject) {1797 result = true1798 }1799 }18001801 result1802 }18031804 fn check_owner_or_admin_permissions(1805 collection_id: CollectionId,1806 subject: T::AccountId,1807 ) -> DispatchResult {1808 Self::collection_exists(collection_id)?;1809 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18101811 ensure!(1812 result,1813 Error::<T>::NoPermission1814 );1815 Ok(())1816 }18171818 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1819 let target_collection = <Collection<T>>::get(collection_id);18201821 match target_collection.mode {1822 CollectionMode::NFT => {1823 <NftItemList<T>>::get(collection_id, item_id).owner == subject1824 }1825 CollectionMode::Fungible(_) => {1826 <FungibleItemList<T>>::contains_key(collection_id, &subject)1827 }1828 CollectionMode::ReFungible(_) => {1829 <ReFungibleItemList<T>>::get(collection_id, item_id)1830 .owner1831 .iter()1832 .any(|i| i.owner == subject)1833 }1834 CollectionMode::Invalid => false,1835 }1836 }18371838 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1839 let mes = Error::<T>::AddresNotInWhiteList;1840 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18411842 Ok(())1843 }18441845 fn transfer_fungible(1846 collection_id: CollectionId,1847 value: u128,1848 owner: &T::AccountId,1849 recipient: &T::AccountId,1850 ) -> DispatchResult {1851 ensure!(1852 <FungibleItemList<T>>::contains_key(collection_id, owner),1853 Error::<T>::TokenNotFound1854 );18551856 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1857 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18581859 // Send balance to recipient (updates balanceOf of recipient)1860 Self::add_fungible_item(collection_id, recipient, value)?;18611862 // update balanceOf of sender1863 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18641865 // Reduce or remove sender1866 if balance.value == value {1867 <FungibleItemList<T>>::remove(collection_id, owner);1868 }1869 else {1870 balance.value -= value;1871 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1872 }18731874 Ok(())1875 }18761877 fn transfer_refungible(1878 collection_id: CollectionId,1879 item_id: TokenId,1880 value: u128,1881 owner: T::AccountId,1882 new_owner: T::AccountId,1883 ) -> DispatchResult {1884 ensure!(1885 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1886 Error::<T>::TokenNotFound1887 );18881889 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1890 let item = full_item1891 .owner1892 .iter()1893 .filter(|i| i.owner == owner)1894 .next()1895 .ok_or(Error::<T>::NumOverflow)?;1896 let amount = item.fraction;18971898 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18991900 // update balance1901 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1902 .checked_sub(value)1903 .ok_or(Error::<T>::NumOverflow)?;1904 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19051906 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1907 .checked_add(value)1908 .ok_or(Error::<T>::NumOverflow)?;1909 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19101911 let old_owner = item.owner.clone();1912 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19131914 // transfer1915 if amount == value && !new_owner_has_account {1916 // change owner1917 // new owner do not have account1918 let mut new_full_item = full_item.clone();1919 new_full_item1920 .owner1921 .iter_mut()1922 .find(|i| i.owner == owner)1923 .unwrap()1924 .owner = new_owner.clone();1925 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19261927 // update index collection1928 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1929 } else {1930 let mut new_full_item = full_item.clone();1931 new_full_item1932 .owner1933 .iter_mut()1934 .find(|i| i.owner == owner)1935 .unwrap()1936 .fraction -= value;19371938 // separate amount1939 if new_owner_has_account {1940 // new owner has account1941 new_full_item1942 .owner1943 .iter_mut()1944 .find(|i| i.owner == new_owner)1945 .unwrap()1946 .fraction += value;1947 } else {1948 // new owner do not have account1949 new_full_item.owner.push(Ownership {1950 owner: new_owner.clone(),1951 fraction: value,1952 });1953 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1954 }19551956 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1957 }19581959 Ok(())1960 }19611962 fn transfer_nft(1963 collection_id: CollectionId,1964 item_id: TokenId,1965 sender: T::AccountId,1966 new_owner: T::AccountId,1967 ) -> DispatchResult {1968 ensure!(1969 <NftItemList<T>>::contains_key(collection_id, item_id),1970 Error::<T>::TokenNotFound1971 );19721973 let mut item = <NftItemList<T>>::get(collection_id, item_id);19741975 ensure!(1976 sender == item.owner,1977 Error::<T>::MustBeTokenOwner1978 );19791980 // update balance1981 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1982 .checked_sub(1)1983 .ok_or(Error::<T>::NumOverflow)?;1984 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19851986 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1987 .checked_add(1)1988 .ok_or(Error::<T>::NumOverflow)?;1989 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19901991 // change owner1992 let old_owner = item.owner.clone();1993 item.owner = new_owner.clone();1994 <NftItemList<T>>::insert(collection_id, item_id, item);19951996 // update index collection1997 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19981999 Ok(())2000 }2001 2002 fn item_exists(2003 collection_id: CollectionId,2004 item_id: TokenId,2005 mode: &CollectionMode2006 ) -> DispatchResult {2007 match mode {2008 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2009 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2010 _ => ()2011 };2012 2013 Ok(())2014 }20152016 fn set_re_fungible_variable_data(2017 collection_id: CollectionId,2018 item_id: TokenId,2019 data: Vec<u8>2020 ) -> DispatchResult {2021 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20222023 item.variable_data = data;20242025 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20262027 Ok(())2028 }20292030 fn set_nft_variable_data(2031 collection_id: CollectionId,2032 item_id: TokenId,2033 data: Vec<u8>2034 ) -> DispatchResult {2035 let mut item = <NftItemList<T>>::get(collection_id, item_id);2036 2037 item.variable_data = data;20382039 <NftItemList<T>>::insert(collection_id, item_id, item);2040 2041 Ok(())2042 }20432044 fn init_collection(item: &CollectionType<T::AccountId>) {2045 // check params2046 assert!(2047 item.decimal_points <= MAX_DECIMAL_POINTS,2048 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2049 );2050 assert!(2051 item.name.len() <= 64,2052 "Collection name can not be longer than 63 char"2053 );2054 assert!(2055 item.name.len() <= 256,2056 "Collection description can not be longer than 255 char"2057 );2058 assert!(2059 item.token_prefix.len() <= 16,2060 "Token prefix can not be longer than 15 char"2061 );20622063 // Generate next collection ID2064 let next_id = CreatedCollectionCount::get()2065 .checked_add(1)2066 .unwrap();20672068 CreatedCollectionCount::put(next_id);2069 }20702071 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2072 let current_index = <ItemListIndex>::get(collection_id)2073 .checked_add(1)2074 .unwrap();20752076 let item_owner = item.owner.clone();2077 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20782079 <ItemListIndex>::insert(collection_id, current_index);20802081 // Update balance2082 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2083 .checked_add(1)2084 .unwrap();2085 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2086 }20872088 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2089 let current_index = <ItemListIndex>::get(collection_id)2090 .checked_add(1)2091 .unwrap();20922093 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();20942095 <ItemListIndex>::insert(collection_id, current_index);20962097 // Update balance2098 let new_balance = <Balance<T>>::get(collection_id, owner)2099 .checked_add(item.value)2100 .unwrap();2101 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2102 }21032104 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2105 let current_index = <ItemListIndex>::get(collection_id)2106 .checked_add(1)2107 .unwrap();21082109 let value = item.owner.first().unwrap().fraction;2110 let owner = item.owner.first().unwrap().owner.clone();21112112 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();21132114 <ItemListIndex>::insert(collection_id, current_index);21152116 // Update balance2117 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2118 .checked_add(value)2119 .unwrap();2120 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2121 }21222123 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21242125 // add to account limit2126 if <AccountItemCount<T>>::contains_key(owner.clone()) {21272128 // bound Owned tokens by a single address2129 let count = <AccountItemCount<T>>::get(owner.clone());2130 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21312132 <AccountItemCount<T>>::insert(owner.clone(), count2133 .checked_add(1)2134 .ok_or(Error::<T>::NumOverflow)?);2135 }2136 else {2137 <AccountItemCount<T>>::insert(owner.clone(), 1);2138 }21392140 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2141 if list_exists {2142 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2143 let item_contains = list.contains(&item_index.clone());21442145 if !item_contains {2146 list.push(item_index.clone());2147 }21482149 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2150 } else {2151 let mut itm = Vec::new();2152 itm.push(item_index.clone());2153 <AddressTokens<T>>::insert(collection_id, owner, itm);2154 2155 }21562157 Ok(())2158 }21592160 fn remove_token_index(2161 collection_id: CollectionId,2162 item_index: TokenId,2163 owner: T::AccountId,2164 ) -> DispatchResult {21652166 // update counter2167 <AccountItemCount<T>>::insert(owner.clone(), 2168 <AccountItemCount<T>>::get(owner.clone())2169 .checked_sub(1)2170 .ok_or(Error::<T>::NumOverflow)?);217121722173 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2174 if list_exists {2175 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2176 let item_contains = list.contains(&item_index.clone());21772178 if item_contains {2179 list.retain(|&item| item != item_index);2180 <AddressTokens<T>>::insert(collection_id, owner, list);2181 }2182 }21832184 Ok(())2185 }21862187 fn move_token_index(2188 collection_id: CollectionId,2189 item_index: TokenId,2190 old_owner: T::AccountId,2191 new_owner: T::AccountId,2192 ) -> DispatchResult {2193 Self::remove_token_index(collection_id, item_index, old_owner)?;2194 Self::add_token_index(collection_id, item_index, new_owner)?;21952196 Ok(())2197 }2198 2199 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2200 if <ContractOwner<T>>::contains_key(contract.clone()) {2201 let owner = <ContractOwner<T>>::get(contract);2202 ensure!(account == owner, Error::<T>::NoPermission);2203 } else {2204 fail!(Error::<T>::NoPermission);2205 }22062207 Ok(())2208 }2209}22102211////////////////////////////////////////////////////////////////////////////////////////////////////2212// Economic models2213// #region22142215/// Fee multiplier.2216pub type Multiplier = FixedU128;22172218type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2219 <T as system::Trait>::AccountId,2220>>::Balance;2221type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2222 <T as system::Trait>::AccountId,2223>>::NegativeImbalance;22242225/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2226/// in the queue.2227#[derive(Encode, Decode, Clone, Eq, PartialEq)]2228pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2229 #[codec(compact)] BalanceOf<T>2230);22312232impl<T: Trait + Send + Sync> sp_std::fmt::Debug2233 for ChargeTransactionPayment<T>2234{2235 #[cfg(feature = "std")]2236 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2237 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2238 }2239 #[cfg(not(feature = "std"))]2240 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2241 Ok(())2242 }2243}22442245impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2246where2247 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2248 BalanceOf<T>: Send + Sync + FixedPointOperand,2249{2250 /// utility constructor. Used only in client/factory code.2251 pub fn from(fee: BalanceOf<T>) -> Self {2252 Self(fee)2253 }22542255 pub fn traditional_fee(2256 len: usize,2257 info: &DispatchInfoOf<T::Call>,2258 tip: BalanceOf<T>,2259 ) -> BalanceOf<T>2260 where2261 T::Call: Dispatchable<Info = DispatchInfo>,2262 {2263 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2264 }22652266 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2267 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2268 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2269 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2270 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2271 }22722273 fn withdraw_fee(2274 &self,2275 who: &T::AccountId,2276 call: &T::Call,2277 info: &DispatchInfoOf<T::Call>,2278 len: usize,2279 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2280 let tip = self.0;22812282 // Set fee based on call type. Creating collection costs 1 Unique.2283 // All other transactions have traditional fees so far2284 // let fee = match call.is_sub_type() {2285 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2286 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2287 // // _ => <BalanceOf<T>>::from(100)2288 // };2289 let fee = Self::traditional_fee(len, info, tip);22902291 // Only mess with balances if fee is not zero.2292 if fee.is_zero() {2293 return Ok((fee, None));2294 }22952296 // Determine who is paying transaction fee based on ecnomic model2297 // Parse call to extract collection ID and access collection sponsor2298 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2299 Some(Call::create_item(collection_id, _owner, _properties)) => {23002301 // sponsor timeout2302 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;23032304 let limit = <Collection<T>>::get(collection_id).limits.sponsor_transfer_timeout;2305 let mut sponsored = true;2306 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2307 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2308 let limit_time = last_tx_block + limit.into();2309 if block_number <= limit_time {2310 sponsored = false;2311 }2312 }2313 if sponsored {2314 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2315 }23162317 // check free create limit2318 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2319 (<Collection<T>>::get(collection_id).sponsor_confirmed) &&2320 (sponsored)2321 {2322 <Collection<T>>::get(collection_id).sponsor2323 } else {2324 T::AccountId::default()2325 }2326 }2327 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2328 2329 let mut sponsor_transfer = false;2330 if <Collection<T>>::get(collection_id).sponsor_confirmed {23312332 let collection_limits = <Collection<T>>::get(collection_id).limits;2333 let collection_mode = <Collection<T>>::get(collection_id).mode;2334 2335 // sponsor timeout2336 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2337 sponsor_transfer = match collection_mode {2338 CollectionMode::NFT => {2339 2340 // get correct limit2341 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2342 collection_limits.sponsor_transfer_timeout2343 } else {2344 ChainLimit::get().nft_sponsor_transfer_timeout2345 };2346 2347 let mut sponsored = true;2348 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2349 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2350 let limit_time = last_tx_block + limit.into();2351 if block_number <= limit_time {2352 sponsored = false;2353 }2354 }2355 if sponsored {2356 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2357 }23582359 sponsored2360 }2361 CollectionMode::Fungible(_) => {2362 2363 // get correct limit2364 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2365 collection_limits.sponsor_transfer_timeout2366 } else {2367 ChainLimit::get().fungible_sponsor_transfer_timeout2368 };2369 2370 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2371 let mut sponsored = true;2372 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2373 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2374 let limit_time = last_tx_block + limit.into();2375 if block_number <= limit_time {2376 sponsored = false;2377 }2378 }2379 if sponsored {2380 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2381 }23822383 sponsored2384 }2385 CollectionMode::ReFungible(_) => {2386 2387 // get correct limit2388 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2389 collection_limits.sponsor_transfer_timeout2390 } else {2391 ChainLimit::get().refungible_sponsor_transfer_timeout2392 };2393 2394 let mut sponsored = true;2395 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2396 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2397 let limit_time = last_tx_block + limit.into();2398 if block_number <= limit_time {2399 sponsored = false;2400 }2401 }2402 if sponsored {2403 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2404 }24052406 sponsored2407 }2408 _ => {2409 false2410 },2411 };2412 }24132414 if !sponsor_transfer {2415 T::AccountId::default()2416 } else {2417 <Collection<T>>::get(collection_id).sponsor2418 }2419 }24202421 _ => T::AccountId::default(),2422 };24232424 // Sponsor smart contracts2425 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24262427 // On instantiation: set the contract owner2428 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24292430 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2431 code_hash,2432 &data,2433 &who,2434 );2435 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24362437 T::AccountId::default()2438 },24392440 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2441 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24422443 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24442445 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2446 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2447 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2448 2449 if !owned_contract && white_list_enabled {2450 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2451 return Err(InvalidTransaction::Call.into());2452 }2453 }24542455 let mut sponsor_transfer = false;2456 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2457 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2458 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2459 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2460 let limit_time = last_tx_block + rate_limit;24612462 if block_number >= limit_time {2463 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2464 sponsor_transfer = true;2465 }2466 } else {2467 sponsor_transfer = false;2468 }2469 2470 2471 let mut sp = T::AccountId::default();2472 if sponsor_transfer {2473 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2474 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2475 sp = called_contract;2476 }2477 }2478 }24792480 sp2481 },24822483 _ => sponsor,2484 };24852486 let mut who_pays_fee: T::AccountId = sponsor.clone();2487 if sponsor == T::AccountId::default() {2488 who_pays_fee = who.clone();2489 }24902491 match <T as transaction_payment::Trait>::Currency::withdraw(2492 &who_pays_fee,2493 fee,2494 if tip.is_zero() {2495 WithdrawReason::TransactionPayment.into()2496 } else {2497 WithdrawReason::TransactionPayment | WithdrawReason::Tip2498 },2499 ExistenceRequirement::KeepAlive,2500 ) {2501 Ok(imbalance) => Ok((fee, Some(imbalance))),2502 Err(_) => Err(InvalidTransaction::Payment.into()),2503 }2504 }2505}250625072508impl<T: Trait + Send + Sync> SignedExtension2509 for ChargeTransactionPayment<T>2510where2511 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2512 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2513{2514 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2515 type AccountId = T::AccountId;2516 type Call = T::Call;2517 type AdditionalSigned = ();2518 type Pre = (2519 BalanceOf<T>,2520 Self::AccountId,2521 Option<NegativeImbalanceOf<T>>,2522 BalanceOf<T>,2523 );2524 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2525 Ok(())2526 }25272528 fn validate(2529 &self,2530 who: &Self::AccountId,2531 call: &Self::Call,2532 info: &DispatchInfoOf<Self::Call>,2533 len: usize,2534 ) -> TransactionValidity {2535 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2536 Ok(ValidTransaction {2537 priority: Self::get_priority(len, info, fee),2538 ..Default::default()2539 })2540 }25412542 fn pre_dispatch(2543 self,2544 who: &Self::AccountId,2545 call: &Self::Call,2546 info: &DispatchInfoOf<Self::Call>,2547 len: usize,2548 ) -> Result<Self::Pre, TransactionValidityError> {2549 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2550 Ok((self.0, who.clone(), imbalance, fee))2551 }25522553 fn post_dispatch(2554 pre: Self::Pre,2555 info: &DispatchInfoOf<Self::Call>,2556 post_info: &PostDispatchInfoOf<Self::Call>,2557 len: usize,2558 _result: &DispatchResult,2559 ) -> Result<(), TransactionValidityError> {2560 let (tip, who, imbalance, fee) = pre;2561 if let Some(payed) = imbalance {2562 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2563 len as u32, info, post_info, tip,2564 );2565 let refund = fee.saturating_sub(actual_fee);2566 let actual_payment =2567 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2568 &who, refund,2569 ) {2570 Ok(refund_imbalance) => {2571 // The refund cannot be larger than the up front payed max weight.2572 // `PostDispatchInfo::calc_unspent` guards against such a case.2573 match payed.offset(refund_imbalance) {2574 Ok(actual_payment) => actual_payment,2575 Err(_) => return Err(InvalidTransaction::Payment.into()),2576 }2577 }2578 // We do not recreate the account using the refund. The up front payment2579 // is gone in that case.2580 Err(_) => payed,2581 };2582 let imbalances = actual_payment.split(tip);2583 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2584 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2585 );2586 }2587 Ok(())2588 }2589}25902591// #endregion1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, WithdrawReason,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial,29 },30 IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69 Invalid,70 NFT,71 // decimal points72 Fungible(DecimalPoints),73 // decimal points74 ReFungible(DecimalPoints),75}7677impl Default for CollectionMode {78 fn default() -> Self {79 Self::Invalid80 }81}8283impl Into<u8> for CollectionMode {84 fn into(self) -> u8 {85 match self {86 CollectionMode::Invalid => 0,87 CollectionMode::NFT => 1,88 CollectionMode::Fungible(_) => 2,89 CollectionMode::ReFungible(_) => 3,90 }91 }92}9394#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]95#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]96pub enum AccessMode {97 Normal,98 WhiteList,99}100impl Default for AccessMode {101 fn default() -> Self {102 Self::Normal103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub owner: AccountId,149 pub const_data: Vec<u8>,150 pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType {156 pub value: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct ReFungibleItemType<AccountId> {162 pub owner: Vec<Ownership<AccountId>>,163 pub const_data: Vec<u8>,164 pub variable_data: Vec<u8>,165}166167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169// pub struct VestingItem<AccountId, Moment> {170// pub sender: AccountId,171// pub recipient: AccountId,172// pub collection_id: CollectionId,173// pub item_id: TokenId,174// pub amount: u64,175// pub vesting_date: Moment,176// }177178#[derive(Encode, Decode, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct CollectionLimits {181 pub account_token_ownership_limit: u32,182 pub sponsored_data_size: u32,183 pub token_limit: u32,184185 // Timeouts for item types in passed blocks186 pub sponsor_transfer_timeout: u32,187}188189impl Default for CollectionLimits {190 fn default() -> CollectionLimits {191 CollectionLimits { 192 account_token_ownership_limit: 10_000_000, 193 token_limit: u32::max_value(),194 sponsored_data_size: u32::max_value(), 195 sponsor_transfer_timeout: 14400 }196 }197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]201pub struct ChainLimits {202 pub collection_numbers_limit: u32,203 pub account_token_ownership_limit: u32,204 pub collections_admins_limit: u64,205 pub custom_data_limit: u32,206207 // Timeouts for item types in passed blocks208 pub nft_sponsor_transfer_timeout: u32,209 pub fungible_sponsor_transfer_timeout: u32,210 pub refungible_sponsor_transfer_timeout: u32,211212 // Schema limits213 pub offchain_schema_limit: u32,214 pub variable_on_chain_schema_limit: u32,215 pub const_on_chain_schema_limit: u32,216}217218pub trait WeightInfo {219 fn create_collection() -> Weight;220 fn destroy_collection() -> Weight;221 fn add_to_white_list() -> Weight;222 fn remove_from_white_list() -> Weight;223 fn set_public_access_mode() -> Weight;224 fn set_mint_permission() -> Weight;225 fn change_collection_owner() -> Weight;226 fn add_collection_admin() -> Weight;227 fn remove_collection_admin() -> Weight;228 fn set_collection_sponsor() -> Weight;229 fn confirm_sponsorship() -> Weight;230 fn remove_collection_sponsor() -> Weight;231 fn create_item(s: usize) -> Weight;232 fn burn_item() -> Weight;233 fn transfer() -> Weight;234 fn approve() -> Weight;235 fn transfer_from() -> Weight;236 fn set_offchain_schema() -> Weight;237 fn set_const_on_chain_schema() -> Weight;238 fn set_variable_on_chain_schema() -> Weight;239 fn set_variable_meta_data() -> Weight;240 fn enable_contract_sponsoring() -> Weight;241 fn set_schema_version() -> Weight;242 fn set_chain_limits() -> Weight;243 fn set_contract_sponsoring_rate_limit() -> Weight;244 fn toggle_contract_white_list() -> Weight;245 fn add_to_contract_white_list() -> Weight;246 fn remove_from_contract_white_list() -> Weight;247 fn set_collection_limits() -> Weight;248}249250#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]251#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]252pub struct CreateNftData {253 pub const_data: Vec<u8>,254 pub variable_data: Vec<u8>,255}256257#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]258#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]259pub struct CreateFungibleData {260 pub value: u128,261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateReFungibleData {266 pub const_data: Vec<u8>,267 pub variable_data: Vec<u8>,268}269270#[derive(Encode, Decode, Debug, Clone, PartialEq)]271#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]272pub enum CreateItemData {273 NFT(CreateNftData),274 Fungible(CreateFungibleData),275 ReFungible(CreateReFungibleData),276}277278impl CreateItemData {279 pub fn len(&self) -> usize {280 let len = match self {281 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),282 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),283 _ => 0284 };285 286 return len;287 }288}289290impl From<CreateNftData> for CreateItemData {291 fn from(item: CreateNftData) -> Self {292 CreateItemData::NFT(item)293 }294}295296impl From<CreateReFungibleData> for CreateItemData {297 fn from(item: CreateReFungibleData) -> Self {298 CreateItemData::ReFungible(item)299 }300}301302impl From<CreateFungibleData> for CreateItemData {303 fn from(item: CreateFungibleData) -> Self {304 CreateItemData::Fungible(item)305 }306}307308309decl_error! {310 /// Error for non-fungible-token module.311 pub enum Error for Module<T: Trait> {312 /// Total collections bound exceeded.313 TotalCollectionsLimitExceeded,314 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.315 CollectionDecimalPointLimitExceeded, 316 /// Collection name can not be longer than 63 char.317 CollectionNameLimitExceeded, 318 /// Collection description can not be longer than 255 char.319 CollectionDescriptionLimitExceeded, 320 /// Token prefix can not be longer than 15 char.321 CollectionTokenPrefixLimitExceeded,322 /// This collection does not exist.323 CollectionNotFound,324 /// Item not exists.325 TokenNotFound,326 /// Arithmetic calculation overflow.327 NumOverflow, 328 /// Account already has admin role.329 AlreadyAdmin, 330 /// You do not own this collection.331 NoPermission,332 /// This address is not set as sponsor, use setCollectionSponsor first.333 ConfirmUnsetSponsorFail,334 /// Collection is not in mint mode.335 PublicMintingNotAllowed,336 /// Sender parameter and item owner must be equal.337 MustBeTokenOwner,338 /// Item balance not enough.339 TokenValueTooLow,340 /// Size of item is too large.341 NftSizeLimitExceeded,342 /// No approve found343 ApproveNotFound,344 /// Requested value more than approved.345 TokenValueNotEnough,346 /// Only approved addresses can call this method.347 ApproveRequired,348 /// Address is not in white list.349 AddresNotInWhiteList,350 /// Number of collection admins bound exceeded.351 CollectionAdminsLimitExceeded,352 /// Owned tokens by a single address bound exceeded.353 AddressOwnershipLimitExceeded,354 /// Length of items properties must be greater than 0.355 EmptyArgument,356 /// const_data exceeded data limit.357 TokenConstDataLimitExceeded,358 /// variable_data exceeded data limit.359 TokenVariableDataLimitExceeded,360 /// Not NFT item data used to mint in NFT collection.361 NotNftDataUsedToMintNftCollectionToken,362 /// Not Fungible item data used to mint in Fungible collection.363 NotFungibleDataUsedToMintFungibleCollectionToken,364 /// Not Re Fungible item data used to mint in Re Fungible collection.365 NotReFungibleDataUsedToMintReFungibleCollectionToken,366 /// Unexpected collection type.367 UnexpectedCollectionType,368 /// Can't store metadata in fungible tokens.369 CantStoreMetadataInFungibleTokens,370 /// Collection token limit exceeded371 CollectionTokenLimitExceeded,372 /// Account token limit exceeded per collection373 AccountTokenLimitExceeded,374 /// Collection limit bounds per collection exceeded375 CollectionLimitBoundsExceeded,376 /// Schema data size limit bound exceeded377 SchemaDataLimitExceeded378 }379}380381pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {382 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;383384 /// Weight information for extrinsics in this pallet.385 type WeightInfo: WeightInfo;386}387388#[cfg(feature = "runtime-benchmarks")]389mod benchmarking;390391// #endregion392393decl_storage! {394 trait Store for Module<T: Trait> as Nft {395396 // Private members397 NextCollectionID: CollectionId;398 CreatedCollectionCount: u32;399 ChainVersion: u64;400 ItemListIndex: map hasher(identity) CollectionId => TokenId;401402 // Chain limits struct403 pub ChainLimit get(fn chain_limit) config(): ChainLimits;404405 // Bound counters406 CollectionCount: u32;407 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;408409 // Basic collections410 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;411 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;412 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;413414 /// Balance owner per collection map415 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;416417 /// second parameter: item id + owner account id + spender account id418 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;419420 /// Item collections421 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;422 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;423 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;424425 /// Index list426 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;427428 /// Tokens transfer baskets429 pub CreateItemBasket get(fn create_item_basket): map hasher(twox_64_concat) (CollectionId, T::AccountId) => T::BlockNumber;430 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;431 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;432 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433434 // Contract Sponsorship and Ownership435 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;436 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;437 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;438 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;439 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 440 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 441 }442 add_extra_genesis {443 build(|config: &GenesisConfig<T>| {444 // Modification of storage445 for (_num, _c) in &config.collection {446 <Module<T>>::init_collection(_c);447 }448449 for (_num, _c, _i) in &config.nft_item_id {450 <Module<T>>::init_nft_token(*_c, _i);451 }452453 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {454 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);455 }456457 for (_num, _c, _i) in &config.refungible_item_id {458 <Module<T>>::init_refungible_token(*_c, _i);459 }460 })461 }462}463464decl_event!(465 pub enum Event<T>466 where467 AccountId = <T as system::Trait>::AccountId,468 {469 /// New collection was created470 /// 471 /// # Arguments472 /// 473 /// * collection_id: Globally unique identifier of newly created collection.474 /// 475 /// * mode: [CollectionMode] converted into u8.476 /// 477 /// * account_id: Collection owner.478 Created(CollectionId, u8, AccountId),479480 /// New item was created.481 /// 482 /// # Arguments483 /// 484 /// * collection_id: Id of the collection where item was created.485 /// 486 /// * item_id: Id of an item. Unique within the collection.487 ItemCreated(CollectionId, TokenId),488489 /// Collection item was burned.490 /// 491 /// # Arguments492 /// 493 /// collection_id.494 /// 495 /// item_id: Identifier of burned NFT.496 ItemDestroyed(CollectionId, TokenId),497 }498);499500decl_module! {501 pub struct Module<T: Trait> for enum Call where origin: T::Origin {502503 fn deposit_event() = default;504 type Error = Error<T>;505506 fn on_initialize(now: T::BlockNumber) -> Weight {507508 if ChainVersion::get() < 2509 {510 let value = NextCollectionID::get();511 CreatedCollectionCount::put(value);512 ChainVersion::put(2);513 }514515 0516 }517518 /// 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.519 /// 520 /// # Permissions521 /// 522 /// * Anyone.523 /// 524 /// # Arguments525 /// 526 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.527 /// 528 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.529 /// 530 /// * token_prefix: UTF-8 string with token prefix.531 /// 532 /// * mode: [CollectionMode] collection type and type dependent data.533 // returns collection ID534 #[weight = T::WeightInfo::create_collection()]535 pub fn create_collection(origin,536 collection_name: Vec<u16>,537 collection_description: Vec<u16>,538 token_prefix: Vec<u8>,539 mode: CollectionMode) -> DispatchResult {540541 // Anyone can create a collection542 let who = ensure_signed(origin)?;543544 let decimal_points = match mode {545 CollectionMode::Fungible(points) => points,546 CollectionMode::ReFungible(points) => points,547 _ => 0548 };549550 // bound Total number of collections551 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);552553 // check params554 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);555 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);556 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);557 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);558559 // Generate next collection ID560 let next_id = CreatedCollectionCount::get()561 .checked_add(1)562 .ok_or(Error::<T>::NumOverflow)?;563564 // bound counter565 let total = CollectionCount::get()566 .checked_add(1)567 .ok_or(Error::<T>::NumOverflow)?;568569 CreatedCollectionCount::put(next_id);570 CollectionCount::put(total);571572 // Create new collection573 let new_collection = CollectionType {574 owner: who.clone(),575 name: collection_name,576 mode: mode.clone(),577 mint_mode: false,578 access: AccessMode::Normal,579 description: collection_description,580 decimal_points: decimal_points,581 token_prefix: token_prefix,582 offchain_schema: Vec::new(),583 schema_version: SchemaVersion::ImageURL,584 sponsor: T::AccountId::default(),585 sponsor_confirmed: false,586 variable_on_chain_schema: Vec::new(),587 const_on_chain_schema: Vec::new(),588 limits: CollectionLimits::default(),589 };590591 // Add new collection to map592 <Collection<T>>::insert(next_id, new_collection);593594 // call event595 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));596597 Ok(())598 }599600 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.601 /// 602 /// # Permissions603 /// 604 /// * Collection Owner.605 /// 606 /// # Arguments607 /// 608 /// * collection_id: collection to destroy.609 #[weight = T::WeightInfo::destroy_collection()]610 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {611612 let sender = ensure_signed(origin)?;613 Self::check_owner_permissions(collection_id, sender)?;614615 <AddressTokens<T>>::remove_prefix(collection_id);616 <Allowances<T>>::remove_prefix(collection_id);617 <Balance<T>>::remove_prefix(collection_id);618 <ItemListIndex>::remove(collection_id);619 <AdminList<T>>::remove(collection_id);620 <Collection<T>>::remove(collection_id);621 <WhiteList<T>>::remove_prefix(collection_id);622623 <NftItemList<T>>::remove_prefix(collection_id);624 <FungibleItemList<T>>::remove_prefix(collection_id);625 <ReFungibleItemList<T>>::remove_prefix(collection_id);626627 <NftTransferBasket<T>>::remove_prefix(collection_id);628 <FungibleTransferBasket<T>>::remove_prefix(collection_id);629 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);630631 if CollectionCount::get() > 0632 {633 // bound couter634 let total = CollectionCount::get()635 .checked_sub(1)636 .ok_or(Error::<T>::NumOverflow)?;637638 CollectionCount::put(total);639 }640641 Ok(())642 }643644 /// Add an address to white list.645 /// 646 /// # Permissions647 /// 648 /// * Collection Owner649 /// * Collection Admin650 /// 651 /// # Arguments652 /// 653 /// * collection_id.654 /// 655 /// * address.656 #[weight = T::WeightInfo::add_to_white_list()]657 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{658659 let sender = ensure_signed(origin)?;660 Self::check_owner_or_admin_permissions(collection_id, sender)?;661662 <WhiteList<T>>::insert(collection_id, address, true);663 664 Ok(())665 }666667 /// Remove an address from white list.668 /// 669 /// # Permissions670 /// 671 /// * Collection Owner672 /// * Collection Admin673 /// 674 /// # Arguments675 /// 676 /// * collection_id.677 /// 678 /// * address.679 #[weight = T::WeightInfo::remove_from_white_list()]680 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{681682 let sender = ensure_signed(origin)?;683 Self::check_owner_or_admin_permissions(collection_id, sender)?;684685 <WhiteList<T>>::remove(collection_id, address);686687 Ok(())688 }689690 /// Toggle between normal and white list access for the methods with access for `Anyone`.691 /// 692 /// # Permissions693 /// 694 /// * Collection Owner.695 /// 696 /// # Arguments697 /// 698 /// * collection_id.699 /// 700 /// * mode: [AccessMode]701 #[weight = T::WeightInfo::set_public_access_mode()]702 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult703 {704 let sender = ensure_signed(origin)?;705706 Self::check_owner_permissions(collection_id, sender)?;707 let mut target_collection = <Collection<T>>::get(collection_id);708 target_collection.access = mode;709 <Collection<T>>::insert(collection_id, target_collection);710711 Ok(())712 }713714 /// Allows Anyone to create tokens if:715 /// * White List is enabled, and716 /// * Address is added to white list, and717 /// * This method was called with True parameter718 /// 719 /// # Permissions720 /// * Collection Owner721 ///722 /// # Arguments723 /// 724 /// * collection_id.725 /// 726 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.727 #[weight = T::WeightInfo::set_mint_permission()]728 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult729 {730 let sender = ensure_signed(origin)?;731732 Self::check_owner_permissions(collection_id, sender)?;733 let mut target_collection = <Collection<T>>::get(collection_id);734 target_collection.mint_mode = mint_permission;735 <Collection<T>>::insert(collection_id, target_collection);736737 Ok(())738 }739740 /// Change the owner of the collection.741 /// 742 /// # Permissions743 /// 744 /// * Collection Owner.745 /// 746 /// # Arguments747 /// 748 /// * collection_id.749 /// 750 /// * new_owner.751 #[weight = T::WeightInfo::change_collection_owner()]752 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {753754 let sender = ensure_signed(origin)?;755 Self::check_owner_permissions(collection_id, sender)?;756 let mut target_collection = <Collection<T>>::get(collection_id);757 target_collection.owner = new_owner;758 <Collection<T>>::insert(collection_id, target_collection);759760 Ok(())761 }762763 /// Adds an admin of the Collection.764 /// 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. 765 /// 766 /// # Permissions767 /// 768 /// * Collection Owner.769 /// * Collection Admin.770 /// 771 /// # Arguments772 /// 773 /// * collection_id: ID of the Collection to add admin for.774 /// 775 /// * new_admin_id: Address of new admin to add.776 #[weight = T::WeightInfo::add_collection_admin()]777 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {778779 let sender = ensure_signed(origin)?;780 Self::check_owner_or_admin_permissions(collection_id, sender)?;781 let mut admin_arr: Vec<T::AccountId> = Vec::new();782783 if <AdminList<T>>::contains_key(collection_id)784 {785 admin_arr = <AdminList<T>>::get(collection_id);786 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);787 }788789 // Number of collection admins790 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);791792 admin_arr.push(new_admin_id);793 <AdminList<T>>::insert(collection_id, admin_arr);794795 Ok(())796 }797798 /// 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.799 ///800 /// # Permissions801 /// 802 /// * Collection Owner.803 /// * Collection Admin.804 /// 805 /// # Arguments806 /// 807 /// * collection_id: ID of the Collection to remove admin for.808 /// 809 /// * account_id: Address of admin to remove.810 #[weight = T::WeightInfo::remove_collection_admin()]811 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {812813 let sender = ensure_signed(origin)?;814 Self::check_owner_or_admin_permissions(collection_id, sender)?;815816 if <AdminList<T>>::contains_key(collection_id)817 {818 let mut admin_arr = <AdminList<T>>::get(collection_id);819 admin_arr.retain(|i| *i != account_id);820 <AdminList<T>>::insert(collection_id, admin_arr);821 }822823 Ok(())824 }825826 /// # Permissions827 /// 828 /// * Collection Owner829 /// 830 /// # Arguments831 /// 832 /// * collection_id.833 /// 834 /// * new_sponsor.835 #[weight = T::WeightInfo::set_collection_sponsor()]836 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {837838 let sender = ensure_signed(origin)?;839 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);840841 let mut target_collection = <Collection<T>>::get(collection_id);842 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);843844 target_collection.sponsor = new_sponsor;845 target_collection.sponsor_confirmed = false;846 <Collection<T>>::insert(collection_id, target_collection);847848 Ok(())849 }850851 /// # Permissions852 /// 853 /// * Sponsor.854 /// 855 /// # Arguments856 /// 857 /// * collection_id.858 #[weight = T::WeightInfo::confirm_sponsorship()]859 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {860861 let sender = ensure_signed(origin)?;862 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);863864 let mut target_collection = <Collection<T>>::get(collection_id);865 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);866867 target_collection.sponsor_confirmed = true;868 <Collection<T>>::insert(collection_id, target_collection);869870 Ok(())871 }872873 /// Switch back to pay-per-own-transaction model.874 ///875 /// # Permissions876 ///877 /// * Collection owner.878 /// 879 /// # Arguments880 /// 881 /// * collection_id.882 #[weight = T::WeightInfo::remove_collection_sponsor()]883 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {884885 let sender = ensure_signed(origin)?;886 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);887888 let mut target_collection = <Collection<T>>::get(collection_id);889 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);890891 target_collection.sponsor = T::AccountId::default();892 target_collection.sponsor_confirmed = false;893 <Collection<T>>::insert(collection_id, target_collection);894895 Ok(())896 }897898 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.899 /// 900 /// # Permissions901 /// 902 /// * Collection Owner.903 /// * Collection Admin.904 /// * Anyone if905 /// * White List is enabled, and906 /// * Address is added to white list, and907 /// * MintPermission is enabled (see SetMintPermission method)908 /// 909 /// # Arguments910 /// 911 /// * collection_id: ID of the collection.912 /// 913 /// * owner: Address, initial owner of the NFT.914 ///915 /// * data: Token data to store on chain.916 // #[weight =917 // (130_000_000 as Weight)918 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))919 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))920 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]921922 #[weight = T::WeightInfo::create_item(data.len())]923 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {924925 let sender = ensure_signed(origin)?;926927 Self::collection_exists(collection_id)?;928929 let target_collection = <Collection<T>>::get(collection_id);930931 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;932 Self::validate_create_item_args(&target_collection, &data)?;933 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;934935 Ok(())936 }937938 /// This method creates multiple instances of NFT Collection created with CreateCollection method.939 /// 940 /// # Permissions941 /// 942 /// * Collection Owner.943 /// * Collection Admin.944 /// * Anyone if945 /// * White List is enabled, and946 /// * Address is added to white list, and947 /// * MintPermission is enabled (see SetMintPermission method)948 /// 949 /// # Arguments950 /// 951 /// * collection_id: ID of the collection.952 /// 953 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].954 /// 955 /// * owner: Address, initial owner of the NFT.956 #[weight = T::WeightInfo::create_item(items_data.into_iter()957 .map(|data| { data.len() })958 .sum())]959 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {960961 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);962 let sender = ensure_signed(origin)?;963964 Self::collection_exists(collection_id)?;965 let target_collection = <Collection<T>>::get(collection_id);966967 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;968969 for data in &items_data {970 Self::validate_create_item_args(&target_collection, data)?;971 }972 for data in &items_data {973 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;974 }975976 Ok(())977 }978979 /// Destroys a concrete instance of NFT.980 /// 981 /// # Permissions982 /// 983 /// * Collection Owner.984 /// * Collection Admin.985 /// * Current NFT Owner.986 /// 987 /// # Arguments988 /// 989 /// * collection_id: ID of the collection.990 /// 991 /// * item_id: ID of NFT to burn.992 #[weight = T::WeightInfo::burn_item()]993 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {994995 let sender = ensure_signed(origin)?;996 Self::collection_exists(collection_id)?;997998 // Transfer permissions check999 let target_collection = <Collection<T>>::get(collection_id);1000 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1001 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1002 Error::<T>::NoPermission);10031004 if target_collection.access == AccessMode::WhiteList {1005 Self::check_white_list(collection_id, &sender)?;1006 }10071008 match target_collection.mode1009 {1010 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1011 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1012 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1013 _ => ()1014 };10151016 // call event1017 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10181019 Ok(())1020 }10211022 /// Change ownership of the token.1023 /// 1024 /// # Permissions1025 /// 1026 /// * Collection Owner1027 /// * Collection Admin1028 /// * Current NFT owner1029 ///1030 /// # Arguments1031 /// 1032 /// * recipient: Address of token recipient.1033 /// 1034 /// * collection_id.1035 /// 1036 /// * item_id: ID of the item1037 /// * Non-Fungible Mode: Required.1038 /// * Fungible Mode: Ignored.1039 /// * Re-Fungible Mode: Required.1040 /// 1041 /// * value: Amount to transfer.1042 /// * Non-Fungible Mode: Ignored1043 /// * Fungible Mode: Must specify transferred amount1044 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1045 #[weight = T::WeightInfo::transfer()]1046 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10471048 let sender = ensure_signed(origin)?;1049 let target_collection = <Collection<T>>::get(collection_id);10501051 // Limits check1052 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10531054 // Transfer permissions check1055 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1056 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1057 Error::<T>::NoPermission);10581059 if target_collection.access == AccessMode::WhiteList {1060 Self::check_white_list(collection_id, &sender)?;1061 Self::check_white_list(collection_id, &recipient)?;1062 }10631064 match target_collection.mode1065 {1066 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1067 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1068 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1069 _ => ()1070 };10711072 Ok(())1073 }10741075 /// Set, change, or remove approved address to transfer the ownership of the NFT.1076 /// 1077 /// # Permissions1078 /// 1079 /// * Collection Owner1080 /// * Collection Admin1081 /// * Current NFT owner1082 /// 1083 /// # Arguments1084 /// 1085 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1086 /// 1087 /// * collection_id.1088 /// 1089 /// * item_id: ID of the item.1090 #[weight = T::WeightInfo::approve()]1091 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10921093 let sender = ensure_signed(origin)?;10941095 // Transfer permissions check1096 let target_collection = <Collection<T>>::get(collection_id);1097 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1098 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1099 Error::<T>::NoPermission);11001101 if target_collection.access == AccessMode::WhiteList {1102 Self::check_white_list(collection_id, &sender)?;1103 Self::check_white_list(collection_id, &spender)?;1104 }11051106 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1107 let mut allowance: u128 = amount;1108 if allowance_exists {1109 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1110 }1111 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11121113 Ok(())1114 }1115 1116 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1117 /// 1118 /// # Permissions1119 /// * Collection Owner1120 /// * Collection Admin1121 /// * Current NFT owner1122 /// * Address approved by current NFT owner1123 /// 1124 /// # Arguments1125 /// 1126 /// * from: Address that owns token.1127 /// 1128 /// * recipient: Address of token recipient.1129 /// 1130 /// * collection_id.1131 /// 1132 /// * item_id: ID of the item.1133 /// 1134 /// * value: Amount to transfer.1135 #[weight = T::WeightInfo::transfer_from()]1136 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11371138 let sender = ensure_signed(origin)?;1139 let mut appoved_transfer = false;11401141 // Check approval1142 let mut approval: u128 = 0;1143 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1144 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1145 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1146 appoved_transfer = true;1147 }11481149 let target_collection = <Collection<T>>::get(collection_id);11501151 // Limits check1152 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11531154 // Transfer permissions check 1155 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1156 Error::<T>::NoPermission);11571158 if target_collection.access == AccessMode::WhiteList {1159 Self::check_white_list(collection_id, &sender)?;1160 Self::check_white_list(collection_id, &recipient)?;1161 }11621163 // Reduce approval by transferred amount or remove if remaining approval drops to 01164 if approval.checked_sub(value).unwrap_or(0) > 0 {1165 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1166 }1167 else {1168 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1169 }11701171 match target_collection.mode1172 {1173 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1174 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1175 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1176 _ => ()1177 };11781179 Ok(())1180 }11811182 #[weight = 0]1183 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11841185 // let no_perm_mes = "You do not have permissions to modify this collection";1186 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1187 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1188 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11891190 // // on_nft_received call11911192 // Self::transfer(origin, collection_id, item_id, new_owner)?;11931194 Ok(())1195 }11961197 /// Set off-chain data schema.1198 /// 1199 /// # Permissions1200 /// 1201 /// * Collection Owner1202 /// * Collection Admin1203 /// 1204 /// # Arguments1205 /// 1206 /// * collection_id.1207 /// 1208 /// * schema: String representing the offchain data schema.1209 #[weight = T::WeightInfo::set_variable_meta_data()]1210 pub fn set_variable_meta_data (1211 origin,1212 collection_id: CollectionId,1213 item_id: TokenId,1214 data: Vec<u8>1215 ) -> DispatchResult {1216 let sender = ensure_signed(origin)?;1217 1218 Self::collection_exists(collection_id)?;1219 1220 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12211222 // Modify permissions check1223 let target_collection = <Collection<T>>::get(collection_id);1224 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1225 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1226 Error::<T>::NoPermission);12271228 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12291230 match target_collection.mode1231 {1232 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1233 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1234 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1235 _ => fail!(Error::<T>::UnexpectedCollectionType)1236 };12371238 Ok(())1239 }1240 1241 /// Set schema standard1242 /// ImageURL1243 /// Unique1244 /// 1245 /// # Permissions1246 /// 1247 /// * Collection Owner1248 /// * Collection Admin1249 /// 1250 /// # Arguments1251 /// 1252 /// * collection_id.1253 /// 1254 /// * schema: SchemaVersion: enum1255 #[weight = T::WeightInfo::set_schema_version()]1256 pub fn set_schema_version(1257 origin,1258 collection_id: CollectionId,1259 version: SchemaVersion1260 ) -> DispatchResult {1261 let sender = ensure_signed(origin)?;1262 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1263 let mut target_collection = <Collection<T>>::get(collection_id);1264 target_collection.schema_version = version;1265 <Collection<T>>::insert(collection_id, target_collection);12661267 Ok(())1268 }12691270 /// Set off-chain data schema.1271 /// 1272 /// # Permissions1273 /// 1274 /// * Collection Owner1275 /// * Collection Admin1276 /// 1277 /// # Arguments1278 /// 1279 /// * collection_id.1280 /// 1281 /// * schema: String representing the offchain data schema.1282 #[weight = T::WeightInfo::set_offchain_schema()]1283 pub fn set_offchain_schema(1284 origin,1285 collection_id: CollectionId,1286 schema: Vec<u8>1287 ) -> DispatchResult {1288 let sender = ensure_signed(origin)?;1289 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12901291 // check schema limit1292 ensure!(schema.len() as u32 > ChainLimit::get().offchain_schema_limit, "");12931294 let mut target_collection = <Collection<T>>::get(collection_id);1295 target_collection.offchain_schema = schema;1296 <Collection<T>>::insert(collection_id, target_collection);12971298 Ok(())1299 }13001301 /// Set const on-chain data schema.1302 /// 1303 /// # Permissions1304 /// 1305 /// * Collection Owner1306 /// * Collection Admin1307 /// 1308 /// # Arguments1309 /// 1310 /// * collection_id.1311 /// 1312 /// * schema: String representing the const on-chain data schema.1313 #[weight = T::WeightInfo::set_const_on_chain_schema()]1314 pub fn set_const_on_chain_schema (1315 origin,1316 collection_id: CollectionId,1317 schema: Vec<u8>1318 ) -> DispatchResult {1319 let sender = ensure_signed(origin)?;1320 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13211322 // check schema limit1323 ensure!(schema.len() as u32 > ChainLimit::get().const_on_chain_schema_limit, "");13241325 let mut target_collection = <Collection<T>>::get(collection_id);1326 target_collection.const_on_chain_schema = schema;1327 <Collection<T>>::insert(collection_id, target_collection);13281329 Ok(())1330 }13311332 /// Set variable on-chain data schema.1333 /// 1334 /// # Permissions1335 /// 1336 /// * Collection Owner1337 /// * Collection Admin1338 /// 1339 /// # Arguments1340 /// 1341 /// * collection_id.1342 /// 1343 /// * schema: String representing the variable on-chain data schema.1344 #[weight = T::WeightInfo::set_const_on_chain_schema()]1345 pub fn set_variable_on_chain_schema (1346 origin,1347 collection_id: CollectionId,1348 schema: Vec<u8>1349 ) -> DispatchResult {1350 let sender = ensure_signed(origin)?;1351 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13521353 // check schema limit1354 ensure!(schema.len() as u32 > ChainLimit::get().variable_on_chain_schema_limit, "");13551356 let mut target_collection = <Collection<T>>::get(collection_id);1357 target_collection.variable_on_chain_schema = schema;1358 <Collection<T>>::insert(collection_id, target_collection);13591360 Ok(())1361 }13621363 // Sudo permissions function1364 #[weight = T::WeightInfo::set_chain_limits()]1365 pub fn set_chain_limits(1366 origin,1367 limits: ChainLimits1368 ) -> DispatchResult {13691370 #[cfg(not(feature = "runtime-benchmarks"))]1371 ensure_root(origin)?;13721373 <ChainLimit>::put(limits);1374 Ok(())1375 }13761377 /// Enable smart contract self-sponsoring.1378 /// 1379 /// # Permissions1380 /// 1381 /// * Contract Owner1382 /// 1383 /// # Arguments1384 /// 1385 /// * contract address1386 /// * enable flag1387 /// 1388 #[weight = T::WeightInfo::enable_contract_sponsoring()]1389 pub fn enable_contract_sponsoring(1390 origin,1391 contract_address: T::AccountId,1392 enable: bool1393 ) -> DispatchResult {13941395 let sender = ensure_signed(origin)?;13961397 #[cfg(feature = "runtime-benchmarks")]1398 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13991400 Self::ensure_contract_owned(sender, &contract_address)?;14011402 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1403 Ok(())1404 }14051406 /// Set the rate limit for contract sponsoring to specified number of blocks.1407 /// 1408 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1409 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1410 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1411 /// from contract endowment if there are at least B blocks between such transactions. 1412 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1413 /// 1414 /// # Permissions1415 /// 1416 /// * Contract Owner1417 /// 1418 /// # Arguments1419 /// 1420 /// -`contract_address`: Address of the contract to sponsor1421 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1422 /// 1423 #[weight = T::WeightInfo::set_contract_sponsoring_rate_limit()]1424 pub fn set_contract_sponsoring_rate_limit(1425 origin,1426 contract_address: T::AccountId,1427 rate_limit: T::BlockNumber1428 ) -> DispatchResult {1429 let sender = ensure_signed(origin)?;14301431 #[cfg(feature = "runtime-benchmarks")]1432 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14331434 Self::ensure_contract_owned(sender, &contract_address)?;1435 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1436 Ok(())1437 }14381439 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1440 /// 1441 /// # Permissions1442 /// 1443 /// * Address that deployed smart contract.1444 /// 1445 /// # Arguments1446 /// 1447 /// -`contract_address`: Address of the contract.1448 /// 1449 /// - `enable`: . 1450 #[weight = T::WeightInfo::toggle_contract_white_list()]1451 pub fn toggle_contract_white_list(1452 origin,1453 contract_address: T::AccountId,1454 enable: bool1455 ) -> DispatchResult {1456 let sender = ensure_signed(origin)?;14571458 #[cfg(feature = "runtime-benchmarks")]1459 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14601461 Self::ensure_contract_owned(sender, &contract_address)?;1462 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1463 Ok(())1464 }1465 1466 /// Add an address to smart contract white list.1467 /// 1468 /// # Permissions1469 /// 1470 /// * Address that deployed smart contract.1471 /// 1472 /// # Arguments1473 /// 1474 /// -`contract_address`: Address of the contract.1475 ///1476 /// -`account_address`: Address to add.1477 #[weight = T::WeightInfo::add_to_contract_white_list()]1478 pub fn add_to_contract_white_list(1479 origin,1480 contract_address: T::AccountId,1481 account_address: T::AccountId1482 ) -> DispatchResult {1483 let sender = ensure_signed(origin)?;14841485 #[cfg(feature = "runtime-benchmarks")]1486 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1487 1488 Self::ensure_contract_owned(sender, &contract_address)?; 1489 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1490 Ok(())1491 }14921493 /// Remove an address from smart contract white list.1494 /// 1495 /// # Permissions1496 /// 1497 /// * Address that deployed smart contract.1498 /// 1499 /// # Arguments1500 /// 1501 /// -`contract_address`: Address of the contract.1502 ///1503 /// -`account_address`: Address to remove.1504 #[weight = T::WeightInfo::remove_from_contract_white_list()]1505 pub fn remove_from_contract_white_list(1506 origin,1507 contract_address: T::AccountId,1508 account_address: T::AccountId1509 ) -> DispatchResult {1510 let sender = ensure_signed(origin)?;15111512 #[cfg(feature = "runtime-benchmarks")]1513 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15141515 Self::ensure_contract_owned(sender, &contract_address)?;1516 <ContractWhiteList<T>>::remove(contract_address, account_address);1517 Ok(())1518 }15191520 #[weight = T::WeightInfo::set_collection_limits()]1521 pub fn set_collection_limits(1522 origin,1523 collection_id: u32,1524 limits: CollectionLimits,1525 ) -> DispatchResult {1526 let sender = ensure_signed(origin)?;1527 Self::check_owner_permissions(collection_id, sender.clone())?;1528 let mut target_collection = <Collection<T>>::get(collection_id);1529 let chain_limits = ChainLimit::get();1530 let climits = target_collection.limits;15311532 // collection bounds1533 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1534 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1535 Error::<T>::CollectionLimitBoundsExceeded);15361537 // token_limit check prev1538 ensure!(climits.token_limit > limits.token_limit && 1539 limits.token_limit <= chain_limits.account_token_ownership_limit, 1540 Error::<T>::AccountTokenLimitExceeded);15411542 target_collection.limits = limits;1543 <Collection<T>>::insert(collection_id, target_collection);15441545 Ok(())1546 } 1547 }1548}15491550impl<T: Trait> Module<T> {15511552 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15531554 // check token limit and account token limit1555 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1556 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1557 1558 Ok(())1559 }15601561 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15621563 // check token limit and account token limit1564 let total_items: u32 = ItemListIndex::get(collection_id);1565 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1566 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1567 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15681569 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1570 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1571 Self::check_white_list(collection_id, owner)?;1572 Self::check_white_list(collection_id, sender)?;1573 }15741575 Ok(())1576 }15771578 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1579 match target_collection.mode1580 {1581 CollectionMode::NFT => {1582 if let CreateItemData::NFT(data) = data {1583 // check sizes1584 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1585 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1586 } else {1587 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1588 }1589 },1590 CollectionMode::Fungible(_) => {1591 if let CreateItemData::Fungible(_) = data {1592 } else {1593 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1594 }1595 },1596 CollectionMode::ReFungible(_) => {1597 if let CreateItemData::ReFungible(data) = data {15981599 // check sizes1600 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1601 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1602 } else {1603 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1604 }1605 },1606 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1607 };16081609 Ok(())1610 }16111612 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1613 match data1614 {1615 CreateItemData::NFT(data) => {1616 let item = NftItemType {1617 owner,1618 const_data: data.const_data,1619 variable_data: data.variable_data1620 };16211622 Self::add_nft_item(collection_id, item)?;1623 },1624 CreateItemData::Fungible(data) => {1625 Self::add_fungible_item(collection_id, &owner, data.value)?;1626 },1627 CreateItemData::ReFungible(data) => {1628 let mut owner_list = Vec::new();1629 let value = (10 as u128).pow(collection.decimal_points as u32);1630 owner_list.push(Ownership {owner: owner.clone(), fraction: value});16311632 let item = ReFungibleItemType {1633 owner: owner_list,1634 const_data: data.const_data,1635 variable_data: data.variable_data1636 };16371638 Self::add_refungible_item(collection_id, item)?;1639 }1640 };16411642 // call event1643 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16441645 Ok(())1646 }16471648 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16491650 // Does new owner already have an account?1651 let mut balance: u128 = 0;1652 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1653 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1654 } 16551656 // Mint 1657 let item = FungibleItemType {1658 value: balance + value1659 };1660 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16611662 // Update balance1663 let new_balance = <Balance<T>>::get(collection_id, owner)1664 .checked_add(value)1665 .ok_or(Error::<T>::NumOverflow)?;1666 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16671668 Ok(())1669 }16701671 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1672 let current_index = <ItemListIndex>::get(collection_id)1673 .checked_add(1)1674 .ok_or(Error::<T>::NumOverflow)?;1675 let itemcopy = item.clone();16761677 let value = item.owner.first().unwrap().fraction;1678 let owner = item.owner.first().unwrap().owner.clone();16791680 Self::add_token_index(collection_id, current_index, owner.clone())?;16811682 <ItemListIndex>::insert(collection_id, current_index);1683 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16841685 // Update balance1686 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1687 .checked_add(value)1688 .ok_or(Error::<T>::NumOverflow)?;1689 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16901691 Ok(())1692 }16931694 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1695 let current_index = <ItemListIndex>::get(collection_id)1696 .checked_add(1)1697 .ok_or(Error::<T>::NumOverflow)?;16981699 let item_owner = item.owner.clone();1700 Self::add_token_index(collection_id, current_index, item.owner.clone())?;17011702 <ItemListIndex>::insert(collection_id, current_index);1703 <NftItemList<T>>::insert(collection_id, current_index, item);17041705 // Update balance1706 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1707 .checked_add(1)1708 .ok_or(Error::<T>::NumOverflow)?;1709 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17101711 Ok(())1712 }17131714 fn burn_refungible_item(1715 collection_id: CollectionId,1716 item_id: TokenId,1717 owner: T::AccountId,1718 ) -> DispatchResult {1719 ensure!(1720 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1721 Error::<T>::TokenNotFound1722 );1723 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1724 let item = collection1725 .owner1726 .iter()1727 .filter(|&i| i.owner == owner)1728 .next()1729 .unwrap();1730 Self::remove_token_index(collection_id, item_id, owner.clone())?;17311732 // update balance1733 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1734 .checked_sub(item.fraction)1735 .ok_or(Error::<T>::NumOverflow)?;1736 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17371738 <ReFungibleItemList<T>>::remove(collection_id, item_id);17391740 Ok(())1741 }17421743 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1744 ensure!(1745 <NftItemList<T>>::contains_key(collection_id, item_id),1746 Error::<T>::TokenNotFound1747 );1748 let item = <NftItemList<T>>::get(collection_id, item_id);1749 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17501751 // update balance1752 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1753 .checked_sub(1)1754 .ok_or(Error::<T>::NumOverflow)?;1755 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1756 <NftItemList<T>>::remove(collection_id, item_id);17571758 Ok(())1759 }17601761 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1762 ensure!(1763 <FungibleItemList<T>>::contains_key(collection_id, owner),1764 Error::<T>::TokenNotFound1765 );1766 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1767 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17681769 // update balance1770 let new_balance = <Balance<T>>::get(collection_id, owner)1771 .checked_sub(value)1772 .ok_or(Error::<T>::NumOverflow)?;1773 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17741775 if balance.value - value > 0 {1776 balance.value -= value;1777 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1778 }1779 else {1780 <FungibleItemList<T>>::remove(collection_id, owner);1781 }17821783 Ok(())1784 }17851786 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1787 ensure!(1788 <Collection<T>>::contains_key(collection_id),1789 Error::<T>::CollectionNotFound1790 );1791 Ok(())1792 }17931794 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1795 Self::collection_exists(collection_id)?;17961797 let target_collection = <Collection<T>>::get(collection_id);1798 ensure!(1799 subject == target_collection.owner,1800 Error::<T>::NoPermission1801 );18021803 Ok(())1804 }18051806 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1807 let target_collection = <Collection<T>>::get(collection_id);1808 let mut result: bool = subject == target_collection.owner;1809 let exists = <AdminList<T>>::contains_key(collection_id);18101811 if !result & exists {1812 if <AdminList<T>>::get(collection_id).contains(&subject) {1813 result = true1814 }1815 }18161817 result1818 }18191820 fn check_owner_or_admin_permissions(1821 collection_id: CollectionId,1822 subject: T::AccountId,1823 ) -> DispatchResult {1824 Self::collection_exists(collection_id)?;1825 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18261827 ensure!(1828 result,1829 Error::<T>::NoPermission1830 );1831 Ok(())1832 }18331834 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1835 let target_collection = <Collection<T>>::get(collection_id);18361837 match target_collection.mode {1838 CollectionMode::NFT => {1839 <NftItemList<T>>::get(collection_id, item_id).owner == subject1840 }1841 CollectionMode::Fungible(_) => {1842 <FungibleItemList<T>>::contains_key(collection_id, &subject)1843 }1844 CollectionMode::ReFungible(_) => {1845 <ReFungibleItemList<T>>::get(collection_id, item_id)1846 .owner1847 .iter()1848 .any(|i| i.owner == subject)1849 }1850 CollectionMode::Invalid => false,1851 }1852 }18531854 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1855 let mes = Error::<T>::AddresNotInWhiteList;1856 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18571858 Ok(())1859 }18601861 fn transfer_fungible(1862 collection_id: CollectionId,1863 value: u128,1864 owner: &T::AccountId,1865 recipient: &T::AccountId,1866 ) -> DispatchResult {1867 ensure!(1868 <FungibleItemList<T>>::contains_key(collection_id, owner),1869 Error::<T>::TokenNotFound1870 );18711872 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1873 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18741875 // Send balance to recipient (updates balanceOf of recipient)1876 Self::add_fungible_item(collection_id, recipient, value)?;18771878 // update balanceOf of sender1879 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18801881 // Reduce or remove sender1882 if balance.value == value {1883 <FungibleItemList<T>>::remove(collection_id, owner);1884 }1885 else {1886 balance.value -= value;1887 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1888 }18891890 Ok(())1891 }18921893 fn transfer_refungible(1894 collection_id: CollectionId,1895 item_id: TokenId,1896 value: u128,1897 owner: T::AccountId,1898 new_owner: T::AccountId,1899 ) -> DispatchResult {1900 ensure!(1901 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1902 Error::<T>::TokenNotFound1903 );19041905 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1906 let item = full_item1907 .owner1908 .iter()1909 .filter(|i| i.owner == owner)1910 .next()1911 .ok_or(Error::<T>::NumOverflow)?;1912 let amount = item.fraction;19131914 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19151916 // update balance1917 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1918 .checked_sub(value)1919 .ok_or(Error::<T>::NumOverflow)?;1920 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19211922 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1923 .checked_add(value)1924 .ok_or(Error::<T>::NumOverflow)?;1925 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19261927 let old_owner = item.owner.clone();1928 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19291930 // transfer1931 if amount == value && !new_owner_has_account {1932 // change owner1933 // new owner do not have account1934 let mut new_full_item = full_item.clone();1935 new_full_item1936 .owner1937 .iter_mut()1938 .find(|i| i.owner == owner)1939 .unwrap()1940 .owner = new_owner.clone();1941 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19421943 // update index collection1944 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1945 } else {1946 let mut new_full_item = full_item.clone();1947 new_full_item1948 .owner1949 .iter_mut()1950 .find(|i| i.owner == owner)1951 .unwrap()1952 .fraction -= value;19531954 // separate amount1955 if new_owner_has_account {1956 // new owner has account1957 new_full_item1958 .owner1959 .iter_mut()1960 .find(|i| i.owner == new_owner)1961 .unwrap()1962 .fraction += value;1963 } else {1964 // new owner do not have account1965 new_full_item.owner.push(Ownership {1966 owner: new_owner.clone(),1967 fraction: value,1968 });1969 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1970 }19711972 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1973 }19741975 Ok(())1976 }19771978 fn transfer_nft(1979 collection_id: CollectionId,1980 item_id: TokenId,1981 sender: T::AccountId,1982 new_owner: T::AccountId,1983 ) -> DispatchResult {1984 ensure!(1985 <NftItemList<T>>::contains_key(collection_id, item_id),1986 Error::<T>::TokenNotFound1987 );19881989 let mut item = <NftItemList<T>>::get(collection_id, item_id);19901991 ensure!(1992 sender == item.owner,1993 Error::<T>::MustBeTokenOwner1994 );19951996 // update balance1997 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1998 .checked_sub(1)1999 .ok_or(Error::<T>::NumOverflow)?;2000 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20012002 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2003 .checked_add(1)2004 .ok_or(Error::<T>::NumOverflow)?;2005 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20062007 // change owner2008 let old_owner = item.owner.clone();2009 item.owner = new_owner.clone();2010 <NftItemList<T>>::insert(collection_id, item_id, item);20112012 // update index collection2013 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20142015 Ok(())2016 }2017 2018 fn item_exists(2019 collection_id: CollectionId,2020 item_id: TokenId,2021 mode: &CollectionMode2022 ) -> DispatchResult {2023 match mode {2024 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2025 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2026 _ => ()2027 };2028 2029 Ok(())2030 }20312032 fn set_re_fungible_variable_data(2033 collection_id: CollectionId,2034 item_id: TokenId,2035 data: Vec<u8>2036 ) -> DispatchResult {2037 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20382039 item.variable_data = data;20402041 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20422043 Ok(())2044 }20452046 fn set_nft_variable_data(2047 collection_id: CollectionId,2048 item_id: TokenId,2049 data: Vec<u8>2050 ) -> DispatchResult {2051 let mut item = <NftItemList<T>>::get(collection_id, item_id);2052 2053 item.variable_data = data;20542055 <NftItemList<T>>::insert(collection_id, item_id, item);2056 2057 Ok(())2058 }20592060 fn init_collection(item: &CollectionType<T::AccountId>) {2061 // check params2062 assert!(2063 item.decimal_points <= MAX_DECIMAL_POINTS,2064 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2065 );2066 assert!(2067 item.name.len() <= 64,2068 "Collection name can not be longer than 63 char"2069 );2070 assert!(2071 item.name.len() <= 256,2072 "Collection description can not be longer than 255 char"2073 );2074 assert!(2075 item.token_prefix.len() <= 16,2076 "Token prefix can not be longer than 15 char"2077 );20782079 // Generate next collection ID2080 let next_id = CreatedCollectionCount::get()2081 .checked_add(1)2082 .unwrap();20832084 CreatedCollectionCount::put(next_id);2085 }20862087 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2088 let current_index = <ItemListIndex>::get(collection_id)2089 .checked_add(1)2090 .unwrap();20912092 let item_owner = item.owner.clone();2093 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20942095 <ItemListIndex>::insert(collection_id, current_index);20962097 // Update balance2098 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2099 .checked_add(1)2100 .unwrap();2101 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2102 }21032104 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2105 let current_index = <ItemListIndex>::get(collection_id)2106 .checked_add(1)2107 .unwrap();21082109 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();21102111 <ItemListIndex>::insert(collection_id, current_index);21122113 // Update balance2114 let new_balance = <Balance<T>>::get(collection_id, owner)2115 .checked_add(item.value)2116 .unwrap();2117 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2118 }21192120 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2121 let current_index = <ItemListIndex>::get(collection_id)2122 .checked_add(1)2123 .unwrap();21242125 let value = item.owner.first().unwrap().fraction;2126 let owner = item.owner.first().unwrap().owner.clone();21272128 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();21292130 <ItemListIndex>::insert(collection_id, current_index);21312132 // Update balance2133 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2134 .checked_add(value)2135 .unwrap();2136 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2137 }21382139 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21402141 // add to account limit2142 if <AccountItemCount<T>>::contains_key(owner.clone()) {21432144 // bound Owned tokens by a single address2145 let count = <AccountItemCount<T>>::get(owner.clone());2146 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21472148 <AccountItemCount<T>>::insert(owner.clone(), count2149 .checked_add(1)2150 .ok_or(Error::<T>::NumOverflow)?);2151 }2152 else {2153 <AccountItemCount<T>>::insert(owner.clone(), 1);2154 }21552156 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2157 if list_exists {2158 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2159 let item_contains = list.contains(&item_index.clone());21602161 if !item_contains {2162 list.push(item_index.clone());2163 }21642165 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2166 } else {2167 let mut itm = Vec::new();2168 itm.push(item_index.clone());2169 <AddressTokens<T>>::insert(collection_id, owner, itm);2170 2171 }21722173 Ok(())2174 }21752176 fn remove_token_index(2177 collection_id: CollectionId,2178 item_index: TokenId,2179 owner: T::AccountId,2180 ) -> DispatchResult {21812182 // update counter2183 <AccountItemCount<T>>::insert(owner.clone(), 2184 <AccountItemCount<T>>::get(owner.clone())2185 .checked_sub(1)2186 .ok_or(Error::<T>::NumOverflow)?);218721882189 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2190 if list_exists {2191 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2192 let item_contains = list.contains(&item_index.clone());21932194 if item_contains {2195 list.retain(|&item| item != item_index);2196 <AddressTokens<T>>::insert(collection_id, owner, list);2197 }2198 }21992200 Ok(())2201 }22022203 fn move_token_index(2204 collection_id: CollectionId,2205 item_index: TokenId,2206 old_owner: T::AccountId,2207 new_owner: T::AccountId,2208 ) -> DispatchResult {2209 Self::remove_token_index(collection_id, item_index, old_owner)?;2210 Self::add_token_index(collection_id, item_index, new_owner)?;22112212 Ok(())2213 }2214 2215 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2216 if <ContractOwner<T>>::contains_key(contract.clone()) {2217 let owner = <ContractOwner<T>>::get(contract);2218 ensure!(account == owner, Error::<T>::NoPermission);2219 } else {2220 fail!(Error::<T>::NoPermission);2221 }22222223 Ok(())2224 }2225}22262227////////////////////////////////////////////////////////////////////////////////////////////////////2228// Economic models2229// #region22302231/// Fee multiplier.2232pub type Multiplier = FixedU128;22332234type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2235 <T as system::Trait>::AccountId,2236>>::Balance;2237type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2238 <T as system::Trait>::AccountId,2239>>::NegativeImbalance;22402241/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2242/// in the queue.2243#[derive(Encode, Decode, Clone, Eq, PartialEq)]2244pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2245 #[codec(compact)] BalanceOf<T>2246);22472248impl<T: Trait + Send + Sync> sp_std::fmt::Debug2249 for ChargeTransactionPayment<T>2250{2251 #[cfg(feature = "std")]2252 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2253 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2254 }2255 #[cfg(not(feature = "std"))]2256 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2257 Ok(())2258 }2259}22602261impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2262where2263 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2264 BalanceOf<T>: Send + Sync + FixedPointOperand,2265{2266 /// utility constructor. Used only in client/factory code.2267 pub fn from(fee: BalanceOf<T>) -> Self {2268 Self(fee)2269 }22702271 pub fn traditional_fee(2272 len: usize,2273 info: &DispatchInfoOf<T::Call>,2274 tip: BalanceOf<T>,2275 ) -> BalanceOf<T>2276 where2277 T::Call: Dispatchable<Info = DispatchInfo>,2278 {2279 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2280 }22812282 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2283 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2284 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2285 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2286 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2287 }22882289 fn withdraw_fee(2290 &self,2291 who: &T::AccountId,2292 call: &T::Call,2293 info: &DispatchInfoOf<T::Call>,2294 len: usize,2295 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2296 let tip = self.0;22972298 // Set fee based on call type. Creating collection costs 1 Unique.2299 // All other transactions have traditional fees so far2300 // let fee = match call.is_sub_type() {2301 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2302 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2303 // // _ => <BalanceOf<T>>::from(100)2304 // };2305 let fee = Self::traditional_fee(len, info, tip);23062307 // Only mess with balances if fee is not zero.2308 if fee.is_zero() {2309 return Ok((fee, None));2310 }23112312 // Determine who is paying transaction fee based on ecnomic model2313 // Parse call to extract collection ID and access collection sponsor2314 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2315 Some(Call::create_item(collection_id, _owner, _properties)) => {23162317 // sponsor timeout2318 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;23192320 let limit = <Collection<T>>::get(collection_id).limits.sponsor_transfer_timeout;2321 let mut sponsored = true;2322 if <CreateItemBasket<T>>::contains_key((collection_id, &who)) {2323 let last_tx_block = <CreateItemBasket<T>>::get((collection_id, &who));2324 let limit_time = last_tx_block + limit.into();2325 if block_number <= limit_time {2326 sponsored = false;2327 }2328 }2329 if sponsored {2330 <CreateItemBasket<T>>::insert((collection_id, who.clone()), block_number);2331 }23322333 // check free create limit2334 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2335 (<Collection<T>>::get(collection_id).sponsor_confirmed) &&2336 (sponsored)2337 {2338 <Collection<T>>::get(collection_id).sponsor2339 } else {2340 T::AccountId::default()2341 }2342 }2343 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2344 2345 let mut sponsor_transfer = false;2346 if <Collection<T>>::get(collection_id).sponsor_confirmed {23472348 let collection_limits = <Collection<T>>::get(collection_id).limits;2349 let collection_mode = <Collection<T>>::get(collection_id).mode;2350 2351 // sponsor timeout2352 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2353 sponsor_transfer = match collection_mode {2354 CollectionMode::NFT => {2355 2356 // get correct limit2357 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2358 collection_limits.sponsor_transfer_timeout2359 } else {2360 ChainLimit::get().nft_sponsor_transfer_timeout2361 };2362 2363 let mut sponsored = true;2364 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2365 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2366 let limit_time = last_tx_block + limit.into();2367 if block_number <= limit_time {2368 sponsored = false;2369 }2370 }2371 if sponsored {2372 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2373 }23742375 sponsored2376 }2377 CollectionMode::Fungible(_) => {2378 2379 // get correct limit2380 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2381 collection_limits.sponsor_transfer_timeout2382 } else {2383 ChainLimit::get().fungible_sponsor_transfer_timeout2384 };2385 2386 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2387 let mut sponsored = true;2388 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2389 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2390 let limit_time = last_tx_block + limit.into();2391 if block_number <= limit_time {2392 sponsored = false;2393 }2394 }2395 if sponsored {2396 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2397 }23982399 sponsored2400 }2401 CollectionMode::ReFungible(_) => {2402 2403 // get correct limit2404 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2405 collection_limits.sponsor_transfer_timeout2406 } else {2407 ChainLimit::get().refungible_sponsor_transfer_timeout2408 };2409 2410 let mut sponsored = true;2411 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2412 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2413 let limit_time = last_tx_block + limit.into();2414 if block_number <= limit_time {2415 sponsored = false;2416 }2417 }2418 if sponsored {2419 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2420 }24212422 sponsored2423 }2424 _ => {2425 false2426 },2427 };2428 }24292430 if !sponsor_transfer {2431 T::AccountId::default()2432 } else {2433 <Collection<T>>::get(collection_id).sponsor2434 }2435 }24362437 _ => T::AccountId::default(),2438 };24392440 // Sponsor smart contracts2441 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24422443 // On instantiation: set the contract owner2444 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24452446 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2447 code_hash,2448 &data,2449 &who,2450 );2451 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24522453 T::AccountId::default()2454 },24552456 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2457 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24582459 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24602461 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2462 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2463 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2464 2465 if !owned_contract && white_list_enabled {2466 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2467 return Err(InvalidTransaction::Call.into());2468 }2469 }24702471 let mut sponsor_transfer = false;2472 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2473 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2474 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2475 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2476 let limit_time = last_tx_block + rate_limit;24772478 if block_number >= limit_time {2479 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2480 sponsor_transfer = true;2481 }2482 } else {2483 sponsor_transfer = false;2484 }2485 2486 2487 let mut sp = T::AccountId::default();2488 if sponsor_transfer {2489 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2490 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2491 sp = called_contract;2492 }2493 }2494 }24952496 sp2497 },24982499 _ => sponsor,2500 };25012502 let mut who_pays_fee: T::AccountId = sponsor.clone();2503 if sponsor == T::AccountId::default() {2504 who_pays_fee = who.clone();2505 }25062507 match <T as transaction_payment::Trait>::Currency::withdraw(2508 &who_pays_fee,2509 fee,2510 if tip.is_zero() {2511 WithdrawReason::TransactionPayment.into()2512 } else {2513 WithdrawReason::TransactionPayment | WithdrawReason::Tip2514 },2515 ExistenceRequirement::KeepAlive,2516 ) {2517 Ok(imbalance) => Ok((fee, Some(imbalance))),2518 Err(_) => Err(InvalidTransaction::Payment.into()),2519 }2520 }2521}252225232524impl<T: Trait + Send + Sync> SignedExtension2525 for ChargeTransactionPayment<T>2526where2527 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2528 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2529{2530 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2531 type AccountId = T::AccountId;2532 type Call = T::Call;2533 type AdditionalSigned = ();2534 type Pre = (2535 BalanceOf<T>,2536 Self::AccountId,2537 Option<NegativeImbalanceOf<T>>,2538 BalanceOf<T>,2539 );2540 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2541 Ok(())2542 }25432544 fn validate(2545 &self,2546 who: &Self::AccountId,2547 call: &Self::Call,2548 info: &DispatchInfoOf<Self::Call>,2549 len: usize,2550 ) -> TransactionValidity {2551 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2552 Ok(ValidTransaction {2553 priority: Self::get_priority(len, info, fee),2554 ..Default::default()2555 })2556 }25572558 fn pre_dispatch(2559 self,2560 who: &Self::AccountId,2561 call: &Self::Call,2562 info: &DispatchInfoOf<Self::Call>,2563 len: usize,2564 ) -> Result<Self::Pre, TransactionValidityError> {2565 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2566 Ok((self.0, who.clone(), imbalance, fee))2567 }25682569 fn post_dispatch(2570 pre: Self::Pre,2571 info: &DispatchInfoOf<Self::Call>,2572 post_info: &PostDispatchInfoOf<Self::Call>,2573 len: usize,2574 _result: &DispatchResult,2575 ) -> Result<(), TransactionValidityError> {2576 let (tip, who, imbalance, fee) = pre;2577 if let Some(payed) = imbalance {2578 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2579 len as u32, info, post_info, tip,2580 );2581 let refund = fee.saturating_sub(actual_fee);2582 let actual_payment =2583 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2584 &who, refund,2585 ) {2586 Ok(refund_imbalance) => {2587 // The refund cannot be larger than the up front payed max weight.2588 // `PostDispatchInfo::calc_unspent` guards against such a case.2589 match payed.offset(refund_imbalance) {2590 Ok(actual_payment) => actual_payment,2591 Err(_) => return Err(InvalidTransaction::Payment.into()),2592 }2593 }2594 // We do not recreate the account using the refund. The up front payment2595 // is gone in that case.2596 Err(_) => payed,2597 };2598 let imbalances = actual_payment.split(tip);2599 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2600 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2601 );2602 }2603 Ok(())2604 }2605}26062607// #endregionpallets/nft/src/types.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/nft/src/types.rs
@@ -0,0 +1,234 @@
+#[cfg(feature = "std")]
+pub use std::*;
+
+#[cfg(feature = "std")]
+pub use serde::*;
+
+use codec::{Decode, Encode};
+
+pub type CollectionId = u32;
+pub type TokenId = u32;
+pub type DecimalPoints = u8;
+
+#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum CollectionMode {
+ Invalid,
+ NFT,
+ // decimal points
+ Fungible(DecimalPoints),
+ // decimal points
+ ReFungible(DecimalPoints),
+}
+
+impl Into<u8> for CollectionMode {
+ fn into(self) -> u8 {
+ match self {
+ CollectionMode::Invalid => 0,
+ CollectionMode::NFT => 1,
+ CollectionMode::Fungible(_) => 2,
+ CollectionMode::ReFungible(_) => 3,
+ }
+ }
+}
+
+#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum AccessMode {
+ Normal,
+ WhiteList,
+}
+impl Default for AccessMode {
+ fn default() -> Self {
+ Self::Normal
+ }
+}
+
+impl Default for CollectionMode {
+ fn default() -> Self {
+ Self::Invalid
+ }
+}
+
+#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum SchemaVersion {
+ ImageURL,
+ Unique,
+}
+impl Default for SchemaVersion {
+ fn default() -> Self {
+ Self::ImageURL
+ }
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct Ownership<AccountId> {
+ pub owner: AccountId,
+ pub fraction: u128,
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct CollectionType<AccountId> {
+ pub owner: AccountId,
+ pub mode: CollectionMode,
+ pub access: AccessMode,
+ pub decimal_points: DecimalPoints,
+ pub name: Vec<u16>, // 64 include null escape char
+ pub description: Vec<u16>, // 256 include null escape char
+ pub token_prefix: Vec<u8>, // 16 include null escape char
+ pub mint_mode: bool,
+ pub offchain_schema: Vec<u8>,
+ pub schema_version: SchemaVersion,
+ pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender
+ pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship
+ pub limits: CollectionLimits, // Collection private restrictions
+ pub variable_on_chain_schema: Vec<u8>, //
+ pub const_on_chain_schema: Vec<u8>, //
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct NftItemType<AccountId> {
+ pub collection: CollectionId,
+ pub owner: AccountId,
+ pub const_data: Vec<u8>,
+ pub variable_data: Vec<u8>,
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct FungibleItemType<AccountId> {
+ pub collection: CollectionId,
+ pub owner: AccountId,
+ pub value: u128,
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct ReFungibleItemType<AccountId> {
+ pub collection: CollectionId,
+ pub owner: Vec<Ownership<AccountId>>,
+ pub const_data: Vec<u8>,
+ pub variable_data: Vec<u8>,
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct ApprovePermissions<AccountId> {
+ pub approved: AccountId,
+ pub amount: u128,
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct VestingItem<AccountId, Moment> {
+ pub sender: AccountId,
+ pub recipient: AccountId,
+ pub collection_id: CollectionId,
+ pub item_id: TokenId,
+ pub amount: u64,
+ pub vesting_date: Moment,
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct BasketItem<AccountId, BlockNumber> {
+ pub address: AccountId,
+ pub start_block: BlockNumber,
+}
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct CollectionLimits {
+ pub account_token_ownership_limit: u32,
+ pub sponsored_data_size: u32,
+ pub token_limit: u32,
+
+ // Timeouts for item types in passed blocks
+ pub sponsor_transfer_timeout: u32,
+}
+
+impl Default for CollectionLimits {
+ fn default() -> CollectionLimits {
+ CollectionLimits {
+ account_token_ownership_limit: 10_000_000,
+ token_limit: u32::max_value(),
+ sponsored_data_size: u32::max_value(),
+ sponsor_transfer_timeout: 14400 }
+ }
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct ChainLimits {
+ pub collection_numbers_limit: u32,
+ pub account_token_ownership_limit: u32,
+ pub collections_admins_limit: u64,
+ pub custom_data_limit: u32,
+
+ // Timeouts for item types in passed blocks
+ pub nft_sponsor_transfer_timeout: u32,
+ pub fungible_sponsor_transfer_timeout: u32,
+ pub refungible_sponsor_transfer_timeout: u32,
+}
+
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct CreateNftData {
+ pub const_data: Vec<u8>,
+ pub variable_data: Vec<u8>,
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct CreateFungibleData {
+}
+
+#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub struct CreateReFungibleData {
+ pub const_data: Vec<u8>,
+ pub variable_data: Vec<u8>,
+}
+
+#[derive(Encode, Decode, Debug, Clone, PartialEq)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+pub enum CreateItemData {
+ NFT(CreateNftData),
+ Fungible(CreateFungibleData),
+ ReFungible(CreateReFungibleData),
+}
+
+impl CreateItemData {
+ pub fn len(&self) -> usize {
+ let len = match self {
+ CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),
+ CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),
+ _ => 0
+ };
+
+ return len;
+ }
+}
+
+impl From<CreateNftData> for CreateItemData {
+ fn from(item: CreateNftData) -> Self {
+ CreateItemData::NFT(item)
+ }
+}
+
+impl From<CreateReFungibleData> for CreateItemData {
+ fn from(item: CreateReFungibleData) -> Self {
+ CreateItemData::ReFungible(item)
+ }
+}
+
+impl From<CreateFungibleData> for CreateItemData {
+ fn from(item: CreateFungibleData) -> Self {
+ CreateItemData::Fungible(item)
+ }
+}