difftreelog
Merge fix
in: master
1 file changed
pallets/nft/src/lib.rsdiffbeforeafterboth1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "std")]4pub use std::*;56#[cfg(feature = "std")]7pub use serde::*;89use codec::{Decode, Encode};10pub use frame_support::{11 construct_runtime, decl_event, decl_module, decl_storage, decl_error,12 dispatch::DispatchResult,13 ensure, fail, parameter_types,14 traits::{15 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,16 Randomness, WithdrawReason,17 },18 weights::{19 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},20 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,21 WeightToFeePolynomial,22 },23 IsSubType, StorageValue,24};2526use frame_system::{self as system, ensure_signed, ensure_root};27use sp_runtime::sp_std::prelude::Vec;28use sp_runtime::{29 traits::{30 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SignedExtension, Zero,31 },32 transaction_validity::{33 InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,34 },35 FixedPointOperand, FixedU128,36};37use pallet_contracts::ContractAddressFor;38use sp_runtime::traits::StaticLookup;3940#[cfg(test)]41mod mock;4243#[cfg(test)]44mod tests;4546mod default_weights;4748pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;4950// Structs51// #region5253pub type CollectionId = u32;54pub type TokenId = u32;5556pub type DecimalPoints = u8;5758#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]59#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]60pub enum CollectionMode {61 Invalid,62 NFT,63 // decimal points64 Fungible(DecimalPoints),65 // decimal points66 ReFungible(DecimalPoints),67}6869impl Into<u8> for CollectionMode {70 fn into(self) -> u8 {71 match self {72 CollectionMode::Invalid => 0,73 CollectionMode::NFT => 1,74 CollectionMode::Fungible(_) => 2,75 CollectionMode::ReFungible(_) => 3,76 }77 }78}7980#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]81#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]82pub enum AccessMode {83 Normal,84 WhiteList,85}86impl Default for AccessMode {87 fn default() -> Self {88 Self::Normal89 }90}9192impl Default for CollectionMode {93 fn default() -> Self {94 Self::Invalid95 }96}9798#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]99#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]100pub struct Ownership<AccountId> {101 pub owner: AccountId,102 pub fraction: u128,103}104105#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]106#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]107pub struct CollectionType<AccountId> {108 pub owner: AccountId,109 pub mode: CollectionMode,110 pub access: AccessMode,111 pub decimal_points: DecimalPoints,112 pub name: Vec<u16>, // 64 include null escape char113 pub description: Vec<u16>, // 256 include null escape char114 pub token_prefix: Vec<u8>, // 16 include null escape char115 pub mint_mode: bool,116 pub offchain_schema: Vec<u8>,117 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender118 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship119 pub limits: CollectionLimits, // Collection private restrictions 120 pub variable_on_chain_schema: Vec<u8>, //121 pub const_on_chain_schema: Vec<u8>, //122}123124#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]125#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]126pub struct NftItemType<AccountId> {127 pub collection: CollectionId,128 pub owner: AccountId,129 pub const_data: Vec<u8>,130 pub variable_data: Vec<u8>,131}132133#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]134#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]135pub struct FungibleItemType<AccountId> {136 pub collection: CollectionId,137 pub owner: AccountId,138 pub value: u128,139}140141#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]142#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]143pub struct ReFungibleItemType<AccountId> {144 pub collection: CollectionId,145 pub owner: Vec<Ownership<AccountId>>,146 pub const_data: Vec<u8>,147 pub variable_data: Vec<u8>,148}149150#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]151#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]152pub struct ApprovePermissions<AccountId> {153 pub approved: AccountId,154 pub amount: u128,155}156157#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]158#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]159pub struct VestingItem<AccountId, Moment> {160 pub sender: AccountId,161 pub recipient: AccountId,162 pub collection_id: CollectionId,163 pub item_id: TokenId,164 pub amount: u64,165 pub vesting_date: Moment,166}167168#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]169#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]170pub struct BasketItem<AccountId, BlockNumber> {171 pub address: AccountId,172 pub start_block: BlockNumber,173}174175#[derive(Encode, Decode, Debug, Clone, PartialEq)]176#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]177pub struct CollectionLimits {178 pub account_token_ownership_limit: u32,179 pub sponsored_data_size: u32,180 pub token_limit: u32,181182 // Timeouts for item types in passed blocks183 pub sponsor_transfer_timeout: u32,184}185186impl Default for CollectionLimits {187 fn default() -> CollectionLimits {188 CollectionLimits { 189 account_token_ownership_limit: u32::max_value(), 190 token_limit: u32::max_value(),191 sponsored_data_size: u32::max_value(), 192 sponsor_transfer_timeout: u32::max_value() }193 }194}195196#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]198pub struct ChainLimits {199 pub collection_numbers_limit: u32,200 pub account_token_ownership_limit: u32,201 pub collections_admins_limit: u64,202 pub custom_data_limit: u32,203204 // Timeouts for item types in passed blocks205 pub nft_sponsor_transfer_timeout: u32,206 pub fungible_sponsor_transfer_timeout: u32,207 pub refungible_sponsor_transfer_timeout: u32,208}209210pub trait WeightInfo {211 fn create_collection() -> Weight;212 fn destroy_collection() -> Weight;213 fn add_to_white_list() -> Weight;214 fn remove_from_white_list() -> Weight;215 fn set_public_access_mode() -> Weight;216 fn set_mint_permission() -> Weight;217 fn change_collection_owner() -> Weight;218 fn add_collection_admin() -> Weight;219 fn remove_collection_admin() -> Weight;220 fn set_collection_sponsor() -> Weight;221 fn confirm_sponsorship() -> Weight;222 fn remove_collection_sponsor() -> Weight;223 fn create_item(s: usize) -> Weight;224 fn burn_item() -> Weight;225 fn transfer() -> Weight;226 fn approve() -> Weight;227 fn transfer_from() -> Weight;228 fn set_offchain_schema() -> Weight;229 fn set_const_on_chain_schema() -> Weight;230 fn set_variable_on_chain_schema() -> Weight;231 fn set_variable_meta_data() -> Weight;232 fn enable_contract_sponsoring() -> Weight;233}234235#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]236#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]237pub struct CreateNftData {238 pub const_data: Vec<u8>,239 pub variable_data: Vec<u8>,240}241242#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]243#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]244pub struct CreateFungibleData {245}246247#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]248#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]249pub struct CreateReFungibleData {250 pub const_data: Vec<u8>,251 pub variable_data: Vec<u8>,252}253254#[derive(Encode, Decode, Debug, Clone, PartialEq)]255#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]256pub enum CreateItemData {257 NFT(CreateNftData),258 Fungible(CreateFungibleData),259 ReFungible(CreateReFungibleData)260}261262impl CreateItemData {263 pub fn len(&self) -> usize {264 let len = match self {265 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),266 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),267 _ => 0268 };269 270 return len;271 }272}273274impl From<CreateNftData> for CreateItemData {275 fn from(item: CreateNftData) -> Self {276 CreateItemData::NFT(item)277 }278}279280impl From<CreateReFungibleData> for CreateItemData {281 fn from(item: CreateReFungibleData) -> Self {282 CreateItemData::ReFungible(item)283 }284}285286impl From<CreateFungibleData> for CreateItemData {287 fn from(item: CreateFungibleData) -> Self {288 CreateItemData::Fungible(item)289 }290}291292293decl_error! {294 /// Error for non-fungible-token module.295 pub enum Error for Module<T: Trait> {296 /// Total collections bound exceeded.297 TotalCollectionsLimitExceeded,298 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.299 CollectionDecimalPointLimitExceeded, 300 /// Collection name can not be longer than 63 char.301 CollectionNameLimitExceeded, 302 /// Collection description can not be longer than 255 char.303 CollectionDescriptionLimitExceeded, 304 /// Token prefix can not be longer than 15 char.305 CollectionTokenPrefixLimitExceeded,306 /// This collection does not exist.307 CollectionNotFound,308 /// Item not exists.309 TokenNotFound,310 /// Arithmetic calculation overflow.311 NumOverflow, 312 /// Account already has admin role.313 AlreadyAdmin, 314 /// You do not own this collection.315 NoPermission,316 /// This address is not set as sponsor, use setCollectionSponsor first.317 ConfirmUnsetSponsorFail,318 /// Collection is not in mint mode.319 PublicMintingNotAllowed,320 /// Sender parameter and item owner must be equal.321 MustBeTokenOwner,322 /// Item balance not enough.323 TokenValueTooLow,324 /// Size of item is too large.325 NftSizeLimitExceeded,326 /// No approve found327 ApproveNotFound,328 /// Requested value more than approved.329 TokenValueNotEnough,330 /// Only approved addresses can call this method.331 ApproveRequired,332 /// Address is not in white list.333 AddresNotInWhiteList,334 /// Number of collection admins bound exceeded.335 CollectionAdminsLimitExceeded,336 /// Owned tokens by a single address bound exceeded.337 AddressOwnershipLimitExceeded,338 /// Length of items properties must be greater than 0.339 EmptyArgument,340 /// const_data exceeded data limit.341 TokenConstDataLimitExceeded,342 /// variable_data exceeded data limit.343 TokenVariableDataLimitExceeded,344 /// Not NFT item data used to mint in NFT collection.345 NotNftDataUsedToMintNftCollectionToken,346 /// Not Fungible item data used to mint in Fungible collection.347 NotFungibleDataUsedToMintFungibleCollectionToken,348 /// Not Re Fungible item data used to mint in Re Fungible collection.349 NotReFungibleDataUsedToMintReFungibleCollectionToken,350 /// Unexpected collection type.351 UnexpectedCollectionType,352 /// Can't store metadata in fungible tokens.353 CantStoreMetadataInFungibleTokens,354 /// Collection token limit exceeded355 CollectionTokenLimitExceeded,356 /// Account token limit exceeded per collection357 AccountTokenLimitExceeded358 }359}360361pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {362 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;363364 /// Weight information for extrinsics in this pallet.365 type WeightInfo: WeightInfo;366}367368#[cfg(feature = "runtime-benchmarks")]369mod benchmarking;370371// #endregion372373decl_storage! {374 trait Store for Module<T: Trait> as Nft {375376 // Private members377 NextCollectionID: CollectionId;378 CreatedCollectionCount: u32;379 ChainVersion: u64;380 ItemListIndex: map hasher(identity) CollectionId => TokenId;381382 // Chain limits struct383 pub ChainLimit get(fn chain_limit) config(): ChainLimits;384385 // Bound counters386 CollectionCount: u32;387 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;388389 // Basic collections390 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;391 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;392 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;393394 /// Balance owner per collection map395 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;396397 /// second parameter: item id + owner account id398 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;399400 /// Item collections401 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;402 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;403 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;404405 /// Index list406 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;407408 /// Tokens transfer baskets409 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;410 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;411 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;412413 // Contract Sponsorship and Ownership414 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;415 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;416 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;417 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;418 }419 add_extra_genesis {420 build(|config: &GenesisConfig<T>| {421 // Modification of storage422 for (_num, _c) in &config.collection {423 <Module<T>>::init_collection(_c);424 }425426 for (_num, _q, _i) in &config.nft_item_id {427 <Module<T>>::init_nft_token(_i);428 }429430 for (_num, _q, _i) in &config.fungible_item_id {431 <Module<T>>::init_fungible_token(_i);432 }433434 for (_num, _q, _i) in &config.refungible_item_id {435 <Module<T>>::init_refungible_token(_i);436 }437 })438 }439}440441decl_event!(442 pub enum Event<T>443 where444 AccountId = <T as system::Trait>::AccountId,445 {446 /// New collection was created447 /// 448 /// # Arguments449 /// 450 /// * collection_id: Globally unique identifier of newly created collection.451 /// 452 /// * mode: [CollectionMode] converted into u8.453 /// 454 /// * account_id: Collection owner.455 Created(CollectionId, u8, AccountId),456457 /// New item was created.458 /// 459 /// # Arguments460 /// 461 /// * collection_id: Id of the collection where item was created.462 /// 463 /// * item_id: Id of an item. Unique within the collection.464 ItemCreated(CollectionId, TokenId),465466 /// Collection item was burned.467 /// 468 /// # Arguments469 /// 470 /// collection_id.471 /// 472 /// item_id: Identifier of burned NFT.473 ItemDestroyed(CollectionId, TokenId),474 }475);476477decl_module! {478 pub struct Module<T: Trait> for enum Call where origin: T::Origin {479480 fn deposit_event() = default;481 type Error = Error<T>;482483 fn on_initialize(now: T::BlockNumber) -> Weight {484485 if ChainVersion::get() < 2486 {487 let value = NextCollectionID::get();488 CreatedCollectionCount::put(value);489 ChainVersion::put(2);490 }491492 0493 }494495 /// 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.496 /// 497 /// # Permissions498 /// 499 /// * Anyone.500 /// 501 /// # Arguments502 /// 503 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.504 /// 505 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.506 /// 507 /// * token_prefix: UTF-8 string with token prefix.508 /// 509 /// * mode: [CollectionMode] collection type and type dependent data.510 // returns collection ID511 #[weight = T::WeightInfo::create_collection()]512 pub fn create_collection(origin,513 collection_name: Vec<u16>,514 collection_description: Vec<u16>,515 token_prefix: Vec<u8>,516 mode: CollectionMode) -> DispatchResult {517518 // Anyone can create a collection519 let who = ensure_signed(origin)?;520521 let decimal_points = match mode {522 CollectionMode::Fungible(points) => points,523 CollectionMode::ReFungible(points) => points,524 _ => 0525 };526527 // bound Total number of collections528 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);529530 // check params531 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);532533 let mut name = collection_name.to_vec();534 name.push(0);535 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);536537 let mut description = collection_description.to_vec();538 description.push(0);539 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);540541 let mut prefix = token_prefix.to_vec();542 prefix.push(0);543 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);544545 // Generate next collection ID546 let next_id = CreatedCollectionCount::get()547 .checked_add(1)548 .ok_or(Error::<T>::NumOverflow)?;549550 // bound counter551 let total = CollectionCount::get()552 .checked_add(1)553 .ok_or(Error::<T>::NumOverflow)?;554555 CreatedCollectionCount::put(next_id);556 CollectionCount::put(total);557558 // Create new collection559 let new_collection = CollectionType {560 owner: who.clone(),561 name: name,562 mode: mode.clone(),563 mint_mode: false,564 access: AccessMode::Normal,565 description: description,566 decimal_points: decimal_points,567 token_prefix: prefix,568 offchain_schema: Vec::new(),569 sponsor: T::AccountId::default(),570 unconfirmed_sponsor: T::AccountId::default(),571 variable_on_chain_schema: Vec::new(),572 const_on_chain_schema: Vec::new(),573 limits: CollectionLimits::default(),574 };575576 // Add new collection to map577 <Collection<T>>::insert(next_id, new_collection);578579 // call event580 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));581582 Ok(())583 }584585 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.586 /// 587 /// # Permissions588 /// 589 /// * Collection Owner.590 /// 591 /// # Arguments592 /// 593 /// * collection_id: collection to destroy.594 #[weight = T::WeightInfo::destroy_collection()]595 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {596597 let sender = ensure_signed(origin)?;598 Self::check_owner_permissions(collection_id, sender)?;599600 <AddressTokens<T>>::remove_prefix(collection_id);601 <ApprovedList<T>>::remove_prefix(collection_id);602 <Balance<T>>::remove_prefix(collection_id);603 <ItemListIndex>::remove(collection_id);604 <AdminList<T>>::remove(collection_id);605 <Collection<T>>::remove(collection_id);606 <WhiteList<T>>::remove(collection_id);607608 <NftItemList<T>>::remove_prefix(collection_id);609 <FungibleItemList<T>>::remove_prefix(collection_id);610 <ReFungibleItemList<T>>::remove_prefix(collection_id);611612 <NftTransferBasket<T>>::remove_prefix(collection_id);613 <FungibleTransferBasket<T>>::remove_prefix(collection_id);614 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);615616 if CollectionCount::get() > 0617 {618 // bound couter619 let total = CollectionCount::get()620 .checked_sub(1)621 .ok_or(Error::<T>::NumOverflow)?;622623 CollectionCount::put(total);624 }625626 Ok(())627 }628629 /// Add an address to white list.630 /// 631 /// # Permissions632 /// 633 /// * Collection Owner634 /// * Collection Admin635 /// 636 /// # Arguments637 /// 638 /// * collection_id.639 /// 640 /// * address.641 #[weight = T::WeightInfo::add_to_white_list()]642 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{643644 let sender = ensure_signed(origin)?;645 Self::check_owner_or_admin_permissions(collection_id, sender)?;646647 let mut white_list_collection: Vec<T::AccountId>;648 if <WhiteList<T>>::contains_key(collection_id) {649 white_list_collection = <WhiteList<T>>::get(collection_id);650 if !white_list_collection.contains(&address.clone())651 {652 white_list_collection.push(address.clone());653 }654 }655 else {656 white_list_collection = Vec::new();657 white_list_collection.push(address.clone());658 }659660 <WhiteList<T>>::insert(collection_id, white_list_collection);661 Ok(())662 }663664 /// Remove an address from white list.665 /// 666 /// # Permissions667 /// 668 /// * Collection Owner669 /// * Collection Admin670 /// 671 /// # Arguments672 /// 673 /// * collection_id.674 /// 675 /// * address.676 #[weight = T::WeightInfo::remove_from_white_list()]677 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{678679 let sender = ensure_signed(origin)?;680 Self::check_owner_or_admin_permissions(collection_id, sender)?;681682 if <WhiteList<T>>::contains_key(collection_id) {683 let mut white_list_collection = <WhiteList<T>>::get(collection_id);684 if white_list_collection.contains(&address.clone())685 {686 white_list_collection.retain(|i| *i != address.clone());687 <WhiteList<T>>::insert(collection_id, white_list_collection);688 }689 }690691 Ok(())692 }693694 /// Toggle between normal and white list access for the methods with access for `Anyone`.695 /// 696 /// # Permissions697 /// 698 /// * Collection Owner.699 /// 700 /// # Arguments701 /// 702 /// * collection_id.703 /// 704 /// * mode: [AccessMode]705 #[weight = T::WeightInfo::set_public_access_mode()]706 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult707 {708 let sender = ensure_signed(origin)?;709710 Self::check_owner_permissions(collection_id, sender)?;711 let mut target_collection = <Collection<T>>::get(collection_id);712 target_collection.access = mode;713 <Collection<T>>::insert(collection_id, target_collection);714715 Ok(())716 }717718 /// Allows Anyone to create tokens if:719 /// * White List is enabled, and720 /// * Address is added to white list, and721 /// * This method was called with True parameter722 /// 723 /// # Permissions724 /// * Collection Owner725 ///726 /// # Arguments727 /// 728 /// * collection_id.729 /// 730 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.731 #[weight = T::WeightInfo::set_mint_permission()]732 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult733 {734 let sender = ensure_signed(origin)?;735736 Self::check_owner_permissions(collection_id, sender)?;737 let mut target_collection = <Collection<T>>::get(collection_id);738 target_collection.mint_mode = mint_permission;739 <Collection<T>>::insert(collection_id, target_collection);740741 Ok(())742 }743744 /// Change the owner of the collection.745 /// 746 /// # Permissions747 /// 748 /// * Collection Owner.749 /// 750 /// # Arguments751 /// 752 /// * collection_id.753 /// 754 /// * new_owner.755 #[weight = T::WeightInfo::change_collection_owner()]756 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {757758 let sender = ensure_signed(origin)?;759 Self::check_owner_permissions(collection_id, sender)?;760 let mut target_collection = <Collection<T>>::get(collection_id);761 target_collection.owner = new_owner;762 <Collection<T>>::insert(collection_id, target_collection);763764 Ok(())765 }766767 /// Adds an admin of the Collection.768 /// 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. 769 /// 770 /// # Permissions771 /// 772 /// * Collection Owner.773 /// * Collection Admin.774 /// 775 /// # Arguments776 /// 777 /// * collection_id: ID of the Collection to add admin for.778 /// 779 /// * new_admin_id: Address of new admin to add.780 #[weight = T::WeightInfo::add_collection_admin()]781 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {782783 let sender = ensure_signed(origin)?;784 Self::check_owner_or_admin_permissions(collection_id, sender)?;785 let mut admin_arr: Vec<T::AccountId> = Vec::new();786787 if <AdminList<T>>::contains_key(collection_id)788 {789 admin_arr = <AdminList<T>>::get(collection_id);790 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);791 }792793 // Number of collection admins794 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);795796 admin_arr.push(new_admin_id);797 <AdminList<T>>::insert(collection_id, admin_arr);798799 Ok(())800 }801802 /// 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.803 ///804 /// # Permissions805 /// 806 /// * Collection Owner.807 /// * Collection Admin.808 /// 809 /// # Arguments810 /// 811 /// * collection_id: ID of the Collection to remove admin for.812 /// 813 /// * account_id: Address of admin to remove.814 #[weight = T::WeightInfo::remove_collection_admin()]815 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {816817 let sender = ensure_signed(origin)?;818 Self::check_owner_or_admin_permissions(collection_id, sender)?;819820 if <AdminList<T>>::contains_key(collection_id)821 {822 let mut admin_arr = <AdminList<T>>::get(collection_id);823 admin_arr.retain(|i| *i != account_id);824 <AdminList<T>>::insert(collection_id, admin_arr);825 }826827 Ok(())828 }829830 /// # Permissions831 /// 832 /// * Collection Owner833 /// 834 /// # Arguments835 /// 836 /// * collection_id.837 /// 838 /// * new_sponsor.839 #[weight = T::WeightInfo::set_collection_sponsor()]840 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {841842 let sender = ensure_signed(origin)?;843 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);844845 let mut target_collection = <Collection<T>>::get(collection_id);846 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);847848 target_collection.unconfirmed_sponsor = new_sponsor;849 <Collection<T>>::insert(collection_id, target_collection);850851 Ok(())852 }853854 /// # Permissions855 /// 856 /// * Sponsor.857 /// 858 /// # Arguments859 /// 860 /// * collection_id.861 #[weight = T::WeightInfo::confirm_sponsorship()]862 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {863864 let sender = ensure_signed(origin)?;865 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);866867 let mut target_collection = <Collection<T>>::get(collection_id);868 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);869870 target_collection.sponsor = target_collection.unconfirmed_sponsor;871 target_collection.unconfirmed_sponsor = T::AccountId::default();872 <Collection<T>>::insert(collection_id, target_collection);873874 Ok(())875 }876877 /// Switch back to pay-per-own-transaction model.878 ///879 /// # Permissions880 ///881 /// * Collection owner.882 /// 883 /// # Arguments884 /// 885 /// * collection_id.886 #[weight = T::WeightInfo::remove_collection_sponsor()]887 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {888889 let sender = ensure_signed(origin)?;890 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);891892 let mut target_collection = <Collection<T>>::get(collection_id);893 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);894895 target_collection.sponsor = T::AccountId::default();896 <Collection<T>>::insert(collection_id, target_collection);897898 Ok(())899 }900901 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.902 /// 903 /// # Permissions904 /// 905 /// * Collection Owner.906 /// * Collection Admin.907 /// * Anyone if908 /// * White List is enabled, and909 /// * Address is added to white list, and910 /// * MintPermission is enabled (see SetMintPermission method)911 /// 912 /// # Arguments913 /// 914 /// * collection_id: ID of the collection.915 /// 916 /// * owner: Address, initial owner of the NFT.917 ///918 /// * data: Token data to store on chain.919 // #[weight =920 // (130_000_000 as Weight)921 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))922 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))923 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]924925 #[weight = T::WeightInfo::create_item(data.len())]926 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {927928 let sender = ensure_signed(origin)?;929930 Self::collection_exists(collection_id)?;931932 let target_collection = <Collection<T>>::get(collection_id);933934 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;935 Self::validate_create_item_args(&target_collection, &data)?;936 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;937938 Ok(())939 }940941 /// This method creates multiple instances of NFT Collection created with CreateCollection method.942 /// 943 /// # Permissions944 /// 945 /// * Collection Owner.946 /// * Collection Admin.947 /// * Anyone if948 /// * White List is enabled, and949 /// * Address is added to white list, and950 /// * MintPermission is enabled (see SetMintPermission method)951 /// 952 /// # Arguments953 /// 954 /// * collection_id: ID of the collection.955 /// 956 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].957 /// 958 /// * owner: Address, initial owner of the NFT.959 #[weight = T::WeightInfo::create_item(items_data.into_iter()960 .map(|data| { data.len() })961 .sum())]962 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {963964 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);965 let sender = ensure_signed(origin)?;966967 Self::collection_exists(collection_id)?;968 let target_collection = <Collection<T>>::get(collection_id);969970 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;971972 for data in &items_data {973 Self::validate_create_item_args(&target_collection, data)?;974 }975 for data in &items_data {976 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;977 }978979 Ok(())980 }981982 /// Destroys a concrete instance of NFT.983 /// 984 /// # Permissions985 /// 986 /// * Collection Owner.987 /// * Collection Admin.988 /// * Current NFT Owner.989 /// 990 /// # Arguments991 /// 992 /// * collection_id: ID of the collection.993 /// 994 /// * item_id: ID of NFT to burn.995 #[weight = T::WeightInfo::burn_item()]996 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {997998 let sender = ensure_signed(origin)?;999 Self::collection_exists(collection_id)?;10001001 // Transfer permissions check1002 let target_collection = <Collection<T>>::get(collection_id);1003 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1004 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1005 Error::<T>::NoPermission);10061007 if target_collection.access == AccessMode::WhiteList {1008 Self::check_white_list(collection_id, &sender)?;1009 }10101011 match target_collection.mode1012 {1013 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1014 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1015 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1016 _ => ()1017 };10181019 // call event1020 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10211022 Ok(())1023 }10241025 /// Change ownership of the token.1026 /// 1027 /// # Permissions1028 /// 1029 /// * Collection Owner1030 /// * Collection Admin1031 /// * Current NFT owner1032 ///1033 /// # Arguments1034 /// 1035 /// * recipient: Address of token recipient.1036 /// 1037 /// * collection_id.1038 /// 1039 /// * item_id: ID of the item1040 /// * Non-Fungible Mode: Required.1041 /// * Fungible Mode: Ignored.1042 /// * Re-Fungible Mode: Required.1043 /// 1044 /// * value: Amount to transfer.1045 /// * Non-Fungible Mode: Ignored1046 /// * Fungible Mode: Must specify transferred amount1047 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1048 #[weight = T::WeightInfo::transfer()]1049 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10501051 let sender = ensure_signed(origin)?;1052 let target_collection = <Collection<T>>::get(collection_id);10531054 // Limits check1055 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10561057 // Transfer permissions check1058 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1059 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1060 Error::<T>::NoPermission);10611062 if target_collection.access == AccessMode::WhiteList {1063 Self::check_white_list(collection_id, &sender)?;1064 Self::check_white_list(collection_id, &recipient)?;1065 }10661067 match target_collection.mode1068 {1069 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1070 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1071 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1072 _ => ()1073 };10741075 Ok(())1076 }10771078 /// Set, change, or remove approved address to transfer the ownership of the NFT.1079 /// 1080 /// # Permissions1081 /// 1082 /// * Collection Owner1083 /// * Collection Admin1084 /// * Current NFT owner1085 /// 1086 /// # Arguments1087 /// 1088 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1089 /// 1090 /// * collection_id.1091 /// 1092 /// * item_id: ID of the item.1093 #[weight = T::WeightInfo::approve()]1094 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10951096 let sender = ensure_signed(origin)?;10971098 // Transfer permissions check1099 let target_collection = <Collection<T>>::get(collection_id);1100 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1101 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1102 Error::<T>::NoPermission);11031104 if target_collection.access == AccessMode::WhiteList {1105 Self::check_white_list(collection_id, &sender)?;1106 Self::check_white_list(collection_id, &approved)?;1107 }11081109 // amount param stub1110 let amount = 100000000;11111112 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1113 if list_exists {11141115 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1116 let item_contains = list.iter().any(|i| i.approved == approved);11171118 if !item_contains {1119 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1120 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1121 }1122 } else {11231124 let mut list = Vec::new();1125 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1126 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1127 }11281129 Ok(())1130 }1131 1132 /// 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.1133 /// 1134 /// # Permissions1135 /// * Collection Owner1136 /// * Collection Admin1137 /// * Current NFT owner1138 /// * Address approved by current NFT owner1139 /// 1140 /// # Arguments1141 /// 1142 /// * from: Address that owns token.1143 /// 1144 /// * recipient: Address of token recipient.1145 /// 1146 /// * collection_id.1147 /// 1148 /// * item_id: ID of the item.1149 /// 1150 /// * value: Amount to transfer.1151 #[weight = T::WeightInfo::transfer_from()]1152 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11531154 let sender = ensure_signed(origin)?;1155 let mut appoved_transfer = false;11561157 // Check approve1158 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1159 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1160 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1161 if opt_item.is_some()1162 {1163 appoved_transfer = true;1164 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1165 }1166 }11671168 let target_collection = <Collection<T>>::get(collection_id);11691170 // Limits check1171 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11721173 // Transfer permissions check 1174 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1175 Error::<T>::NoPermission);11761177 if target_collection.access == AccessMode::WhiteList {1178 Self::check_white_list(collection_id, &sender)?;1179 Self::check_white_list(collection_id, &recipient)?;1180 }11811182 // remove approve1183 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1184 .into_iter().filter(|i| i.approved != sender.clone()).collect();1185 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);118611871188 match target_collection.mode1189 {1190 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1191 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1192 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1193 _ => ()1194 };11951196 Ok(())1197 }11981199 ///1200 #[weight = 0]1201 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12021203 // let no_perm_mes = "You do not have permissions to modify this collection";1204 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1205 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1206 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12071208 // // on_nft_received call12091210 // Self::transfer(origin, collection_id, item_id, new_owner)?;12111212 Ok(())1213 }12141215 /// Set off-chain data schema.1216 /// 1217 /// # Permissions1218 /// 1219 /// * Collection Owner1220 /// * Collection Admin1221 /// 1222 /// # Arguments1223 /// 1224 /// * collection_id.1225 /// 1226 /// * schema: String representing the offchain data schema.1227 #[weight = T::WeightInfo::set_variable_meta_data()]1228 pub fn set_variable_meta_data (1229 origin,1230 collection_id: CollectionId,1231 item_id: TokenId,1232 data: Vec<u8>1233 ) -> DispatchResult {1234 let sender = ensure_signed(origin)?;1235 1236 Self::collection_exists(collection_id)?;1237 1238 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12391240 // Modify permissions check1241 let target_collection = <Collection<T>>::get(collection_id);1242 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1243 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1244 Error::<T>::NoPermission);12451246 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12471248 match target_collection.mode1249 {1250 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1251 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1252 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1253 _ => fail!(Error::<T>::UnexpectedCollectionType)1254 };12551256 Ok(())1257 }1258 12591260 /// Set off-chain data schema.1261 /// 1262 /// # Permissions1263 /// 1264 /// * Collection Owner1265 /// * Collection Admin1266 /// 1267 /// # Arguments1268 /// 1269 /// * collection_id.1270 /// 1271 /// * schema: String representing the offchain data schema.1272 #[weight = T::WeightInfo::set_offchain_schema()]1273 pub fn set_offchain_schema(1274 origin,1275 collection_id: CollectionId,1276 schema: Vec<u8>1277 ) -> DispatchResult {1278 let sender = ensure_signed(origin)?;1279 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12801281 let mut target_collection = <Collection<T>>::get(collection_id);1282 target_collection.offchain_schema = schema;1283 <Collection<T>>::insert(collection_id, target_collection);12841285 Ok(())1286 }12871288 /// Set const on-chain data schema.1289 /// 1290 /// # Permissions1291 /// 1292 /// * Collection Owner1293 /// * Collection Admin1294 /// 1295 /// # Arguments1296 /// 1297 /// * collection_id.1298 /// 1299 /// * schema: String representing the const on-chain data schema.1300 #[weight = T::WeightInfo::set_const_on_chain_schema()]1301 pub fn set_const_on_chain_schema (1302 origin,1303 collection_id: CollectionId,1304 schema: Vec<u8>1305 ) -> DispatchResult {1306 let sender = ensure_signed(origin)?;1307 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13081309 let mut target_collection = <Collection<T>>::get(collection_id);1310 target_collection.const_on_chain_schema = schema;1311 <Collection<T>>::insert(collection_id, target_collection);13121313 Ok(())1314 }13151316 /// Set variable on-chain data schema.1317 /// 1318 /// # Permissions1319 /// 1320 /// * Collection Owner1321 /// * Collection Admin1322 /// 1323 /// # Arguments1324 /// 1325 /// * collection_id.1326 /// 1327 /// * schema: String representing the variable on-chain data schema.1328 #[weight = T::WeightInfo::set_const_on_chain_schema()]1329 pub fn set_variable_on_chain_schema (1330 origin,1331 collection_id: CollectionId,1332 schema: Vec<u8>1333 ) -> DispatchResult {1334 let sender = ensure_signed(origin)?;1335 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13361337 let mut target_collection = <Collection<T>>::get(collection_id);1338 target_collection.variable_on_chain_schema = schema;1339 <Collection<T>>::insert(collection_id, target_collection);13401341 Ok(())1342 }13431344 // Sudo permissions function1345 #[weight = 0]1346 pub fn set_chain_limits(1347 origin,1348 limits: ChainLimits1349 ) -> DispatchResult {1350 ensure_root(origin)?;1351 <ChainLimit>::put(limits);1352 Ok(())1353 }13541355 /// Enable smart contract self-sponsoring.1356 /// 1357 /// # Permissions1358 /// 1359 /// * Contract Owner1360 /// 1361 /// # Arguments1362 /// 1363 /// * contract address1364 /// * enable flag1365 /// 1366 #[weight = T::WeightInfo::enable_contract_sponsoring()]1367 pub fn enable_contract_sponsoring(1368 origin,1369 contract_address: T::AccountId,1370 enable: bool1371 ) -> DispatchResult {13721373 let sender = ensure_signed(origin)?;13741375 #[cfg(feature = "runtime-benchmarks")]1376 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13771378 let mut is_owner = false;1379 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1380 let owner = <ContractOwner<T>>::get(&contract_address);1381 is_owner = sender == owner;1382 }1383 ensure!(is_owner, Error::<T>::NoPermission);13841385 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1386 Ok(())1387 }13881389 /// Set the rate limit for contract sponsoring to specified number of blocks.1390 /// 1391 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1392 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1393 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1394 /// from contract endowment if there are at least B blocks between such transactions. 1395 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1396 /// 1397 /// # Permissions1398 /// 1399 /// * Contract Owner1400 /// 1401 /// # Arguments1402 /// 1403 /// -`contract_address`: Address of the contract to sponsor1404 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1405 /// 1406 #[weight = 0]1407 pub fn set_contract_sponsoring_rate_limit(1408 origin,1409 contract_address: T::AccountId,1410 rate_limit: T::BlockNumber1411 ) -> DispatchResult {1412 let sender = ensure_signed(origin)?;1413 let mut is_owner = false;1414 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1415 let owner = <ContractOwner<T>>::get(&contract_address);1416 is_owner = sender == owner;1417 }1418 ensure!(is_owner, Error::<T>::NoPermission);14191420 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1421 Ok(())1422 }14231424 #[weight = 0]1425 pub fn set_collection_limits(1426 origin,1427 collection_id: u32,1428 limits: CollectionLimits,1429 ) -> DispatchResult {1430 let sender = ensure_signed(origin)?;1431 Self::check_owner_permissions(collection_id, sender.clone())?;1432 let mut target_collection = <Collection<T>>::get(collection_id);1433 let chain_limits = ChainLimit::get();1434 let climits = target_collection.limits;14351436 // token_limit check prev1437 ensure!(climits.token_limit > limits.token_limit && 1438 climits.token_limit <= chain_limits.account_token_ownership_limit, 1439 Error::<T>::AccountTokenLimitExceeded);14401441 target_collection.limits = limits;1442 <Collection<T>>::insert(collection_id, target_collection);14431444 Ok(())1445 } 1446 }1447}14481449impl<T: Trait> Module<T> {14501451 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {14521453 if !Self::is_owner_or_admin_permissions(collection_id, recipient.clone()) {14541455 // check token limit and account token limit1456 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1457 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1458 }14591460 Ok(())1461 }14621463 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {14641465 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {14661467 // check token limit and account token limit1468 let total_items: u32 = ItemListIndex::get(collection_id);1469 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1470 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1471 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1472 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1473 Self::check_white_list(collection_id, owner)?;1474 Self::check_white_list(collection_id, sender)?;1475 }14761477 Ok(())1478 }14791480 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1481 match target_collection.mode1482 {1483 CollectionMode::NFT => {1484 if let CreateItemData::NFT(data) = data {1485 // check sizes1486 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1487 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1488 } else {1489 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1490 }1491 },1492 CollectionMode::Fungible(_) => {1493 if let CreateItemData::Fungible(_) = data {1494 } else {1495 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1496 }1497 },1498 CollectionMode::ReFungible(_) => {1499 if let CreateItemData::ReFungible(data) = data {15001501 // check sizes1502 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1503 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1504 } else {1505 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1506 }1507 },1508 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1509 };15101511 Ok(())1512 }15131514 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1515 match data1516 {1517 CreateItemData::NFT(data) => {1518 let item = NftItemType {1519 collection: collection_id,1520 owner,1521 const_data: data.const_data,1522 variable_data: data.variable_data1523 };15241525 Self::add_nft_item(item)?;1526 },1527 CreateItemData::Fungible(_) => {1528 let item = FungibleItemType {1529 collection: collection_id,1530 owner,1531 value: (10 as u128).pow(collection.decimal_points as u32)1532 };15331534 Self::add_fungible_item(item)?;1535 },1536 CreateItemData::ReFungible(data) => {1537 let mut owner_list = Vec::new();1538 let value = (10 as u128).pow(collection.decimal_points as u32);1539 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15401541 let item = ReFungibleItemType {1542 collection: collection_id,1543 owner: owner_list,1544 const_data: data.const_data,1545 variable_data: data.variable_data1546 };15471548 Self::add_refungible_item(item)?;1549 }1550 };15511552 // call event1553 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));15541555 Ok(())1556 }15571558 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1559 let current_index = <ItemListIndex>::get(item.collection)1560 .checked_add(1)1561 .ok_or(Error::<T>::NumOverflow)?;1562 let itemcopy = item.clone();1563 let owner = item.owner.clone();15641565 Self::add_token_index(item.collection, current_index, owner.clone())?;15661567 <ItemListIndex>::insert(item.collection, current_index);1568 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15691570 // Add current block1571 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1572 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1573 1574 // Update balance1575 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1576 .checked_add(item.value)1577 .ok_or(Error::<T>::NumOverflow)?;1578 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15791580 Ok(())1581 }15821583 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1584 let current_index = <ItemListIndex>::get(item.collection)1585 .checked_add(1)1586 .ok_or(Error::<T>::NumOverflow)?;1587 let itemcopy = item.clone();15881589 let value = item.owner.first().unwrap().fraction;1590 let owner = item.owner.first().unwrap().owner.clone();15911592 Self::add_token_index(item.collection, current_index, owner.clone())?;15931594 <ItemListIndex>::insert(item.collection, current_index);1595 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15961597 // Add current block1598 let block_number: T::BlockNumber = 0.into();1599 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16001601 // Update balance1602 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1603 .checked_add(value)1604 .ok_or(Error::<T>::NumOverflow)?;1605 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16061607 Ok(())1608 }16091610 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1611 let current_index = <ItemListIndex>::get(item.collection)1612 .checked_add(1)1613 .ok_or(Error::<T>::NumOverflow)?;16141615 let item_owner = item.owner.clone();1616 let collection_id = item.collection.clone();1617 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16181619 <ItemListIndex>::insert(collection_id, current_index);1620 <NftItemList<T>>::insert(collection_id, current_index, item);16211622 // Add current block1623 let block_number: T::BlockNumber = 0.into();1624 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);16251626 // Update balance1627 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1628 .checked_add(1)1629 .ok_or(Error::<T>::NumOverflow)?;1630 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16311632 Ok(())1633 }16341635 fn burn_refungible_item(1636 collection_id: CollectionId,1637 item_id: TokenId,1638 owner: T::AccountId,1639 ) -> DispatchResult {1640 ensure!(1641 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1642 Error::<T>::TokenNotFound1643 );1644 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1645 let item = collection1646 .owner1647 .iter()1648 .filter(|&i| i.owner == owner)1649 .next()1650 .unwrap();1651 Self::remove_token_index(collection_id, item_id, owner.clone())?;16521653 // remove approve list1654 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));16551656 // update balance1657 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1658 .checked_sub(item.fraction)1659 .ok_or(Error::<T>::NumOverflow)?;1660 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16611662 <ReFungibleItemList<T>>::remove(collection_id, item_id);16631664 Ok(())1665 }16661667 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1668 ensure!(1669 <NftItemList<T>>::contains_key(collection_id, item_id),1670 Error::<T>::TokenNotFound1671 );1672 let item = <NftItemList<T>>::get(collection_id, item_id);1673 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16741675 // remove approve list1676 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16771678 // update balance1679 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1680 .checked_sub(1)1681 .ok_or(Error::<T>::NumOverflow)?;1682 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1683 <NftItemList<T>>::remove(collection_id, item_id);16841685 Ok(())1686 }16871688 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1689 ensure!(1690 <FungibleItemList<T>>::contains_key(collection_id, item_id),1691 Error::<T>::TokenNotFound1692 );1693 let item = <FungibleItemList<T>>::get(collection_id, item_id);1694 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16951696 // remove approve list1697 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16981699 // update balance1700 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1701 .checked_sub(item.value)1702 .ok_or(Error::<T>::NumOverflow)?;1703 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17041705 <FungibleItemList<T>>::remove(collection_id, item_id);17061707 Ok(())1708 }17091710 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1711 ensure!(1712 <Collection<T>>::contains_key(collection_id),1713 Error::<T>::CollectionNotFound1714 );1715 Ok(())1716 }17171718 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1719 Self::collection_exists(collection_id)?;17201721 let target_collection = <Collection<T>>::get(collection_id);1722 ensure!(1723 subject == target_collection.owner,1724 Error::<T>::NoPermission1725 );17261727 Ok(())1728 }17291730 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1731 let target_collection = <Collection<T>>::get(collection_id);1732 let mut result: bool = subject == target_collection.owner;1733 let exists = <AdminList<T>>::contains_key(collection_id);17341735 if !result & exists {1736 if <AdminList<T>>::get(collection_id).contains(&subject) {1737 result = true1738 }1739 }17401741 result1742 }17431744 fn check_owner_or_admin_permissions(1745 collection_id: CollectionId,1746 subject: T::AccountId,1747 ) -> DispatchResult {1748 Self::collection_exists(collection_id)?;1749 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17501751 ensure!(1752 result,1753 Error::<T>::NoPermission1754 );1755 Ok(())1756 }17571758 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1759 let target_collection = <Collection<T>>::get(collection_id);17601761 match target_collection.mode {1762 CollectionMode::NFT => {1763 <NftItemList<T>>::get(collection_id, item_id).owner == subject1764 }1765 CollectionMode::Fungible(_) => {1766 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1767 }1768 CollectionMode::ReFungible(_) => {1769 <ReFungibleItemList<T>>::get(collection_id, item_id)1770 .owner1771 .iter()1772 .any(|i| i.owner == subject)1773 }1774 CollectionMode::Invalid => false,1775 }1776 }17771778 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1779 let mes = Error::<T>::AddresNotInWhiteList;1780 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1781 let wl = <WhiteList<T>>::get(collection_id);1782 ensure!(wl.contains(address), mes);17831784 Ok(())1785 }17861787 fn transfer_fungible(1788 collection_id: CollectionId,1789 item_id: TokenId,1790 value: u128,1791 owner: T::AccountId,1792 new_owner: T::AccountId,1793 ) -> DispatchResult {1794 ensure!(1795 <FungibleItemList<T>>::contains_key(collection_id, item_id),1796 Error::<T>::TokenNotFound1797 );17981799 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1800 let amount = full_item.value;18011802 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18031804 // update balance1805 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1806 .checked_sub(value)1807 .ok_or(Error::<T>::NumOverflow)?;1808 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);18091810 let mut new_owner_account_id = 0;1811 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1812 if new_owner_items.len() > 0 {1813 new_owner_account_id = new_owner_items[0];1814 }18151816 // transfer1817 if amount == value && new_owner_account_id == 0 {1818 // change owner1819 // new owner do not have account1820 let mut new_full_item = full_item.clone();1821 new_full_item.owner = new_owner.clone();1822 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18231824 // update balance1825 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1826 .checked_add(value)1827 .ok_or(Error::<T>::NumOverflow)?;1828 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18291830 // update index collection1831 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1832 } else {1833 let mut new_full_item = full_item.clone();1834 new_full_item.value -= value;18351836 // separate amount1837 if new_owner_account_id > 0 {1838 // new owner has account1839 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1840 item.value += value;18411842 // update balance1843 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1844 .checked_add(value)1845 .ok_or(Error::<T>::NumOverflow)?;1846 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18471848 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1849 } else {1850 // new owner do not have account1851 let item = FungibleItemType {1852 collection: collection_id,1853 owner: new_owner.clone(),1854 value1855 };18561857 Self::add_fungible_item(item)?;1858 }18591860 if amount == value {1861 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;18621863 // remove approve list1864 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1865 <FungibleItemList<T>>::remove(collection_id, item_id);1866 }18671868 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1869 }18701871 Ok(())1872 }18731874 fn transfer_refungible(1875 collection_id: CollectionId,1876 item_id: TokenId,1877 value: u128,1878 owner: T::AccountId,1879 new_owner: T::AccountId,1880 ) -> DispatchResult {1881 ensure!(1882 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1883 Error::<T>::TokenNotFound1884 );18851886 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1887 let item = full_item1888 .owner1889 .iter()1890 .filter(|i| i.owner == owner)1891 .next()1892 .ok_or(Error::<T>::NumOverflow)?;1893 let amount = item.fraction;18941895 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18961897 // update balance1898 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1899 .checked_sub(value)1900 .ok_or(Error::<T>::NumOverflow)?;1901 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19021903 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1904 .checked_add(value)1905 .ok_or(Error::<T>::NumOverflow)?;1906 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19071908 let old_owner = item.owner.clone();1909 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19101911 // transfer1912 if amount == value && !new_owner_has_account {1913 // change owner1914 // new owner do not have account1915 let mut new_full_item = full_item.clone();1916 new_full_item1917 .owner1918 .iter_mut()1919 .find(|i| i.owner == owner)1920 .unwrap()1921 .owner = new_owner.clone();1922 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19231924 // update index collection1925 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1926 } else {1927 let mut new_full_item = full_item.clone();1928 new_full_item1929 .owner1930 .iter_mut()1931 .find(|i| i.owner == owner)1932 .unwrap()1933 .fraction -= value;19341935 // separate amount1936 if new_owner_has_account {1937 // new owner has account1938 new_full_item1939 .owner1940 .iter_mut()1941 .find(|i| i.owner == new_owner)1942 .unwrap()1943 .fraction += value;1944 } else {1945 // new owner do not have account1946 new_full_item.owner.push(Ownership {1947 owner: new_owner.clone(),1948 fraction: value,1949 });1950 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1951 }19521953 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1954 }19551956 Ok(())1957 }19581959 fn transfer_nft(1960 collection_id: CollectionId,1961 item_id: TokenId,1962 sender: T::AccountId,1963 new_owner: T::AccountId,1964 ) -> DispatchResult {1965 ensure!(1966 <NftItemList<T>>::contains_key(collection_id, item_id),1967 Error::<T>::TokenNotFound1968 );19691970 let mut item = <NftItemList<T>>::get(collection_id, item_id);19711972 ensure!(1973 sender == item.owner,1974 Error::<T>::MustBeTokenOwner1975 );19761977 // update balance1978 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1979 .checked_sub(1)1980 .ok_or(Error::<T>::NumOverflow)?;1981 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19821983 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1984 .checked_add(1)1985 .ok_or(Error::<T>::NumOverflow)?;1986 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19871988 // change owner1989 let old_owner = item.owner.clone();1990 item.owner = new_owner.clone();1991 <NftItemList<T>>::insert(collection_id, item_id, item);19921993 // update index collection1994 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19951996 // reset approved list1997 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1998 Ok(())1999 }2000 2001 fn item_exists(2002 collection_id: CollectionId,2003 item_id: TokenId,2004 mode: &CollectionMode2005 ) -> DispatchResult {2006 match mode {2007 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2008 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2009 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<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(item: &NftItemType<T::AccountId>) {2072 let current_index = <ItemListIndex>::get(item.collection)2073 .checked_add(1)2074 .unwrap();20752076 let item_owner = item.owner.clone();2077 let collection_id = item.collection.clone();2078 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20792080 <ItemListIndex>::insert(collection_id, current_index);20812082 // Update balance2083 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2084 .checked_add(1)2085 .unwrap();2086 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2087 }20882089 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2090 let current_index = <ItemListIndex>::get(item.collection)2091 .checked_add(1)2092 .unwrap();2093 let owner = item.owner.clone();20942095 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20962097 <ItemListIndex>::insert(item.collection, current_index);20982099 // Update balance2100 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2101 .checked_add(item.value)2102 .unwrap();2103 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2104 }21052106 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2107 let current_index = <ItemListIndex>::get(item.collection)2108 .checked_add(1)2109 .unwrap();21102111 let value = item.owner.first().unwrap().fraction;2112 let owner = item.owner.first().unwrap().owner.clone();21132114 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21152116 <ItemListIndex>::insert(item.collection, current_index);21172118 // Update balance2119 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2120 .checked_add(value)2121 .unwrap();2122 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2123 }21242125 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21262127 // add to account limit2128 if <AccountItemCount<T>>::contains_key(owner.clone()) {21292130 // bound Owned tokens by a single address2131 let count = <AccountItemCount<T>>::get(owner.clone());2132 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21332134 <AccountItemCount<T>>::insert(owner.clone(), count2135 .checked_add(1)2136 .ok_or(Error::<T>::NumOverflow)?);2137 }2138 else {2139 <AccountItemCount<T>>::insert(owner.clone(), 1);2140 }21412142 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2143 if list_exists {2144 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2145 let item_contains = list.contains(&item_index.clone());21462147 if !item_contains {2148 list.push(item_index.clone());2149 }21502151 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2152 } else {2153 let mut itm = Vec::new();2154 itm.push(item_index.clone());2155 <AddressTokens<T>>::insert(collection_id, owner, itm);2156 2157 }21582159 Ok(())2160 }21612162 fn remove_token_index(2163 collection_id: CollectionId,2164 item_index: TokenId,2165 owner: T::AccountId,2166 ) -> DispatchResult {21672168 // update counter2169 <AccountItemCount<T>>::insert(owner.clone(), 2170 <AccountItemCount<T>>::get(owner.clone())2171 .checked_sub(1)2172 .ok_or(Error::<T>::NumOverflow)?);217321742175 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2176 if list_exists {2177 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2178 let item_contains = list.contains(&item_index.clone());21792180 if item_contains {2181 list.retain(|&item| item != item_index);2182 <AddressTokens<T>>::insert(collection_id, owner, list);2183 }2184 }21852186 Ok(())2187 }21882189 fn move_token_index(2190 collection_id: CollectionId,2191 item_index: TokenId,2192 old_owner: T::AccountId,2193 new_owner: T::AccountId,2194 ) -> DispatchResult {2195 Self::remove_token_index(collection_id, item_index, old_owner)?;2196 Self::add_token_index(collection_id, item_index, new_owner)?;21972198 Ok(())2199 }2200}22012202////////////////////////////////////////////////////////////////////////////////////////////////////2203// Economic models2204// #region22052206/// Fee multiplier.2207pub type Multiplier = FixedU128;22082209type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2210 <T as system::Trait>::AccountId,2211>>::Balance;2212type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2213 <T as system::Trait>::AccountId,2214>>::NegativeImbalance;22152216/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2217/// in the queue.2218#[derive(Encode, Decode, Clone, Eq, PartialEq)]2219pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2220 #[codec(compact)] BalanceOf<T>2221);22222223impl<T: Trait + Send + Sync> sp_std::fmt::Debug2224 for ChargeTransactionPayment<T>2225{2226 #[cfg(feature = "std")]2227 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2228 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2229 }2230 #[cfg(not(feature = "std"))]2231 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2232 Ok(())2233 }2234}22352236impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2237where2238 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2239 BalanceOf<T>: Send + Sync + FixedPointOperand,2240{2241 /// utility constructor. Used only in client/factory code.2242 pub fn from(fee: BalanceOf<T>) -> Self {2243 Self(fee)2244 }22452246 pub fn traditional_fee(2247 len: usize,2248 info: &DispatchInfoOf<T::Call>,2249 tip: BalanceOf<T>,2250 ) -> BalanceOf<T>2251 where2252 T::Call: Dispatchable<Info = DispatchInfo>,2253 {2254 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2255 }22562257 fn withdraw_fee(2258 &self,2259 who: &T::AccountId,2260 call: &T::Call,2261 info: &DispatchInfoOf<T::Call>,2262 len: usize,2263 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2264 let tip = self.0;22652266 // Set fee based on call type. Creating collection costs 1 Unique.2267 // All other transactions have traditional fees so far2268 // let fee = match call.is_sub_type() {2269 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2270 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2271 // // _ => <BalanceOf<T>>::from(100)2272 // };2273 let fee = Self::traditional_fee(len, info, tip);22742275 // Determine who is paying transaction fee based on ecnomic model2276 // Parse call to extract collection ID and access collection sponsor2277 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2278 Some(Call::create_item(collection_id, _owner, _properties)) => {22792280 // check free create limit2281 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2282 {2283 <Collection<T>>::get(collection_id).sponsor2284 } else {2285 T::AccountId::default()2286 }2287 }2288 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2289 2290 let _collection_limits = <Collection<T>>::get(collection_id).limits;2291 let _collection_mode = <Collection<T>>::get(collection_id).mode;22922293 // sponsor timeout2294 let sponsor_transfer = match _collection_mode {2295 CollectionMode::NFT => {22962297 // get correct limit2298 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2299 _collection_limits.sponsor_transfer_timeout2300 } else {2301 ChainLimit::get().nft_sponsor_transfer_timeout2302 };23032304 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2305 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2306 let limit_time = basket + limit.into();2307 if block_number >= limit_time {2308 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2309 true2310 }2311 else {2312 false2313 }2314 }2315 CollectionMode::Fungible(_) => {23162317 // get correct limit2318 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2319 _collection_limits.sponsor_transfer_timeout2320 } else {2321 ChainLimit::get().fungible_sponsor_transfer_timeout2322 };23232324 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2325 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2326 if basket.iter().any(|i| i.address == _new_owner.clone())2327 {2328 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2329 let limit_time = item.start_block + limit.into();2330 if block_number >= limit_time {2331 basket.retain(|x| x.address == item.address);2332 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2333 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2334 true2335 }2336 else {2337 false2338 }2339 }2340 else {2341 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2342 true2343 }2344 }2345 CollectionMode::ReFungible(_) => {23462347 // get correct limit2348 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2349 _collection_limits.sponsor_transfer_timeout2350 } else {2351 ChainLimit::get().refungible_sponsor_transfer_timeout2352 };23532354 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2355 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2356 let limit_time = basket + limit.into();2357 if block_number >= limit_time {2358 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2359 true2360 } else {2361 false2362 }2363 }2364 _ => {2365 false2366 },2367 };23682369 if !sponsor_transfer {2370 T::AccountId::default()2371 } else {2372 <Collection<T>>::get(collection_id).sponsor2373 }2374 }23752376 _ => T::AccountId::default(),2377 };23782379 // Sponsor smart contracts2380 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {23812382 // On instantiation: set the contract owner2383 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {23842385 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2386 code_hash,2387 &data,2388 &who,2389 );2390 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());23912392 T::AccountId::default()2393 },23942395 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2396 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {23972398 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());23992400 let mut sponsor_transfer = false;2401 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2402 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2403 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2404 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2405 let limit_time = last_tx_block + rate_limit;24062407 if block_number >= limit_time {2408 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2409 sponsor_transfer = true;2410 }2411 } else {2412 sponsor_transfer = false;2413 }2414 2415 2416 let mut sp = T::AccountId::default();2417 if sponsor_transfer {2418 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2419 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2420 sp = called_contract;2421 }2422 }2423 }24242425 sp2426 },24272428 _ => sponsor,2429 };24302431 let mut who_pays_fee: T::AccountId = sponsor.clone();2432 if sponsor == T::AccountId::default() {2433 who_pays_fee = who.clone();2434 }24352436 // Only mess with balances if fee is not zero.2437 if fee.is_zero() {2438 return Ok((fee, None));2439 }24402441 match <T as transaction_payment::Trait>::Currency::withdraw(2442 &who_pays_fee,2443 fee,2444 if tip.is_zero() {2445 WithdrawReason::TransactionPayment.into()2446 } else {2447 WithdrawReason::TransactionPayment | WithdrawReason::Tip2448 },2449 ExistenceRequirement::KeepAlive,2450 ) {2451 Ok(imbalance) => Ok((fee, Some(imbalance))),2452 Err(_) => Err(InvalidTransaction::Payment.into()),2453 }2454 }2455}245624572458impl<T: Trait + Send + Sync> SignedExtension2459 for ChargeTransactionPayment<T>2460where2461 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2462 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2463{2464 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2465 type AccountId = T::AccountId;2466 type Call = T::Call;2467 type AdditionalSigned = ();2468 type Pre = (2469 BalanceOf<T>,2470 Self::AccountId,2471 Option<NegativeImbalanceOf<T>>,2472 BalanceOf<T>,2473 );2474 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2475 Ok(())2476 }24772478 fn validate(2479 &self,2480 _who: &Self::AccountId,2481 _call: &Self::Call,2482 _info: &DispatchInfoOf<Self::Call>,2483 _len: usize,2484 ) -> TransactionValidity {2485 Ok(ValidTransaction::default())2486 }24872488 fn pre_dispatch(2489 self,2490 who: &Self::AccountId,2491 call: &Self::Call,2492 info: &DispatchInfoOf<Self::Call>,2493 len: usize,2494 ) -> Result<Self::Pre, TransactionValidityError> {2495 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2496 Ok((self.0, who.clone(), imbalance, fee))2497 }24982499 fn post_dispatch(2500 pre: Self::Pre,2501 info: &DispatchInfoOf<Self::Call>,2502 post_info: &PostDispatchInfoOf<Self::Call>,2503 len: usize,2504 _result: &DispatchResult,2505 ) -> Result<(), TransactionValidityError> {2506 let (tip, who, imbalance, fee) = pre;2507 if let Some(payed) = imbalance {2508 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2509 len as u32, info, post_info, tip,2510 );2511 let refund = fee.saturating_sub(actual_fee);2512 let actual_payment =2513 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2514 &who, refund,2515 ) {2516 Ok(refund_imbalance) => {2517 // The refund cannot be larger than the up front payed max weight.2518 // `PostDispatchInfo::calc_unspent` guards against such a case.2519 match payed.offset(refund_imbalance) {2520 Ok(actual_payment) => actual_payment,2521 Err(_) => return Err(InvalidTransaction::Payment.into()),2522 }2523 }2524 // We do not recreate the account using the refund. The up front payment2525 // is gone in that case.2526 Err(_) => payed,2527 };2528 let imbalances = actual_payment.split(tip);2529 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2530 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2531 );2532 }2533 Ok(())2534 }2535}25362537// #endregion1#![cfg_attr(not(feature = "std"), no_std)]23#[cfg(feature = "std")]4pub use std::*;56#[cfg(feature = "std")]7pub use serde::*;89use codec::{Decode, Encode};10pub use frame_support::{11 construct_runtime, decl_event, decl_module, decl_storage, decl_error,12 dispatch::DispatchResult,13 ensure, fail, parameter_types,14 traits::{15 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,16 Randomness, WithdrawReason,17 },18 weights::{19 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},20 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,21 WeightToFeePolynomial,22 },23 IsSubType, StorageValue,24};2526use frame_system::{self as system, ensure_signed, ensure_root};27use sp_runtime::sp_std::prelude::Vec;28use sp_runtime::{29 traits::{30 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SignedExtension, Zero,31 },32 transaction_validity::{33 InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,34 },35 FixedPointOperand, FixedU128,36};37use pallet_contracts::ContractAddressFor;38use sp_runtime::traits::StaticLookup;3940#[cfg(test)]41mod mock;4243#[cfg(test)]44mod tests;4546mod default_weights;4748pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;49pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;50pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5152// Structs53// #region5455pub type CollectionId = u32;56pub type TokenId = u32;5758pub type DecimalPoints = u8;5960#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]61#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]62pub enum CollectionMode {63 Invalid,64 NFT,65 // decimal points66 Fungible(DecimalPoints),67 // decimal points68 ReFungible(DecimalPoints),69}7071impl Into<u8> for CollectionMode {72 fn into(self) -> u8 {73 match self {74 CollectionMode::Invalid => 0,75 CollectionMode::NFT => 1,76 CollectionMode::Fungible(_) => 2,77 CollectionMode::ReFungible(_) => 3,78 }79 }80}8182#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]83#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]84pub enum AccessMode {85 Normal,86 WhiteList,87}88impl Default for AccessMode {89 fn default() -> Self {90 Self::Normal91 }92}9394impl Default for CollectionMode {95 fn default() -> Self {96 Self::Invalid97 }98}99100#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]101#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]102pub struct Ownership<AccountId> {103 pub owner: AccountId,104 pub fraction: u128,105}106107#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]108#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]109pub struct CollectionType<AccountId> {110 pub owner: AccountId,111 pub mode: CollectionMode,112 pub access: AccessMode,113 pub decimal_points: DecimalPoints,114 pub name: Vec<u16>, // 64 include null escape char115 pub description: Vec<u16>, // 256 include null escape char116 pub token_prefix: Vec<u8>, // 16 include null escape char117 pub mint_mode: bool,118 pub offchain_schema: Vec<u8>,119 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender120 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship121 pub limits: CollectionLimits, // Collection private restrictions 122 pub variable_on_chain_schema: Vec<u8>, //123 pub const_on_chain_schema: Vec<u8>, //124}125126#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]127#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]128pub struct NftItemType<AccountId> {129 pub collection: CollectionId,130 pub owner: AccountId,131 pub const_data: Vec<u8>,132 pub variable_data: Vec<u8>,133}134135#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]136#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]137pub struct FungibleItemType<AccountId> {138 pub collection: CollectionId,139 pub owner: AccountId,140 pub value: u128,141}142143#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]144#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]145pub struct ReFungibleItemType<AccountId> {146 pub collection: CollectionId,147 pub owner: Vec<Ownership<AccountId>>,148 pub const_data: Vec<u8>,149 pub variable_data: Vec<u8>,150}151152#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]153#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]154pub struct ApprovePermissions<AccountId> {155 pub approved: AccountId,156 pub amount: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct VestingItem<AccountId, Moment> {162 pub sender: AccountId,163 pub recipient: AccountId,164 pub collection_id: CollectionId,165 pub item_id: TokenId,166 pub amount: u64,167 pub vesting_date: Moment,168}169170#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]171#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]172pub struct BasketItem<AccountId, BlockNumber> {173 pub address: AccountId,174 pub start_block: BlockNumber,175}176177#[derive(Encode, Decode, Debug, Clone, PartialEq)]178#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]179pub struct CollectionLimits {180 pub account_token_ownership_limit: u32,181 pub sponsored_data_size: u32,182 pub token_limit: u32,183184 // Timeouts for item types in passed blocks185 pub sponsor_transfer_timeout: u32,186}187188impl Default for CollectionLimits {189 fn default() -> CollectionLimits {190 CollectionLimits { 191 account_token_ownership_limit: 10_000_000, 192 token_limit: u32::max_value(),193 sponsored_data_size: u32::max_value(), 194 sponsor_transfer_timeout: 14400 }195 }196}197198#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]199#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]200pub struct ChainLimits {201 pub collection_numbers_limit: u32,202 pub account_token_ownership_limit: u32,203 pub collections_admins_limit: u64,204 pub custom_data_limit: u32,205206 // Timeouts for item types in passed blocks207 pub nft_sponsor_transfer_timeout: u32,208 pub fungible_sponsor_transfer_timeout: u32,209 pub refungible_sponsor_transfer_timeout: u32,210}211212pub trait WeightInfo {213 fn create_collection() -> Weight;214 fn destroy_collection() -> Weight;215 fn add_to_white_list() -> Weight;216 fn remove_from_white_list() -> Weight;217 fn set_public_access_mode() -> Weight;218 fn set_mint_permission() -> Weight;219 fn change_collection_owner() -> Weight;220 fn add_collection_admin() -> Weight;221 fn remove_collection_admin() -> Weight;222 fn set_collection_sponsor() -> Weight;223 fn confirm_sponsorship() -> Weight;224 fn remove_collection_sponsor() -> Weight;225 fn create_item(s: usize) -> Weight;226 fn burn_item() -> Weight;227 fn transfer() -> Weight;228 fn approve() -> Weight;229 fn transfer_from() -> Weight;230 fn set_offchain_schema() -> Weight;231 fn set_const_on_chain_schema() -> Weight;232 fn set_variable_on_chain_schema() -> Weight;233 fn set_variable_meta_data() -> Weight;234 fn enable_contract_sponsoring() -> Weight;235}236237#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]238#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]239pub struct CreateNftData {240 pub const_data: Vec<u8>,241 pub variable_data: Vec<u8>,242}243244#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]245#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]246pub struct CreateFungibleData {247}248249#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]250#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]251pub struct CreateReFungibleData {252 pub const_data: Vec<u8>,253 pub variable_data: Vec<u8>,254}255256#[derive(Encode, Decode, Debug, Clone, PartialEq)]257#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]258pub enum CreateItemData {259 NFT(CreateNftData),260 Fungible(CreateFungibleData),261 ReFungible(CreateReFungibleData)262}263264impl CreateItemData {265 pub fn len(&self) -> usize {266 let len = match self {267 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),268 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),269 _ => 0270 };271 272 return len;273 }274}275276impl From<CreateNftData> for CreateItemData {277 fn from(item: CreateNftData) -> Self {278 CreateItemData::NFT(item)279 }280}281282impl From<CreateReFungibleData> for CreateItemData {283 fn from(item: CreateReFungibleData) -> Self {284 CreateItemData::ReFungible(item)285 }286}287288impl From<CreateFungibleData> for CreateItemData {289 fn from(item: CreateFungibleData) -> Self {290 CreateItemData::Fungible(item)291 }292}293294295decl_error! {296 /// Error for non-fungible-token module.297 pub enum Error for Module<T: Trait> {298 /// Total collections bound exceeded.299 TotalCollectionsLimitExceeded,300 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.301 CollectionDecimalPointLimitExceeded, 302 /// Collection name can not be longer than 63 char.303 CollectionNameLimitExceeded, 304 /// Collection description can not be longer than 255 char.305 CollectionDescriptionLimitExceeded, 306 /// Token prefix can not be longer than 15 char.307 CollectionTokenPrefixLimitExceeded,308 /// This collection does not exist.309 CollectionNotFound,310 /// Item not exists.311 TokenNotFound,312 /// Arithmetic calculation overflow.313 NumOverflow, 314 /// Account already has admin role.315 AlreadyAdmin, 316 /// You do not own this collection.317 NoPermission,318 /// This address is not set as sponsor, use setCollectionSponsor first.319 ConfirmUnsetSponsorFail,320 /// Collection is not in mint mode.321 PublicMintingNotAllowed,322 /// Sender parameter and item owner must be equal.323 MustBeTokenOwner,324 /// Item balance not enough.325 TokenValueTooLow,326 /// Size of item is too large.327 NftSizeLimitExceeded,328 /// No approve found329 ApproveNotFound,330 /// Requested value more than approved.331 TokenValueNotEnough,332 /// Only approved addresses can call this method.333 ApproveRequired,334 /// Address is not in white list.335 AddresNotInWhiteList,336 /// Number of collection admins bound exceeded.337 CollectionAdminsLimitExceeded,338 /// Owned tokens by a single address bound exceeded.339 AddressOwnershipLimitExceeded,340 /// Length of items properties must be greater than 0.341 EmptyArgument,342 /// const_data exceeded data limit.343 TokenConstDataLimitExceeded,344 /// variable_data exceeded data limit.345 TokenVariableDataLimitExceeded,346 /// Not NFT item data used to mint in NFT collection.347 NotNftDataUsedToMintNftCollectionToken,348 /// Not Fungible item data used to mint in Fungible collection.349 NotFungibleDataUsedToMintFungibleCollectionToken,350 /// Not Re Fungible item data used to mint in Re Fungible collection.351 NotReFungibleDataUsedToMintReFungibleCollectionToken,352 /// Unexpected collection type.353 UnexpectedCollectionType,354 /// Can't store metadata in fungible tokens.355 CantStoreMetadataInFungibleTokens,356 /// Collection token limit exceeded357 CollectionTokenLimitExceeded,358 /// Account token limit exceeded per collection359 AccountTokenLimitExceeded,360 /// Collection limit bounds per collection exceeded361 CollectionLimitBoundsExceeded362 }363}364365pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {366 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;367368 /// Weight information for extrinsics in this pallet.369 type WeightInfo: WeightInfo;370}371372#[cfg(feature = "runtime-benchmarks")]373mod benchmarking;374375// #endregion376377decl_storage! {378 trait Store for Module<T: Trait> as Nft {379380 // Private members381 NextCollectionID: CollectionId;382 CreatedCollectionCount: u32;383 ChainVersion: u64;384 ItemListIndex: map hasher(identity) CollectionId => TokenId;385386 // Chain limits struct387 pub ChainLimit get(fn chain_limit) config(): ChainLimits;388389 // Bound counters390 CollectionCount: u32;391 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;392393 // Basic collections394 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;395 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;396 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;397398 /// Balance owner per collection map399 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;400401 /// second parameter: item id + owner account id402 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;403404 /// Item collections405 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;406 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;407 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;408409 /// Index list410 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;411412 /// Tokens transfer baskets413 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;414 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;415 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;416417 // Contract Sponsorship and Ownership418 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;419 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;420 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;421 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;422 }423 add_extra_genesis {424 build(|config: &GenesisConfig<T>| {425 // Modification of storage426 for (_num, _c) in &config.collection {427 <Module<T>>::init_collection(_c);428 }429430 for (_num, _q, _i) in &config.nft_item_id {431 <Module<T>>::init_nft_token(_i);432 }433434 for (_num, _q, _i) in &config.fungible_item_id {435 <Module<T>>::init_fungible_token(_i);436 }437438 for (_num, _q, _i) in &config.refungible_item_id {439 <Module<T>>::init_refungible_token(_i);440 }441 })442 }443}444445decl_event!(446 pub enum Event<T>447 where448 AccountId = <T as system::Trait>::AccountId,449 {450 /// New collection was created451 /// 452 /// # Arguments453 /// 454 /// * collection_id: Globally unique identifier of newly created collection.455 /// 456 /// * mode: [CollectionMode] converted into u8.457 /// 458 /// * account_id: Collection owner.459 Created(CollectionId, u8, AccountId),460461 /// New item was created.462 /// 463 /// # Arguments464 /// 465 /// * collection_id: Id of the collection where item was created.466 /// 467 /// * item_id: Id of an item. Unique within the collection.468 ItemCreated(CollectionId, TokenId),469470 /// Collection item was burned.471 /// 472 /// # Arguments473 /// 474 /// collection_id.475 /// 476 /// item_id: Identifier of burned NFT.477 ItemDestroyed(CollectionId, TokenId),478 }479);480481decl_module! {482 pub struct Module<T: Trait> for enum Call where origin: T::Origin {483484 fn deposit_event() = default;485 type Error = Error<T>;486487 fn on_initialize(now: T::BlockNumber) -> Weight {488489 if ChainVersion::get() < 2490 {491 let value = NextCollectionID::get();492 CreatedCollectionCount::put(value);493 ChainVersion::put(2);494 }495496 0497 }498499 /// 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.500 /// 501 /// # Permissions502 /// 503 /// * Anyone.504 /// 505 /// # Arguments506 /// 507 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.508 /// 509 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.510 /// 511 /// * token_prefix: UTF-8 string with token prefix.512 /// 513 /// * mode: [CollectionMode] collection type and type dependent data.514 // returns collection ID515 #[weight = T::WeightInfo::create_collection()]516 pub fn create_collection(origin,517 collection_name: Vec<u16>,518 collection_description: Vec<u16>,519 token_prefix: Vec<u8>,520 mode: CollectionMode) -> DispatchResult {521522 // Anyone can create a collection523 let who = ensure_signed(origin)?;524525 let decimal_points = match mode {526 CollectionMode::Fungible(points) => points,527 CollectionMode::ReFungible(points) => points,528 _ => 0529 };530531 // bound Total number of collections532 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);533534 // check params535 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);536537 let mut name = collection_name.to_vec();538 name.push(0);539 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);540541 let mut description = collection_description.to_vec();542 description.push(0);543 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);544545 let mut prefix = token_prefix.to_vec();546 prefix.push(0);547 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);548549 // Generate next collection ID550 let next_id = CreatedCollectionCount::get()551 .checked_add(1)552 .ok_or(Error::<T>::NumOverflow)?;553554 // bound counter555 let total = CollectionCount::get()556 .checked_add(1)557 .ok_or(Error::<T>::NumOverflow)?;558559 CreatedCollectionCount::put(next_id);560 CollectionCount::put(total);561562 // Create new collection563 let new_collection = CollectionType {564 owner: who.clone(),565 name: name,566 mode: mode.clone(),567 mint_mode: false,568 access: AccessMode::Normal,569 description: description,570 decimal_points: decimal_points,571 token_prefix: prefix,572 offchain_schema: Vec::new(),573 sponsor: T::AccountId::default(),574 unconfirmed_sponsor: T::AccountId::default(),575 variable_on_chain_schema: Vec::new(),576 const_on_chain_schema: Vec::new(),577 limits: CollectionLimits::default(),578 };579580 // Add new collection to map581 <Collection<T>>::insert(next_id, new_collection);582583 // call event584 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));585586 Ok(())587 }588589 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.590 /// 591 /// # Permissions592 /// 593 /// * Collection Owner.594 /// 595 /// # Arguments596 /// 597 /// * collection_id: collection to destroy.598 #[weight = T::WeightInfo::destroy_collection()]599 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {600601 let sender = ensure_signed(origin)?;602 Self::check_owner_permissions(collection_id, sender)?;603604 <AddressTokens<T>>::remove_prefix(collection_id);605 <ApprovedList<T>>::remove_prefix(collection_id);606 <Balance<T>>::remove_prefix(collection_id);607 <ItemListIndex>::remove(collection_id);608 <AdminList<T>>::remove(collection_id);609 <Collection<T>>::remove(collection_id);610 <WhiteList<T>>::remove(collection_id);611612 <NftItemList<T>>::remove_prefix(collection_id);613 <FungibleItemList<T>>::remove_prefix(collection_id);614 <ReFungibleItemList<T>>::remove_prefix(collection_id);615616 <NftTransferBasket<T>>::remove_prefix(collection_id);617 <FungibleTransferBasket<T>>::remove_prefix(collection_id);618 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);619620 if CollectionCount::get() > 0621 {622 // bound couter623 let total = CollectionCount::get()624 .checked_sub(1)625 .ok_or(Error::<T>::NumOverflow)?;626627 CollectionCount::put(total);628 }629630 Ok(())631 }632633 /// Add an address to white list.634 /// 635 /// # Permissions636 /// 637 /// * Collection Owner638 /// * Collection Admin639 /// 640 /// # Arguments641 /// 642 /// * collection_id.643 /// 644 /// * address.645 #[weight = T::WeightInfo::add_to_white_list()]646 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{647648 let sender = ensure_signed(origin)?;649 Self::check_owner_or_admin_permissions(collection_id, sender)?;650651 let mut white_list_collection: Vec<T::AccountId>;652 if <WhiteList<T>>::contains_key(collection_id) {653 white_list_collection = <WhiteList<T>>::get(collection_id);654 if !white_list_collection.contains(&address.clone())655 {656 white_list_collection.push(address.clone());657 }658 }659 else {660 white_list_collection = Vec::new();661 white_list_collection.push(address.clone());662 }663664 <WhiteList<T>>::insert(collection_id, white_list_collection);665 Ok(())666 }667668 /// Remove an address from white list.669 /// 670 /// # Permissions671 /// 672 /// * Collection Owner673 /// * Collection Admin674 /// 675 /// # Arguments676 /// 677 /// * collection_id.678 /// 679 /// * address.680 #[weight = T::WeightInfo::remove_from_white_list()]681 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{682683 let sender = ensure_signed(origin)?;684 Self::check_owner_or_admin_permissions(collection_id, sender)?;685686 if <WhiteList<T>>::contains_key(collection_id) {687 let mut white_list_collection = <WhiteList<T>>::get(collection_id);688 if white_list_collection.contains(&address.clone())689 {690 white_list_collection.retain(|i| *i != address.clone());691 <WhiteList<T>>::insert(collection_id, white_list_collection);692 }693 }694695 Ok(())696 }697698 /// Toggle between normal and white list access for the methods with access for `Anyone`.699 /// 700 /// # Permissions701 /// 702 /// * Collection Owner.703 /// 704 /// # Arguments705 /// 706 /// * collection_id.707 /// 708 /// * mode: [AccessMode]709 #[weight = T::WeightInfo::set_public_access_mode()]710 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult711 {712 let sender = ensure_signed(origin)?;713714 Self::check_owner_permissions(collection_id, sender)?;715 let mut target_collection = <Collection<T>>::get(collection_id);716 target_collection.access = mode;717 <Collection<T>>::insert(collection_id, target_collection);718719 Ok(())720 }721722 /// Allows Anyone to create tokens if:723 /// * White List is enabled, and724 /// * Address is added to white list, and725 /// * This method was called with True parameter726 /// 727 /// # Permissions728 /// * Collection Owner729 ///730 /// # Arguments731 /// 732 /// * collection_id.733 /// 734 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.735 #[weight = T::WeightInfo::set_mint_permission()]736 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult737 {738 let sender = ensure_signed(origin)?;739740 Self::check_owner_permissions(collection_id, sender)?;741 let mut target_collection = <Collection<T>>::get(collection_id);742 target_collection.mint_mode = mint_permission;743 <Collection<T>>::insert(collection_id, target_collection);744745 Ok(())746 }747748 /// Change the owner of the collection.749 /// 750 /// # Permissions751 /// 752 /// * Collection Owner.753 /// 754 /// # Arguments755 /// 756 /// * collection_id.757 /// 758 /// * new_owner.759 #[weight = T::WeightInfo::change_collection_owner()]760 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {761762 let sender = ensure_signed(origin)?;763 Self::check_owner_permissions(collection_id, sender)?;764 let mut target_collection = <Collection<T>>::get(collection_id);765 target_collection.owner = new_owner;766 <Collection<T>>::insert(collection_id, target_collection);767768 Ok(())769 }770771 /// Adds an admin of the Collection.772 /// 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. 773 /// 774 /// # Permissions775 /// 776 /// * Collection Owner.777 /// * Collection Admin.778 /// 779 /// # Arguments780 /// 781 /// * collection_id: ID of the Collection to add admin for.782 /// 783 /// * new_admin_id: Address of new admin to add.784 #[weight = T::WeightInfo::add_collection_admin()]785 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {786787 let sender = ensure_signed(origin)?;788 Self::check_owner_or_admin_permissions(collection_id, sender)?;789 let mut admin_arr: Vec<T::AccountId> = Vec::new();790791 if <AdminList<T>>::contains_key(collection_id)792 {793 admin_arr = <AdminList<T>>::get(collection_id);794 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);795 }796797 // Number of collection admins798 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);799800 admin_arr.push(new_admin_id);801 <AdminList<T>>::insert(collection_id, admin_arr);802803 Ok(())804 }805806 /// 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.807 ///808 /// # Permissions809 /// 810 /// * Collection Owner.811 /// * Collection Admin.812 /// 813 /// # Arguments814 /// 815 /// * collection_id: ID of the Collection to remove admin for.816 /// 817 /// * account_id: Address of admin to remove.818 #[weight = T::WeightInfo::remove_collection_admin()]819 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {820821 let sender = ensure_signed(origin)?;822 Self::check_owner_or_admin_permissions(collection_id, sender)?;823824 if <AdminList<T>>::contains_key(collection_id)825 {826 let mut admin_arr = <AdminList<T>>::get(collection_id);827 admin_arr.retain(|i| *i != account_id);828 <AdminList<T>>::insert(collection_id, admin_arr);829 }830831 Ok(())832 }833834 /// # Permissions835 /// 836 /// * Collection Owner837 /// 838 /// # Arguments839 /// 840 /// * collection_id.841 /// 842 /// * new_sponsor.843 #[weight = T::WeightInfo::set_collection_sponsor()]844 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {845846 let sender = ensure_signed(origin)?;847 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);848849 let mut target_collection = <Collection<T>>::get(collection_id);850 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);851852 target_collection.unconfirmed_sponsor = new_sponsor;853 <Collection<T>>::insert(collection_id, target_collection);854855 Ok(())856 }857858 /// # Permissions859 /// 860 /// * Sponsor.861 /// 862 /// # Arguments863 /// 864 /// * collection_id.865 #[weight = T::WeightInfo::confirm_sponsorship()]866 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {867868 let sender = ensure_signed(origin)?;869 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);870871 let mut target_collection = <Collection<T>>::get(collection_id);872 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);873874 target_collection.sponsor = target_collection.unconfirmed_sponsor;875 target_collection.unconfirmed_sponsor = T::AccountId::default();876 <Collection<T>>::insert(collection_id, target_collection);877878 Ok(())879 }880881 /// Switch back to pay-per-own-transaction model.882 ///883 /// # Permissions884 ///885 /// * Collection owner.886 /// 887 /// # Arguments888 /// 889 /// * collection_id.890 #[weight = T::WeightInfo::remove_collection_sponsor()]891 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {892893 let sender = ensure_signed(origin)?;894 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);895896 let mut target_collection = <Collection<T>>::get(collection_id);897 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);898899 target_collection.sponsor = T::AccountId::default();900 <Collection<T>>::insert(collection_id, target_collection);901902 Ok(())903 }904905 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.906 /// 907 /// # Permissions908 /// 909 /// * Collection Owner.910 /// * Collection Admin.911 /// * Anyone if912 /// * White List is enabled, and913 /// * Address is added to white list, and914 /// * MintPermission is enabled (see SetMintPermission method)915 /// 916 /// # Arguments917 /// 918 /// * collection_id: ID of the collection.919 /// 920 /// * owner: Address, initial owner of the NFT.921 ///922 /// * data: Token data to store on chain.923 // #[weight =924 // (130_000_000 as Weight)925 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))926 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))927 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]928929 #[weight = T::WeightInfo::create_item(data.len())]930 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {931932 let sender = ensure_signed(origin)?;933934 Self::collection_exists(collection_id)?;935936 let target_collection = <Collection<T>>::get(collection_id);937938 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;939 Self::validate_create_item_args(&target_collection, &data)?;940 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;941942 Ok(())943 }944945 /// This method creates multiple instances of NFT Collection created with CreateCollection method.946 /// 947 /// # Permissions948 /// 949 /// * Collection Owner.950 /// * Collection Admin.951 /// * Anyone if952 /// * White List is enabled, and953 /// * Address is added to white list, and954 /// * MintPermission is enabled (see SetMintPermission method)955 /// 956 /// # Arguments957 /// 958 /// * collection_id: ID of the collection.959 /// 960 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].961 /// 962 /// * owner: Address, initial owner of the NFT.963 #[weight = T::WeightInfo::create_item(items_data.into_iter()964 .map(|data| { data.len() })965 .sum())]966 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {967968 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);969 let sender = ensure_signed(origin)?;970971 Self::collection_exists(collection_id)?;972 let target_collection = <Collection<T>>::get(collection_id);973974 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;975976 for data in &items_data {977 Self::validate_create_item_args(&target_collection, data)?;978 }979 for data in &items_data {980 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;981 }982983 Ok(())984 }985986 /// Destroys a concrete instance of NFT.987 /// 988 /// # Permissions989 /// 990 /// * Collection Owner.991 /// * Collection Admin.992 /// * Current NFT Owner.993 /// 994 /// # Arguments995 /// 996 /// * collection_id: ID of the collection.997 /// 998 /// * item_id: ID of NFT to burn.999 #[weight = T::WeightInfo::burn_item()]1000 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10011002 let sender = ensure_signed(origin)?;1003 Self::collection_exists(collection_id)?;10041005 // Transfer permissions check1006 let target_collection = <Collection<T>>::get(collection_id);1007 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1008 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1009 Error::<T>::NoPermission);10101011 if target_collection.access == AccessMode::WhiteList {1012 Self::check_white_list(collection_id, &sender)?;1013 }10141015 match target_collection.mode1016 {1017 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1018 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1019 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1020 _ => ()1021 };10221023 // call event1024 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10251026 Ok(())1027 }10281029 /// Change ownership of the token.1030 /// 1031 /// # Permissions1032 /// 1033 /// * Collection Owner1034 /// * Collection Admin1035 /// * Current NFT owner1036 ///1037 /// # Arguments1038 /// 1039 /// * recipient: Address of token recipient.1040 /// 1041 /// * collection_id.1042 /// 1043 /// * item_id: ID of the item1044 /// * Non-Fungible Mode: Required.1045 /// * Fungible Mode: Ignored.1046 /// * Re-Fungible Mode: Required.1047 /// 1048 /// * value: Amount to transfer.1049 /// * Non-Fungible Mode: Ignored1050 /// * Fungible Mode: Must specify transferred amount1051 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1052 #[weight = T::WeightInfo::transfer()]1053 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10541055 let sender = ensure_signed(origin)?;1056 let target_collection = <Collection<T>>::get(collection_id);10571058 // Limits check1059 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10601061 // Transfer permissions check1062 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1063 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1064 Error::<T>::NoPermission);10651066 if target_collection.access == AccessMode::WhiteList {1067 Self::check_white_list(collection_id, &sender)?;1068 Self::check_white_list(collection_id, &recipient)?;1069 }10701071 match target_collection.mode1072 {1073 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1074 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1075 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1076 _ => ()1077 };10781079 Ok(())1080 }10811082 /// Set, change, or remove approved address to transfer the ownership of the NFT.1083 /// 1084 /// # Permissions1085 /// 1086 /// * Collection Owner1087 /// * Collection Admin1088 /// * Current NFT owner1089 /// 1090 /// # Arguments1091 /// 1092 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1093 /// 1094 /// * collection_id.1095 /// 1096 /// * item_id: ID of the item.1097 #[weight = T::WeightInfo::approve()]1098 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10991100 let sender = ensure_signed(origin)?;11011102 // Transfer permissions check1103 let target_collection = <Collection<T>>::get(collection_id);1104 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1105 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1106 Error::<T>::NoPermission);11071108 if target_collection.access == AccessMode::WhiteList {1109 Self::check_white_list(collection_id, &sender)?;1110 Self::check_white_list(collection_id, &approved)?;1111 }11121113 // amount param stub1114 let amount = 100000000;11151116 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1117 if list_exists {11181119 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1120 let item_contains = list.iter().any(|i| i.approved == approved);11211122 if !item_contains {1123 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1124 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1125 }1126 } else {11271128 let mut list = Vec::new();1129 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1130 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1131 }11321133 Ok(())1134 }1135 1136 /// 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.1137 /// 1138 /// # Permissions1139 /// * Collection Owner1140 /// * Collection Admin1141 /// * Current NFT owner1142 /// * Address approved by current NFT owner1143 /// 1144 /// # Arguments1145 /// 1146 /// * from: Address that owns token.1147 /// 1148 /// * recipient: Address of token recipient.1149 /// 1150 /// * collection_id.1151 /// 1152 /// * item_id: ID of the item.1153 /// 1154 /// * value: Amount to transfer.1155 #[weight = T::WeightInfo::transfer_from()]1156 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11571158 let sender = ensure_signed(origin)?;1159 let mut appoved_transfer = false;11601161 // Check approve1162 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1163 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1164 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1165 if opt_item.is_some()1166 {1167 appoved_transfer = true;1168 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1169 }1170 }11711172 let target_collection = <Collection<T>>::get(collection_id);11731174 // Limits check1175 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11761177 // Transfer permissions check 1178 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1179 Error::<T>::NoPermission);11801181 if target_collection.access == AccessMode::WhiteList {1182 Self::check_white_list(collection_id, &sender)?;1183 Self::check_white_list(collection_id, &recipient)?;1184 }11851186 // remove approve1187 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1188 .into_iter().filter(|i| i.approved != sender.clone()).collect();1189 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);119011911192 match target_collection.mode1193 {1194 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1195 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1196 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1197 _ => ()1198 };11991200 Ok(())1201 }12021203 ///1204 #[weight = 0]1205 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12061207 // let no_perm_mes = "You do not have permissions to modify this collection";1208 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1209 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1210 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12111212 // // on_nft_received call12131214 // Self::transfer(origin, collection_id, item_id, new_owner)?;12151216 Ok(())1217 }12181219 /// Set off-chain data schema.1220 /// 1221 /// # Permissions1222 /// 1223 /// * Collection Owner1224 /// * Collection Admin1225 /// 1226 /// # Arguments1227 /// 1228 /// * collection_id.1229 /// 1230 /// * schema: String representing the offchain data schema.1231 #[weight = T::WeightInfo::set_variable_meta_data()]1232 pub fn set_variable_meta_data (1233 origin,1234 collection_id: CollectionId,1235 item_id: TokenId,1236 data: Vec<u8>1237 ) -> DispatchResult {1238 let sender = ensure_signed(origin)?;1239 1240 Self::collection_exists(collection_id)?;1241 1242 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12431244 // Modify permissions check1245 let target_collection = <Collection<T>>::get(collection_id);1246 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1247 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1248 Error::<T>::NoPermission);12491250 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12511252 match target_collection.mode1253 {1254 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1255 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1256 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1257 _ => fail!(Error::<T>::UnexpectedCollectionType)1258 };12591260 Ok(())1261 }1262 12631264 /// Set off-chain data schema.1265 /// 1266 /// # Permissions1267 /// 1268 /// * Collection Owner1269 /// * Collection Admin1270 /// 1271 /// # Arguments1272 /// 1273 /// * collection_id.1274 /// 1275 /// * schema: String representing the offchain data schema.1276 #[weight = T::WeightInfo::set_offchain_schema()]1277 pub fn set_offchain_schema(1278 origin,1279 collection_id: CollectionId,1280 schema: Vec<u8>1281 ) -> DispatchResult {1282 let sender = ensure_signed(origin)?;1283 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12841285 let mut target_collection = <Collection<T>>::get(collection_id);1286 target_collection.offchain_schema = schema;1287 <Collection<T>>::insert(collection_id, target_collection);12881289 Ok(())1290 }12911292 /// Set const on-chain data schema.1293 /// 1294 /// # Permissions1295 /// 1296 /// * Collection Owner1297 /// * Collection Admin1298 /// 1299 /// # Arguments1300 /// 1301 /// * collection_id.1302 /// 1303 /// * schema: String representing the const on-chain data schema.1304 #[weight = T::WeightInfo::set_const_on_chain_schema()]1305 pub fn set_const_on_chain_schema (1306 origin,1307 collection_id: CollectionId,1308 schema: Vec<u8>1309 ) -> DispatchResult {1310 let sender = ensure_signed(origin)?;1311 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13121313 let mut target_collection = <Collection<T>>::get(collection_id);1314 target_collection.const_on_chain_schema = schema;1315 <Collection<T>>::insert(collection_id, target_collection);13161317 Ok(())1318 }13191320 /// Set variable on-chain data schema.1321 /// 1322 /// # Permissions1323 /// 1324 /// * Collection Owner1325 /// * Collection Admin1326 /// 1327 /// # Arguments1328 /// 1329 /// * collection_id.1330 /// 1331 /// * schema: String representing the variable on-chain data schema.1332 #[weight = T::WeightInfo::set_const_on_chain_schema()]1333 pub fn set_variable_on_chain_schema (1334 origin,1335 collection_id: CollectionId,1336 schema: Vec<u8>1337 ) -> DispatchResult {1338 let sender = ensure_signed(origin)?;1339 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13401341 let mut target_collection = <Collection<T>>::get(collection_id);1342 target_collection.variable_on_chain_schema = schema;1343 <Collection<T>>::insert(collection_id, target_collection);13441345 Ok(())1346 }13471348 // Sudo permissions function1349 #[weight = 0]1350 pub fn set_chain_limits(1351 origin,1352 limits: ChainLimits1353 ) -> DispatchResult {1354 ensure_root(origin)?;1355 <ChainLimit>::put(limits);1356 Ok(())1357 }13581359 /// Enable smart contract self-sponsoring.1360 /// 1361 /// # Permissions1362 /// 1363 /// * Contract Owner1364 /// 1365 /// # Arguments1366 /// 1367 /// * contract address1368 /// * enable flag1369 /// 1370 #[weight = T::WeightInfo::enable_contract_sponsoring()]1371 pub fn enable_contract_sponsoring(1372 origin,1373 contract_address: T::AccountId,1374 enable: bool1375 ) -> DispatchResult {13761377 let sender = ensure_signed(origin)?;13781379 #[cfg(feature = "runtime-benchmarks")]1380 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13811382 let mut is_owner = false;1383 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1384 let owner = <ContractOwner<T>>::get(&contract_address);1385 is_owner = sender == owner;1386 }1387 ensure!(is_owner, Error::<T>::NoPermission);13881389 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1390 Ok(())1391 }13921393 /// Set the rate limit for contract sponsoring to specified number of blocks.1394 /// 1395 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1396 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1397 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1398 /// from contract endowment if there are at least B blocks between such transactions. 1399 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1400 /// 1401 /// # Permissions1402 /// 1403 /// * Contract Owner1404 /// 1405 /// # Arguments1406 /// 1407 /// -`contract_address`: Address of the contract to sponsor1408 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1409 /// 1410 #[weight = 0]1411 pub fn set_contract_sponsoring_rate_limit(1412 origin,1413 contract_address: T::AccountId,1414 rate_limit: T::BlockNumber1415 ) -> DispatchResult {1416 let sender = ensure_signed(origin)?;1417 let mut is_owner = false;1418 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1419 let owner = <ContractOwner<T>>::get(&contract_address);1420 is_owner = sender == owner;1421 }1422 ensure!(is_owner, Error::<T>::NoPermission);14231424 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1425 Ok(())1426 }14271428 #[weight = 0]1429 pub fn set_collection_limits(1430 origin,1431 collection_id: u32,1432 limits: CollectionLimits,1433 ) -> DispatchResult {1434 let sender = ensure_signed(origin)?;1435 Self::check_owner_permissions(collection_id, sender.clone())?;1436 let mut target_collection = <Collection<T>>::get(collection_id);1437 let chain_limits = ChainLimit::get();1438 let climits = target_collection.limits;14391440 // collection bounds1441 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1442 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1443 Error::<T>::CollectionLimitBoundsExceeded);14441445 // token_limit check prev1446 ensure!(climits.token_limit > limits.token_limit && 1447 limits.token_limit <= chain_limits.account_token_ownership_limit, 1448 Error::<T>::AccountTokenLimitExceeded);14491450 target_collection.limits = limits;1451 <Collection<T>>::insert(collection_id, target_collection);14521453 Ok(())1454 } 1455 }1456}14571458impl<T: Trait> Module<T> {14591460 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {14611462 if !Self::is_owner_or_admin_permissions(collection_id, recipient.clone()) {14631464 // check token limit and account token limit1465 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1466 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1467 }14681469 Ok(())1470 }14711472 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {14731474 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {14751476 // check token limit and account token limit1477 let total_items: u32 = ItemListIndex::get(collection_id);1478 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1479 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1480 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1481 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1482 Self::check_white_list(collection_id, owner)?;1483 Self::check_white_list(collection_id, sender)?;1484 }14851486 Ok(())1487 }14881489 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1490 match target_collection.mode1491 {1492 CollectionMode::NFT => {1493 if let CreateItemData::NFT(data) = data {1494 // check sizes1495 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1496 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1497 } else {1498 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1499 }1500 },1501 CollectionMode::Fungible(_) => {1502 if let CreateItemData::Fungible(_) = data {1503 } else {1504 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1505 }1506 },1507 CollectionMode::ReFungible(_) => {1508 if let CreateItemData::ReFungible(data) = data {15091510 // check sizes1511 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1512 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1513 } else {1514 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1515 }1516 },1517 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1518 };15191520 Ok(())1521 }15221523 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1524 match data1525 {1526 CreateItemData::NFT(data) => {1527 let item = NftItemType {1528 collection: collection_id,1529 owner,1530 const_data: data.const_data,1531 variable_data: data.variable_data1532 };15331534 Self::add_nft_item(item)?;1535 },1536 CreateItemData::Fungible(_) => {1537 let item = FungibleItemType {1538 collection: collection_id,1539 owner,1540 value: (10 as u128).pow(collection.decimal_points as u32)1541 };15421543 Self::add_fungible_item(item)?;1544 },1545 CreateItemData::ReFungible(data) => {1546 let mut owner_list = Vec::new();1547 let value = (10 as u128).pow(collection.decimal_points as u32);1548 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15491550 let item = ReFungibleItemType {1551 collection: collection_id,1552 owner: owner_list,1553 const_data: data.const_data,1554 variable_data: data.variable_data1555 };15561557 Self::add_refungible_item(item)?;1558 }1559 };15601561 // call event1562 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));15631564 Ok(())1565 }15661567 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1568 let current_index = <ItemListIndex>::get(item.collection)1569 .checked_add(1)1570 .ok_or(Error::<T>::NumOverflow)?;1571 let itemcopy = item.clone();1572 let owner = item.owner.clone();15731574 Self::add_token_index(item.collection, current_index, owner.clone())?;15751576 <ItemListIndex>::insert(item.collection, current_index);1577 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15781579 // Add current block1580 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1581 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1582 1583 // Update balance1584 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1585 .checked_add(item.value)1586 .ok_or(Error::<T>::NumOverflow)?;1587 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15881589 Ok(())1590 }15911592 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1593 let current_index = <ItemListIndex>::get(item.collection)1594 .checked_add(1)1595 .ok_or(Error::<T>::NumOverflow)?;1596 let itemcopy = item.clone();15971598 let value = item.owner.first().unwrap().fraction;1599 let owner = item.owner.first().unwrap().owner.clone();16001601 Self::add_token_index(item.collection, current_index, owner.clone())?;16021603 <ItemListIndex>::insert(item.collection, current_index);1604 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16051606 // Add current block1607 let block_number: T::BlockNumber = 0.into();1608 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16091610 // Update balance1611 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1612 .checked_add(value)1613 .ok_or(Error::<T>::NumOverflow)?;1614 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16151616 Ok(())1617 }16181619 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1620 let current_index = <ItemListIndex>::get(item.collection)1621 .checked_add(1)1622 .ok_or(Error::<T>::NumOverflow)?;16231624 let item_owner = item.owner.clone();1625 let collection_id = item.collection.clone();1626 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16271628 <ItemListIndex>::insert(collection_id, current_index);1629 <NftItemList<T>>::insert(collection_id, current_index, item);16301631 // Add current block1632 let block_number: T::BlockNumber = 0.into();1633 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);16341635 // Update balance1636 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1637 .checked_add(1)1638 .ok_or(Error::<T>::NumOverflow)?;1639 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16401641 Ok(())1642 }16431644 fn burn_refungible_item(1645 collection_id: CollectionId,1646 item_id: TokenId,1647 owner: T::AccountId,1648 ) -> DispatchResult {1649 ensure!(1650 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1651 Error::<T>::TokenNotFound1652 );1653 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1654 let item = collection1655 .owner1656 .iter()1657 .filter(|&i| i.owner == owner)1658 .next()1659 .unwrap();1660 Self::remove_token_index(collection_id, item_id, owner.clone())?;16611662 // remove approve list1663 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));16641665 // update balance1666 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1667 .checked_sub(item.fraction)1668 .ok_or(Error::<T>::NumOverflow)?;1669 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16701671 <ReFungibleItemList<T>>::remove(collection_id, item_id);16721673 Ok(())1674 }16751676 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1677 ensure!(1678 <NftItemList<T>>::contains_key(collection_id, item_id),1679 Error::<T>::TokenNotFound1680 );1681 let item = <NftItemList<T>>::get(collection_id, item_id);1682 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16831684 // remove approve list1685 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16861687 // update balance1688 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1689 .checked_sub(1)1690 .ok_or(Error::<T>::NumOverflow)?;1691 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1692 <NftItemList<T>>::remove(collection_id, item_id);16931694 Ok(())1695 }16961697 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1698 ensure!(1699 <FungibleItemList<T>>::contains_key(collection_id, item_id),1700 Error::<T>::TokenNotFound1701 );1702 let item = <FungibleItemList<T>>::get(collection_id, item_id);1703 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17041705 // remove approve list1706 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17071708 // update balance1709 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1710 .checked_sub(item.value)1711 .ok_or(Error::<T>::NumOverflow)?;1712 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17131714 <FungibleItemList<T>>::remove(collection_id, item_id);17151716 Ok(())1717 }17181719 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1720 ensure!(1721 <Collection<T>>::contains_key(collection_id),1722 Error::<T>::CollectionNotFound1723 );1724 Ok(())1725 }17261727 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1728 Self::collection_exists(collection_id)?;17291730 let target_collection = <Collection<T>>::get(collection_id);1731 ensure!(1732 subject == target_collection.owner,1733 Error::<T>::NoPermission1734 );17351736 Ok(())1737 }17381739 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1740 let target_collection = <Collection<T>>::get(collection_id);1741 let mut result: bool = subject == target_collection.owner;1742 let exists = <AdminList<T>>::contains_key(collection_id);17431744 if !result & exists {1745 if <AdminList<T>>::get(collection_id).contains(&subject) {1746 result = true1747 }1748 }17491750 result1751 }17521753 fn check_owner_or_admin_permissions(1754 collection_id: CollectionId,1755 subject: T::AccountId,1756 ) -> DispatchResult {1757 Self::collection_exists(collection_id)?;1758 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17591760 ensure!(1761 result,1762 Error::<T>::NoPermission1763 );1764 Ok(())1765 }17661767 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1768 let target_collection = <Collection<T>>::get(collection_id);17691770 match target_collection.mode {1771 CollectionMode::NFT => {1772 <NftItemList<T>>::get(collection_id, item_id).owner == subject1773 }1774 CollectionMode::Fungible(_) => {1775 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1776 }1777 CollectionMode::ReFungible(_) => {1778 <ReFungibleItemList<T>>::get(collection_id, item_id)1779 .owner1780 .iter()1781 .any(|i| i.owner == subject)1782 }1783 CollectionMode::Invalid => false,1784 }1785 }17861787 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1788 let mes = Error::<T>::AddresNotInWhiteList;1789 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1790 let wl = <WhiteList<T>>::get(collection_id);1791 ensure!(wl.contains(address), mes);17921793 Ok(())1794 }17951796 fn transfer_fungible(1797 collection_id: CollectionId,1798 item_id: TokenId,1799 value: u128,1800 owner: T::AccountId,1801 new_owner: T::AccountId,1802 ) -> DispatchResult {1803 ensure!(1804 <FungibleItemList<T>>::contains_key(collection_id, item_id),1805 Error::<T>::TokenNotFound1806 );18071808 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1809 let amount = full_item.value;18101811 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18121813 // update balance1814 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1815 .checked_sub(value)1816 .ok_or(Error::<T>::NumOverflow)?;1817 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);18181819 let mut new_owner_account_id = 0;1820 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1821 if new_owner_items.len() > 0 {1822 new_owner_account_id = new_owner_items[0];1823 }18241825 // transfer1826 if amount == value && new_owner_account_id == 0 {1827 // change owner1828 // new owner do not have account1829 let mut new_full_item = full_item.clone();1830 new_full_item.owner = new_owner.clone();1831 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18321833 // update balance1834 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1835 .checked_add(value)1836 .ok_or(Error::<T>::NumOverflow)?;1837 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18381839 // update index collection1840 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1841 } else {1842 let mut new_full_item = full_item.clone();1843 new_full_item.value -= value;18441845 // separate amount1846 if new_owner_account_id > 0 {1847 // new owner has account1848 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1849 item.value += value;18501851 // update balance1852 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1853 .checked_add(value)1854 .ok_or(Error::<T>::NumOverflow)?;1855 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18561857 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1858 } else {1859 // new owner do not have account1860 let item = FungibleItemType {1861 collection: collection_id,1862 owner: new_owner.clone(),1863 value1864 };18651866 Self::add_fungible_item(item)?;1867 }18681869 if amount == value {1870 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;18711872 // remove approve list1873 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1874 <FungibleItemList<T>>::remove(collection_id, item_id);1875 }18761877 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1878 }18791880 Ok(())1881 }18821883 fn transfer_refungible(1884 collection_id: CollectionId,1885 item_id: TokenId,1886 value: u128,1887 owner: T::AccountId,1888 new_owner: T::AccountId,1889 ) -> DispatchResult {1890 ensure!(1891 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1892 Error::<T>::TokenNotFound1893 );18941895 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1896 let item = full_item1897 .owner1898 .iter()1899 .filter(|i| i.owner == owner)1900 .next()1901 .ok_or(Error::<T>::NumOverflow)?;1902 let amount = item.fraction;19031904 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19051906 // update balance1907 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1908 .checked_sub(value)1909 .ok_or(Error::<T>::NumOverflow)?;1910 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19111912 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1913 .checked_add(value)1914 .ok_or(Error::<T>::NumOverflow)?;1915 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19161917 let old_owner = item.owner.clone();1918 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19191920 // transfer1921 if amount == value && !new_owner_has_account {1922 // change owner1923 // new owner do not have account1924 let mut new_full_item = full_item.clone();1925 new_full_item1926 .owner1927 .iter_mut()1928 .find(|i| i.owner == owner)1929 .unwrap()1930 .owner = new_owner.clone();1931 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19321933 // update index collection1934 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1935 } else {1936 let mut new_full_item = full_item.clone();1937 new_full_item1938 .owner1939 .iter_mut()1940 .find(|i| i.owner == owner)1941 .unwrap()1942 .fraction -= value;19431944 // separate amount1945 if new_owner_has_account {1946 // new owner has account1947 new_full_item1948 .owner1949 .iter_mut()1950 .find(|i| i.owner == new_owner)1951 .unwrap()1952 .fraction += value;1953 } else {1954 // new owner do not have account1955 new_full_item.owner.push(Ownership {1956 owner: new_owner.clone(),1957 fraction: value,1958 });1959 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1960 }19611962 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1963 }19641965 Ok(())1966 }19671968 fn transfer_nft(1969 collection_id: CollectionId,1970 item_id: TokenId,1971 sender: T::AccountId,1972 new_owner: T::AccountId,1973 ) -> DispatchResult {1974 ensure!(1975 <NftItemList<T>>::contains_key(collection_id, item_id),1976 Error::<T>::TokenNotFound1977 );19781979 let mut item = <NftItemList<T>>::get(collection_id, item_id);19801981 ensure!(1982 sender == item.owner,1983 Error::<T>::MustBeTokenOwner1984 );19851986 // update balance1987 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1988 .checked_sub(1)1989 .ok_or(Error::<T>::NumOverflow)?;1990 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19911992 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1993 .checked_add(1)1994 .ok_or(Error::<T>::NumOverflow)?;1995 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19961997 // change owner1998 let old_owner = item.owner.clone();1999 item.owner = new_owner.clone();2000 <NftItemList<T>>::insert(collection_id, item_id, item);20012002 // update index collection2003 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20042005 // reset approved list2006 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2007 Ok(())2008 }2009 2010 fn item_exists(2011 collection_id: CollectionId,2012 item_id: TokenId,2013 mode: &CollectionMode2014 ) -> DispatchResult {2015 match mode {2016 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2017 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2018 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2019 _ => ()2020 };2021 2022 Ok(())2023 }20242025 fn set_re_fungible_variable_data(2026 collection_id: CollectionId,2027 item_id: TokenId,2028 data: Vec<u8>2029 ) -> DispatchResult {2030 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20312032 item.variable_data = data;20332034 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20352036 Ok(())2037 }20382039 fn set_nft_variable_data(2040 collection_id: CollectionId,2041 item_id: TokenId,2042 data: Vec<u8>2043 ) -> DispatchResult {2044 let mut item = <NftItemList<T>>::get(collection_id, item_id);2045 2046 item.variable_data = data;20472048 <NftItemList<T>>::insert(collection_id, item_id, item);2049 2050 Ok(())2051 }20522053 fn init_collection(item: &CollectionType<T::AccountId>) {2054 // check params2055 assert!(2056 item.decimal_points <= MAX_DECIMAL_POINTS,2057 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2058 );2059 assert!(2060 item.name.len() <= 64,2061 "Collection name can not be longer than 63 char"2062 );2063 assert!(2064 item.name.len() <= 256,2065 "Collection description can not be longer than 255 char"2066 );2067 assert!(2068 item.token_prefix.len() <= 16,2069 "Token prefix can not be longer than 15 char"2070 );20712072 // Generate next collection ID2073 let next_id = CreatedCollectionCount::get()2074 .checked_add(1)2075 .unwrap();20762077 CreatedCollectionCount::put(next_id);2078 }20792080 fn init_nft_token(item: &NftItemType<T::AccountId>) {2081 let current_index = <ItemListIndex>::get(item.collection)2082 .checked_add(1)2083 .unwrap();20842085 let item_owner = item.owner.clone();2086 let collection_id = item.collection.clone();2087 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20882089 <ItemListIndex>::insert(collection_id, current_index);20902091 // Update balance2092 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2093 .checked_add(1)2094 .unwrap();2095 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2096 }20972098 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2099 let current_index = <ItemListIndex>::get(item.collection)2100 .checked_add(1)2101 .unwrap();2102 let owner = item.owner.clone();21032104 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21052106 <ItemListIndex>::insert(item.collection, current_index);21072108 // Update balance2109 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2110 .checked_add(item.value)2111 .unwrap();2112 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2113 }21142115 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2116 let current_index = <ItemListIndex>::get(item.collection)2117 .checked_add(1)2118 .unwrap();21192120 let value = item.owner.first().unwrap().fraction;2121 let owner = item.owner.first().unwrap().owner.clone();21222123 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21242125 <ItemListIndex>::insert(item.collection, current_index);21262127 // Update balance2128 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2129 .checked_add(value)2130 .unwrap();2131 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2132 }21332134 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21352136 // add to account limit2137 if <AccountItemCount<T>>::contains_key(owner.clone()) {21382139 // bound Owned tokens by a single address2140 let count = <AccountItemCount<T>>::get(owner.clone());2141 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21422143 <AccountItemCount<T>>::insert(owner.clone(), count2144 .checked_add(1)2145 .ok_or(Error::<T>::NumOverflow)?);2146 }2147 else {2148 <AccountItemCount<T>>::insert(owner.clone(), 1);2149 }21502151 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2152 if list_exists {2153 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2154 let item_contains = list.contains(&item_index.clone());21552156 if !item_contains {2157 list.push(item_index.clone());2158 }21592160 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2161 } else {2162 let mut itm = Vec::new();2163 itm.push(item_index.clone());2164 <AddressTokens<T>>::insert(collection_id, owner, itm);2165 2166 }21672168 Ok(())2169 }21702171 fn remove_token_index(2172 collection_id: CollectionId,2173 item_index: TokenId,2174 owner: T::AccountId,2175 ) -> DispatchResult {21762177 // update counter2178 <AccountItemCount<T>>::insert(owner.clone(), 2179 <AccountItemCount<T>>::get(owner.clone())2180 .checked_sub(1)2181 .ok_or(Error::<T>::NumOverflow)?);218221832184 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2185 if list_exists {2186 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2187 let item_contains = list.contains(&item_index.clone());21882189 if item_contains {2190 list.retain(|&item| item != item_index);2191 <AddressTokens<T>>::insert(collection_id, owner, list);2192 }2193 }21942195 Ok(())2196 }21972198 fn move_token_index(2199 collection_id: CollectionId,2200 item_index: TokenId,2201 old_owner: T::AccountId,2202 new_owner: T::AccountId,2203 ) -> DispatchResult {2204 Self::remove_token_index(collection_id, item_index, old_owner)?;2205 Self::add_token_index(collection_id, item_index, new_owner)?;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 withdraw_fee(2267 &self,2268 who: &T::AccountId,2269 call: &T::Call,2270 info: &DispatchInfoOf<T::Call>,2271 len: usize,2272 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2273 let tip = self.0;22742275 // Set fee based on call type. Creating collection costs 1 Unique.2276 // All other transactions have traditional fees so far2277 // let fee = match call.is_sub_type() {2278 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2279 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2280 // // _ => <BalanceOf<T>>::from(100)2281 // };2282 let fee = Self::traditional_fee(len, info, tip);22832284 // Determine who is paying transaction fee based on ecnomic model2285 // Parse call to extract collection ID and access collection sponsor2286 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2287 Some(Call::create_item(collection_id, _owner, _properties)) => {22882289 // check free create limit2290 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2291 {2292 <Collection<T>>::get(collection_id).sponsor2293 } else {2294 T::AccountId::default()2295 }2296 }2297 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2298 2299 let _collection_limits = <Collection<T>>::get(collection_id).limits;2300 let _collection_mode = <Collection<T>>::get(collection_id).mode;23012302 // sponsor timeout2303 let sponsor_transfer = match _collection_mode {2304 CollectionMode::NFT => {23052306 // get correct limit2307 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2308 _collection_limits.sponsor_transfer_timeout2309 } else {2310 ChainLimit::get().nft_sponsor_transfer_timeout2311 };23122313 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2314 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2315 let limit_time = basket + limit.into();2316 if block_number >= limit_time {2317 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2318 true2319 }2320 else {2321 false2322 }2323 }2324 CollectionMode::Fungible(_) => {23252326 // get correct limit2327 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2328 _collection_limits.sponsor_transfer_timeout2329 } else {2330 ChainLimit::get().fungible_sponsor_transfer_timeout2331 };23322333 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2334 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2335 if basket.iter().any(|i| i.address == _new_owner.clone())2336 {2337 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2338 let limit_time = item.start_block + limit.into();2339 if block_number >= limit_time {2340 basket.retain(|x| x.address == item.address);2341 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2342 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2343 true2344 }2345 else {2346 false2347 }2348 }2349 else {2350 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2351 true2352 }2353 }2354 CollectionMode::ReFungible(_) => {23552356 // get correct limit2357 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2358 _collection_limits.sponsor_transfer_timeout2359 } else {2360 ChainLimit::get().refungible_sponsor_transfer_timeout2361 };23622363 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2364 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2365 let limit_time = basket + limit.into();2366 if block_number >= limit_time {2367 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2368 true2369 } else {2370 false2371 }2372 }2373 _ => {2374 false2375 },2376 };23772378 if !sponsor_transfer {2379 T::AccountId::default()2380 } else {2381 <Collection<T>>::get(collection_id).sponsor2382 }2383 }23842385 _ => T::AccountId::default(),2386 };23872388 // Sponsor smart contracts2389 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {23902391 // On instantiation: set the contract owner2392 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {23932394 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2395 code_hash,2396 &data,2397 &who,2398 );2399 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24002401 T::AccountId::default()2402 },24032404 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2405 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24062407 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24082409 let mut sponsor_transfer = false;2410 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2411 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2412 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2413 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2414 let limit_time = last_tx_block + rate_limit;24152416 if block_number >= limit_time {2417 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2418 sponsor_transfer = true;2419 }2420 } else {2421 sponsor_transfer = false;2422 }2423 2424 2425 let mut sp = T::AccountId::default();2426 if sponsor_transfer {2427 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2428 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2429 sp = called_contract;2430 }2431 }2432 }24332434 sp2435 },24362437 _ => sponsor,2438 };24392440 let mut who_pays_fee: T::AccountId = sponsor.clone();2441 if sponsor == T::AccountId::default() {2442 who_pays_fee = who.clone();2443 }24442445 // Only mess with balances if fee is not zero.2446 if fee.is_zero() {2447 return Ok((fee, None));2448 }24492450 match <T as transaction_payment::Trait>::Currency::withdraw(2451 &who_pays_fee,2452 fee,2453 if tip.is_zero() {2454 WithdrawReason::TransactionPayment.into()2455 } else {2456 WithdrawReason::TransactionPayment | WithdrawReason::Tip2457 },2458 ExistenceRequirement::KeepAlive,2459 ) {2460 Ok(imbalance) => Ok((fee, Some(imbalance))),2461 Err(_) => Err(InvalidTransaction::Payment.into()),2462 }2463 }2464}246524662467impl<T: Trait + Send + Sync> SignedExtension2468 for ChargeTransactionPayment<T>2469where2470 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2471 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2472{2473 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2474 type AccountId = T::AccountId;2475 type Call = T::Call;2476 type AdditionalSigned = ();2477 type Pre = (2478 BalanceOf<T>,2479 Self::AccountId,2480 Option<NegativeImbalanceOf<T>>,2481 BalanceOf<T>,2482 );2483 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2484 Ok(())2485 }24862487 fn validate(2488 &self,2489 _who: &Self::AccountId,2490 _call: &Self::Call,2491 _info: &DispatchInfoOf<Self::Call>,2492 _len: usize,2493 ) -> TransactionValidity {2494 Ok(ValidTransaction::default())2495 }24962497 fn pre_dispatch(2498 self,2499 who: &Self::AccountId,2500 call: &Self::Call,2501 info: &DispatchInfoOf<Self::Call>,2502 len: usize,2503 ) -> Result<Self::Pre, TransactionValidityError> {2504 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2505 Ok((self.0, who.clone(), imbalance, fee))2506 }25072508 fn post_dispatch(2509 pre: Self::Pre,2510 info: &DispatchInfoOf<Self::Call>,2511 post_info: &PostDispatchInfoOf<Self::Call>,2512 len: usize,2513 _result: &DispatchResult,2514 ) -> Result<(), TransactionValidityError> {2515 let (tip, who, imbalance, fee) = pre;2516 if let Some(payed) = imbalance {2517 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2518 len as u32, info, post_info, tip,2519 );2520 let refund = fee.saturating_sub(actual_fee);2521 let actual_payment =2522 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2523 &who, refund,2524 ) {2525 Ok(refund_imbalance) => {2526 // The refund cannot be larger than the up front payed max weight.2527 // `PostDispatchInfo::calc_unspent` guards against such a case.2528 match payed.offset(refund_imbalance) {2529 Ok(actual_payment) => actual_payment,2530 Err(_) => return Err(InvalidTransaction::Payment.into()),2531 }2532 }2533 // We do not recreate the account using the refund. The up front payment2534 // is gone in that case.2535 Err(_) => payed,2536 };2537 let imbalances = actual_payment.split(tip);2538 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2539 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2540 );2541 }2542 Ok(())2543 }2544}25452546// #endregion