difftreelog
Update custom types
in: master
3 files 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: 0, 190 token_limit: 0,191 sponsored_data_size: 0, 192 sponsor_transfer_timeout: 0 }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)?;10521053 // Transfer permissions check1054 let target_collection = <Collection<T>>::get(collection_id);1055 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1056 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1057 Error::<T>::NoPermission);10581059 if target_collection.access == AccessMode::WhiteList {1060 Self::check_white_list(collection_id, &sender)?;1061 Self::check_white_list(collection_id, &recipient)?;1062 }10631064 match target_collection.mode1065 {1066 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1067 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1068 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1069 _ => ()1070 };10711072 Ok(())1073 }10741075 /// Set, change, or remove approved address to transfer the ownership of the NFT.1076 /// 1077 /// # Permissions1078 /// 1079 /// * Collection Owner1080 /// * Collection Admin1081 /// * Current NFT owner1082 /// 1083 /// # Arguments1084 /// 1085 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1086 /// 1087 /// * collection_id.1088 /// 1089 /// * item_id: ID of the item.1090 #[weight = T::WeightInfo::approve()]1091 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10921093 let sender = ensure_signed(origin)?;10941095 // Transfer permissions check1096 let target_collection = <Collection<T>>::get(collection_id);1097 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1098 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1099 Error::<T>::NoPermission);11001101 if target_collection.access == AccessMode::WhiteList {1102 Self::check_white_list(collection_id, &sender)?;1103 Self::check_white_list(collection_id, &approved)?;1104 }11051106 // amount param stub1107 let amount = 100000000;11081109 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1110 if list_exists {11111112 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1113 let item_contains = list.iter().any(|i| i.approved == approved);11141115 if !item_contains {1116 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1117 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1118 }1119 } else {11201121 let mut list = Vec::new();1122 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1123 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1124 }11251126 Ok(())1127 }1128 1129 /// 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.1130 /// 1131 /// # Permissions1132 /// * Collection Owner1133 /// * Collection Admin1134 /// * Current NFT owner1135 /// * Address approved by current NFT owner1136 /// 1137 /// # Arguments1138 /// 1139 /// * from: Address that owns token.1140 /// 1141 /// * recipient: Address of token recipient.1142 /// 1143 /// * collection_id.1144 /// 1145 /// * item_id: ID of the item.1146 /// 1147 /// * value: Amount to transfer.1148 #[weight = T::WeightInfo::transfer_from()]1149 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11501151 let sender = ensure_signed(origin)?;1152 let mut appoved_transfer = false;11531154 // Check approve1155 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1156 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1157 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1158 if opt_item.is_some()1159 {1160 appoved_transfer = true;1161 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1162 }1163 }11641165 // Transfer permissions check1166 let target_collection = <Collection<T>>::get(collection_id);1167 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1168 Error::<T>::NoPermission);11691170 if target_collection.access == AccessMode::WhiteList {1171 Self::check_white_list(collection_id, &sender)?;1172 Self::check_white_list(collection_id, &recipient)?;1173 }11741175 // remove approve1176 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1177 .into_iter().filter(|i| i.approved != sender.clone()).collect();1178 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);117911801181 match target_collection.mode1182 {1183 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1184 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1185 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1186 _ => ()1187 };11881189 Ok(())1190 }11911192 ///1193 #[weight = 0]1194 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11951196 // let no_perm_mes = "You do not have permissions to modify this collection";1197 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1198 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1199 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12001201 // // on_nft_received call12021203 // Self::transfer(origin, collection_id, item_id, new_owner)?;12041205 Ok(())1206 }12071208 /// Set off-chain data schema.1209 /// 1210 /// # Permissions1211 /// 1212 /// * Collection Owner1213 /// * Collection Admin1214 /// 1215 /// # Arguments1216 /// 1217 /// * collection_id.1218 /// 1219 /// * schema: String representing the offchain data schema.1220 #[weight = T::WeightInfo::set_variable_meta_data()]1221 pub fn set_variable_meta_data (1222 origin,1223 collection_id: CollectionId,1224 item_id: TokenId,1225 data: Vec<u8>1226 ) -> DispatchResult {1227 let sender = ensure_signed(origin)?;1228 1229 Self::collection_exists(collection_id)?;1230 1231 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12321233 // Modify permissions check1234 let target_collection = <Collection<T>>::get(collection_id);1235 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1236 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1237 Error::<T>::NoPermission);12381239 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12401241 match target_collection.mode1242 {1243 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1244 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1245 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1246 _ => fail!(Error::<T>::UnexpectedCollectionType)1247 };12481249 Ok(())1250 }1251 12521253 /// Set off-chain data schema.1254 /// 1255 /// # Permissions1256 /// 1257 /// * Collection Owner1258 /// * Collection Admin1259 /// 1260 /// # Arguments1261 /// 1262 /// * collection_id.1263 /// 1264 /// * schema: String representing the offchain data schema.1265 #[weight = T::WeightInfo::set_offchain_schema()]1266 pub fn set_offchain_schema(1267 origin,1268 collection_id: CollectionId,1269 schema: Vec<u8>1270 ) -> DispatchResult {1271 let sender = ensure_signed(origin)?;1272 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12731274 let mut target_collection = <Collection<T>>::get(collection_id);1275 target_collection.offchain_schema = schema;1276 <Collection<T>>::insert(collection_id, target_collection);12771278 Ok(())1279 }12801281 /// Set const on-chain data schema.1282 /// 1283 /// # Permissions1284 /// 1285 /// * Collection Owner1286 /// * Collection Admin1287 /// 1288 /// # Arguments1289 /// 1290 /// * collection_id.1291 /// 1292 /// * schema: String representing the const on-chain data schema.1293 #[weight = T::WeightInfo::set_const_on_chain_schema()]1294 pub fn set_const_on_chain_schema (1295 origin,1296 collection_id: CollectionId,1297 schema: Vec<u8>1298 ) -> DispatchResult {1299 let sender = ensure_signed(origin)?;1300 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13011302 let mut target_collection = <Collection<T>>::get(collection_id);1303 target_collection.const_on_chain_schema = schema;1304 <Collection<T>>::insert(collection_id, target_collection);13051306 Ok(())1307 }13081309 /// Set variable on-chain data schema.1310 /// 1311 /// # Permissions1312 /// 1313 /// * Collection Owner1314 /// * Collection Admin1315 /// 1316 /// # Arguments1317 /// 1318 /// * collection_id.1319 /// 1320 /// * schema: String representing the variable on-chain data schema.1321 #[weight = T::WeightInfo::set_const_on_chain_schema()]1322 pub fn set_variable_on_chain_schema (1323 origin,1324 collection_id: CollectionId,1325 schema: Vec<u8>1326 ) -> DispatchResult {1327 let sender = ensure_signed(origin)?;1328 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13291330 let mut target_collection = <Collection<T>>::get(collection_id);1331 target_collection.variable_on_chain_schema = schema;1332 <Collection<T>>::insert(collection_id, target_collection);13331334 Ok(())1335 }13361337 // Sudo permissions function1338 #[weight = 0]1339 pub fn set_chain_limits(1340 origin,1341 limits: ChainLimits1342 ) -> DispatchResult {1343 ensure_root(origin)?;1344 <ChainLimit>::put(limits);1345 Ok(())1346 }13471348 /// Enable smart contract self-sponsoring.1349 /// 1350 /// # Permissions1351 /// 1352 /// * Contract Owner1353 /// 1354 /// # Arguments1355 /// 1356 /// * contract address1357 /// * enable flag1358 /// 1359 #[weight = T::WeightInfo::enable_contract_sponsoring()]1360 pub fn enable_contract_sponsoring(1361 origin,1362 contract_address: T::AccountId,1363 enable: bool1364 ) -> DispatchResult {13651366 let sender = ensure_signed(origin)?;13671368 #[cfg(feature = "runtime-benchmarks")]1369 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13701371 let mut is_owner = false;1372 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1373 let owner = <ContractOwner<T>>::get(&contract_address);1374 is_owner = sender == owner;1375 }1376 ensure!(is_owner, Error::<T>::NoPermission);13771378 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1379 Ok(())1380 }13811382 /// Set the rate limit for contract sponsoring to specified number of blocks.1383 /// 1384 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1385 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1386 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1387 /// from contract endowment if there are at least B blocks between such transactions. 1388 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1389 /// 1390 /// # Permissions1391 /// 1392 /// * Contract Owner1393 /// 1394 /// # Arguments1395 /// 1396 /// -`contract_address`: Address of the contract to sponsor1397 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1398 /// 1399 #[weight = 0]1400 pub fn set_contract_sponsoring_rate_limit(1401 origin,1402 contract_address: T::AccountId,1403 rate_limit: T::BlockNumber1404 ) -> DispatchResult {1405 let sender = ensure_signed(origin)?;1406 let mut is_owner = false;1407 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1408 let owner = <ContractOwner<T>>::get(&contract_address);1409 is_owner = sender == owner;1410 }1411 ensure!(is_owner, Error::<T>::NoPermission);14121413 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1414 Ok(())1415 }14161417 #[weight = 0]1418 pub fn set_collection_limits(1419 origin,1420 collection_id: u64,1421 limits: CollectionLimits,1422 ) -> DispatchResult {1423 let sender = ensure_signed(origin)?;1424 Self::check_owner_permissions(collection_id, sender.clone())?;14251426 let mut target_collection = <Collection<T>>::get(collection_id);1427 target_collection.limits = limits;1428 <Collection<T>>::insert(collection_id, target_collection);14291430 Ok(())1431 } 1432 }1433}14341435impl<T: Trait> Module<T> {14361437 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {14381439 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {14401441 // check token limit and account token limit1442 let total_items: u64 = ItemListIndex::get(collection_id);1443 let account_items: u32 = <AddressTokens<T>>::get(collection_id, sender.clone()).len() as u32;1444 ensure!(collection.limits.token_limit as u64 > total_items, Error::<T>::CollectionTokenLimitExceeded);1445 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1446 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1447 Self::check_white_list(collection_id, owner)?;1448 Self::check_white_list(collection_id, sender)?;1449 }14501451 Ok(())1452 }14531454 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1455 match target_collection.mode1456 {1457 CollectionMode::NFT => {1458 if let CreateItemData::NFT(data) = data {1459 // check sizes1460 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1461 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1462 } else {1463 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1464 }1465 },1466 CollectionMode::Fungible(_) => {1467 if let CreateItemData::Fungible(_) = data {1468 } else {1469 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1470 }1471 },1472 CollectionMode::ReFungible(_) => {1473 if let CreateItemData::ReFungible(data) = data {14741475 // check sizes1476 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1477 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1478 } else {1479 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1480 }1481 },1482 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1483 };14841485 Ok(())1486 }14871488 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1489 match data1490 {1491 CreateItemData::NFT(data) => {1492 let item = NftItemType {1493 collection: collection_id,1494 owner,1495 const_data: data.const_data,1496 variable_data: data.variable_data1497 };14981499 Self::add_nft_item(item)?;1500 },1501 CreateItemData::Fungible(_) => {1502 let item = FungibleItemType {1503 collection: collection_id,1504 owner,1505 value: (10 as u128).pow(collection.decimal_points as u32)1506 };15071508 Self::add_fungible_item(item)?;1509 },1510 CreateItemData::ReFungible(data) => {1511 let mut owner_list = Vec::new();1512 let value = (10 as u128).pow(collection.decimal_points as u32);1513 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15141515 let item = ReFungibleItemType {1516 collection: collection_id,1517 owner: owner_list,1518 const_data: data.const_data,1519 variable_data: data.variable_data1520 };15211522 Self::add_refungible_item(item)?;1523 }1524 };15251526 // call event1527 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));15281529 Ok(())1530 }15311532 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1533 let current_index = <ItemListIndex>::get(item.collection)1534 .checked_add(1)1535 .ok_or(Error::<T>::NumOverflow)?;1536 let itemcopy = item.clone();1537 let owner = item.owner.clone();15381539 Self::add_token_index(item.collection, current_index, owner.clone())?;15401541 <ItemListIndex>::insert(item.collection, current_index);1542 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15431544 // Add current block1545 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1546 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1547 1548 // Update balance1549 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1550 .checked_add(item.value)1551 .ok_or(Error::<T>::NumOverflow)?;1552 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15531554 Ok(())1555 }15561557 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1558 let current_index = <ItemListIndex>::get(item.collection)1559 .checked_add(1)1560 .ok_or(Error::<T>::NumOverflow)?;1561 let itemcopy = item.clone();15621563 let value = item.owner.first().unwrap().fraction;1564 let owner = item.owner.first().unwrap().owner.clone();15651566 Self::add_token_index(item.collection, current_index, owner.clone())?;15671568 <ItemListIndex>::insert(item.collection, current_index);1569 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);15701571 // Add current block1572 let block_number: T::BlockNumber = 0.into();1573 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);15741575 // Update balance1576 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1577 .checked_add(value)1578 .ok_or(Error::<T>::NumOverflow)?;1579 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15801581 Ok(())1582 }15831584 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1585 let current_index = <ItemListIndex>::get(item.collection)1586 .checked_add(1)1587 .ok_or(Error::<T>::NumOverflow)?;15881589 let item_owner = item.owner.clone();1590 let collection_id = item.collection.clone();1591 Self::add_token_index(collection_id, current_index, item.owner.clone())?;15921593 <ItemListIndex>::insert(collection_id, current_index);1594 <NftItemList<T>>::insert(collection_id, current_index, item);15951596 // Add current block1597 let block_number: T::BlockNumber = 0.into();1598 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);15991600 // Update balance1601 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1602 .checked_add(1)1603 .ok_or(Error::<T>::NumOverflow)?;1604 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16051606 Ok(())1607 }16081609 fn burn_refungible_item(1610 collection_id: CollectionId,1611 item_id: TokenId,1612 owner: T::AccountId,1613 ) -> DispatchResult {1614 ensure!(1615 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1616 Error::<T>::TokenNotFound1617 );1618 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1619 let item = collection1620 .owner1621 .iter()1622 .filter(|&i| i.owner == owner)1623 .next()1624 .unwrap();1625 Self::remove_token_index(collection_id, item_id, owner.clone())?;16261627 // remove approve list1628 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));16291630 // update balance1631 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1632 .checked_sub(item.fraction)1633 .ok_or(Error::<T>::NumOverflow)?;1634 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16351636 <ReFungibleItemList<T>>::remove(collection_id, item_id);16371638 Ok(())1639 }16401641 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1642 ensure!(1643 <NftItemList<T>>::contains_key(collection_id, item_id),1644 Error::<T>::TokenNotFound1645 );1646 let item = <NftItemList<T>>::get(collection_id, item_id);1647 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16481649 // remove approve list1650 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16511652 // update balance1653 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1654 .checked_sub(1)1655 .ok_or(Error::<T>::NumOverflow)?;1656 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1657 <NftItemList<T>>::remove(collection_id, item_id);16581659 Ok(())1660 }16611662 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1663 ensure!(1664 <FungibleItemList<T>>::contains_key(collection_id, item_id),1665 Error::<T>::TokenNotFound1666 );1667 let item = <FungibleItemList<T>>::get(collection_id, item_id);1668 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16691670 // remove approve list1671 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));16721673 // update balance1674 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1675 .checked_sub(item.value)1676 .ok_or(Error::<T>::NumOverflow)?;1677 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16781679 <FungibleItemList<T>>::remove(collection_id, item_id);16801681 Ok(())1682 }16831684 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1685 ensure!(1686 <Collection<T>>::contains_key(collection_id),1687 Error::<T>::CollectionNotFound1688 );1689 Ok(())1690 }16911692 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1693 Self::collection_exists(collection_id)?;16941695 let target_collection = <Collection<T>>::get(collection_id);1696 ensure!(1697 subject == target_collection.owner,1698 Error::<T>::NoPermission1699 );17001701 Ok(())1702 }17031704 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1705 let target_collection = <Collection<T>>::get(collection_id);1706 let mut result: bool = subject == target_collection.owner;1707 let exists = <AdminList<T>>::contains_key(collection_id);17081709 if !result & exists {1710 if <AdminList<T>>::get(collection_id).contains(&subject) {1711 result = true1712 }1713 }17141715 result1716 }17171718 fn check_owner_or_admin_permissions(1719 collection_id: CollectionId,1720 subject: T::AccountId,1721 ) -> DispatchResult {1722 Self::collection_exists(collection_id)?;1723 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17241725 ensure!(1726 result,1727 Error::<T>::NoPermission1728 );1729 Ok(())1730 }17311732 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1733 let target_collection = <Collection<T>>::get(collection_id);17341735 match target_collection.mode {1736 CollectionMode::NFT => {1737 <NftItemList<T>>::get(collection_id, item_id).owner == subject1738 }1739 CollectionMode::Fungible(_) => {1740 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1741 }1742 CollectionMode::ReFungible(_) => {1743 <ReFungibleItemList<T>>::get(collection_id, item_id)1744 .owner1745 .iter()1746 .any(|i| i.owner == subject)1747 }1748 CollectionMode::Invalid => false,1749 }1750 }17511752 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1753 let mes = Error::<T>::AddresNotInWhiteList;1754 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1755 let wl = <WhiteList<T>>::get(collection_id);1756 ensure!(wl.contains(address), mes);17571758 Ok(())1759 }17601761 fn transfer_fungible(1762 collection_id: CollectionId,1763 item_id: TokenId,1764 value: u128,1765 owner: T::AccountId,1766 new_owner: T::AccountId,1767 ) -> DispatchResult {1768 ensure!(1769 <FungibleItemList<T>>::contains_key(collection_id, item_id),1770 Error::<T>::TokenNotFound1771 );17721773 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1774 let amount = full_item.value;17751776 ensure!(amount >= value, Error::<T>::TokenValueTooLow);17771778 // update balance1779 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1780 .checked_sub(value)1781 .ok_or(Error::<T>::NumOverflow)?;1782 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);17831784 let mut new_owner_account_id = 0;1785 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1786 if new_owner_items.len() > 0 {1787 new_owner_account_id = new_owner_items[0];1788 }17891790 // transfer1791 if amount == value && new_owner_account_id == 0 {1792 // change owner1793 // new owner do not have account1794 let mut new_full_item = full_item.clone();1795 new_full_item.owner = new_owner.clone();1796 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17971798 // update balance1799 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1800 .checked_add(value)1801 .ok_or(Error::<T>::NumOverflow)?;1802 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18031804 // update index collection1805 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1806 } else {1807 let mut new_full_item = full_item.clone();1808 new_full_item.value -= value;18091810 // separate amount1811 if new_owner_account_id > 0 {1812 // new owner has account1813 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1814 item.value += value;18151816 // update balance1817 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1818 .checked_add(value)1819 .ok_or(Error::<T>::NumOverflow)?;1820 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18211822 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1823 } else {1824 // new owner do not have account1825 let item = FungibleItemType {1826 collection: collection_id,1827 owner: new_owner.clone(),1828 value1829 };18301831 Self::add_fungible_item(item)?;1832 }18331834 if amount == value {1835 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;18361837 // remove approve list1838 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1839 <FungibleItemList<T>>::remove(collection_id, item_id);1840 }18411842 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1843 }18441845 Ok(())1846 }18471848 fn transfer_refungible(1849 collection_id: CollectionId,1850 item_id: TokenId,1851 value: u128,1852 owner: T::AccountId,1853 new_owner: T::AccountId,1854 ) -> DispatchResult {1855 ensure!(1856 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1857 Error::<T>::TokenNotFound1858 );18591860 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1861 let item = full_item1862 .owner1863 .iter()1864 .filter(|i| i.owner == owner)1865 .next()1866 .ok_or(Error::<T>::NumOverflow)?;1867 let amount = item.fraction;18681869 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18701871 // update balance1872 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1873 .checked_sub(value)1874 .ok_or(Error::<T>::NumOverflow)?;1875 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18761877 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1878 .checked_add(value)1879 .ok_or(Error::<T>::NumOverflow)?;1880 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18811882 let old_owner = item.owner.clone();1883 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);18841885 // transfer1886 if amount == value && !new_owner_has_account {1887 // change owner1888 // new owner do not have account1889 let mut new_full_item = full_item.clone();1890 new_full_item1891 .owner1892 .iter_mut()1893 .find(|i| i.owner == owner)1894 .unwrap()1895 .owner = new_owner.clone();1896 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18971898 // update index collection1899 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1900 } else {1901 let mut new_full_item = full_item.clone();1902 new_full_item1903 .owner1904 .iter_mut()1905 .find(|i| i.owner == owner)1906 .unwrap()1907 .fraction -= value;19081909 // separate amount1910 if new_owner_has_account {1911 // new owner has account1912 new_full_item1913 .owner1914 .iter_mut()1915 .find(|i| i.owner == new_owner)1916 .unwrap()1917 .fraction += value;1918 } else {1919 // new owner do not have account1920 new_full_item.owner.push(Ownership {1921 owner: new_owner.clone(),1922 fraction: value,1923 });1924 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1925 }19261927 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1928 }19291930 Ok(())1931 }19321933 fn transfer_nft(1934 collection_id: CollectionId,1935 item_id: TokenId,1936 sender: T::AccountId,1937 new_owner: T::AccountId,1938 ) -> DispatchResult {1939 ensure!(1940 <NftItemList<T>>::contains_key(collection_id, item_id),1941 Error::<T>::TokenNotFound1942 );19431944 let mut item = <NftItemList<T>>::get(collection_id, item_id);19451946 ensure!(1947 sender == item.owner,1948 Error::<T>::MustBeTokenOwner1949 );19501951 // update balance1952 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1953 .checked_sub(1)1954 .ok_or(Error::<T>::NumOverflow)?;1955 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19561957 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1958 .checked_add(1)1959 .ok_or(Error::<T>::NumOverflow)?;1960 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19611962 // change owner1963 let old_owner = item.owner.clone();1964 item.owner = new_owner.clone();1965 <NftItemList<T>>::insert(collection_id, item_id, item);19661967 // update index collection1968 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19691970 // reset approved list1971 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1972 Ok(())1973 }1974 1975 fn item_exists(1976 collection_id: CollectionId,1977 item_id: TokenId,1978 mode: &CollectionMode1979 ) -> DispatchResult {1980 match mode {1981 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1982 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1983 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1984 _ => ()1985 };1986 1987 Ok(())1988 }19891990 fn set_re_fungible_variable_data(1991 collection_id: CollectionId,1992 item_id: TokenId,1993 data: Vec<u8>1994 ) -> DispatchResult {1995 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19961997 item.variable_data = data;19981999 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20002001 Ok(())2002 }20032004 fn set_nft_variable_data(2005 collection_id: CollectionId,2006 item_id: TokenId,2007 data: Vec<u8>2008 ) -> DispatchResult {2009 let mut item = <NftItemList<T>>::get(collection_id, item_id);2010 2011 item.variable_data = data;20122013 <NftItemList<T>>::insert(collection_id, item_id, item);2014 2015 Ok(())2016 }20172018 fn init_collection(item: &CollectionType<T::AccountId>) {2019 // check params2020 assert!(2021 item.decimal_points <= MAX_DECIMAL_POINTS,2022 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2023 );2024 assert!(2025 item.name.len() <= 64,2026 "Collection name can not be longer than 63 char"2027 );2028 assert!(2029 item.name.len() <= 256,2030 "Collection description can not be longer than 255 char"2031 );2032 assert!(2033 item.token_prefix.len() <= 16,2034 "Token prefix can not be longer than 15 char"2035 );20362037 // Generate next collection ID2038 let next_id = CreatedCollectionCount::get()2039 .checked_add(1)2040 .unwrap();20412042 CreatedCollectionCount::put(next_id);2043 }20442045 fn init_nft_token(item: &NftItemType<T::AccountId>) {2046 let current_index = <ItemListIndex>::get(item.collection)2047 .checked_add(1)2048 .unwrap();20492050 let item_owner = item.owner.clone();2051 let collection_id = item.collection.clone();2052 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20532054 <ItemListIndex>::insert(collection_id, current_index);20552056 // Update balance2057 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2058 .checked_add(1)2059 .unwrap();2060 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2061 }20622063 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2064 let current_index = <ItemListIndex>::get(item.collection)2065 .checked_add(1)2066 .unwrap();2067 let owner = item.owner.clone();20682069 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20702071 <ItemListIndex>::insert(item.collection, current_index);20722073 // Update balance2074 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2075 .checked_add(item.value)2076 .unwrap();2077 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2078 }20792080 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2081 let current_index = <ItemListIndex>::get(item.collection)2082 .checked_add(1)2083 .unwrap();20842085 let value = item.owner.first().unwrap().fraction;2086 let owner = item.owner.first().unwrap().owner.clone();20872088 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20892090 <ItemListIndex>::insert(item.collection, current_index);20912092 // Update balance2093 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2094 .checked_add(value)2095 .unwrap();2096 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2097 }20982099 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21002101 // add to account limit2102 if <AccountItemCount<T>>::contains_key(owner.clone()) {21032104 // bound Owned tokens by a single address2105 let count = <AccountItemCount<T>>::get(owner.clone());2106 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21072108 <AccountItemCount<T>>::insert(owner.clone(), count2109 .checked_add(1)2110 .ok_or(Error::<T>::NumOverflow)?);2111 }2112 else {2113 <AccountItemCount<T>>::insert(owner.clone(), 1);2114 }21152116 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2117 if list_exists {2118 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2119 let item_contains = list.contains(&item_index.clone());21202121 if !item_contains {2122 list.push(item_index.clone());2123 }21242125 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2126 } else {2127 let mut itm = Vec::new();2128 itm.push(item_index.clone());2129 <AddressTokens<T>>::insert(collection_id, owner, itm);2130 2131 }21322133 Ok(())2134 }21352136 fn remove_token_index(2137 collection_id: CollectionId,2138 item_index: TokenId,2139 owner: T::AccountId,2140 ) -> DispatchResult {21412142 // update counter2143 <AccountItemCount<T>>::insert(owner.clone(), 2144 <AccountItemCount<T>>::get(owner.clone())2145 .checked_sub(1)2146 .ok_or(Error::<T>::NumOverflow)?);214721482149 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2150 if list_exists {2151 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2152 let item_contains = list.contains(&item_index.clone());21532154 if item_contains {2155 list.retain(|&item| item != item_index);2156 <AddressTokens<T>>::insert(collection_id, owner, list);2157 }2158 }21592160 Ok(())2161 }21622163 fn move_token_index(2164 collection_id: CollectionId,2165 item_index: TokenId,2166 old_owner: T::AccountId,2167 new_owner: T::AccountId,2168 ) -> DispatchResult {2169 Self::remove_token_index(collection_id, item_index, old_owner)?;2170 Self::add_token_index(collection_id, item_index, new_owner)?;21712172 Ok(())2173 }2174}21752176////////////////////////////////////////////////////////////////////////////////////////////////////2177// Economic models2178// #region21792180/// Fee multiplier.2181pub type Multiplier = FixedU128;21822183type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2184 <T as system::Trait>::AccountId,2185>>::Balance;2186type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2187 <T as system::Trait>::AccountId,2188>>::NegativeImbalance;21892190/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2191/// in the queue.2192#[derive(Encode, Decode, Clone, Eq, PartialEq)]2193pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2194 #[codec(compact)] BalanceOf<T>2195);21962197impl<T: Trait + Send + Sync> sp_std::fmt::Debug2198 for ChargeTransactionPayment<T>2199{2200 #[cfg(feature = "std")]2201 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2202 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2203 }2204 #[cfg(not(feature = "std"))]2205 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2206 Ok(())2207 }2208}22092210impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2211where2212 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2213 BalanceOf<T>: Send + Sync + FixedPointOperand,2214{2215 /// utility constructor. Used only in client/factory code.2216 pub fn from(fee: BalanceOf<T>) -> Self {2217 Self(fee)2218 }22192220 pub fn traditional_fee(2221 len: usize,2222 info: &DispatchInfoOf<T::Call>,2223 tip: BalanceOf<T>,2224 ) -> BalanceOf<T>2225 where2226 T::Call: Dispatchable<Info = DispatchInfo>,2227 {2228 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2229 }22302231 fn withdraw_fee(2232 &self,2233 who: &T::AccountId,2234 call: &T::Call,2235 info: &DispatchInfoOf<T::Call>,2236 len: usize,2237 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2238 let tip = self.0;22392240 // Set fee based on call type. Creating collection costs 1 Unique.2241 // All other transactions have traditional fees so far2242 // let fee = match call.is_sub_type() {2243 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2244 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2245 // // _ => <BalanceOf<T>>::from(100)2246 // };2247 let fee = Self::traditional_fee(len, info, tip);22482249 // Determine who is paying transaction fee based on ecnomic model2250 // Parse call to extract collection ID and access collection sponsor2251 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2252 Some(Call::create_item(collection_id, _owner, _properties)) => {22532254 // check free create limit2255 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2256 {2257 <Collection<T>>::get(collection_id).sponsor2258 } else {2259 T::AccountId::default()2260 }2261 }2262 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2263 2264 let _collection_limits = <Collection<T>>::get(collection_id).limits;2265 let _collection_mode = <Collection<T>>::get(collection_id).mode;22662267 // sponsor timeout2268 let sponsor_transfer = match _collection_mode {2269 CollectionMode::NFT => {22702271 // get correct limit2272 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2273 _collection_limits.sponsor_transfer_timeout2274 } else {2275 ChainLimit::get().nft_sponsor_transfer_timeout2276 };22772278 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2279 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2280 let limit_time = basket + limit.into();2281 if block_number >= limit_time {2282 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2283 true2284 }2285 else {2286 false2287 }2288 }2289 CollectionMode::Fungible(_) => {22902291 // get correct limit2292 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2293 _collection_limits.sponsor_transfer_timeout2294 } else {2295 ChainLimit::get().fungible_sponsor_transfer_timeout2296 };22972298 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2299 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2300 if basket.iter().any(|i| i.address == _new_owner.clone())2301 {2302 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2303 let limit_time = item.start_block + limit.into();2304 if block_number >= limit_time {2305 basket.retain(|x| x.address == item.address);2306 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2307 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2308 true2309 }2310 else {2311 false2312 }2313 }2314 else {2315 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2316 true2317 }2318 }2319 CollectionMode::ReFungible(_) => {23202321 // get correct limit2322 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2323 _collection_limits.sponsor_transfer_timeout2324 } else {2325 ChainLimit::get().refungible_sponsor_transfer_timeout2326 };23272328 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2329 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2330 let limit_time = basket + limit.into();2331 if block_number >= limit_time {2332 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2333 true2334 } else {2335 false2336 }2337 }2338 _ => {2339 false2340 },2341 };23422343 if !sponsor_transfer {2344 T::AccountId::default()2345 } else {2346 <Collection<T>>::get(collection_id).sponsor2347 }2348 }23492350 _ => T::AccountId::default(),2351 };23522353 // Sponsor smart contracts2354 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {23552356 // On instantiation: set the contract owner2357 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {23582359 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2360 code_hash,2361 &data,2362 &who,2363 );2364 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());23652366 T::AccountId::default()2367 },23682369 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2370 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {23712372 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());23732374 let mut sponsor_transfer = false;2375 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2376 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2377 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2378 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2379 let limit_time = last_tx_block + rate_limit;23802381 if block_number >= limit_time {2382 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2383 sponsor_transfer = true;2384 }2385 } else {2386 sponsor_transfer = false;2387 }2388 2389 2390 let mut sp = T::AccountId::default();2391 if sponsor_transfer {2392 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2393 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2394 sp = called_contract;2395 }2396 }2397 }23982399 sp2400 },24012402 _ => sponsor,2403 };24042405 let mut who_pays_fee: T::AccountId = sponsor.clone();2406 if sponsor == T::AccountId::default() {2407 who_pays_fee = who.clone();2408 }24092410 // Only mess with balances if fee is not zero.2411 if fee.is_zero() {2412 return Ok((fee, None));2413 }24142415 match <T as transaction_payment::Trait>::Currency::withdraw(2416 &who_pays_fee,2417 fee,2418 if tip.is_zero() {2419 WithdrawReason::TransactionPayment.into()2420 } else {2421 WithdrawReason::TransactionPayment | WithdrawReason::Tip2422 },2423 ExistenceRequirement::KeepAlive,2424 ) {2425 Ok(imbalance) => Ok((fee, Some(imbalance))),2426 Err(_) => Err(InvalidTransaction::Payment.into()),2427 }2428 }2429}243024312432impl<T: Trait + Send + Sync> SignedExtension2433 for ChargeTransactionPayment<T>2434where2435 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2436 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2437{2438 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2439 type AccountId = T::AccountId;2440 type Call = T::Call;2441 type AdditionalSigned = ();2442 type Pre = (2443 BalanceOf<T>,2444 Self::AccountId,2445 Option<NegativeImbalanceOf<T>>,2446 BalanceOf<T>,2447 );2448 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2449 Ok(())2450 }24512452 fn validate(2453 &self,2454 _who: &Self::AccountId,2455 _call: &Self::Call,2456 _info: &DispatchInfoOf<Self::Call>,2457 _len: usize,2458 ) -> TransactionValidity {2459 Ok(ValidTransaction::default())2460 }24612462 fn pre_dispatch(2463 self,2464 who: &Self::AccountId,2465 call: &Self::Call,2466 info: &DispatchInfoOf<Self::Call>,2467 len: usize,2468 ) -> Result<Self::Pre, TransactionValidityError> {2469 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2470 Ok((self.0, who.clone(), imbalance, fee))2471 }24722473 fn post_dispatch(2474 pre: Self::Pre,2475 info: &DispatchInfoOf<Self::Call>,2476 post_info: &PostDispatchInfoOf<Self::Call>,2477 len: usize,2478 _result: &DispatchResult,2479 ) -> Result<(), TransactionValidityError> {2480 let (tip, who, imbalance, fee) = pre;2481 if let Some(payed) = imbalance {2482 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2483 len as u32, info, post_info, tip,2484 );2485 let refund = fee.saturating_sub(actual_fee);2486 let actual_payment =2487 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2488 &who, refund,2489 ) {2490 Ok(refund_imbalance) => {2491 // The refund cannot be larger than the up front payed max weight.2492 // `PostDispatchInfo::calc_unspent` guards against such a case.2493 match payed.offset(refund_imbalance) {2494 Ok(actual_payment) => actual_payment,2495 Err(_) => return Err(InvalidTransaction::Payment.into()),2496 }2497 }2498 // We do not recreate the account using the refund. The up front payment2499 // is gone in that case.2500 Err(_) => payed,2501 };2502 let imbalances = actual_payment.split(tip);2503 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2504 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2505 );2506 }2507 Ok(())2508 }2509}25102511// #endregionpallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -3,7 +3,7 @@
use crate::mock::*;
use crate::{AccessMode, ApprovePermissions, CollectionMode,
Ownership, ChainLimits, CreateItemData, CreateNftData, CreateFungibleData, CreateReFungibleData,
- CollectionId, TokenId, MAX_DECIMAL_POINTS}; //Err
+ CollectionId, TokenId, MAX_DECIMAL_POINTS};
use frame_support::{assert_noop, assert_ok};
use frame_system::{ RawOrigin };
runtime_types.jsondiffbeforeafterboth--- a/runtime_types.json
+++ b/runtime_types.json
@@ -46,20 +46,11 @@
"Owner": "AccountId",
"Value": "u128"
},
- "ReFungibleItemType": {
- "Collection": "CollectionId",
- "Owner": "Vec<Ownership>",
- "Data": "Vec<u8>"
- },
"NftItemType": {
"Collection": "CollectionId",
"Owner": "AccountId",
"ConstData": "Vec<u8>",
"VariableData": "Vec<u8>"
- },
- "Ownership": {
- "owner": "AccountId",
- "fraction": "u128"
},
"ReFungibleItemType": {
"Collection": "CollectionId",
@@ -79,6 +70,7 @@
"OffchainSchema": "Vec<u8>",
"Sponsor": "AccountId",
"UnconfirmedSponsor": "AccountId",
+ "Limits": "CollectionLimits",
"VariableOnChainSchema": "Vec<u8>",
"ConstOnChainSchema": "Vec<u8>"
},
@@ -120,6 +112,12 @@
"nft_sponsor_transfer_timeout": "u32",
"fungible_sponsor_transfer_timeout": "u32",
"refungible_sponsor_transfer_timeout": "u32"
+ },
+ "CollectionLimits": {
+ "AccountTokenOwnershipLimit": "u32",
+ "SponsoredDataSize": "u32",
+ "TokenLimit": "u32",
+ "SponsorTransferTimeout": "u32"
}
}
\ No newline at end of file