difftreelog
Merge pull request #25 from usetech-llc/feature/NFTPAR-110_contract_spam
in: master
Feature/nftpar 110 contract spam protection
4 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3737,7 +3737,10 @@
"frame-support",
"frame-system",
"log",
+ "pallet-balances",
"pallet-contracts",
+ "pallet-randomness-collective-flip",
+ "pallet-timestamp",
"pallet-transaction-payment",
"parity-scale-codec",
"serde",
pallets/nft/src/default_weights.rsdiffbeforeafterboth--- a/pallets/nft/src/default_weights.rs
+++ b/pallets/nft/src/default_weights.rs
@@ -107,9 +107,19 @@
.saturating_add(DbWeight::get().reads(2 as Weight))
.saturating_add(DbWeight::get().writes(1 as Weight))
}
+ // fn set_chain_limits() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
// fn enable_contract_sponsoring() -> Weight {
// (0 as Weight)
// .saturating_add(DbWeight::get().reads(1 as Weight))
// .saturating_add(DbWeight::get().writes(1 as Weight))
// }
+ // fn set_contract_sponsoring_rate_limit() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
}
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;4748// Structs49// #region5051#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]52#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]53pub enum CollectionMode {54 Invalid,55 NFT,56 // decimal points57 Fungible(u32),58 // decimal points59 ReFungible(u32),60}6162impl Into<u8> for CollectionMode {63 fn into(self) -> u8 {64 match self {65 CollectionMode::Invalid => 0,66 CollectionMode::NFT => 1,67 CollectionMode::Fungible(_) => 2,68 CollectionMode::ReFungible(_) => 3,69 }70 }71}7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]75pub enum AccessMode {76 Normal,77 WhiteList,78}79impl Default for AccessMode {80 fn default() -> Self {81 Self::Normal82 }83}8485impl Default for CollectionMode {86 fn default() -> Self {87 Self::Invalid88 }89}9091#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]92#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]93pub struct Ownership<AccountId> {94 pub owner: AccountId,95 pub fraction: u128,96}9798#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]99#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]100pub struct CollectionType<AccountId> {101 pub owner: AccountId,102 pub mode: CollectionMode,103 pub access: AccessMode,104 pub decimal_points: u32,105 pub name: Vec<u16>, // 64 include null escape char106 pub description: Vec<u16>, // 256 include null escape char107 pub token_prefix: Vec<u8>, // 16 include null escape char108 pub mint_mode: bool,109 pub offchain_schema: Vec<u8>,110 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender111 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship112 pub variable_on_chain_schema: Vec<u8>, //113 pub const_on_chain_schema: Vec<u8>, //114}115116#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]117#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]118pub struct NftItemType<AccountId> {119 pub collection: u64,120 pub owner: AccountId,121 pub const_data: Vec<u8>,122 pub variable_data: Vec<u8>,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct FungibleItemType<AccountId> {128 pub collection: u64,129 pub owner: AccountId,130 pub value: u128,131}132133#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]134#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]135pub struct ReFungibleItemType<AccountId> {136 pub collection: u64,137 pub owner: Vec<Ownership<AccountId>>,138 pub const_data: Vec<u8>,139 pub variable_data: Vec<u8>,140}141142#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]143#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]144pub struct ApprovePermissions<AccountId> {145 pub approved: AccountId,146 pub amount: u64,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct VestingItem<AccountId, Moment> {152 pub sender: AccountId,153 pub recipient: AccountId,154 pub collection_id: u64,155 pub item_id: u64,156 pub amount: u64,157 pub vesting_date: Moment,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct BasketItem<AccountId, BlockNumber> {163 pub address: AccountId,164 pub start_block: BlockNumber,165}166167#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct ChainLimits {170 pub collection_numbers_limit: u64,171 pub account_token_ownership_limit: u64,172 pub collections_admins_limit: u64,173 pub custom_data_limit: u32,174175 // Timeouts for item types in passed blocks176 pub nft_sponsor_transfer_timeout: u32,177 pub fungible_sponsor_transfer_timeout: u32,178 pub refungible_sponsor_transfer_timeout: u32,179}180181pub trait WeightInfo {182 fn create_collection() -> Weight;183 fn destroy_collection() -> Weight;184 fn add_to_white_list() -> Weight;185 fn remove_from_white_list() -> Weight;186 fn set_public_access_mode() -> Weight;187 fn set_mint_permission() -> Weight;188 fn change_collection_owner() -> Weight;189 fn add_collection_admin() -> Weight;190 fn remove_collection_admin() -> Weight;191 fn set_collection_sponsor() -> Weight;192 fn confirm_sponsorship() -> Weight;193 fn remove_collection_sponsor() -> Weight;194 fn create_item(s: usize) -> Weight;195 fn burn_item() -> Weight;196 fn transfer() -> Weight;197 fn approve() -> Weight;198 fn transfer_from() -> Weight;199 fn set_offchain_schema() -> Weight;200 fn set_const_on_chain_schema() -> Weight;201 fn set_variable_on_chain_schema() -> Weight;202 fn set_variable_meta_data() -> Weight;203 // fn enable_contract_sponsoring() -> Weight;204}205206#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]207#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]208pub struct CreateNftData {209 pub const_data: Vec<u8>,210 pub variable_data: Vec<u8>,211}212213#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]214#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]215pub struct CreateFungibleData {216}217218#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]219#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]220pub struct CreateReFungibleData {221 pub const_data: Vec<u8>,222 pub variable_data: Vec<u8>,223}224225#[derive(Encode, Decode, Debug, Clone, PartialEq)]226#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]227pub enum CreateItemData {228 NFT(CreateNftData),229 Fungible(CreateFungibleData),230 ReFungible(CreateReFungibleData)231}232233impl CreateItemData {234 pub fn len(&self) -> usize {235 let len = match self {236 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),237 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),238 _ => 0239 };240 241 return len;242 }243}244245impl From<CreateNftData> for CreateItemData {246 fn from(item: CreateNftData) -> Self {247 CreateItemData::NFT(item)248 }249}250251impl From<CreateReFungibleData> for CreateItemData {252 fn from(item: CreateReFungibleData) -> Self {253 CreateItemData::ReFungible(item)254 }255}256257impl From<CreateFungibleData> for CreateItemData {258 fn from(item: CreateFungibleData) -> Self {259 CreateItemData::Fungible(item)260 }261}262263264decl_error! {265 /// Error for non-fungible-token module.266 pub enum Error for Module<T: Trait> {267 /// Total collections bound exceeded268 TotalCollectionsLimitExceeded,269 /// Decimal_points parameter must be lower than 4270 CollectionDecimalPointLimitExceeded, 271 /// Collection name can not be longer than 63 char272 CollectionNameLimitExceeded, 273 /// Collection description can not be longer than 255 char274 CollectionDescriptionLimitExceeded, 275 /// Token prefix can not be longer than 15 char276 CollectionTokenPrefixLimitExceeded,277 /// This collection does not exist278 CollectionNotFound,279 /// Item not exists280 TokenNotFound,281 /// Arithmetic calculation overflow282 NumOverflow, 283 /// Account already has admin role284 AlreadyAdmin, 285 /// You do not own this collection286 NoPermission,287 /// This address is not set as sponsor, use setCollectionSponsor first288 ConfirmUnsetSponsorFail,289 /// Collection is not in mint mode290 PublicMintingNotAllowed,291 /// Sender parameter and item owner must be equal292 MustBeTokenOwner,293 /// Item balance not enouth294 TokenValueTooLow,295 /// Size of item is too large296 NftSizeLimitExceeded,297 /// Size of item must be 0 with fungible type298 FungibleUnexpectedParam,299 /// No approve found300 ApproveNotFound,301 /// Requested value more than approved302 TokenValueNotEnough,303 /// Only approved addresses can call this method304 ApproveRequired,305 /// Address is not in white list306 AddresNotInWhiteList,307 /// Number of collection admins bound exceeded308 CollectionAdminsLimitExceeded,309 /// Owned tokens by a single address bound exceeded310 AddressOwnershipLimitExceeded,311 /// Length of items properties must be greater than 0312 EmptyArgument,313 }314}315316pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {317 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;318319 /// Weight information for extrinsics in this pallet.320 type WeightInfo: WeightInfo;321}322323#[cfg(feature = "runtime-benchmarks")]324mod benchmarking;325326// #endregion327328decl_storage! {329 trait Store for Module<T: Trait> as Nft {330331 // Private members332 NextCollectionID: u64;333 CreatedCollectionCount: u64;334 ChainVersion: u64;335 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;336337 // Chain limits struct338 pub ChainLimit get(fn chain_limit) config(): ChainLimits;339340 // Bound counters341 CollectionCount: u64;342 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;343344 // Basic collections345 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;346 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;347 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;348349 /// Balance owner per collection map350 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;351352 /// second parameter: item id + owner account id353 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;354355 /// Item collections356 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;357 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;358 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;359360 /// Index list361 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;362363 /// Tokens transfer baskets364 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;365 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec<BasketItem<T::AccountId, T::BlockNumber>>;366 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;367368 // Contract Sponsorship and Ownership369 pub ContractOwner get(fn contract_owner): map hasher(identity) T::AccountId => T::AccountId;370 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(identity) T::AccountId => bool;371 }372 add_extra_genesis {373 build(|config: &GenesisConfig<T>| {374 // Modification of storage375 for (_num, _c) in &config.collection {376 <Module<T>>::init_collection(_c);377 }378379 for (_num, _q, _i) in &config.nft_item_id {380 <Module<T>>::init_nft_token(_i);381 }382383 for (_num, _q, _i) in &config.fungible_item_id {384 <Module<T>>::init_fungible_token(_i);385 }386387 for (_num, _q, _i) in &config.refungible_item_id {388 <Module<T>>::init_refungible_token(_i);389 }390 })391 }392}393394decl_event!(395 pub enum Event<T>396 where397 AccountId = <T as system::Trait>::AccountId,398 {399 /// New collection was created400 /// 401 /// # Arguments402 /// 403 /// * collection_id: Globally unique identifier of newly created collection.404 /// 405 /// * mode: [CollectionMode] converted into u8.406 /// 407 /// * account_id: Collection owner.408 Created(u64, u8, AccountId),409410 /// New item was created.411 /// 412 /// # Arguments413 /// 414 /// * collection_id: Id of the collection where item was created.415 /// 416 /// * item_id: Id of an item. Unique within the collection.417 ItemCreated(u64, u64),418419 /// Collection item was burned.420 /// 421 /// # Arguments422 /// 423 /// collection_id.424 /// 425 /// item_id: Identifier of burned NFT.426 ItemDestroyed(u64, u64),427 }428);429430decl_module! {431 pub struct Module<T: Trait> for enum Call where origin: T::Origin {432433 fn deposit_event() = default;434 type Error = Error<T>;435436 fn on_initialize(now: T::BlockNumber) -> Weight {437438 if ChainVersion::get() < 2439 {440 let value = NextCollectionID::get();441 CreatedCollectionCount::put(value);442 ChainVersion::put(2);443 }444445 0446 }447448 /// 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.449 /// 450 /// # Permissions451 /// 452 /// * Anyone.453 /// 454 /// # Arguments455 /// 456 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.457 /// 458 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.459 /// 460 /// * token_prefix: UTF-8 string with token prefix.461 /// 462 /// * mode: [CollectionMode] collection type and type dependent data.463 // returns collection ID464 #[weight = T::WeightInfo::create_collection()]465 pub fn create_collection(origin,466 collection_name: Vec<u16>,467 collection_description: Vec<u16>,468 token_prefix: Vec<u8>,469 mode: CollectionMode) -> DispatchResult {470471 // Anyone can create a collection472 let who = ensure_signed(origin)?;473474 let decimal_points = match mode {475 CollectionMode::Fungible(points) => points,476 CollectionMode::ReFungible(points) => points,477 _ => 0478 };479480 // bound Total number of collections481 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);482483 // check params484 ensure!(decimal_points <= 4, Error::<T>::CollectionDecimalPointLimitExceeded);485486 let mut name = collection_name.to_vec();487 name.push(0);488 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);489490 let mut description = collection_description.to_vec();491 description.push(0);492 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);493494 let mut prefix = token_prefix.to_vec();495 prefix.push(0);496 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);497498 // Generate next collection ID499 let next_id = CreatedCollectionCount::get()500 .checked_add(1)501 .ok_or(Error::<T>::NumOverflow)?;502503 // bound counter504 let total = CollectionCount::get()505 .checked_add(1)506 .ok_or(Error::<T>::NumOverflow)?;507508 CreatedCollectionCount::put(next_id);509 CollectionCount::put(total);510511 // Create new collection512 let new_collection = CollectionType {513 owner: who.clone(),514 name: name,515 mode: mode.clone(),516 mint_mode: false,517 access: AccessMode::Normal,518 description: description,519 decimal_points: decimal_points,520 token_prefix: prefix,521 offchain_schema: Vec::new(),522 sponsor: T::AccountId::default(),523 unconfirmed_sponsor: T::AccountId::default(),524 variable_on_chain_schema: Vec::new(),525 const_on_chain_schema: Vec::new(),526 };527528 // Add new collection to map529 <Collection<T>>::insert(next_id, new_collection);530531 // call event532 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));533534 Ok(())535 }536537 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.538 /// 539 /// # Permissions540 /// 541 /// * Collection Owner.542 /// 543 /// # Arguments544 /// 545 /// * collection_id: collection to destroy.546 #[weight = T::WeightInfo::destroy_collection()]547 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {548549 let sender = ensure_signed(origin)?;550 Self::check_owner_permissions(collection_id, sender)?;551552 <AddressTokens<T>>::remove_prefix(collection_id);553 <ApprovedList<T>>::remove_prefix(collection_id);554 <Balance<T>>::remove_prefix(collection_id);555 <ItemListIndex>::remove(collection_id);556 <AdminList<T>>::remove(collection_id);557 <Collection<T>>::remove(collection_id);558 <WhiteList<T>>::remove(collection_id);559560 <NftItemList<T>>::remove_prefix(collection_id);561 <FungibleItemList<T>>::remove_prefix(collection_id);562 <ReFungibleItemList<T>>::remove_prefix(collection_id);563564 <NftTransferBasket<T>>::remove_prefix(collection_id);565 <FungibleTransferBasket<T>>::remove_prefix(collection_id);566 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);567568 if CollectionCount::get() > 0569 {570 // bound couter571 let total = CollectionCount::get()572 .checked_sub(1)573 .ok_or(Error::<T>::NumOverflow)?;574575 CollectionCount::put(total);576 }577578 Ok(())579 }580581 /// Add an address to white list.582 /// 583 /// # Permissions584 /// 585 /// * Collection Owner586 /// * Collection Admin587 /// 588 /// # Arguments589 /// 590 /// * collection_id.591 /// 592 /// * address.593 #[weight = T::WeightInfo::add_to_white_list()]594 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{595596 let sender = ensure_signed(origin)?;597 Self::check_owner_or_admin_permissions(collection_id, sender)?;598599 let mut white_list_collection: Vec<T::AccountId>;600 if <WhiteList<T>>::contains_key(collection_id) {601 white_list_collection = <WhiteList<T>>::get(collection_id);602 if !white_list_collection.contains(&address.clone())603 {604 white_list_collection.push(address.clone());605 }606 }607 else {608 white_list_collection = Vec::new();609 white_list_collection.push(address.clone());610 }611612 <WhiteList<T>>::insert(collection_id, white_list_collection);613 Ok(())614 }615616 /// Remove an address from white list.617 /// 618 /// # Permissions619 /// 620 /// * Collection Owner621 /// * Collection Admin622 /// 623 /// # Arguments624 /// 625 /// * collection_id.626 /// 627 /// * address.628 #[weight = T::WeightInfo::remove_from_white_list()]629 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{630631 let sender = ensure_signed(origin)?;632 Self::check_owner_or_admin_permissions(collection_id, sender)?;633634 if <WhiteList<T>>::contains_key(collection_id) {635 let mut white_list_collection = <WhiteList<T>>::get(collection_id);636 if white_list_collection.contains(&address.clone())637 {638 white_list_collection.retain(|i| *i != address.clone());639 <WhiteList<T>>::insert(collection_id, white_list_collection);640 }641 }642643 Ok(())644 }645646 /// Toggle between normal and white list access for the methods with access for `Anyone`.647 /// 648 /// # Permissions649 /// 650 /// * Collection Owner.651 /// 652 /// # Arguments653 /// 654 /// * collection_id.655 /// 656 /// * mode: [AccessMode]657 #[weight = T::WeightInfo::set_public_access_mode()]658 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult659 {660 let sender = ensure_signed(origin)?;661662 Self::check_owner_permissions(collection_id, sender)?;663 let mut target_collection = <Collection<T>>::get(collection_id);664 target_collection.access = mode;665 <Collection<T>>::insert(collection_id, target_collection);666667 Ok(())668 }669670 /// Allows Anyone to create tokens if:671 /// * White List is enabled, and672 /// * Address is added to white list, and673 /// * This method was called with True parameter674 /// 675 /// # Permissions676 /// * Collection Owner677 ///678 /// # Arguments679 /// 680 /// * collection_id.681 /// 682 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.683 #[weight = T::WeightInfo::set_mint_permission()]684 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult685 {686 let sender = ensure_signed(origin)?;687688 Self::check_owner_permissions(collection_id, sender)?;689 let mut target_collection = <Collection<T>>::get(collection_id);690 target_collection.mint_mode = mint_permission;691 <Collection<T>>::insert(collection_id, target_collection);692693 Ok(())694 }695696 /// Change the owner of the collection.697 /// 698 /// # Permissions699 /// 700 /// * Collection Owner.701 /// 702 /// # Arguments703 /// 704 /// * collection_id.705 /// 706 /// * new_owner.707 #[weight = T::WeightInfo::change_collection_owner()]708 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {709710 let sender = ensure_signed(origin)?;711 Self::check_owner_permissions(collection_id, sender)?;712 let mut target_collection = <Collection<T>>::get(collection_id);713 target_collection.owner = new_owner;714 <Collection<T>>::insert(collection_id, target_collection);715716 Ok(())717 }718719 /// Adds an admin of the Collection.720 /// 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. 721 /// 722 /// # Permissions723 /// 724 /// * Collection Owner.725 /// * Collection Admin.726 /// 727 /// # Arguments728 /// 729 /// * collection_id: ID of the Collection to add admin for.730 /// 731 /// * new_admin_id: Address of new admin to add.732 #[weight = T::WeightInfo::add_collection_admin()]733 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {734735 let sender = ensure_signed(origin)?;736 Self::check_owner_or_admin_permissions(collection_id, sender)?;737 let mut admin_arr: Vec<T::AccountId> = Vec::new();738739 if <AdminList<T>>::contains_key(collection_id)740 {741 admin_arr = <AdminList<T>>::get(collection_id);742 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);743 }744745 // Number of collection admins746 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);747748 admin_arr.push(new_admin_id);749 <AdminList<T>>::insert(collection_id, admin_arr);750751 Ok(())752 }753754 /// 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.755 ///756 /// # Permissions757 /// 758 /// * Collection Owner.759 /// * Collection Admin.760 /// 761 /// # Arguments762 /// 763 /// * collection_id: ID of the Collection to remove admin for.764 /// 765 /// * account_id: Address of admin to remove.766 #[weight = T::WeightInfo::remove_collection_admin()]767 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {768769 let sender = ensure_signed(origin)?;770 Self::check_owner_or_admin_permissions(collection_id, sender)?;771772 if <AdminList<T>>::contains_key(collection_id)773 {774 let mut admin_arr = <AdminList<T>>::get(collection_id);775 admin_arr.retain(|i| *i != account_id);776 <AdminList<T>>::insert(collection_id, admin_arr);777 }778779 Ok(())780 }781782 /// # Permissions783 /// 784 /// * Collection Owner785 /// 786 /// # Arguments787 /// 788 /// * collection_id.789 /// 790 /// * new_sponsor.791 #[weight = T::WeightInfo::set_collection_sponsor()]792 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {793794 let sender = ensure_signed(origin)?;795 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);796797 let mut target_collection = <Collection<T>>::get(collection_id);798 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);799800 target_collection.unconfirmed_sponsor = new_sponsor;801 <Collection<T>>::insert(collection_id, target_collection);802803 Ok(())804 }805806 /// # Permissions807 /// 808 /// * Sponsor.809 /// 810 /// # Arguments811 /// 812 /// * collection_id.813 #[weight = T::WeightInfo::confirm_sponsorship()]814 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {815816 let sender = ensure_signed(origin)?;817 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);818819 let mut target_collection = <Collection<T>>::get(collection_id);820 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);821822 target_collection.sponsor = target_collection.unconfirmed_sponsor;823 target_collection.unconfirmed_sponsor = T::AccountId::default();824 <Collection<T>>::insert(collection_id, target_collection);825826 Ok(())827 }828829 /// Switch back to pay-per-own-transaction model.830 ///831 /// # Permissions832 ///833 /// * Collection owner.834 /// 835 /// # Arguments836 /// 837 /// * collection_id.838 #[weight = T::WeightInfo::remove_collection_sponsor()]839 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {840841 let sender = ensure_signed(origin)?;842 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);843844 let mut target_collection = <Collection<T>>::get(collection_id);845 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);846847 target_collection.sponsor = T::AccountId::default();848 <Collection<T>>::insert(collection_id, target_collection);849850 Ok(())851 }852853 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.854 /// 855 /// # Permissions856 /// 857 /// * Collection Owner.858 /// * Collection Admin.859 /// * Anyone if860 /// * White List is enabled, and861 /// * Address is added to white list, and862 /// * MintPermission is enabled (see SetMintPermission method)863 /// 864 /// # Arguments865 /// 866 /// * collection_id: ID of the collection.867 /// 868 /// * owner: Address, initial owner of the NFT.869 ///870 /// * data: Token data to store on chain.871 // #[weight =872 // (130_000_000 as Weight)873 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))874 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))875 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]876877 #[weight = T::WeightInfo::create_item(data.len())]878 pub fn create_item(origin, collection_id: u64, owner: T::AccountId, data: CreateItemData) -> DispatchResult {879880 let sender = ensure_signed(origin)?;881882 Self::collection_exists(collection_id)?;883884 let target_collection = <Collection<T>>::get(collection_id);885886 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;887 Self::validate_create_item_args(&target_collection, &data)?;888 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;889890 Ok(())891 }892893 /// This method creates multiple instances of NFT Collection created with CreateCollection method.894 /// 895 /// # Permissions896 /// 897 /// * Collection Owner.898 /// * Collection Admin.899 /// * Anyone if900 /// * White List is enabled, and901 /// * Address is added to white list, and902 /// * MintPermission is enabled (see SetMintPermission method)903 /// 904 /// # Arguments905 /// 906 /// * collection_id: ID of the collection.907 /// 908 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].909 /// 910 /// * owner: Address, initial owner of the NFT.911 #[weight = T::WeightInfo::create_item(items_data.into_iter()912 .map(|data| { data.len() })913 .sum())]914 pub fn create_multiple_items(origin, collection_id: u64, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {915916 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);917 let sender = ensure_signed(origin)?;918919 Self::collection_exists(collection_id)?;920 let target_collection = <Collection<T>>::get(collection_id);921922 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;923924 for data in &items_data {925 Self::validate_create_item_args(&target_collection, data)?;926 }927 for data in &items_data {928 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;929 }930931 Ok(())932 }933934 /// Destroys a concrete instance of NFT.935 /// 936 /// # Permissions937 /// 938 /// * Collection Owner.939 /// * Collection Admin.940 /// * Current NFT Owner.941 /// 942 /// # Arguments943 /// 944 /// * collection_id: ID of the collection.945 /// 946 /// * item_id: ID of NFT to burn.947 #[weight = T::WeightInfo::burn_item()]948 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {949950 let sender = ensure_signed(origin)?;951 Self::collection_exists(collection_id)?;952953 // Transfer permissions check954 let target_collection = <Collection<T>>::get(collection_id);955 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||956 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),957 Error::<T>::NoPermission);958959 if target_collection.access == AccessMode::WhiteList {960 Self::check_white_list(collection_id, &sender)?;961 }962963 match target_collection.mode964 {965 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,966 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,967 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,968 _ => ()969 };970971 // call event972 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));973974 Ok(())975 }976977 /// Change ownership of the token.978 /// 979 /// # Permissions980 /// 981 /// * Collection Owner982 /// * Collection Admin983 /// * Current NFT owner984 ///985 /// # Arguments986 /// 987 /// * recipient: Address of token recipient.988 /// 989 /// * collection_id.990 /// 991 /// * item_id: ID of the item992 /// * Non-Fungible Mode: Required.993 /// * Fungible Mode: Ignored.994 /// * Re-Fungible Mode: Required.995 /// 996 /// * value: Amount to transfer.997 /// * Non-Fungible Mode: Ignored998 /// * Fungible Mode: Must specify transferred amount999 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1000 #[weight = T::WeightInfo::transfer()]1001 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {10021003 let sender = ensure_signed(origin)?;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 Self::check_white_list(collection_id, &recipient)?;1014 }10151016 match target_collection.mode1017 {1018 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1019 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1020 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1021 _ => ()1022 };10231024 Ok(())1025 }10261027 /// Set, change, or remove approved address to transfer the ownership of the NFT.1028 /// 1029 /// # Permissions1030 /// 1031 /// * Collection Owner1032 /// * Collection Admin1033 /// * Current NFT owner1034 /// 1035 /// # Arguments1036 /// 1037 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1038 /// 1039 /// * collection_id.1040 /// 1041 /// * item_id: ID of the item.1042 #[weight = T::WeightInfo::approve()]1043 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {10441045 let sender = ensure_signed(origin)?;10461047 // Transfer permissions check1048 let target_collection = <Collection<T>>::get(collection_id);1049 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1050 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1051 Error::<T>::NoPermission);10521053 if target_collection.access == AccessMode::WhiteList {1054 Self::check_white_list(collection_id, &sender)?;1055 Self::check_white_list(collection_id, &approved)?;1056 }10571058 // amount param stub1059 let amount = 100000000;10601061 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1062 if list_exists {10631064 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1065 let item_contains = list.iter().any(|i| i.approved == approved);10661067 if !item_contains {1068 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1069 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1070 }1071 } else {10721073 let mut list = Vec::new();1074 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1075 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1076 }10771078 Ok(())1079 }1080 1081 /// 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.1082 /// 1083 /// # Permissions1084 /// * Collection Owner1085 /// * Collection Admin1086 /// * Current NFT owner1087 /// * Address approved by current NFT owner1088 /// 1089 /// # Arguments1090 /// 1091 /// * from: Address that owns token.1092 /// 1093 /// * recipient: Address of token recipient.1094 /// 1095 /// * collection_id.1096 /// 1097 /// * item_id: ID of the item.1098 /// 1099 /// * value: Amount to transfer.1100 #[weight = T::WeightInfo::transfer_from()]1101 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {11021103 let sender = ensure_signed(origin)?;1104 let mut appoved_transfer = false;11051106 // Check approve1107 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1108 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1109 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1110 if opt_item.is_some()1111 {1112 appoved_transfer = true;1113 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1114 }1115 }11161117 // Transfer permissions check1118 let target_collection = <Collection<T>>::get(collection_id);1119 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1120 Error::<T>::NoPermission);11211122 if target_collection.access == AccessMode::WhiteList {1123 Self::check_white_list(collection_id, &sender)?;1124 Self::check_white_list(collection_id, &recipient)?;1125 }11261127 // remove approve1128 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1129 .into_iter().filter(|i| i.approved != sender.clone()).collect();1130 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);113111321133 match target_collection.mode1134 {1135 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1136 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1137 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1138 _ => ()1139 };11401141 Ok(())1142 }11431144 ///1145 #[weight = 0]1146 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {11471148 // let no_perm_mes = "You do not have permissions to modify this collection";1149 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1150 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1151 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11521153 // // on_nft_received call11541155 // Self::transfer(origin, collection_id, item_id, new_owner)?;11561157 Ok(())1158 }11591160 /// Set off-chain data schema.1161 /// 1162 /// # Permissions1163 /// 1164 /// * Collection Owner1165 /// * Collection Admin1166 /// 1167 /// # Arguments1168 /// 1169 /// * collection_id.1170 /// 1171 /// * schema: String representing the offchain data schema.1172 #[weight = T::WeightInfo::set_variable_meta_data()]1173 pub fn set_variable_meta_data (1174 origin,1175 collection_id: u64,1176 item_id: u64,1177 data: Vec<u8>1178 ) -> DispatchResult {1179 let sender = ensure_signed(origin)?;1180 1181 Self::collection_exists(collection_id)?;11821183 // Modify permissions check1184 let target_collection = <Collection<T>>::get(collection_id);1185 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1186 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1187 Error::<T>::NoPermission);11881189 Self::item_exists(collection_id, item_id, &target_collection.mode)?;11901191 match target_collection.mode1192 {1193 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1194 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1195 _ => ()1196 };11971198 Ok(())1199 }1200 12011202 /// Set off-chain data schema.1203 /// 1204 /// # Permissions1205 /// 1206 /// * Collection Owner1207 /// * Collection Admin1208 /// 1209 /// # Arguments1210 /// 1211 /// * collection_id.1212 /// 1213 /// * schema: String representing the offchain data schema.1214 #[weight = T::WeightInfo::set_offchain_schema()]1215 pub fn set_offchain_schema(1216 origin,1217 collection_id: u64,1218 schema: Vec<u8>1219 ) -> DispatchResult {1220 let sender = ensure_signed(origin)?;1221 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12221223 let mut target_collection = <Collection<T>>::get(collection_id);1224 target_collection.offchain_schema = schema;1225 <Collection<T>>::insert(collection_id, target_collection);12261227 Ok(())1228 }12291230 /// Set const on-chain data schema.1231 /// 1232 /// # Permissions1233 /// 1234 /// * Collection Owner1235 /// * Collection Admin1236 /// 1237 /// # Arguments1238 /// 1239 /// * collection_id.1240 /// 1241 /// * schema: String representing the const on-chain data schema.1242 #[weight = T::WeightInfo::set_const_on_chain_schema()]1243 pub fn set_const_on_chain_schema (1244 origin,1245 collection_id: u64,1246 schema: Vec<u8>1247 ) -> DispatchResult {1248 let sender = ensure_signed(origin)?;1249 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12501251 let mut target_collection = <Collection<T>>::get(collection_id);1252 target_collection.const_on_chain_schema = schema;1253 <Collection<T>>::insert(collection_id, target_collection);12541255 Ok(())1256 }12571258 /// Set variable on-chain data schema.1259 /// 1260 /// # Permissions1261 /// 1262 /// * Collection Owner1263 /// * Collection Admin1264 /// 1265 /// # Arguments1266 /// 1267 /// * collection_id.1268 /// 1269 /// * schema: String representing the variable on-chain data schema.1270 #[weight = T::WeightInfo::set_const_on_chain_schema()]1271 pub fn set_variable_on_chain_schema (1272 origin,1273 collection_id: u64,1274 schema: Vec<u8>1275 ) -> DispatchResult {1276 let sender = ensure_signed(origin)?;1277 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12781279 let mut target_collection = <Collection<T>>::get(collection_id);1280 target_collection.variable_on_chain_schema = schema;1281 <Collection<T>>::insert(collection_id, target_collection);12821283 Ok(())1284 }12851286 // Sudo permissions function1287 #[weight = 0]1288 pub fn set_chain_limits(1289 origin,1290 limits: ChainLimits1291 ) -> DispatchResult {1292 ensure_root(origin)?;1293 <ChainLimit>::put(limits);1294 Ok(())1295 }12961297 /// Enable smart contract self-sponsoring.1298 /// 1299 /// # Permissions1300 /// 1301 /// * Contract Owner1302 /// 1303 /// # Arguments1304 /// 1305 /// * contract address1306 /// * enable flag1307 /// 1308 #[weight = 0]1309 pub fn enable_contract_sponsoring(1310 origin,1311 contract_address: T::AccountId,1312 enable: bool1313 ) -> DispatchResult {1314 let sender = ensure_signed(origin)?;1315 let mut is_owner = false;1316 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1317 let owner = <ContractOwner<T>>::get(&contract_address);1318 is_owner = sender == owner;1319 }1320 ensure!(is_owner, Error::<T>::NoPermission);13211322 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1323 Ok(())1324 }13251326 }1327}13281329impl<T: Trait> Module<T> {13301331 fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {13321333 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1334 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1335 Self::check_white_list(collection_id, owner)?;1336 Self::check_white_list(collection_id, sender)?;1337 }13381339 Ok(())1340 }13411342 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1343 match target_collection.mode1344 {1345 CollectionMode::NFT => {1346 if let CreateItemData::NFT(data) = data {1347 // check sizes1348 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1349 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1350 } else {1351 fail!("Not NFT item data used to mint in NFT collection.");1352 }1353 },1354 CollectionMode::Fungible(_) => {1355 if let CreateItemData::Fungible(_) = data {1356 } else {1357 fail!("Not Fungible item data used to mint in Fungible collection.");1358 }1359 },1360 CollectionMode::ReFungible(_) => {1361 if let CreateItemData::ReFungible(data) = data {13621363 // check sizes1364 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1365 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1366 } else {1367 fail!("Not Re Fungible item data used to mint in Re Fungible collection.");1368 }1369 },1370 _ => { fail!("Unexpected collection type."); }1371 };13721373 Ok(())1374 }13751376 fn create_item_no_validation(collection_id: u64, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1377 match data1378 {1379 CreateItemData::NFT(data) => {1380 let item = NftItemType {1381 collection: collection_id,1382 owner,1383 const_data: data.const_data,1384 variable_data: data.variable_data1385 };13861387 Self::add_nft_item(item)?;1388 },1389 CreateItemData::Fungible(_) => {1390 let item = FungibleItemType {1391 collection: collection_id,1392 owner,1393 value: (10 as u128).pow(collection.decimal_points)1394 };13951396 Self::add_fungible_item(item)?;1397 },1398 CreateItemData::ReFungible(data) => {1399 let mut owner_list = Vec::new();1400 let value = (10 as u128).pow(collection.decimal_points);1401 owner_list.push(Ownership {owner: owner.clone(), fraction: value});14021403 let item = ReFungibleItemType {1404 collection: collection_id,1405 owner: owner_list,1406 const_data: data.const_data,1407 variable_data: data.variable_data1408 };14091410 Self::add_refungible_item(item)?;1411 }1412 };141314141415 // call event1416 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));14171418 Ok(())1419 }14201421 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1422 let current_index = <ItemListIndex>::get(item.collection)1423 .checked_add(1)1424 .ok_or(Error::<T>::NumOverflow)?;1425 let itemcopy = item.clone();1426 let owner = item.owner.clone();1427 let value = item.value as u64;14281429 Self::add_token_index(item.collection, current_index, owner.clone())?;14301431 <ItemListIndex>::insert(item.collection, current_index);1432 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14331434 // Add current block1435 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1436 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1437 1438 // Update balance1439 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1440 .checked_add(value)1441 .ok_or(Error::<T>::NumOverflow)?;1442 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14431444 Ok(())1445 }14461447 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1448 let current_index = <ItemListIndex>::get(item.collection)1449 .checked_add(1)1450 .ok_or(Error::<T>::NumOverflow)?;1451 let itemcopy = item.clone();14521453 let value = item.owner.first().unwrap().fraction as u64;1454 let owner = item.owner.first().unwrap().owner.clone();14551456 Self::add_token_index(item.collection, current_index, owner.clone())?;14571458 <ItemListIndex>::insert(item.collection, current_index);1459 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14601461 // Add current block1462 let block_number: T::BlockNumber = 0.into();1463 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);14641465 // Update balance1466 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1467 .checked_add(value)1468 .ok_or(Error::<T>::NumOverflow)?;1469 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14701471 Ok(())1472 }14731474 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1475 let current_index = <ItemListIndex>::get(item.collection)1476 .checked_add(1)1477 .ok_or(Error::<T>::NumOverflow)?;14781479 let item_owner = item.owner.clone();1480 let collection_id = item.collection.clone();1481 Self::add_token_index(collection_id, current_index, item.owner.clone())?;14821483 <ItemListIndex>::insert(collection_id, current_index);1484 <NftItemList<T>>::insert(collection_id, current_index, item);14851486 // Add current block1487 let block_number: T::BlockNumber = 0.into();1488 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);14891490 // Update balance1491 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1492 .checked_add(1)1493 .ok_or(Error::<T>::NumOverflow)?;1494 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);14951496 Ok(())1497 }14981499 fn burn_refungible_item(1500 collection_id: u64,1501 item_id: u64,1502 owner: T::AccountId,1503 ) -> DispatchResult {1504 ensure!(1505 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1506 Error::<T>::TokenNotFound1507 );1508 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1509 let item = collection1510 .owner1511 .iter()1512 .filter(|&i| i.owner == owner)1513 .next()1514 .unwrap();1515 Self::remove_token_index(collection_id, item_id, owner.clone())?;15161517 // remove approve list1518 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15191520 // update balance1521 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1522 .checked_sub(item.fraction as u64)1523 .ok_or(Error::<T>::NumOverflow)?;1524 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15251526 <ReFungibleItemList<T>>::remove(collection_id, item_id);15271528 Ok(())1529 }15301531 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1532 ensure!(1533 <NftItemList<T>>::contains_key(collection_id, item_id),1534 Error::<T>::TokenNotFound1535 );1536 let item = <NftItemList<T>>::get(collection_id, item_id);1537 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15381539 // remove approve list1540 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15411542 // update balance1543 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1544 .checked_sub(1)1545 .ok_or(Error::<T>::NumOverflow)?;1546 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1547 <NftItemList<T>>::remove(collection_id, item_id);15481549 Ok(())1550 }15511552 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1553 ensure!(1554 <FungibleItemList<T>>::contains_key(collection_id, item_id),1555 Error::<T>::TokenNotFound1556 );1557 let item = <FungibleItemList<T>>::get(collection_id, item_id);1558 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15591560 // remove approve list1561 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15621563 // update balance1564 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1565 .checked_sub(item.value as u64)1566 .ok_or(Error::<T>::NumOverflow)?;1567 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15681569 <FungibleItemList<T>>::remove(collection_id, item_id);15701571 Ok(())1572 }15731574 fn collection_exists(collection_id: u64) -> DispatchResult {1575 ensure!(1576 <Collection<T>>::contains_key(collection_id),1577 Error::<T>::CollectionNotFound1578 );1579 Ok(())1580 }15811582 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1583 Self::collection_exists(collection_id)?;15841585 let target_collection = <Collection<T>>::get(collection_id);1586 ensure!(1587 subject == target_collection.owner,1588 Error::<T>::NoPermission1589 );15901591 Ok(())1592 }15931594 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1595 let target_collection = <Collection<T>>::get(collection_id);1596 let mut result: bool = subject == target_collection.owner;1597 let exists = <AdminList<T>>::contains_key(collection_id);15981599 if !result & exists {1600 if <AdminList<T>>::get(collection_id).contains(&subject) {1601 result = true1602 }1603 }16041605 result1606 }16071608 fn check_owner_or_admin_permissions(1609 collection_id: u64,1610 subject: T::AccountId,1611 ) -> DispatchResult {1612 Self::collection_exists(collection_id)?;1613 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16141615 ensure!(1616 result,1617 Error::<T>::NoPermission1618 );1619 Ok(())1620 }16211622 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1623 let target_collection = <Collection<T>>::get(collection_id);16241625 match target_collection.mode {1626 CollectionMode::NFT => {1627 <NftItemList<T>>::get(collection_id, item_id).owner == subject1628 }1629 CollectionMode::Fungible(_) => {1630 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1631 }1632 CollectionMode::ReFungible(_) => {1633 <ReFungibleItemList<T>>::get(collection_id, item_id)1634 .owner1635 .iter()1636 .any(|i| i.owner == subject)1637 }1638 CollectionMode::Invalid => false,1639 }1640 }16411642 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1643 let mes = Error::<T>::AddresNotInWhiteList;1644 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1645 let wl = <WhiteList<T>>::get(collection_id);1646 ensure!(wl.contains(address), mes);16471648 Ok(())1649 }16501651 fn transfer_fungible(1652 collection_id: u64,1653 item_id: u64,1654 value: u64,1655 owner: T::AccountId,1656 new_owner: T::AccountId,1657 ) -> DispatchResult {1658 ensure!(1659 <FungibleItemList<T>>::contains_key(collection_id, item_id),1660 Error::<T>::TokenNotFound1661 );16621663 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1664 let amount = full_item.value;16651666 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);16671668 // update balance1669 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1670 .checked_sub(value)1671 .ok_or(Error::<T>::NumOverflow)?;1672 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);16731674 let mut new_owner_account_id = 0;1675 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1676 if new_owner_items.len() > 0 {1677 new_owner_account_id = new_owner_items[0];1678 }16791680 let val64 = value.into();16811682 // transfer1683 if amount == val64 && new_owner_account_id == 0 {1684 // change owner1685 // new owner do not have account1686 let mut new_full_item = full_item.clone();1687 new_full_item.owner = new_owner.clone();1688 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);16891690 // update balance1691 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1692 .checked_add(value)1693 .ok_or(Error::<T>::NumOverflow)?;1694 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);16951696 // update index collection1697 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1698 } else {1699 let mut new_full_item = full_item.clone();1700 new_full_item.value -= val64;17011702 // separate amount1703 if new_owner_account_id > 0 {1704 // new owner has account1705 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1706 item.value += val64;17071708 // update balance1709 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1710 .checked_add(value)1711 .ok_or(Error::<T>::NumOverflow)?;1712 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17131714 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1715 } else {1716 // new owner do not have account1717 let item = FungibleItemType {1718 collection: collection_id,1719 owner: new_owner.clone(),1720 value: val64,1721 };17221723 Self::add_fungible_item(item)?;1724 }17251726 if amount == val64 {1727 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;17281729 // remove approve list1730 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1731 <FungibleItemList<T>>::remove(collection_id, item_id);1732 }17331734 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1735 }17361737 Ok(())1738 }17391740 fn transfer_refungible(1741 collection_id: u64,1742 item_id: u64,1743 value: u64,1744 owner: T::AccountId,1745 new_owner: T::AccountId,1746 ) -> DispatchResult {1747 ensure!(1748 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1749 Error::<T>::TokenNotFound1750 );17511752 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1753 let item = full_item1754 .owner1755 .iter()1756 .filter(|i| i.owner == owner)1757 .next()1758 .ok_or(Error::<T>::NumOverflow)?;1759 let amount = item.fraction;17601761 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17621763 // update balance1764 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1765 .checked_sub(value)1766 .ok_or(Error::<T>::NumOverflow)?;1767 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);17681769 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1770 .checked_add(value)1771 .ok_or(Error::<T>::NumOverflow)?;1772 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17731774 let old_owner = item.owner.clone();1775 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1776 let val64 = value.into();17771778 // transfer1779 if amount == val64 && !new_owner_has_account {1780 // change owner1781 // new owner do not have account1782 let mut new_full_item = full_item.clone();1783 new_full_item1784 .owner1785 .iter_mut()1786 .find(|i| i.owner == owner)1787 .unwrap()1788 .owner = new_owner.clone();1789 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17901791 // update index collection1792 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1793 } else {1794 let mut new_full_item = full_item.clone();1795 new_full_item1796 .owner1797 .iter_mut()1798 .find(|i| i.owner == owner)1799 .unwrap()1800 .fraction -= val64;18011802 // separate amount1803 if new_owner_has_account {1804 // new owner has account1805 new_full_item1806 .owner1807 .iter_mut()1808 .find(|i| i.owner == new_owner)1809 .unwrap()1810 .fraction += val64;1811 } else {1812 // new owner do not have account1813 new_full_item.owner.push(Ownership {1814 owner: new_owner.clone(),1815 fraction: val64,1816 });1817 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1818 }18191820 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1821 }18221823 Ok(())1824 }18251826 fn transfer_nft(1827 collection_id: u64,1828 item_id: u64,1829 sender: T::AccountId,1830 new_owner: T::AccountId,1831 ) -> DispatchResult {1832 ensure!(1833 <NftItemList<T>>::contains_key(collection_id, item_id),1834 Error::<T>::TokenNotFound1835 );18361837 let mut item = <NftItemList<T>>::get(collection_id, item_id);18381839 ensure!(1840 sender == item.owner,1841 Error::<T>::MustBeTokenOwner1842 );18431844 // update balance1845 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1846 .checked_sub(1)1847 .ok_or(Error::<T>::NumOverflow)?;1848 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18491850 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1851 .checked_add(1)1852 .ok_or(Error::<T>::NumOverflow)?;1853 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18541855 // change owner1856 let old_owner = item.owner.clone();1857 item.owner = new_owner.clone();1858 <NftItemList<T>>::insert(collection_id, item_id, item);18591860 // update index collection1861 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;18621863 // reset approved list1864 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1865 Ok(())1866 }1867 1868 fn item_exists(1869 collection_id: u64,1870 item_id: u64,1871 mode: &CollectionMode1872 ) -> DispatchResult {1873 match mode {1874 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1875 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1876 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1877 _ => ()1878 };1879 1880 Ok(())1881 }18821883 fn set_re_fungible_variable_data(1884 collection_id: u64,1885 item_id: u64,1886 data: Vec<u8>1887 ) -> DispatchResult {1888 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);18891890 item.variable_data = data;18911892 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);18931894 Ok(())1895 }18961897 fn set_nft_variable_data(1898 collection_id: u64,1899 item_id: u64,1900 data: Vec<u8>1901 ) -> DispatchResult {1902 let mut item = <NftItemList<T>>::get(collection_id, item_id);1903 1904 item.variable_data = data;19051906 <NftItemList<T>>::insert(collection_id, item_id, item);1907 1908 Ok(())1909 }19101911 fn init_collection(item: &CollectionType<T::AccountId>) {1912 // check params1913 assert!(1914 item.decimal_points <= 4,1915 "decimal_points parameter must be lower than 4"1916 );1917 assert!(1918 item.name.len() <= 64,1919 "Collection name can not be longer than 63 char"1920 );1921 assert!(1922 item.name.len() <= 256,1923 "Collection description can not be longer than 255 char"1924 );1925 assert!(1926 item.token_prefix.len() <= 16,1927 "Token prefix can not be longer than 15 char"1928 );19291930 // Generate next collection ID1931 let next_id = CreatedCollectionCount::get()1932 .checked_add(1)1933 .unwrap();19341935 CreatedCollectionCount::put(next_id);1936 }19371938 fn init_nft_token(item: &NftItemType<T::AccountId>) {1939 let current_index = <ItemListIndex>::get(item.collection)1940 .checked_add(1)1941 .unwrap();19421943 let item_owner = item.owner.clone();1944 let collection_id = item.collection.clone();1945 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();19461947 <ItemListIndex>::insert(collection_id, current_index);19481949 // Update balance1950 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1951 .checked_add(1)1952 .unwrap();1953 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1954 }19551956 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1957 let current_index = <ItemListIndex>::get(item.collection)1958 .checked_add(1)1959 .unwrap();1960 let owner = item.owner.clone();1961 let value = item.value as u64;19621963 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19641965 <ItemListIndex>::insert(item.collection, current_index);19661967 // Update balance1968 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1969 .checked_add(value)1970 .unwrap();1971 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1972 }19731974 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {1975 let current_index = <ItemListIndex>::get(item.collection)1976 .checked_add(1)1977 .unwrap();19781979 let value = item.owner.first().unwrap().fraction as u64;1980 let owner = item.owner.first().unwrap().owner.clone();19811982 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();19831984 <ItemListIndex>::insert(item.collection, current_index);19851986 // Update balance1987 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1988 .checked_add(value)1989 .unwrap();1990 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1991 }19921993 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {19941995 // add to account limit1996 if <AccountItemCount<T>>::contains_key(owner.clone()) {19971998 // bound Owned tokens by a single address1999 let count = <AccountItemCount<T>>::get(owner.clone());2000 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20012002 <AccountItemCount<T>>::insert(owner.clone(), count2003 .checked_add(1)2004 .ok_or(Error::<T>::NumOverflow)?);2005 }2006 else {2007 <AccountItemCount<T>>::insert(owner.clone(), 1);2008 }20092010 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2011 if list_exists {2012 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2013 let item_contains = list.contains(&item_index.clone());20142015 if !item_contains {2016 list.push(item_index.clone());2017 }20182019 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2020 } else {2021 let mut itm = Vec::new();2022 itm.push(item_index.clone());2023 <AddressTokens<T>>::insert(collection_id, owner, itm);2024 2025 }20262027 Ok(())2028 }20292030 fn remove_token_index(2031 collection_id: u64,2032 item_index: u64,2033 owner: T::AccountId,2034 ) -> DispatchResult {20352036 // update counter2037 <AccountItemCount<T>>::insert(owner.clone(), 2038 <AccountItemCount<T>>::get(owner.clone())2039 .checked_sub(1)2040 .ok_or(Error::<T>::NumOverflow)?);204120422043 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2044 if list_exists {2045 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2046 let item_contains = list.contains(&item_index.clone());20472048 if item_contains {2049 list.retain(|&item| item != item_index);2050 <AddressTokens<T>>::insert(collection_id, owner, list);2051 }2052 }20532054 Ok(())2055 }20562057 fn move_token_index(2058 collection_id: u64,2059 item_index: u64,2060 old_owner: T::AccountId,2061 new_owner: T::AccountId,2062 ) -> DispatchResult {2063 Self::remove_token_index(collection_id, item_index, old_owner)?;2064 Self::add_token_index(collection_id, item_index, new_owner)?;20652066 Ok(())2067 }2068}20692070////////////////////////////////////////////////////////////////////////////////////////////////////2071// Economic models2072// #region20732074/// Fee multiplier.2075pub type Multiplier = FixedU128;20762077type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2078 <T as system::Trait>::AccountId,2079>>::Balance;2080type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2081 <T as system::Trait>::AccountId,2082>>::NegativeImbalance;20832084/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2085/// in the queue.2086#[derive(Encode, Decode, Clone, Eq, PartialEq)]2087pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2088 #[codec(compact)] BalanceOf<T>2089);20902091impl<T: Trait + Send + Sync> sp_std::fmt::Debug2092 for ChargeTransactionPayment<T>2093{2094 #[cfg(feature = "std")]2095 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2096 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2097 }2098 #[cfg(not(feature = "std"))]2099 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2100 Ok(())2101 }2102}21032104impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2105where2106 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2107 BalanceOf<T>: Send + Sync + FixedPointOperand,2108{2109 /// utility constructor. Used only in client/factory code.2110 pub fn from(fee: BalanceOf<T>) -> Self {2111 Self(fee)2112 }21132114 pub fn traditional_fee(2115 len: usize,2116 info: &DispatchInfoOf<T::Call>,2117 tip: BalanceOf<T>,2118 ) -> BalanceOf<T>2119 where2120 T::Call: Dispatchable<Info = DispatchInfo>,2121 {2122 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2123 }21242125 fn withdraw_fee(2126 &self,2127 who: &T::AccountId,2128 call: &T::Call,2129 info: &DispatchInfoOf<T::Call>,2130 len: usize,2131 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2132 let tip = self.0;21332134 // Set fee based on call type. Creating collection costs 1 Unique.2135 // All other transactions have traditional fees so far2136 // let fee = match call.is_sub_type() {2137 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2138 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2139 // // _ => <BalanceOf<T>>::from(100)2140 // };2141 let fee = Self::traditional_fee(len, info, tip);21422143 // Determine who is paying transaction fee based on ecnomic model2144 // Parse call to extract collection ID and access collection sponsor2145 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2146 Some(Call::create_item(collection_id, _properties, _owner)) => {2147 <Collection<T>>::get(collection_id).sponsor2148 }2149 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2150 let _collection_mode = <Collection<T>>::get(collection_id).mode;21512152 // sponsor timeout2153 let sponsor_transfer = match _collection_mode {2154 CollectionMode::NFT => {2155 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2156 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2157 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2158 if block_number >= limit_time {2159 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2160 true2161 }2162 else {2163 false2164 }2165 }2166 CollectionMode::Fungible(_) => {2167 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2168 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2169 if basket.iter().any(|i| i.address == _new_owner.clone())2170 {2171 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2172 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2173 if block_number >= limit_time {2174 basket.retain(|x| x.address == item.address);2175 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2176 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2177 true2178 }2179 else {2180 false2181 }2182 }2183 else {2184 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2185 true2186 }2187 }2188 CollectionMode::ReFungible(_) => {2189 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2190 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2191 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2192 if block_number >= limit_time {2193 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2194 true2195 } else {2196 false2197 }2198 }2199 _ => {2200 false2201 },2202 };22032204 if !sponsor_transfer {2205 T::AccountId::default()2206 } else {2207 <Collection<T>>::get(collection_id).sponsor2208 }2209 }22102211 _ => T::AccountId::default(),2212 };22132214 // Sponsor smart contracts2215 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22162217 // On instantiation: set the contract owner2218 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22192220 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2221 code_hash,2222 &data,2223 &who,2224 );2225 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());22262227 T::AccountId::default()2228 },22292230 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2231 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {22322233 let mut sp = T::AccountId::default();2234 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());2235 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2236 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2237 sp = called_contract;2238 }2239 }22402241 sp2242 },22432244 _ => sponsor,2245 };22462247 let mut who_pays_fee: T::AccountId = sponsor.clone();2248 if sponsor == T::AccountId::default() {2249 who_pays_fee = who.clone();2250 }22512252 // Only mess with balances if fee is not zero.2253 if fee.is_zero() {2254 return Ok((fee, None));2255 }22562257 match <T as transaction_payment::Trait>::Currency::withdraw(2258 &who_pays_fee,2259 fee,2260 if tip.is_zero() {2261 WithdrawReason::TransactionPayment.into()2262 } else {2263 WithdrawReason::TransactionPayment | WithdrawReason::Tip2264 },2265 ExistenceRequirement::KeepAlive,2266 ) {2267 Ok(imbalance) => Ok((fee, Some(imbalance))),2268 Err(_) => Err(InvalidTransaction::Payment.into()),2269 }2270 }2271}227222732274impl<T: Trait + Send + Sync> SignedExtension2275 for ChargeTransactionPayment<T>2276where2277 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2278 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2279{2280 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2281 type AccountId = T::AccountId;2282 type Call = T::Call;2283 type AdditionalSigned = ();2284 type Pre = (2285 BalanceOf<T>,2286 Self::AccountId,2287 Option<NegativeImbalanceOf<T>>,2288 BalanceOf<T>,2289 );2290 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2291 Ok(())2292 }22932294 fn validate(2295 &self,2296 _who: &Self::AccountId,2297 _call: &Self::Call,2298 _info: &DispatchInfoOf<Self::Call>,2299 _len: usize,2300 ) -> TransactionValidity {2301 Ok(ValidTransaction::default())2302 }23032304 fn pre_dispatch(2305 self,2306 who: &Self::AccountId,2307 call: &Self::Call,2308 info: &DispatchInfoOf<Self::Call>,2309 len: usize,2310 ) -> Result<Self::Pre, TransactionValidityError> {2311 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2312 Ok((self.0, who.clone(), imbalance, fee))2313 }23142315 fn post_dispatch(2316 pre: Self::Pre,2317 info: &DispatchInfoOf<Self::Call>,2318 post_info: &PostDispatchInfoOf<Self::Call>,2319 len: usize,2320 _result: &DispatchResult,2321 ) -> Result<(), TransactionValidityError> {2322 let (tip, who, imbalance, fee) = pre;2323 if let Some(payed) = imbalance {2324 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2325 len as u32, info, post_info, tip,2326 );2327 let refund = fee.saturating_sub(actual_fee);2328 let actual_payment =2329 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2330 &who, refund,2331 ) {2332 Ok(refund_imbalance) => {2333 // The refund cannot be larger than the up front payed max weight.2334 // `PostDispatchInfo::calc_unspent` guards against such a case.2335 match payed.offset(refund_imbalance) {2336 Ok(actual_payment) => actual_payment,2337 Err(_) => return Err(InvalidTransaction::Payment.into()),2338 }2339 }2340 // We do not recreate the account using the refund. The up front payment2341 // is gone in that case.2342 Err(_) => payed,2343 };2344 let imbalances = actual_payment.split(tip);2345 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2346 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2347 );2348 }2349 Ok(())2350 }2351}23522353// #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;4748// Structs49// #region5051#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]52#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]53pub enum CollectionMode {54 Invalid,55 NFT,56 // decimal points57 Fungible(u32),58 // decimal points59 ReFungible(u32),60}6162impl Into<u8> for CollectionMode {63 fn into(self) -> u8 {64 match self {65 CollectionMode::Invalid => 0,66 CollectionMode::NFT => 1,67 CollectionMode::Fungible(_) => 2,68 CollectionMode::ReFungible(_) => 3,69 }70 }71}7273#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]74#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]75pub enum AccessMode {76 Normal,77 WhiteList,78}79impl Default for AccessMode {80 fn default() -> Self {81 Self::Normal82 }83}8485impl Default for CollectionMode {86 fn default() -> Self {87 Self::Invalid88 }89}9091#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]92#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]93pub struct Ownership<AccountId> {94 pub owner: AccountId,95 pub fraction: u128,96}9798#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]99#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]100pub struct CollectionType<AccountId> {101 pub owner: AccountId,102 pub mode: CollectionMode,103 pub access: AccessMode,104 pub decimal_points: u32,105 pub name: Vec<u16>, // 64 include null escape char106 pub description: Vec<u16>, // 256 include null escape char107 pub token_prefix: Vec<u8>, // 16 include null escape char108 pub mint_mode: bool,109 pub offchain_schema: Vec<u8>,110 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender111 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship112 pub variable_on_chain_schema: Vec<u8>, //113 pub const_on_chain_schema: Vec<u8>, //114}115116#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]117#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]118pub struct NftItemType<AccountId> {119 pub collection: u64,120 pub owner: AccountId,121 pub const_data: Vec<u8>,122 pub variable_data: Vec<u8>,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct FungibleItemType<AccountId> {128 pub collection: u64,129 pub owner: AccountId,130 pub value: u128,131}132133#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]134#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]135pub struct ReFungibleItemType<AccountId> {136 pub collection: u64,137 pub owner: Vec<Ownership<AccountId>>,138 pub const_data: Vec<u8>,139 pub variable_data: Vec<u8>,140}141142#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]143#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]144pub struct ApprovePermissions<AccountId> {145 pub approved: AccountId,146 pub amount: u64,147}148149#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]150#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]151pub struct VestingItem<AccountId, Moment> {152 pub sender: AccountId,153 pub recipient: AccountId,154 pub collection_id: u64,155 pub item_id: u64,156 pub amount: u64,157 pub vesting_date: Moment,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct BasketItem<AccountId, BlockNumber> {163 pub address: AccountId,164 pub start_block: BlockNumber,165}166167#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169pub struct ChainLimits {170 pub collection_numbers_limit: u64,171 pub account_token_ownership_limit: u64,172 pub collections_admins_limit: u64,173 pub custom_data_limit: u32,174175 // Timeouts for item types in passed blocks176 pub nft_sponsor_transfer_timeout: u32,177 pub fungible_sponsor_transfer_timeout: u32,178 pub refungible_sponsor_transfer_timeout: u32,179}180181pub trait WeightInfo {182 fn create_collection() -> Weight;183 fn destroy_collection() -> Weight;184 fn add_to_white_list() -> Weight;185 fn remove_from_white_list() -> Weight;186 fn set_public_access_mode() -> Weight;187 fn set_mint_permission() -> Weight;188 fn change_collection_owner() -> Weight;189 fn add_collection_admin() -> Weight;190 fn remove_collection_admin() -> Weight;191 fn set_collection_sponsor() -> Weight;192 fn confirm_sponsorship() -> Weight;193 fn remove_collection_sponsor() -> Weight;194 fn create_item(s: usize) -> Weight;195 fn burn_item() -> Weight;196 fn transfer() -> Weight;197 fn approve() -> Weight;198 fn transfer_from() -> Weight;199 fn set_offchain_schema() -> Weight;200 fn set_const_on_chain_schema() -> Weight;201 fn set_variable_on_chain_schema() -> Weight;202 fn set_variable_meta_data() -> Weight;203 // fn enable_contract_sponsoring() -> Weight;204}205206#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]207#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]208pub struct CreateNftData {209 pub const_data: Vec<u8>,210 pub variable_data: Vec<u8>,211}212213#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]214#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]215pub struct CreateFungibleData {216}217218#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]219#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]220pub struct CreateReFungibleData {221 pub const_data: Vec<u8>,222 pub variable_data: Vec<u8>,223}224225#[derive(Encode, Decode, Debug, Clone, PartialEq)]226#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]227pub enum CreateItemData {228 NFT(CreateNftData),229 Fungible(CreateFungibleData),230 ReFungible(CreateReFungibleData)231}232233impl CreateItemData {234 pub fn len(&self) -> usize {235 let len = match self {236 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),237 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),238 _ => 0239 };240 241 return len;242 }243}244245impl From<CreateNftData> for CreateItemData {246 fn from(item: CreateNftData) -> Self {247 CreateItemData::NFT(item)248 }249}250251impl From<CreateReFungibleData> for CreateItemData {252 fn from(item: CreateReFungibleData) -> Self {253 CreateItemData::ReFungible(item)254 }255}256257impl From<CreateFungibleData> for CreateItemData {258 fn from(item: CreateFungibleData) -> Self {259 CreateItemData::Fungible(item)260 }261}262263264decl_error! {265 /// Error for non-fungible-token module.266 pub enum Error for Module<T: Trait> {267 /// Total collections bound exceeded268 TotalCollectionsLimitExceeded,269 /// Decimal_points parameter must be lower than 4270 CollectionDecimalPointLimitExceeded, 271 /// Collection name can not be longer than 63 char272 CollectionNameLimitExceeded, 273 /// Collection description can not be longer than 255 char274 CollectionDescriptionLimitExceeded, 275 /// Token prefix can not be longer than 15 char276 CollectionTokenPrefixLimitExceeded,277 /// This collection does not exist278 CollectionNotFound,279 /// Item not exists280 TokenNotFound,281 /// Arithmetic calculation overflow282 NumOverflow, 283 /// Account already has admin role284 AlreadyAdmin, 285 /// You do not own this collection286 NoPermission,287 /// This address is not set as sponsor, use setCollectionSponsor first288 ConfirmUnsetSponsorFail,289 /// Collection is not in mint mode290 PublicMintingNotAllowed,291 /// Sender parameter and item owner must be equal292 MustBeTokenOwner,293 /// Item balance not enouth294 TokenValueTooLow,295 /// Size of item is too large296 NftSizeLimitExceeded,297 /// Size of item must be 0 with fungible type298 FungibleUnexpectedParam,299 /// No approve found300 ApproveNotFound,301 /// Requested value more than approved302 TokenValueNotEnough,303 /// Only approved addresses can call this method304 ApproveRequired,305 /// Address is not in white list306 AddresNotInWhiteList,307 /// Number of collection admins bound exceeded308 CollectionAdminsLimitExceeded,309 /// Owned tokens by a single address bound exceeded310 AddressOwnershipLimitExceeded,311 /// Length of items properties must be greater than 0312 EmptyArgument,313 }314}315316pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {317 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;318319 /// Weight information for extrinsics in this pallet.320 type WeightInfo: WeightInfo;321}322323#[cfg(feature = "runtime-benchmarks")]324mod benchmarking;325326// #endregion327328decl_storage! {329 trait Store for Module<T: Trait> as Nft {330331 // Private members332 NextCollectionID: u64;333 CreatedCollectionCount: u64;334 ChainVersion: u64;335 ItemListIndex: map hasher(blake2_128_concat) u64 => u64;336337 // Chain limits struct338 pub ChainLimit get(fn chain_limit) config(): ChainLimits;339340 // Bound counters341 CollectionCount: u64;342 pub AccountItemCount get(fn account_item_count): map hasher(identity) T::AccountId => u64;343344 // Basic collections345 pub Collection get(fn collection) config(): map hasher(identity) u64 => CollectionType<T::AccountId>;346 pub AdminList get(fn admin_list_collection): map hasher(identity) u64 => Vec<T::AccountId>;347 pub WhiteList get(fn white_list): map hasher(identity) u64 => Vec<T::AccountId>;348349 /// Balance owner per collection map350 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => u64;351352 /// second parameter: item id + owner account id353 pub ApprovedList get(fn approved): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) (u64, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;354355 /// Item collections356 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => NftItemType<T::AccountId>;357 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => FungibleItemType<T::AccountId>;358 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => ReFungibleItemType<T::AccountId>;359360 /// Index list361 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) T::AccountId => Vec<u64>;362363 /// Tokens transfer baskets364 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;365 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => Vec<BasketItem<T::AccountId, T::BlockNumber>>;366 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) u64, hasher(blake2_128_concat) u64 => T::BlockNumber;367368 // Contract Sponsorship and Ownership369 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;370 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;371 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;372 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;373 }374 add_extra_genesis {375 build(|config: &GenesisConfig<T>| {376 // Modification of storage377 for (_num, _c) in &config.collection {378 <Module<T>>::init_collection(_c);379 }380381 for (_num, _q, _i) in &config.nft_item_id {382 <Module<T>>::init_nft_token(_i);383 }384385 for (_num, _q, _i) in &config.fungible_item_id {386 <Module<T>>::init_fungible_token(_i);387 }388389 for (_num, _q, _i) in &config.refungible_item_id {390 <Module<T>>::init_refungible_token(_i);391 }392 })393 }394}395396decl_event!(397 pub enum Event<T>398 where399 AccountId = <T as system::Trait>::AccountId,400 {401 /// New collection was created402 /// 403 /// # Arguments404 /// 405 /// * collection_id: Globally unique identifier of newly created collection.406 /// 407 /// * mode: [CollectionMode] converted into u8.408 /// 409 /// * account_id: Collection owner.410 Created(u64, u8, AccountId),411412 /// New item was created.413 /// 414 /// # Arguments415 /// 416 /// * collection_id: Id of the collection where item was created.417 /// 418 /// * item_id: Id of an item. Unique within the collection.419 ItemCreated(u64, u64),420421 /// Collection item was burned.422 /// 423 /// # Arguments424 /// 425 /// collection_id.426 /// 427 /// item_id: Identifier of burned NFT.428 ItemDestroyed(u64, u64),429 }430);431432decl_module! {433 pub struct Module<T: Trait> for enum Call where origin: T::Origin {434435 fn deposit_event() = default;436 type Error = Error<T>;437438 fn on_initialize(now: T::BlockNumber) -> Weight {439440 if ChainVersion::get() < 2441 {442 let value = NextCollectionID::get();443 CreatedCollectionCount::put(value);444 ChainVersion::put(2);445 }446447 0448 }449450 /// 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.451 /// 452 /// # Permissions453 /// 454 /// * Anyone.455 /// 456 /// # Arguments457 /// 458 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.459 /// 460 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.461 /// 462 /// * token_prefix: UTF-8 string with token prefix.463 /// 464 /// * mode: [CollectionMode] collection type and type dependent data.465 // returns collection ID466 #[weight = T::WeightInfo::create_collection()]467 pub fn create_collection(origin,468 collection_name: Vec<u16>,469 collection_description: Vec<u16>,470 token_prefix: Vec<u8>,471 mode: CollectionMode) -> DispatchResult {472473 // Anyone can create a collection474 let who = ensure_signed(origin)?;475476 let decimal_points = match mode {477 CollectionMode::Fungible(points) => points,478 CollectionMode::ReFungible(points) => points,479 _ => 0480 };481482 // bound Total number of collections483 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);484485 // check params486 ensure!(decimal_points <= 4, Error::<T>::CollectionDecimalPointLimitExceeded);487488 let mut name = collection_name.to_vec();489 name.push(0);490 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);491492 let mut description = collection_description.to_vec();493 description.push(0);494 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);495496 let mut prefix = token_prefix.to_vec();497 prefix.push(0);498 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);499500 // Generate next collection ID501 let next_id = CreatedCollectionCount::get()502 .checked_add(1)503 .ok_or(Error::<T>::NumOverflow)?;504505 // bound counter506 let total = CollectionCount::get()507 .checked_add(1)508 .ok_or(Error::<T>::NumOverflow)?;509510 CreatedCollectionCount::put(next_id);511 CollectionCount::put(total);512513 // Create new collection514 let new_collection = CollectionType {515 owner: who.clone(),516 name: name,517 mode: mode.clone(),518 mint_mode: false,519 access: AccessMode::Normal,520 description: description,521 decimal_points: decimal_points,522 token_prefix: prefix,523 offchain_schema: Vec::new(),524 sponsor: T::AccountId::default(),525 unconfirmed_sponsor: T::AccountId::default(),526 variable_on_chain_schema: Vec::new(),527 const_on_chain_schema: Vec::new(),528 };529530 // Add new collection to map531 <Collection<T>>::insert(next_id, new_collection);532533 // call event534 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));535536 Ok(())537 }538539 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.540 /// 541 /// # Permissions542 /// 543 /// * Collection Owner.544 /// 545 /// # Arguments546 /// 547 /// * collection_id: collection to destroy.548 #[weight = T::WeightInfo::destroy_collection()]549 pub fn destroy_collection(origin, collection_id: u64) -> DispatchResult {550551 let sender = ensure_signed(origin)?;552 Self::check_owner_permissions(collection_id, sender)?;553554 <AddressTokens<T>>::remove_prefix(collection_id);555 <ApprovedList<T>>::remove_prefix(collection_id);556 <Balance<T>>::remove_prefix(collection_id);557 <ItemListIndex>::remove(collection_id);558 <AdminList<T>>::remove(collection_id);559 <Collection<T>>::remove(collection_id);560 <WhiteList<T>>::remove(collection_id);561562 <NftItemList<T>>::remove_prefix(collection_id);563 <FungibleItemList<T>>::remove_prefix(collection_id);564 <ReFungibleItemList<T>>::remove_prefix(collection_id);565566 <NftTransferBasket<T>>::remove_prefix(collection_id);567 <FungibleTransferBasket<T>>::remove_prefix(collection_id);568 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);569570 if CollectionCount::get() > 0571 {572 // bound couter573 let total = CollectionCount::get()574 .checked_sub(1)575 .ok_or(Error::<T>::NumOverflow)?;576577 CollectionCount::put(total);578 }579580 Ok(())581 }582583 /// Add an address to white list.584 /// 585 /// # Permissions586 /// 587 /// * Collection Owner588 /// * Collection Admin589 /// 590 /// # Arguments591 /// 592 /// * collection_id.593 /// 594 /// * address.595 #[weight = T::WeightInfo::add_to_white_list()]596 pub fn add_to_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{597598 let sender = ensure_signed(origin)?;599 Self::check_owner_or_admin_permissions(collection_id, sender)?;600601 let mut white_list_collection: Vec<T::AccountId>;602 if <WhiteList<T>>::contains_key(collection_id) {603 white_list_collection = <WhiteList<T>>::get(collection_id);604 if !white_list_collection.contains(&address.clone())605 {606 white_list_collection.push(address.clone());607 }608 }609 else {610 white_list_collection = Vec::new();611 white_list_collection.push(address.clone());612 }613614 <WhiteList<T>>::insert(collection_id, white_list_collection);615 Ok(())616 }617618 /// Remove an address from white list.619 /// 620 /// # Permissions621 /// 622 /// * Collection Owner623 /// * Collection Admin624 /// 625 /// # Arguments626 /// 627 /// * collection_id.628 /// 629 /// * address.630 #[weight = T::WeightInfo::remove_from_white_list()]631 pub fn remove_from_white_list(origin, collection_id: u64, address: T::AccountId) -> DispatchResult{632633 let sender = ensure_signed(origin)?;634 Self::check_owner_or_admin_permissions(collection_id, sender)?;635636 if <WhiteList<T>>::contains_key(collection_id) {637 let mut white_list_collection = <WhiteList<T>>::get(collection_id);638 if white_list_collection.contains(&address.clone())639 {640 white_list_collection.retain(|i| *i != address.clone());641 <WhiteList<T>>::insert(collection_id, white_list_collection);642 }643 }644645 Ok(())646 }647648 /// Toggle between normal and white list access for the methods with access for `Anyone`.649 /// 650 /// # Permissions651 /// 652 /// * Collection Owner.653 /// 654 /// # Arguments655 /// 656 /// * collection_id.657 /// 658 /// * mode: [AccessMode]659 #[weight = T::WeightInfo::set_public_access_mode()]660 pub fn set_public_access_mode(origin, collection_id: u64, mode: AccessMode) -> DispatchResult661 {662 let sender = ensure_signed(origin)?;663664 Self::check_owner_permissions(collection_id, sender)?;665 let mut target_collection = <Collection<T>>::get(collection_id);666 target_collection.access = mode;667 <Collection<T>>::insert(collection_id, target_collection);668669 Ok(())670 }671672 /// Allows Anyone to create tokens if:673 /// * White List is enabled, and674 /// * Address is added to white list, and675 /// * This method was called with True parameter676 /// 677 /// # Permissions678 /// * Collection Owner679 ///680 /// # Arguments681 /// 682 /// * collection_id.683 /// 684 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.685 #[weight = T::WeightInfo::set_mint_permission()]686 pub fn set_mint_permission(origin, collection_id: u64, mint_permission: bool) -> DispatchResult687 {688 let sender = ensure_signed(origin)?;689690 Self::check_owner_permissions(collection_id, sender)?;691 let mut target_collection = <Collection<T>>::get(collection_id);692 target_collection.mint_mode = mint_permission;693 <Collection<T>>::insert(collection_id, target_collection);694695 Ok(())696 }697698 /// Change the owner of the collection.699 /// 700 /// # Permissions701 /// 702 /// * Collection Owner.703 /// 704 /// # Arguments705 /// 706 /// * collection_id.707 /// 708 /// * new_owner.709 #[weight = T::WeightInfo::change_collection_owner()]710 pub fn change_collection_owner(origin, collection_id: u64, new_owner: T::AccountId) -> DispatchResult {711712 let sender = ensure_signed(origin)?;713 Self::check_owner_permissions(collection_id, sender)?;714 let mut target_collection = <Collection<T>>::get(collection_id);715 target_collection.owner = new_owner;716 <Collection<T>>::insert(collection_id, target_collection);717718 Ok(())719 }720721 /// Adds an admin of the Collection.722 /// 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. 723 /// 724 /// # Permissions725 /// 726 /// * Collection Owner.727 /// * Collection Admin.728 /// 729 /// # Arguments730 /// 731 /// * collection_id: ID of the Collection to add admin for.732 /// 733 /// * new_admin_id: Address of new admin to add.734 #[weight = T::WeightInfo::add_collection_admin()]735 pub fn add_collection_admin(origin, collection_id: u64, new_admin_id: T::AccountId) -> DispatchResult {736737 let sender = ensure_signed(origin)?;738 Self::check_owner_or_admin_permissions(collection_id, sender)?;739 let mut admin_arr: Vec<T::AccountId> = Vec::new();740741 if <AdminList<T>>::contains_key(collection_id)742 {743 admin_arr = <AdminList<T>>::get(collection_id);744 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);745 }746747 // Number of collection admins748 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);749750 admin_arr.push(new_admin_id);751 <AdminList<T>>::insert(collection_id, admin_arr);752753 Ok(())754 }755756 /// 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.757 ///758 /// # Permissions759 /// 760 /// * Collection Owner.761 /// * Collection Admin.762 /// 763 /// # Arguments764 /// 765 /// * collection_id: ID of the Collection to remove admin for.766 /// 767 /// * account_id: Address of admin to remove.768 #[weight = T::WeightInfo::remove_collection_admin()]769 pub fn remove_collection_admin(origin, collection_id: u64, account_id: T::AccountId) -> DispatchResult {770771 let sender = ensure_signed(origin)?;772 Self::check_owner_or_admin_permissions(collection_id, sender)?;773774 if <AdminList<T>>::contains_key(collection_id)775 {776 let mut admin_arr = <AdminList<T>>::get(collection_id);777 admin_arr.retain(|i| *i != account_id);778 <AdminList<T>>::insert(collection_id, admin_arr);779 }780781 Ok(())782 }783784 /// # Permissions785 /// 786 /// * Collection Owner787 /// 788 /// # Arguments789 /// 790 /// * collection_id.791 /// 792 /// * new_sponsor.793 #[weight = T::WeightInfo::set_collection_sponsor()]794 pub fn set_collection_sponsor(origin, collection_id: u64, new_sponsor: T::AccountId) -> DispatchResult {795796 let sender = ensure_signed(origin)?;797 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);798799 let mut target_collection = <Collection<T>>::get(collection_id);800 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);801802 target_collection.unconfirmed_sponsor = new_sponsor;803 <Collection<T>>::insert(collection_id, target_collection);804805 Ok(())806 }807808 /// # Permissions809 /// 810 /// * Sponsor.811 /// 812 /// # Arguments813 /// 814 /// * collection_id.815 #[weight = T::WeightInfo::confirm_sponsorship()]816 pub fn confirm_sponsorship(origin, collection_id: u64) -> DispatchResult {817818 let sender = ensure_signed(origin)?;819 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);820821 let mut target_collection = <Collection<T>>::get(collection_id);822 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);823824 target_collection.sponsor = target_collection.unconfirmed_sponsor;825 target_collection.unconfirmed_sponsor = T::AccountId::default();826 <Collection<T>>::insert(collection_id, target_collection);827828 Ok(())829 }830831 /// Switch back to pay-per-own-transaction model.832 ///833 /// # Permissions834 ///835 /// * Collection owner.836 /// 837 /// # Arguments838 /// 839 /// * collection_id.840 #[weight = T::WeightInfo::remove_collection_sponsor()]841 pub fn remove_collection_sponsor(origin, collection_id: u64) -> DispatchResult {842843 let sender = ensure_signed(origin)?;844 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);845846 let mut target_collection = <Collection<T>>::get(collection_id);847 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);848849 target_collection.sponsor = T::AccountId::default();850 <Collection<T>>::insert(collection_id, target_collection);851852 Ok(())853 }854855 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.856 /// 857 /// # Permissions858 /// 859 /// * Collection Owner.860 /// * Collection Admin.861 /// * Anyone if862 /// * White List is enabled, and863 /// * Address is added to white list, and864 /// * MintPermission is enabled (see SetMintPermission method)865 /// 866 /// # Arguments867 /// 868 /// * collection_id: ID of the collection.869 /// 870 /// * owner: Address, initial owner of the NFT.871 ///872 /// * data: Token data to store on chain.873 // #[weight =874 // (130_000_000 as Weight)875 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))876 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))877 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]878879 #[weight = T::WeightInfo::create_item(data.len())]880 pub fn create_item(origin, collection_id: u64, owner: T::AccountId, data: CreateItemData) -> DispatchResult {881882 let sender = ensure_signed(origin)?;883884 Self::collection_exists(collection_id)?;885886 let target_collection = <Collection<T>>::get(collection_id);887888 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;889 Self::validate_create_item_args(&target_collection, &data)?;890 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;891892 Ok(())893 }894895 /// This method creates multiple instances of NFT Collection created with CreateCollection method.896 /// 897 /// # Permissions898 /// 899 /// * Collection Owner.900 /// * Collection Admin.901 /// * Anyone if902 /// * White List is enabled, and903 /// * Address is added to white list, and904 /// * MintPermission is enabled (see SetMintPermission method)905 /// 906 /// # Arguments907 /// 908 /// * collection_id: ID of the collection.909 /// 910 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].911 /// 912 /// * owner: Address, initial owner of the NFT.913 #[weight = T::WeightInfo::create_item(items_data.into_iter()914 .map(|data| { data.len() })915 .sum())]916 pub fn create_multiple_items(origin, collection_id: u64, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {917918 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);919 let sender = ensure_signed(origin)?;920921 Self::collection_exists(collection_id)?;922 let target_collection = <Collection<T>>::get(collection_id);923924 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;925926 for data in &items_data {927 Self::validate_create_item_args(&target_collection, data)?;928 }929 for data in &items_data {930 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;931 }932933 Ok(())934 }935936 /// Destroys a concrete instance of NFT.937 /// 938 /// # Permissions939 /// 940 /// * Collection Owner.941 /// * Collection Admin.942 /// * Current NFT Owner.943 /// 944 /// # Arguments945 /// 946 /// * collection_id: ID of the collection.947 /// 948 /// * item_id: ID of NFT to burn.949 #[weight = T::WeightInfo::burn_item()]950 pub fn burn_item(origin, collection_id: u64, item_id: u64) -> DispatchResult {951952 let sender = ensure_signed(origin)?;953 Self::collection_exists(collection_id)?;954955 // Transfer permissions check956 let target_collection = <Collection<T>>::get(collection_id);957 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||958 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),959 Error::<T>::NoPermission);960961 if target_collection.access == AccessMode::WhiteList {962 Self::check_white_list(collection_id, &sender)?;963 }964965 match target_collection.mode966 {967 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,968 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,969 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,970 _ => ()971 };972973 // call event974 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));975976 Ok(())977 }978979 /// Change ownership of the token.980 /// 981 /// # Permissions982 /// 983 /// * Collection Owner984 /// * Collection Admin985 /// * Current NFT owner986 ///987 /// # Arguments988 /// 989 /// * recipient: Address of token recipient.990 /// 991 /// * collection_id.992 /// 993 /// * item_id: ID of the item994 /// * Non-Fungible Mode: Required.995 /// * Fungible Mode: Ignored.996 /// * Re-Fungible Mode: Required.997 /// 998 /// * value: Amount to transfer.999 /// * Non-Fungible Mode: Ignored1000 /// * Fungible Mode: Must specify transferred amount1001 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1002 #[weight = T::WeightInfo::transfer()]1003 pub fn transfer(origin, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64) -> DispatchResult {10041005 let sender = ensure_signed(origin)?;10061007 // Transfer permissions check1008 let target_collection = <Collection<T>>::get(collection_id);1009 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1010 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1011 Error::<T>::NoPermission);10121013 if target_collection.access == AccessMode::WhiteList {1014 Self::check_white_list(collection_id, &sender)?;1015 Self::check_white_list(collection_id, &recipient)?;1016 }10171018 match target_collection.mode1019 {1020 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1021 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1022 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1023 _ => ()1024 };10251026 Ok(())1027 }10281029 /// Set, change, or remove approved address to transfer the ownership of the NFT.1030 /// 1031 /// # Permissions1032 /// 1033 /// * Collection Owner1034 /// * Collection Admin1035 /// * Current NFT owner1036 /// 1037 /// # Arguments1038 /// 1039 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1040 /// 1041 /// * collection_id.1042 /// 1043 /// * item_id: ID of the item.1044 #[weight = T::WeightInfo::approve()]1045 pub fn approve(origin, approved: T::AccountId, collection_id: u64, item_id: u64) -> DispatchResult {10461047 let sender = ensure_signed(origin)?;10481049 // Transfer permissions check1050 let target_collection = <Collection<T>>::get(collection_id);1051 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1052 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1053 Error::<T>::NoPermission);10541055 if target_collection.access == AccessMode::WhiteList {1056 Self::check_white_list(collection_id, &sender)?;1057 Self::check_white_list(collection_id, &approved)?;1058 }10591060 // amount param stub1061 let amount = 100000000;10621063 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1064 if list_exists {10651066 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1067 let item_contains = list.iter().any(|i| i.approved == approved);10681069 if !item_contains {1070 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1071 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1072 }1073 } else {10741075 let mut list = Vec::new();1076 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1077 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1078 }10791080 Ok(())1081 }1082 1083 /// 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.1084 /// 1085 /// # Permissions1086 /// * Collection Owner1087 /// * Collection Admin1088 /// * Current NFT owner1089 /// * Address approved by current NFT owner1090 /// 1091 /// # Arguments1092 /// 1093 /// * from: Address that owns token.1094 /// 1095 /// * recipient: Address of token recipient.1096 /// 1097 /// * collection_id.1098 /// 1099 /// * item_id: ID of the item.1100 /// 1101 /// * value: Amount to transfer.1102 #[weight = T::WeightInfo::transfer_from()]1103 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: u64, item_id: u64, value: u64 ) -> DispatchResult {11041105 let sender = ensure_signed(origin)?;1106 let mut appoved_transfer = false;11071108 // Check approve1109 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1110 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1111 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1112 if opt_item.is_some()1113 {1114 appoved_transfer = true;1115 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1116 }1117 }11181119 // Transfer permissions check1120 let target_collection = <Collection<T>>::get(collection_id);1121 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1122 Error::<T>::NoPermission);11231124 if target_collection.access == AccessMode::WhiteList {1125 Self::check_white_list(collection_id, &sender)?;1126 Self::check_white_list(collection_id, &recipient)?;1127 }11281129 // remove approve1130 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1131 .into_iter().filter(|i| i.approved != sender.clone()).collect();1132 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);113311341135 match target_collection.mode1136 {1137 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1138 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1139 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1140 _ => ()1141 };11421143 Ok(())1144 }11451146 ///1147 #[weight = 0]1148 pub fn safe_transfer_from(origin, collection_id: u64, item_id: u64, new_owner: T::AccountId) -> DispatchResult {11491150 // let no_perm_mes = "You do not have permissions to modify this collection";1151 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1152 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1153 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11541155 // // on_nft_received call11561157 // Self::transfer(origin, collection_id, item_id, new_owner)?;11581159 Ok(())1160 }11611162 /// Set off-chain data schema.1163 /// 1164 /// # Permissions1165 /// 1166 /// * Collection Owner1167 /// * Collection Admin1168 /// 1169 /// # Arguments1170 /// 1171 /// * collection_id.1172 /// 1173 /// * schema: String representing the offchain data schema.1174 #[weight = T::WeightInfo::set_variable_meta_data()]1175 pub fn set_variable_meta_data (1176 origin,1177 collection_id: u64,1178 item_id: u64,1179 data: Vec<u8>1180 ) -> DispatchResult {1181 let sender = ensure_signed(origin)?;1182 1183 Self::collection_exists(collection_id)?;11841185 // Modify permissions check1186 let target_collection = <Collection<T>>::get(collection_id);1187 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1188 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1189 Error::<T>::NoPermission);11901191 Self::item_exists(collection_id, item_id, &target_collection.mode)?;11921193 match target_collection.mode1194 {1195 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1196 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1197 _ => ()1198 };11991200 Ok(())1201 }1202 12031204 /// Set off-chain data schema.1205 /// 1206 /// # Permissions1207 /// 1208 /// * Collection Owner1209 /// * Collection Admin1210 /// 1211 /// # Arguments1212 /// 1213 /// * collection_id.1214 /// 1215 /// * schema: String representing the offchain data schema.1216 #[weight = T::WeightInfo::set_offchain_schema()]1217 pub fn set_offchain_schema(1218 origin,1219 collection_id: u64,1220 schema: Vec<u8>1221 ) -> DispatchResult {1222 let sender = ensure_signed(origin)?;1223 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12241225 let mut target_collection = <Collection<T>>::get(collection_id);1226 target_collection.offchain_schema = schema;1227 <Collection<T>>::insert(collection_id, target_collection);12281229 Ok(())1230 }12311232 /// Set const on-chain data schema.1233 /// 1234 /// # Permissions1235 /// 1236 /// * Collection Owner1237 /// * Collection Admin1238 /// 1239 /// # Arguments1240 /// 1241 /// * collection_id.1242 /// 1243 /// * schema: String representing the const on-chain data schema.1244 #[weight = T::WeightInfo::set_const_on_chain_schema()]1245 pub fn set_const_on_chain_schema (1246 origin,1247 collection_id: u64,1248 schema: Vec<u8>1249 ) -> DispatchResult {1250 let sender = ensure_signed(origin)?;1251 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12521253 let mut target_collection = <Collection<T>>::get(collection_id);1254 target_collection.const_on_chain_schema = schema;1255 <Collection<T>>::insert(collection_id, target_collection);12561257 Ok(())1258 }12591260 /// Set variable on-chain data schema.1261 /// 1262 /// # Permissions1263 /// 1264 /// * Collection Owner1265 /// * Collection Admin1266 /// 1267 /// # Arguments1268 /// 1269 /// * collection_id.1270 /// 1271 /// * schema: String representing the variable on-chain data schema.1272 #[weight = T::WeightInfo::set_const_on_chain_schema()]1273 pub fn set_variable_on_chain_schema (1274 origin,1275 collection_id: u64,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.variable_on_chain_schema = schema;1283 <Collection<T>>::insert(collection_id, target_collection);12841285 Ok(())1286 }12871288 // Sudo permissions function1289 #[weight = 0]1290 pub fn set_chain_limits(1291 origin,1292 limits: ChainLimits1293 ) -> DispatchResult {1294 ensure_root(origin)?;1295 <ChainLimit>::put(limits);1296 Ok(())1297 }12981299 /// Enable smart contract self-sponsoring.1300 /// 1301 /// # Permissions1302 /// 1303 /// * Contract Owner1304 /// 1305 /// # Arguments1306 /// 1307 /// * contract address1308 /// * enable flag1309 /// 1310 #[weight = 0]1311 pub fn enable_contract_sponsoring(1312 origin,1313 contract_address: T::AccountId,1314 enable: bool1315 ) -> DispatchResult {1316 let sender = ensure_signed(origin)?;1317 let mut is_owner = false;1318 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1319 let owner = <ContractOwner<T>>::get(&contract_address);1320 is_owner = sender == owner;1321 }1322 ensure!(is_owner, Error::<T>::NoPermission);13231324 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1325 Ok(())1326 }13271328 /// Set the rate limit for contract sponsoring to specified number of blocks.1329 /// 1330 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1331 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1332 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1333 /// from contract endowment if there are at least B blocks between such transactions. 1334 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1335 /// 1336 /// # Permissions1337 /// 1338 /// * Contract Owner1339 /// 1340 /// # Arguments1341 /// 1342 /// -`contract_address`: Address of the contract to sponsor1343 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1344 /// 1345 #[weight = 0]1346 pub fn set_contract_sponsoring_rate_limit(1347 origin,1348 contract_address: T::AccountId,1349 rate_limit: T::BlockNumber1350 ) -> DispatchResult {1351 let sender = ensure_signed(origin)?;1352 let mut is_owner = false;1353 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1354 let owner = <ContractOwner<T>>::get(&contract_address);1355 is_owner = sender == owner;1356 }1357 ensure!(is_owner, Error::<T>::NoPermission);13581359 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1360 Ok(())1361 }13621363 }1364}13651366impl<T: Trait> Module<T> {13671368 fn can_create_items_in_collection(collection_id: u64, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {13691370 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1371 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1372 Self::check_white_list(collection_id, owner)?;1373 Self::check_white_list(collection_id, sender)?;1374 }13751376 Ok(())1377 }13781379 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1380 match target_collection.mode1381 {1382 CollectionMode::NFT => {1383 if let CreateItemData::NFT(data) = data {1384 // check sizes1385 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1386 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1387 } else {1388 fail!("Not NFT item data used to mint in NFT collection.");1389 }1390 },1391 CollectionMode::Fungible(_) => {1392 if let CreateItemData::Fungible(_) = data {1393 } else {1394 fail!("Not Fungible item data used to mint in Fungible collection.");1395 }1396 },1397 CollectionMode::ReFungible(_) => {1398 if let CreateItemData::ReFungible(data) = data {13991400 // check sizes1401 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, "const_data exceeded data limit.");1402 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, "variable_data exceeded data limit.");1403 } else {1404 fail!("Not Re Fungible item data used to mint in Re Fungible collection.");1405 }1406 },1407 _ => { fail!("Unexpected collection type."); }1408 };14091410 Ok(())1411 }14121413 fn create_item_no_validation(collection_id: u64, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1414 match data1415 {1416 CreateItemData::NFT(data) => {1417 let item = NftItemType {1418 collection: collection_id,1419 owner,1420 const_data: data.const_data,1421 variable_data: data.variable_data1422 };14231424 Self::add_nft_item(item)?;1425 },1426 CreateItemData::Fungible(_) => {1427 let item = FungibleItemType {1428 collection: collection_id,1429 owner,1430 value: (10 as u128).pow(collection.decimal_points)1431 };14321433 Self::add_fungible_item(item)?;1434 },1435 CreateItemData::ReFungible(data) => {1436 let mut owner_list = Vec::new();1437 let value = (10 as u128).pow(collection.decimal_points);1438 owner_list.push(Ownership {owner: owner.clone(), fraction: value});14391440 let item = ReFungibleItemType {1441 collection: collection_id,1442 owner: owner_list,1443 const_data: data.const_data,1444 variable_data: data.variable_data1445 };14461447 Self::add_refungible_item(item)?;1448 }1449 };145014511452 // call event1453 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));14541455 Ok(())1456 }14571458 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1459 let current_index = <ItemListIndex>::get(item.collection)1460 .checked_add(1)1461 .ok_or(Error::<T>::NumOverflow)?;1462 let itemcopy = item.clone();1463 let owner = item.owner.clone();1464 let value = item.value as u64;14651466 Self::add_token_index(item.collection, current_index, owner.clone())?;14671468 <ItemListIndex>::insert(item.collection, current_index);1469 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14701471 // Add current block1472 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1473 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1474 1475 // Update balance1476 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1477 .checked_add(value)1478 .ok_or(Error::<T>::NumOverflow)?;1479 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);14801481 Ok(())1482 }14831484 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1485 let current_index = <ItemListIndex>::get(item.collection)1486 .checked_add(1)1487 .ok_or(Error::<T>::NumOverflow)?;1488 let itemcopy = item.clone();14891490 let value = item.owner.first().unwrap().fraction as u64;1491 let owner = item.owner.first().unwrap().owner.clone();14921493 Self::add_token_index(item.collection, current_index, owner.clone())?;14941495 <ItemListIndex>::insert(item.collection, current_index);1496 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);14971498 // Add current block1499 let block_number: T::BlockNumber = 0.into();1500 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);15011502 // Update balance1503 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1504 .checked_add(value)1505 .ok_or(Error::<T>::NumOverflow)?;1506 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);15071508 Ok(())1509 }15101511 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1512 let current_index = <ItemListIndex>::get(item.collection)1513 .checked_add(1)1514 .ok_or(Error::<T>::NumOverflow)?;15151516 let item_owner = item.owner.clone();1517 let collection_id = item.collection.clone();1518 Self::add_token_index(collection_id, current_index, item.owner.clone())?;15191520 <ItemListIndex>::insert(collection_id, current_index);1521 <NftItemList<T>>::insert(collection_id, current_index, item);15221523 // Add current block1524 let block_number: T::BlockNumber = 0.into();1525 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);15261527 // Update balance1528 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1529 .checked_add(1)1530 .ok_or(Error::<T>::NumOverflow)?;1531 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);15321533 Ok(())1534 }15351536 fn burn_refungible_item(1537 collection_id: u64,1538 item_id: u64,1539 owner: T::AccountId,1540 ) -> DispatchResult {1541 ensure!(1542 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1543 Error::<T>::TokenNotFound1544 );1545 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1546 let item = collection1547 .owner1548 .iter()1549 .filter(|&i| i.owner == owner)1550 .next()1551 .unwrap();1552 Self::remove_token_index(collection_id, item_id, owner.clone())?;15531554 // remove approve list1555 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));15561557 // update balance1558 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1559 .checked_sub(item.fraction as u64)1560 .ok_or(Error::<T>::NumOverflow)?;1561 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);15621563 <ReFungibleItemList<T>>::remove(collection_id, item_id);15641565 Ok(())1566 }15671568 fn burn_nft_item(collection_id: u64, item_id: u64) -> DispatchResult {1569 ensure!(1570 <NftItemList<T>>::contains_key(collection_id, item_id),1571 Error::<T>::TokenNotFound1572 );1573 let item = <NftItemList<T>>::get(collection_id, item_id);1574 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15751576 // remove approve list1577 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15781579 // update balance1580 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1581 .checked_sub(1)1582 .ok_or(Error::<T>::NumOverflow)?;1583 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1584 <NftItemList<T>>::remove(collection_id, item_id);15851586 Ok(())1587 }15881589 fn burn_fungible_item(collection_id: u64, item_id: u64) -> DispatchResult {1590 ensure!(1591 <FungibleItemList<T>>::contains_key(collection_id, item_id),1592 Error::<T>::TokenNotFound1593 );1594 let item = <FungibleItemList<T>>::get(collection_id, item_id);1595 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;15961597 // remove approve list1598 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));15991600 // update balance1601 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1602 .checked_sub(item.value as u64)1603 .ok_or(Error::<T>::NumOverflow)?;1604 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16051606 <FungibleItemList<T>>::remove(collection_id, item_id);16071608 Ok(())1609 }16101611 fn collection_exists(collection_id: u64) -> DispatchResult {1612 ensure!(1613 <Collection<T>>::contains_key(collection_id),1614 Error::<T>::CollectionNotFound1615 );1616 Ok(())1617 }16181619 fn check_owner_permissions(collection_id: u64, subject: T::AccountId) -> DispatchResult {1620 Self::collection_exists(collection_id)?;16211622 let target_collection = <Collection<T>>::get(collection_id);1623 ensure!(1624 subject == target_collection.owner,1625 Error::<T>::NoPermission1626 );16271628 Ok(())1629 }16301631 fn is_owner_or_admin_permissions(collection_id: u64, subject: T::AccountId) -> bool {1632 let target_collection = <Collection<T>>::get(collection_id);1633 let mut result: bool = subject == target_collection.owner;1634 let exists = <AdminList<T>>::contains_key(collection_id);16351636 if !result & exists {1637 if <AdminList<T>>::get(collection_id).contains(&subject) {1638 result = true1639 }1640 }16411642 result1643 }16441645 fn check_owner_or_admin_permissions(1646 collection_id: u64,1647 subject: T::AccountId,1648 ) -> DispatchResult {1649 Self::collection_exists(collection_id)?;1650 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());16511652 ensure!(1653 result,1654 Error::<T>::NoPermission1655 );1656 Ok(())1657 }16581659 fn is_item_owner(subject: T::AccountId, collection_id: u64, item_id: u64) -> bool {1660 let target_collection = <Collection<T>>::get(collection_id);16611662 match target_collection.mode {1663 CollectionMode::NFT => {1664 <NftItemList<T>>::get(collection_id, item_id).owner == subject1665 }1666 CollectionMode::Fungible(_) => {1667 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1668 }1669 CollectionMode::ReFungible(_) => {1670 <ReFungibleItemList<T>>::get(collection_id, item_id)1671 .owner1672 .iter()1673 .any(|i| i.owner == subject)1674 }1675 CollectionMode::Invalid => false,1676 }1677 }16781679 fn check_white_list(collection_id: u64, address: &T::AccountId) -> DispatchResult {1680 let mes = Error::<T>::AddresNotInWhiteList;1681 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1682 let wl = <WhiteList<T>>::get(collection_id);1683 ensure!(wl.contains(address), mes);16841685 Ok(())1686 }16871688 fn transfer_fungible(1689 collection_id: u64,1690 item_id: u64,1691 value: u64,1692 owner: T::AccountId,1693 new_owner: T::AccountId,1694 ) -> DispatchResult {1695 ensure!(1696 <FungibleItemList<T>>::contains_key(collection_id, item_id),1697 Error::<T>::TokenNotFound1698 );16991700 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1701 let amount = full_item.value;17021703 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17041705 // update balance1706 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1707 .checked_sub(value)1708 .ok_or(Error::<T>::NumOverflow)?;1709 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);17101711 let mut new_owner_account_id = 0;1712 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1713 if new_owner_items.len() > 0 {1714 new_owner_account_id = new_owner_items[0];1715 }17161717 let val64 = value.into();17181719 // transfer1720 if amount == val64 && new_owner_account_id == 0 {1721 // change owner1722 // new owner do not have account1723 let mut new_full_item = full_item.clone();1724 new_full_item.owner = new_owner.clone();1725 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);17261727 // update balance1728 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1729 .checked_add(value)1730 .ok_or(Error::<T>::NumOverflow)?;1731 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17321733 // update index collection1734 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1735 } else {1736 let mut new_full_item = full_item.clone();1737 new_full_item.value -= val64;17381739 // separate amount1740 if new_owner_account_id > 0 {1741 // new owner has account1742 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1743 item.value += val64;17441745 // update balance1746 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1747 .checked_add(value)1748 .ok_or(Error::<T>::NumOverflow)?;1749 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);17501751 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1752 } else {1753 // new owner do not have account1754 let item = FungibleItemType {1755 collection: collection_id,1756 owner: new_owner.clone(),1757 value: val64,1758 };17591760 Self::add_fungible_item(item)?;1761 }17621763 if amount == val64 {1764 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;17651766 // remove approve list1767 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1768 <FungibleItemList<T>>::remove(collection_id, item_id);1769 }17701771 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1772 }17731774 Ok(())1775 }17761777 fn transfer_refungible(1778 collection_id: u64,1779 item_id: u64,1780 value: u64,1781 owner: T::AccountId,1782 new_owner: T::AccountId,1783 ) -> DispatchResult {1784 ensure!(1785 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1786 Error::<T>::TokenNotFound1787 );17881789 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1790 let item = full_item1791 .owner1792 .iter()1793 .filter(|i| i.owner == owner)1794 .next()1795 .ok_or(Error::<T>::NumOverflow)?;1796 let amount = item.fraction;17971798 ensure!(amount >= value.into(), Error::<T>::TokenValueTooLow);17991800 // update balance1801 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1802 .checked_sub(value)1803 .ok_or(Error::<T>::NumOverflow)?;1804 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18051806 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1807 .checked_add(value)1808 .ok_or(Error::<T>::NumOverflow)?;1809 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18101811 let old_owner = item.owner.clone();1812 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1813 let val64 = value.into();18141815 // transfer1816 if amount == val64 && !new_owner_has_account {1817 // change owner1818 // new owner do not have account1819 let mut new_full_item = full_item.clone();1820 new_full_item1821 .owner1822 .iter_mut()1823 .find(|i| i.owner == owner)1824 .unwrap()1825 .owner = new_owner.clone();1826 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18271828 // update index collection1829 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1830 } else {1831 let mut new_full_item = full_item.clone();1832 new_full_item1833 .owner1834 .iter_mut()1835 .find(|i| i.owner == owner)1836 .unwrap()1837 .fraction -= val64;18381839 // separate amount1840 if new_owner_has_account {1841 // new owner has account1842 new_full_item1843 .owner1844 .iter_mut()1845 .find(|i| i.owner == new_owner)1846 .unwrap()1847 .fraction += val64;1848 } else {1849 // new owner do not have account1850 new_full_item.owner.push(Ownership {1851 owner: new_owner.clone(),1852 fraction: val64,1853 });1854 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1855 }18561857 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1858 }18591860 Ok(())1861 }18621863 fn transfer_nft(1864 collection_id: u64,1865 item_id: u64,1866 sender: T::AccountId,1867 new_owner: T::AccountId,1868 ) -> DispatchResult {1869 ensure!(1870 <NftItemList<T>>::contains_key(collection_id, item_id),1871 Error::<T>::TokenNotFound1872 );18731874 let mut item = <NftItemList<T>>::get(collection_id, item_id);18751876 ensure!(1877 sender == item.owner,1878 Error::<T>::MustBeTokenOwner1879 );18801881 // update balance1882 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1883 .checked_sub(1)1884 .ok_or(Error::<T>::NumOverflow)?;1885 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18861887 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1888 .checked_add(1)1889 .ok_or(Error::<T>::NumOverflow)?;1890 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18911892 // change owner1893 let old_owner = item.owner.clone();1894 item.owner = new_owner.clone();1895 <NftItemList<T>>::insert(collection_id, item_id, item);18961897 // update index collection1898 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;18991900 // reset approved list1901 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));1902 Ok(())1903 }1904 1905 fn item_exists(1906 collection_id: u64,1907 item_id: u64,1908 mode: &CollectionMode1909 ) -> DispatchResult {1910 match mode {1911 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1912 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1913 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1914 _ => ()1915 };1916 1917 Ok(())1918 }19191920 fn set_re_fungible_variable_data(1921 collection_id: u64,1922 item_id: u64,1923 data: Vec<u8>1924 ) -> DispatchResult {1925 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19261927 item.variable_data = data;19281929 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);19301931 Ok(())1932 }19331934 fn set_nft_variable_data(1935 collection_id: u64,1936 item_id: u64,1937 data: Vec<u8>1938 ) -> DispatchResult {1939 let mut item = <NftItemList<T>>::get(collection_id, item_id);1940 1941 item.variable_data = data;19421943 <NftItemList<T>>::insert(collection_id, item_id, item);1944 1945 Ok(())1946 }19471948 fn init_collection(item: &CollectionType<T::AccountId>) {1949 // check params1950 assert!(1951 item.decimal_points <= 4,1952 "decimal_points parameter must be lower than 4"1953 );1954 assert!(1955 item.name.len() <= 64,1956 "Collection name can not be longer than 63 char"1957 );1958 assert!(1959 item.name.len() <= 256,1960 "Collection description can not be longer than 255 char"1961 );1962 assert!(1963 item.token_prefix.len() <= 16,1964 "Token prefix can not be longer than 15 char"1965 );19661967 // Generate next collection ID1968 let next_id = CreatedCollectionCount::get()1969 .checked_add(1)1970 .unwrap();19711972 CreatedCollectionCount::put(next_id);1973 }19741975 fn init_nft_token(item: &NftItemType<T::AccountId>) {1976 let current_index = <ItemListIndex>::get(item.collection)1977 .checked_add(1)1978 .unwrap();19791980 let item_owner = item.owner.clone();1981 let collection_id = item.collection.clone();1982 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();19831984 <ItemListIndex>::insert(collection_id, current_index);19851986 // Update balance1987 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1988 .checked_add(1)1989 .unwrap();1990 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);1991 }19921993 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {1994 let current_index = <ItemListIndex>::get(item.collection)1995 .checked_add(1)1996 .unwrap();1997 let owner = item.owner.clone();1998 let value = item.value as u64;19992000 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20012002 <ItemListIndex>::insert(item.collection, current_index);20032004 // Update balance2005 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2006 .checked_add(value)2007 .unwrap();2008 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2009 }20102011 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2012 let current_index = <ItemListIndex>::get(item.collection)2013 .checked_add(1)2014 .unwrap();20152016 let value = item.owner.first().unwrap().fraction as u64;2017 let owner = item.owner.first().unwrap().owner.clone();20182019 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();20202021 <ItemListIndex>::insert(item.collection, current_index);20222023 // Update balance2024 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2025 .checked_add(value)2026 .unwrap();2027 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2028 }20292030 fn add_token_index(collection_id: u64, item_index: u64, owner: T::AccountId) -> DispatchResult {20312032 // add to account limit2033 if <AccountItemCount<T>>::contains_key(owner.clone()) {20342035 // bound Owned tokens by a single address2036 let count = <AccountItemCount<T>>::get(owner.clone());2037 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20382039 <AccountItemCount<T>>::insert(owner.clone(), count2040 .checked_add(1)2041 .ok_or(Error::<T>::NumOverflow)?);2042 }2043 else {2044 <AccountItemCount<T>>::insert(owner.clone(), 1);2045 }20462047 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2048 if list_exists {2049 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2050 let item_contains = list.contains(&item_index.clone());20512052 if !item_contains {2053 list.push(item_index.clone());2054 }20552056 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2057 } else {2058 let mut itm = Vec::new();2059 itm.push(item_index.clone());2060 <AddressTokens<T>>::insert(collection_id, owner, itm);2061 2062 }20632064 Ok(())2065 }20662067 fn remove_token_index(2068 collection_id: u64,2069 item_index: u64,2070 owner: T::AccountId,2071 ) -> DispatchResult {20722073 // update counter2074 <AccountItemCount<T>>::insert(owner.clone(), 2075 <AccountItemCount<T>>::get(owner.clone())2076 .checked_sub(1)2077 .ok_or(Error::<T>::NumOverflow)?);207820792080 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2081 if list_exists {2082 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2083 let item_contains = list.contains(&item_index.clone());20842085 if item_contains {2086 list.retain(|&item| item != item_index);2087 <AddressTokens<T>>::insert(collection_id, owner, list);2088 }2089 }20902091 Ok(())2092 }20932094 fn move_token_index(2095 collection_id: u64,2096 item_index: u64,2097 old_owner: T::AccountId,2098 new_owner: T::AccountId,2099 ) -> DispatchResult {2100 Self::remove_token_index(collection_id, item_index, old_owner)?;2101 Self::add_token_index(collection_id, item_index, new_owner)?;21022103 Ok(())2104 }2105}21062107////////////////////////////////////////////////////////////////////////////////////////////////////2108// Economic models2109// #region21102111/// Fee multiplier.2112pub type Multiplier = FixedU128;21132114type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2115 <T as system::Trait>::AccountId,2116>>::Balance;2117type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2118 <T as system::Trait>::AccountId,2119>>::NegativeImbalance;21202121/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2122/// in the queue.2123#[derive(Encode, Decode, Clone, Eq, PartialEq)]2124pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2125 #[codec(compact)] BalanceOf<T>2126);21272128impl<T: Trait + Send + Sync> sp_std::fmt::Debug2129 for ChargeTransactionPayment<T>2130{2131 #[cfg(feature = "std")]2132 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2133 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2134 }2135 #[cfg(not(feature = "std"))]2136 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2137 Ok(())2138 }2139}21402141impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2142where2143 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2144 BalanceOf<T>: Send + Sync + FixedPointOperand,2145{2146 /// utility constructor. Used only in client/factory code.2147 pub fn from(fee: BalanceOf<T>) -> Self {2148 Self(fee)2149 }21502151 pub fn traditional_fee(2152 len: usize,2153 info: &DispatchInfoOf<T::Call>,2154 tip: BalanceOf<T>,2155 ) -> BalanceOf<T>2156 where2157 T::Call: Dispatchable<Info = DispatchInfo>,2158 {2159 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2160 }21612162 fn withdraw_fee(2163 &self,2164 who: &T::AccountId,2165 call: &T::Call,2166 info: &DispatchInfoOf<T::Call>,2167 len: usize,2168 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2169 let tip = self.0;21702171 // Set fee based on call type. Creating collection costs 1 Unique.2172 // All other transactions have traditional fees so far2173 // let fee = match call.is_sub_type() {2174 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2175 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2176 // // _ => <BalanceOf<T>>::from(100)2177 // };2178 let fee = Self::traditional_fee(len, info, tip);21792180 // Determine who is paying transaction fee based on ecnomic model2181 // Parse call to extract collection ID and access collection sponsor2182 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2183 Some(Call::create_item(collection_id, _properties, _owner)) => {2184 <Collection<T>>::get(collection_id).sponsor2185 }2186 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2187 let _collection_mode = <Collection<T>>::get(collection_id).mode;21882189 // sponsor timeout2190 let sponsor_transfer = match _collection_mode {2191 CollectionMode::NFT => {2192 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2193 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2194 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2195 if block_number >= limit_time {2196 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2197 true2198 }2199 else {2200 false2201 }2202 }2203 CollectionMode::Fungible(_) => {2204 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2205 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2206 if basket.iter().any(|i| i.address == _new_owner.clone())2207 {2208 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2209 let limit_time = item.start_block + ChainLimit::get().fungible_sponsor_transfer_timeout.into();2210 if block_number >= limit_time {2211 basket.retain(|x| x.address == item.address);2212 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2213 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2214 true2215 }2216 else {2217 false2218 }2219 }2220 else {2221 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2222 true2223 }2224 }2225 CollectionMode::ReFungible(_) => {2226 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2227 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2228 let limit_time = basket + ChainLimit::get().nft_sponsor_transfer_timeout.into();2229 if block_number >= limit_time {2230 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2231 true2232 } else {2233 false2234 }2235 }2236 _ => {2237 false2238 },2239 };22402241 if !sponsor_transfer {2242 T::AccountId::default()2243 } else {2244 <Collection<T>>::get(collection_id).sponsor2245 }2246 }22472248 _ => T::AccountId::default(),2249 };22502251 // Sponsor smart contracts2252 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {22532254 // On instantiation: set the contract owner2255 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {22562257 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2258 code_hash,2259 &data,2260 &who,2261 );2262 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());22632264 T::AccountId::default()2265 },22662267 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2268 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {22692270 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());22712272 let mut sponsor_transfer = false;2273 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2274 let last_tx_block = <ContractSponsorBasket<T>>::get(&called_contract);2275 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2276 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2277 let limit_time = last_tx_block + rate_limit;22782279 if block_number >= limit_time {2280 <ContractSponsorBasket<T>>::insert(called_contract.clone(), block_number);2281 sponsor_transfer = true;2282 }2283 } else {2284 sponsor_transfer = false;2285 }2286 2287 2288 let mut sp = T::AccountId::default();2289 if sponsor_transfer {2290 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2291 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2292 sp = called_contract;2293 }2294 }2295 }22962297 sp2298 },22992300 _ => sponsor,2301 };23022303 let mut who_pays_fee: T::AccountId = sponsor.clone();2304 if sponsor == T::AccountId::default() {2305 who_pays_fee = who.clone();2306 }23072308 // Only mess with balances if fee is not zero.2309 if fee.is_zero() {2310 return Ok((fee, None));2311 }23122313 match <T as transaction_payment::Trait>::Currency::withdraw(2314 &who_pays_fee,2315 fee,2316 if tip.is_zero() {2317 WithdrawReason::TransactionPayment.into()2318 } else {2319 WithdrawReason::TransactionPayment | WithdrawReason::Tip2320 },2321 ExistenceRequirement::KeepAlive,2322 ) {2323 Ok(imbalance) => Ok((fee, Some(imbalance))),2324 Err(_) => Err(InvalidTransaction::Payment.into()),2325 }2326 }2327}232823292330impl<T: Trait + Send + Sync> SignedExtension2331 for ChargeTransactionPayment<T>2332where2333 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2334 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2335{2336 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2337 type AccountId = T::AccountId;2338 type Call = T::Call;2339 type AdditionalSigned = ();2340 type Pre = (2341 BalanceOf<T>,2342 Self::AccountId,2343 Option<NegativeImbalanceOf<T>>,2344 BalanceOf<T>,2345 );2346 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2347 Ok(())2348 }23492350 fn validate(2351 &self,2352 _who: &Self::AccountId,2353 _call: &Self::Call,2354 _info: &DispatchInfoOf<Self::Call>,2355 _len: usize,2356 ) -> TransactionValidity {2357 Ok(ValidTransaction::default())2358 }23592360 fn pre_dispatch(2361 self,2362 who: &Self::AccountId,2363 call: &Self::Call,2364 info: &DispatchInfoOf<Self::Call>,2365 len: usize,2366 ) -> Result<Self::Pre, TransactionValidityError> {2367 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2368 Ok((self.0, who.clone(), imbalance, fee))2369 }23702371 fn post_dispatch(2372 pre: Self::Pre,2373 info: &DispatchInfoOf<Self::Call>,2374 post_info: &PostDispatchInfoOf<Self::Call>,2375 len: usize,2376 _result: &DispatchResult,2377 ) -> Result<(), TransactionValidityError> {2378 let (tip, who, imbalance, fee) = pre;2379 if let Some(payed) = imbalance {2380 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2381 len as u32, info, post_info, tip,2382 );2383 let refund = fee.saturating_sub(actual_fee);2384 let actual_payment =2385 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2386 &who, refund,2387 ) {2388 Ok(refund_imbalance) => {2389 // The refund cannot be larger than the up front payed max weight.2390 // `PostDispatchInfo::calc_unspent` guards against such a case.2391 match payed.offset(refund_imbalance) {2392 Ok(actual_payment) => actual_payment,2393 Err(_) => return Err(InvalidTransaction::Payment.into()),2394 }2395 }2396 // We do not recreate the account using the refund. The up front payment2397 // is gone in that case.2398 Err(_) => payed,2399 };2400 let imbalances = actual_payment.split(tip);2401 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2402 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2403 );2404 }2405 Ok(())2406 }2407}24082409// #endregionruntime/src/nft_weights.rsdiffbeforeafterboth--- a/runtime/src/nft_weights.rs
+++ b/runtime/src/nft_weights.rs
@@ -108,4 +108,19 @@
.saturating_add(DbWeight::get().reads(2 as Weight))
.saturating_add(DbWeight::get().writes(1 as Weight))
}
+ // fn set_chain_limits() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
+ // fn enable_contract_sponsoring() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
+ // fn set_contract_sponsoring_rate_limit() -> Weight {
+ // (0 as Weight)
+ // .saturating_add(DbWeight::get().reads(1 as Weight))
+ // .saturating_add(DbWeight::get().writes(1 as Weight))
+ // }
}