difftreelog
Refactor fungible logic, add positive tests for sponsorship confirmation
in: master
4 files changed
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, WithdrawReason,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial,29 },30 IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69 Invalid,70 NFT,71 // decimal points72 Fungible(DecimalPoints),73 // decimal points74 ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78 fn into(self) -> u8 {79 match self {80 CollectionMode::Invalid => 0,81 CollectionMode::NFT => 1,82 CollectionMode::Fungible(_) => 2,83 CollectionMode::ReFungible(_) => 3,84 }85 }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91 Normal,92 WhiteList,93}94impl Default for AccessMode {95 fn default() -> Self {96 Self::Normal97 }98}99100impl Default for CollectionMode {101 fn default() -> Self {102 Self::Invalid103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub owner: AccountId,149 pub const_data: Vec<u8>,150 pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType<AccountId> {156 pub owner: AccountId,157 pub value: u128,158}159160#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]161#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]162pub struct ReFungibleItemType<AccountId> {163 pub owner: Vec<Ownership<AccountId>>,164 pub const_data: Vec<u8>,165 pub variable_data: Vec<u8>,166}167168#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]169#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]170pub struct ApprovePermissions<AccountId> {171 pub approved: AccountId,172 pub amount: u128,173}174175// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]176// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]177// pub struct VestingItem<AccountId, Moment> {178// pub sender: AccountId,179// pub recipient: AccountId,180// pub collection_id: CollectionId,181// pub item_id: TokenId,182// pub amount: u64,183// pub vesting_date: Moment,184// }185186#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]187#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]188pub struct BasketItem<AccountId, BlockNumber> {189 pub address: AccountId,190 pub start_block: BlockNumber,191}192193#[derive(Encode, Decode, Debug, Clone, PartialEq)]194#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]195pub struct CollectionLimits {196 pub account_token_ownership_limit: u32,197 pub sponsored_data_size: u32,198 pub token_limit: u32,199200 // Timeouts for item types in passed blocks201 pub sponsor_transfer_timeout: u32,202}203204impl Default for CollectionLimits {205 fn default() -> CollectionLimits {206 CollectionLimits { 207 account_token_ownership_limit: 10_000_000, 208 token_limit: u32::max_value(),209 sponsored_data_size: u32::max_value(), 210 sponsor_transfer_timeout: 14400 }211 }212}213214#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]215#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]216pub struct ChainLimits {217 pub collection_numbers_limit: u32,218 pub account_token_ownership_limit: u32,219 pub collections_admins_limit: u64,220 pub custom_data_limit: u32,221222 // Timeouts for item types in passed blocks223 pub nft_sponsor_transfer_timeout: u32,224 pub fungible_sponsor_transfer_timeout: u32,225 pub refungible_sponsor_transfer_timeout: u32,226}227228pub trait WeightInfo {229 fn create_collection() -> Weight;230 fn destroy_collection() -> Weight;231 fn add_to_white_list() -> Weight;232 fn remove_from_white_list() -> Weight;233 fn set_public_access_mode() -> Weight;234 fn set_mint_permission() -> Weight;235 fn change_collection_owner() -> Weight;236 fn add_collection_admin() -> Weight;237 fn remove_collection_admin() -> Weight;238 fn set_collection_sponsor() -> Weight;239 fn confirm_sponsorship() -> Weight;240 fn remove_collection_sponsor() -> Weight;241 fn create_item(s: usize) -> Weight;242 fn burn_item() -> Weight;243 fn transfer() -> Weight;244 fn approve() -> Weight;245 fn transfer_from() -> Weight;246 fn set_offchain_schema() -> Weight;247 fn set_const_on_chain_schema() -> Weight;248 fn set_variable_on_chain_schema() -> Weight;249 fn set_variable_meta_data() -> Weight;250 fn enable_contract_sponsoring() -> Weight;251}252253#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]254#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]255pub struct CreateNftData {256 pub const_data: Vec<u8>,257 pub variable_data: Vec<u8>,258}259260#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]261#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]262pub struct CreateFungibleData {263}264265#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]266#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]267pub struct CreateReFungibleData {268 pub const_data: Vec<u8>,269 pub variable_data: Vec<u8>,270}271272#[derive(Encode, Decode, Debug, Clone, PartialEq)]273#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]274pub enum CreateItemData {275 NFT(CreateNftData),276 Fungible(CreateFungibleData),277 ReFungible(CreateReFungibleData),278}279280impl CreateItemData {281 pub fn len(&self) -> usize {282 let len = match self {283 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),284 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),285 _ => 0286 };287 288 return len;289 }290}291292impl From<CreateNftData> for CreateItemData {293 fn from(item: CreateNftData) -> Self {294 CreateItemData::NFT(item)295 }296}297298impl From<CreateReFungibleData> for CreateItemData {299 fn from(item: CreateReFungibleData) -> Self {300 CreateItemData::ReFungible(item)301 }302}303304impl From<CreateFungibleData> for CreateItemData {305 fn from(item: CreateFungibleData) -> Self {306 CreateItemData::Fungible(item)307 }308}309310311decl_error! {312 /// Error for non-fungible-token module.313 pub enum Error for Module<T: Trait> {314 /// Total collections bound exceeded.315 TotalCollectionsLimitExceeded,316 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.317 CollectionDecimalPointLimitExceeded, 318 /// Collection name can not be longer than 63 char.319 CollectionNameLimitExceeded, 320 /// Collection description can not be longer than 255 char.321 CollectionDescriptionLimitExceeded, 322 /// Token prefix can not be longer than 15 char.323 CollectionTokenPrefixLimitExceeded,324 /// This collection does not exist.325 CollectionNotFound,326 /// Item not exists.327 TokenNotFound,328 /// Arithmetic calculation overflow.329 NumOverflow, 330 /// Account already has admin role.331 AlreadyAdmin, 332 /// You do not own this collection.333 NoPermission,334 /// This address is not set as sponsor, use setCollectionSponsor first.335 ConfirmUnsetSponsorFail,336 /// Collection is not in mint mode.337 PublicMintingNotAllowed,338 /// Sender parameter and item owner must be equal.339 MustBeTokenOwner,340 /// Item balance not enough.341 TokenValueTooLow,342 /// Size of item is too large.343 NftSizeLimitExceeded,344 /// No approve found345 ApproveNotFound,346 /// Requested value more than approved.347 TokenValueNotEnough,348 /// Only approved addresses can call this method.349 ApproveRequired,350 /// Address is not in white list.351 AddresNotInWhiteList,352 /// Number of collection admins bound exceeded.353 CollectionAdminsLimitExceeded,354 /// Owned tokens by a single address bound exceeded.355 AddressOwnershipLimitExceeded,356 /// Length of items properties must be greater than 0.357 EmptyArgument,358 /// const_data exceeded data limit.359 TokenConstDataLimitExceeded,360 /// variable_data exceeded data limit.361 TokenVariableDataLimitExceeded,362 /// Not NFT item data used to mint in NFT collection.363 NotNftDataUsedToMintNftCollectionToken,364 /// Not Fungible item data used to mint in Fungible collection.365 NotFungibleDataUsedToMintFungibleCollectionToken,366 /// Not Re Fungible item data used to mint in Re Fungible collection.367 NotReFungibleDataUsedToMintReFungibleCollectionToken,368 /// Unexpected collection type.369 UnexpectedCollectionType,370 /// Can't store metadata in fungible tokens.371 CantStoreMetadataInFungibleTokens,372 /// Collection token limit exceeded373 CollectionTokenLimitExceeded,374 /// Account token limit exceeded per collection375 AccountTokenLimitExceeded,376 /// Collection limit bounds per collection exceeded377 CollectionLimitBoundsExceeded378 }379}380381pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {382 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;383384 /// Weight information for extrinsics in this pallet.385 type WeightInfo: WeightInfo;386}387388#[cfg(feature = "runtime-benchmarks")]389mod benchmarking;390391// #endregion392393decl_storage! {394 trait Store for Module<T: Trait> as Nft {395396 // Private members397 NextCollectionID: CollectionId;398 CreatedCollectionCount: u32;399 ChainVersion: u64;400 ItemListIndex: map hasher(identity) CollectionId => TokenId;401402 // Chain limits struct403 pub ChainLimit get(fn chain_limit) config(): ChainLimits;404405 // Bound counters406 CollectionCount: u32;407 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;408409 // Basic collections410 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;411 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;412 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;413414 /// Balance owner per collection map415 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;416417 /// second parameter: item id + owner account id418 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;419420 /// Item collections421 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;422 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;423 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;424425 /// Index list426 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;427428 /// Tokens transfer baskets429 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;430 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;431 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;432433 // Contract Sponsorship and Ownership434 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;435 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;436 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;437 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;438 }439 add_extra_genesis {440 build(|config: &GenesisConfig<T>| {441 // Modification of storage442 for (_num, _c) in &config.collection {443 <Module<T>>::init_collection(_c);444 }445446 for (_num, _c, _i) in &config.nft_item_id {447 <Module<T>>::init_nft_token(*_c, _i);448 }449450 for (_num, _c, _i) in &config.fungible_item_id {451 <Module<T>>::init_fungible_token(*_c, _i);452 }453454 for (_num, _c, _i) in &config.refungible_item_id {455 <Module<T>>::init_refungible_token(*_c, _i);456 }457 })458 }459}460461decl_event!(462 pub enum Event<T>463 where464 AccountId = <T as system::Trait>::AccountId,465 {466 /// New collection was created467 /// 468 /// # Arguments469 /// 470 /// * collection_id: Globally unique identifier of newly created collection.471 /// 472 /// * mode: [CollectionMode] converted into u8.473 /// 474 /// * account_id: Collection owner.475 Created(CollectionId, u8, AccountId),476477 /// New item was created.478 /// 479 /// # Arguments480 /// 481 /// * collection_id: Id of the collection where item was created.482 /// 483 /// * item_id: Id of an item. Unique within the collection.484 ItemCreated(CollectionId, TokenId),485486 /// Collection item was burned.487 /// 488 /// # Arguments489 /// 490 /// collection_id.491 /// 492 /// item_id: Identifier of burned NFT.493 ItemDestroyed(CollectionId, TokenId),494 }495);496497decl_module! {498 pub struct Module<T: Trait> for enum Call where origin: T::Origin {499500 fn deposit_event() = default;501 type Error = Error<T>;502503 fn on_initialize(now: T::BlockNumber) -> Weight {504505 if ChainVersion::get() < 2506 {507 let value = NextCollectionID::get();508 CreatedCollectionCount::put(value);509 ChainVersion::put(2);510 }511512 0513 }514515 /// 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.516 /// 517 /// # Permissions518 /// 519 /// * Anyone.520 /// 521 /// # Arguments522 /// 523 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.524 /// 525 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.526 /// 527 /// * token_prefix: UTF-8 string with token prefix.528 /// 529 /// * mode: [CollectionMode] collection type and type dependent data.530 // returns collection ID531 #[weight = T::WeightInfo::create_collection()]532 pub fn create_collection(origin,533 collection_name: Vec<u16>,534 collection_description: Vec<u16>,535 token_prefix: Vec<u8>,536 mode: CollectionMode) -> DispatchResult {537538 // Anyone can create a collection539 let who = ensure_signed(origin)?;540541 let decimal_points = match mode {542 CollectionMode::Fungible(points) => points,543 CollectionMode::ReFungible(points) => points,544 _ => 0545 };546547 // bound Total number of collections548 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);549550 // check params551 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);552553 let mut name = collection_name.to_vec();554 name.push(0);555 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);556557 let mut description = collection_description.to_vec();558 description.push(0);559 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);560561 let mut prefix = token_prefix.to_vec();562 prefix.push(0);563 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);564565 // Generate next collection ID566 let next_id = CreatedCollectionCount::get()567 .checked_add(1)568 .ok_or(Error::<T>::NumOverflow)?;569570 // bound counter571 let total = CollectionCount::get()572 .checked_add(1)573 .ok_or(Error::<T>::NumOverflow)?;574575 CreatedCollectionCount::put(next_id);576 CollectionCount::put(total);577578 // Create new collection579 let new_collection = CollectionType {580 owner: who.clone(),581 name: name,582 mode: mode.clone(),583 mint_mode: false,584 access: AccessMode::Normal,585 description: description,586 decimal_points: decimal_points,587 token_prefix: prefix,588 offchain_schema: Vec::new(),589 schema_version: SchemaVersion::ImageURL,590 sponsor: T::AccountId::default(),591 sponsor_confirmed: false,592 variable_on_chain_schema: Vec::new(),593 const_on_chain_schema: Vec::new(),594 limits: CollectionLimits::default(),595 };596597 // Add new collection to map598 <Collection<T>>::insert(next_id, new_collection);599600 // call event601 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));602603 Ok(())604 }605606 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.607 /// 608 /// # Permissions609 /// 610 /// * Collection Owner.611 /// 612 /// # Arguments613 /// 614 /// * collection_id: collection to destroy.615 #[weight = T::WeightInfo::destroy_collection()]616 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {617618 let sender = ensure_signed(origin)?;619 Self::check_owner_permissions(collection_id, sender)?;620621 <AddressTokens<T>>::remove_prefix(collection_id);622 <ApprovedList<T>>::remove_prefix(collection_id);623 <Balance<T>>::remove_prefix(collection_id);624 <ItemListIndex>::remove(collection_id);625 <AdminList<T>>::remove(collection_id);626 <Collection<T>>::remove(collection_id);627 <WhiteList<T>>::remove(collection_id);628629 <NftItemList<T>>::remove_prefix(collection_id);630 <FungibleItemList<T>>::remove_prefix(collection_id);631 <ReFungibleItemList<T>>::remove_prefix(collection_id);632633 <NftTransferBasket<T>>::remove_prefix(collection_id);634 <FungibleTransferBasket<T>>::remove_prefix(collection_id);635 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);636637 if CollectionCount::get() > 0638 {639 // bound couter640 let total = CollectionCount::get()641 .checked_sub(1)642 .ok_or(Error::<T>::NumOverflow)?;643644 CollectionCount::put(total);645 }646647 Ok(())648 }649650 /// Add an address to white list.651 /// 652 /// # Permissions653 /// 654 /// * Collection Owner655 /// * Collection Admin656 /// 657 /// # Arguments658 /// 659 /// * collection_id.660 /// 661 /// * address.662 #[weight = T::WeightInfo::add_to_white_list()]663 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{664665 let sender = ensure_signed(origin)?;666 Self::check_owner_or_admin_permissions(collection_id, sender)?;667668 let mut white_list_collection: Vec<T::AccountId>;669 if <WhiteList<T>>::contains_key(collection_id) {670 white_list_collection = <WhiteList<T>>::get(collection_id);671 if !white_list_collection.contains(&address.clone())672 {673 white_list_collection.push(address.clone());674 }675 }676 else {677 white_list_collection = Vec::new();678 white_list_collection.push(address.clone());679 }680681 <WhiteList<T>>::insert(collection_id, white_list_collection);682 Ok(())683 }684685 /// Remove an address from white list.686 /// 687 /// # Permissions688 /// 689 /// * Collection Owner690 /// * Collection Admin691 /// 692 /// # Arguments693 /// 694 /// * collection_id.695 /// 696 /// * address.697 #[weight = T::WeightInfo::remove_from_white_list()]698 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{699700 let sender = ensure_signed(origin)?;701 Self::check_owner_or_admin_permissions(collection_id, sender)?;702703 if <WhiteList<T>>::contains_key(collection_id) {704 let mut white_list_collection = <WhiteList<T>>::get(collection_id);705 if white_list_collection.contains(&address.clone())706 {707 white_list_collection.retain(|i| *i != address.clone());708 <WhiteList<T>>::insert(collection_id, white_list_collection);709 }710 }711712 Ok(())713 }714715 /// Toggle between normal and white list access for the methods with access for `Anyone`.716 /// 717 /// # Permissions718 /// 719 /// * Collection Owner.720 /// 721 /// # Arguments722 /// 723 /// * collection_id.724 /// 725 /// * mode: [AccessMode]726 #[weight = T::WeightInfo::set_public_access_mode()]727 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult728 {729 let sender = ensure_signed(origin)?;730731 Self::check_owner_permissions(collection_id, sender)?;732 let mut target_collection = <Collection<T>>::get(collection_id);733 target_collection.access = mode;734 <Collection<T>>::insert(collection_id, target_collection);735736 Ok(())737 }738739 /// Allows Anyone to create tokens if:740 /// * White List is enabled, and741 /// * Address is added to white list, and742 /// * This method was called with True parameter743 /// 744 /// # Permissions745 /// * Collection Owner746 ///747 /// # Arguments748 /// 749 /// * collection_id.750 /// 751 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.752 #[weight = T::WeightInfo::set_mint_permission()]753 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult754 {755 let sender = ensure_signed(origin)?;756757 Self::check_owner_permissions(collection_id, sender)?;758 let mut target_collection = <Collection<T>>::get(collection_id);759 target_collection.mint_mode = mint_permission;760 <Collection<T>>::insert(collection_id, target_collection);761762 Ok(())763 }764765 /// Change the owner of the collection.766 /// 767 /// # Permissions768 /// 769 /// * Collection Owner.770 /// 771 /// # Arguments772 /// 773 /// * collection_id.774 /// 775 /// * new_owner.776 #[weight = T::WeightInfo::change_collection_owner()]777 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {778779 let sender = ensure_signed(origin)?;780 Self::check_owner_permissions(collection_id, sender)?;781 let mut target_collection = <Collection<T>>::get(collection_id);782 target_collection.owner = new_owner;783 <Collection<T>>::insert(collection_id, target_collection);784785 Ok(())786 }787788 /// Adds an admin of the Collection.789 /// 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. 790 /// 791 /// # Permissions792 /// 793 /// * Collection Owner.794 /// * Collection Admin.795 /// 796 /// # Arguments797 /// 798 /// * collection_id: ID of the Collection to add admin for.799 /// 800 /// * new_admin_id: Address of new admin to add.801 #[weight = T::WeightInfo::add_collection_admin()]802 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {803804 let sender = ensure_signed(origin)?;805 Self::check_owner_or_admin_permissions(collection_id, sender)?;806 let mut admin_arr: Vec<T::AccountId> = Vec::new();807808 if <AdminList<T>>::contains_key(collection_id)809 {810 admin_arr = <AdminList<T>>::get(collection_id);811 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);812 }813814 // Number of collection admins815 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);816817 admin_arr.push(new_admin_id);818 <AdminList<T>>::insert(collection_id, admin_arr);819820 Ok(())821 }822823 /// 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.824 ///825 /// # Permissions826 /// 827 /// * Collection Owner.828 /// * Collection Admin.829 /// 830 /// # Arguments831 /// 832 /// * collection_id: ID of the Collection to remove admin for.833 /// 834 /// * account_id: Address of admin to remove.835 #[weight = T::WeightInfo::remove_collection_admin()]836 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {837838 let sender = ensure_signed(origin)?;839 Self::check_owner_or_admin_permissions(collection_id, sender)?;840841 if <AdminList<T>>::contains_key(collection_id)842 {843 let mut admin_arr = <AdminList<T>>::get(collection_id);844 admin_arr.retain(|i| *i != account_id);845 <AdminList<T>>::insert(collection_id, admin_arr);846 }847848 Ok(())849 }850851 /// # Permissions852 /// 853 /// * Collection Owner854 /// 855 /// # Arguments856 /// 857 /// * collection_id.858 /// 859 /// * new_sponsor.860 #[weight = T::WeightInfo::set_collection_sponsor()]861 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {862863 let sender = ensure_signed(origin)?;864 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);865866 let mut target_collection = <Collection<T>>::get(collection_id);867 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);868869 target_collection.sponsor = new_sponsor;870 target_collection.sponsor_confirmed = false;871 <Collection<T>>::insert(collection_id, target_collection);872873 Ok(())874 }875876 /// # Permissions877 /// 878 /// * Sponsor.879 /// 880 /// # Arguments881 /// 882 /// * collection_id.883 #[weight = T::WeightInfo::confirm_sponsorship()]884 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {885886 let sender = ensure_signed(origin)?;887 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);888889 let mut target_collection = <Collection<T>>::get(collection_id);890 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);891892 target_collection.sponsor_confirmed = true;893 <Collection<T>>::insert(collection_id, target_collection);894895 Ok(())896 }897898 /// Switch back to pay-per-own-transaction model.899 ///900 /// # Permissions901 ///902 /// * Collection owner.903 /// 904 /// # Arguments905 /// 906 /// * collection_id.907 #[weight = T::WeightInfo::remove_collection_sponsor()]908 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {909910 let sender = ensure_signed(origin)?;911 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);912913 let mut target_collection = <Collection<T>>::get(collection_id);914 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);915916 target_collection.sponsor = T::AccountId::default();917 target_collection.sponsor_confirmed = false;918 <Collection<T>>::insert(collection_id, target_collection);919920 Ok(())921 }922923 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.924 /// 925 /// # Permissions926 /// 927 /// * Collection Owner.928 /// * Collection Admin.929 /// * Anyone if930 /// * White List is enabled, and931 /// * Address is added to white list, and932 /// * MintPermission is enabled (see SetMintPermission method)933 /// 934 /// # Arguments935 /// 936 /// * collection_id: ID of the collection.937 /// 938 /// * owner: Address, initial owner of the NFT.939 ///940 /// * data: Token data to store on chain.941 // #[weight =942 // (130_000_000 as Weight)943 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))944 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))945 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]946947 #[weight = T::WeightInfo::create_item(data.len())]948 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {949950 let sender = ensure_signed(origin)?;951952 Self::collection_exists(collection_id)?;953954 let target_collection = <Collection<T>>::get(collection_id);955956 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;957 Self::validate_create_item_args(&target_collection, &data)?;958 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;959960 Ok(())961 }962963 /// This method creates multiple instances of NFT Collection created with CreateCollection method.964 /// 965 /// # Permissions966 /// 967 /// * Collection Owner.968 /// * Collection Admin.969 /// * Anyone if970 /// * White List is enabled, and971 /// * Address is added to white list, and972 /// * MintPermission is enabled (see SetMintPermission method)973 /// 974 /// # Arguments975 /// 976 /// * collection_id: ID of the collection.977 /// 978 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].979 /// 980 /// * owner: Address, initial owner of the NFT.981 #[weight = T::WeightInfo::create_item(items_data.into_iter()982 .map(|data| { data.len() })983 .sum())]984 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {985986 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);987 let sender = ensure_signed(origin)?;988989 Self::collection_exists(collection_id)?;990 let target_collection = <Collection<T>>::get(collection_id);991992 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;993994 for data in &items_data {995 Self::validate_create_item_args(&target_collection, data)?;996 }997 for data in &items_data {998 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;999 }10001001 Ok(())1002 }10031004 /// Destroys a concrete instance of NFT.1005 /// 1006 /// # Permissions1007 /// 1008 /// * Collection Owner.1009 /// * Collection Admin.1010 /// * Current NFT Owner.1011 /// 1012 /// # Arguments1013 /// 1014 /// * collection_id: ID of the collection.1015 /// 1016 /// * item_id: ID of NFT to burn.1017 #[weight = T::WeightInfo::burn_item()]1018 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10191020 let sender = ensure_signed(origin)?;1021 Self::collection_exists(collection_id)?;10221023 // Transfer permissions check1024 let target_collection = <Collection<T>>::get(collection_id);1025 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1026 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1027 Error::<T>::NoPermission);10281029 if target_collection.access == AccessMode::WhiteList {1030 Self::check_white_list(collection_id, &sender)?;1031 }10321033 match target_collection.mode1034 {1035 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1036 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1037 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1038 _ => ()1039 };10401041 // call event1042 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10431044 Ok(())1045 }10461047 /// Change ownership of the token.1048 /// 1049 /// # Permissions1050 /// 1051 /// * Collection Owner1052 /// * Collection Admin1053 /// * Current NFT owner1054 ///1055 /// # Arguments1056 /// 1057 /// * recipient: Address of token recipient.1058 /// 1059 /// * collection_id.1060 /// 1061 /// * item_id: ID of the item1062 /// * Non-Fungible Mode: Required.1063 /// * Fungible Mode: Ignored.1064 /// * Re-Fungible Mode: Required.1065 /// 1066 /// * value: Amount to transfer.1067 /// * Non-Fungible Mode: Ignored1068 /// * Fungible Mode: Must specify transferred amount1069 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1070 #[weight = T::WeightInfo::transfer()]1071 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10721073 let sender = ensure_signed(origin)?;1074 let target_collection = <Collection<T>>::get(collection_id);10751076 // Limits check1077 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10781079 // Transfer permissions check1080 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1081 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1082 Error::<T>::NoPermission);10831084 if target_collection.access == AccessMode::WhiteList {1085 Self::check_white_list(collection_id, &sender)?;1086 Self::check_white_list(collection_id, &recipient)?;1087 }10881089 match target_collection.mode1090 {1091 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1092 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1093 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1094 _ => ()1095 };10961097 Ok(())1098 }10991100 /// Set, change, or remove approved address to transfer the ownership of the NFT.1101 /// 1102 /// # Permissions1103 /// 1104 /// * Collection Owner1105 /// * Collection Admin1106 /// * Current NFT owner1107 /// 1108 /// # Arguments1109 /// 1110 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1111 /// 1112 /// * collection_id.1113 /// 1114 /// * item_id: ID of the item.1115 #[weight = T::WeightInfo::approve()]1116 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {11171118 let sender = ensure_signed(origin)?;11191120 // Transfer permissions check1121 let target_collection = <Collection<T>>::get(collection_id);1122 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1123 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1124 Error::<T>::NoPermission);11251126 if target_collection.access == AccessMode::WhiteList {1127 Self::check_white_list(collection_id, &sender)?;1128 Self::check_white_list(collection_id, &approved)?;1129 }11301131 // amount param stub1132 let amount = 100000000;11331134 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1135 if list_exists {11361137 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1138 let item_contains = list.iter().any(|i| i.approved == approved);11391140 if !item_contains {1141 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1142 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1143 }1144 } else {11451146 let mut list = Vec::new();1147 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1148 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1149 }11501151 Ok(())1152 }1153 1154 /// 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.1155 /// 1156 /// # Permissions1157 /// * Collection Owner1158 /// * Collection Admin1159 /// * Current NFT owner1160 /// * Address approved by current NFT owner1161 /// 1162 /// # Arguments1163 /// 1164 /// * from: Address that owns token.1165 /// 1166 /// * recipient: Address of token recipient.1167 /// 1168 /// * collection_id.1169 /// 1170 /// * item_id: ID of the item.1171 /// 1172 /// * value: Amount to transfer.1173 #[weight = T::WeightInfo::transfer_from()]1174 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11751176 let sender = ensure_signed(origin)?;1177 let mut appoved_transfer = false;11781179 // Check approve1180 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1181 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1182 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1183 if opt_item.is_some()1184 {1185 appoved_transfer = true;1186 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1187 }1188 }11891190 let target_collection = <Collection<T>>::get(collection_id);11911192 // Limits check1193 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11941195 // Transfer permissions check 1196 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1197 Error::<T>::NoPermission);11981199 if target_collection.access == AccessMode::WhiteList {1200 Self::check_white_list(collection_id, &sender)?;1201 Self::check_white_list(collection_id, &recipient)?;1202 }12031204 // remove approve1205 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1206 .into_iter().filter(|i| i.approved != sender.clone()).collect();1207 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);120812091210 match target_collection.mode1211 {1212 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1213 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1214 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1215 _ => ()1216 };12171218 Ok(())1219 }12201221 #[weight = 0]1222 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12231224 // let no_perm_mes = "You do not have permissions to modify this collection";1225 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1226 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1227 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12281229 // // on_nft_received call12301231 // Self::transfer(origin, collection_id, item_id, new_owner)?;12321233 Ok(())1234 }12351236 /// Set off-chain data schema.1237 /// 1238 /// # Permissions1239 /// 1240 /// * Collection Owner1241 /// * Collection Admin1242 /// 1243 /// # Arguments1244 /// 1245 /// * collection_id.1246 /// 1247 /// * schema: String representing the offchain data schema.1248 #[weight = T::WeightInfo::set_variable_meta_data()]1249 pub fn set_variable_meta_data (1250 origin,1251 collection_id: CollectionId,1252 item_id: TokenId,1253 data: Vec<u8>1254 ) -> DispatchResult {1255 let sender = ensure_signed(origin)?;1256 1257 Self::collection_exists(collection_id)?;1258 1259 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12601261 // Modify permissions check1262 let target_collection = <Collection<T>>::get(collection_id);1263 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1264 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1265 Error::<T>::NoPermission);12661267 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12681269 match target_collection.mode1270 {1271 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1272 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1273 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1274 _ => fail!(Error::<T>::UnexpectedCollectionType)1275 };12761277 Ok(())1278 }1279 1280 /// Set schema standard1281 /// ImageURL1282 /// Unique1283 /// 1284 /// # Permissions1285 /// 1286 /// * Collection Owner1287 /// * Collection Admin1288 /// 1289 /// # Arguments1290 /// 1291 /// * collection_id.1292 /// 1293 /// * schema: SchemaVersion: enum1294 #[weight = 0]1295 pub fn set_schema_version(1296 origin,1297 collection_id: CollectionId,1298 version: SchemaVersion1299 ) -> DispatchResult {1300 let sender = ensure_signed(origin)?;1301 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1302 let mut target_collection = <Collection<T>>::get(collection_id);1303 target_collection.schema_version = version;1304 <Collection<T>>::insert(collection_id, target_collection);13051306 Ok(())1307 }13081309 /// Set off-chain data schema.1310 /// 1311 /// # Permissions1312 /// 1313 /// * Collection Owner1314 /// * Collection Admin1315 /// 1316 /// # Arguments1317 /// 1318 /// * collection_id.1319 /// 1320 /// * schema: String representing the offchain data schema.1321 #[weight = T::WeightInfo::set_offchain_schema()]1322 pub fn set_offchain_schema(1323 origin,1324 collection_id: CollectionId,1325 schema: Vec<u8>1326 ) -> DispatchResult {1327 let sender = ensure_signed(origin)?;1328 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13291330 let mut target_collection = <Collection<T>>::get(collection_id);1331 target_collection.offchain_schema = schema;1332 <Collection<T>>::insert(collection_id, target_collection);13331334 Ok(())1335 }13361337 /// Set const on-chain data schema.1338 /// 1339 /// # Permissions1340 /// 1341 /// * Collection Owner1342 /// * Collection Admin1343 /// 1344 /// # Arguments1345 /// 1346 /// * collection_id.1347 /// 1348 /// * schema: String representing the const on-chain data schema.1349 #[weight = T::WeightInfo::set_const_on_chain_schema()]1350 pub fn set_const_on_chain_schema (1351 origin,1352 collection_id: CollectionId,1353 schema: Vec<u8>1354 ) -> DispatchResult {1355 let sender = ensure_signed(origin)?;1356 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13571358 let mut target_collection = <Collection<T>>::get(collection_id);1359 target_collection.const_on_chain_schema = schema;1360 <Collection<T>>::insert(collection_id, target_collection);13611362 Ok(())1363 }13641365 /// Set variable on-chain data schema.1366 /// 1367 /// # Permissions1368 /// 1369 /// * Collection Owner1370 /// * Collection Admin1371 /// 1372 /// # Arguments1373 /// 1374 /// * collection_id.1375 /// 1376 /// * schema: String representing the variable on-chain data schema.1377 #[weight = T::WeightInfo::set_const_on_chain_schema()]1378 pub fn set_variable_on_chain_schema (1379 origin,1380 collection_id: CollectionId,1381 schema: Vec<u8>1382 ) -> DispatchResult {1383 let sender = ensure_signed(origin)?;1384 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13851386 let mut target_collection = <Collection<T>>::get(collection_id);1387 target_collection.variable_on_chain_schema = schema;1388 <Collection<T>>::insert(collection_id, target_collection);13891390 Ok(())1391 }13921393 // Sudo permissions function1394 #[weight = 0]1395 pub fn set_chain_limits(1396 origin,1397 limits: ChainLimits1398 ) -> DispatchResult {1399 ensure_root(origin)?;1400 <ChainLimit>::put(limits);1401 Ok(())1402 }14031404 /// Enable smart contract self-sponsoring.1405 /// 1406 /// # Permissions1407 /// 1408 /// * Contract Owner1409 /// 1410 /// # Arguments1411 /// 1412 /// * contract address1413 /// * enable flag1414 /// 1415 #[weight = T::WeightInfo::enable_contract_sponsoring()]1416 pub fn enable_contract_sponsoring(1417 origin,1418 contract_address: T::AccountId,1419 enable: bool1420 ) -> DispatchResult {14211422 let sender = ensure_signed(origin)?;14231424 #[cfg(feature = "runtime-benchmarks")]1425 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14261427 let mut is_owner = false;1428 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1429 let owner = <ContractOwner<T>>::get(&contract_address);1430 is_owner = sender == owner;1431 }1432 ensure!(is_owner, Error::<T>::NoPermission);14331434 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1435 Ok(())1436 }14371438 /// Set the rate limit for contract sponsoring to specified number of blocks.1439 /// 1440 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1441 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1442 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1443 /// from contract endowment if there are at least B blocks between such transactions. 1444 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1445 /// 1446 /// # Permissions1447 /// 1448 /// * Contract Owner1449 /// 1450 /// # Arguments1451 /// 1452 /// -`contract_address`: Address of the contract to sponsor1453 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1454 /// 1455 #[weight = 0]1456 pub fn set_contract_sponsoring_rate_limit(1457 origin,1458 contract_address: T::AccountId,1459 rate_limit: T::BlockNumber1460 ) -> DispatchResult {1461 let sender = ensure_signed(origin)?;1462 let mut is_owner = false;1463 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1464 let owner = <ContractOwner<T>>::get(&contract_address);1465 is_owner = sender == owner;1466 }1467 ensure!(is_owner, Error::<T>::NoPermission);14681469 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1470 Ok(())1471 }14721473 #[weight = 0]1474 pub fn set_collection_limits(1475 origin,1476 collection_id: u32,1477 limits: CollectionLimits,1478 ) -> DispatchResult {1479 let sender = ensure_signed(origin)?;1480 Self::check_owner_permissions(collection_id, sender.clone())?;1481 let mut target_collection = <Collection<T>>::get(collection_id);1482 let chain_limits = ChainLimit::get();1483 let climits = target_collection.limits;14841485 // collection bounds1486 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1487 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1488 Error::<T>::CollectionLimitBoundsExceeded);14891490 // token_limit check prev1491 ensure!(climits.token_limit > limits.token_limit && 1492 limits.token_limit <= chain_limits.account_token_ownership_limit, 1493 Error::<T>::AccountTokenLimitExceeded);14941495 target_collection.limits = limits;1496 <Collection<T>>::insert(collection_id, target_collection);14971498 Ok(())1499 } 1500 }1501}15021503impl<T: Trait> Module<T> {15041505 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15061507 // check token limit and account token limit1508 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1509 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1510 1511 Ok(())1512 }15131514 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15151516 // check token limit and account token limit1517 let total_items: u32 = ItemListIndex::get(collection_id);1518 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1519 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1520 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15211522 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1523 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1524 Self::check_white_list(collection_id, owner)?;1525 Self::check_white_list(collection_id, sender)?;1526 }15271528 Ok(())1529 }15301531 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1532 match target_collection.mode1533 {1534 CollectionMode::NFT => {1535 if let CreateItemData::NFT(data) = data {1536 // check sizes1537 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1538 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1539 } else {1540 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1541 }1542 },1543 CollectionMode::Fungible(_) => {1544 if let CreateItemData::Fungible(_) = data {1545 } else {1546 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1547 }1548 },1549 CollectionMode::ReFungible(_) => {1550 if let CreateItemData::ReFungible(data) = data {15511552 // check sizes1553 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1554 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1555 } else {1556 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1557 }1558 },1559 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1560 };15611562 Ok(())1563 }15641565 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1566 match data1567 {1568 CreateItemData::NFT(data) => {1569 let item = NftItemType {1570 owner,1571 const_data: data.const_data,1572 variable_data: data.variable_data1573 };15741575 Self::add_nft_item(collection_id, item)?;1576 },1577 CreateItemData::Fungible(_) => {1578 let item = FungibleItemType {1579 owner,1580 value: (10 as u128).pow(collection.decimal_points as u32)1581 };15821583 Self::add_fungible_item(collection_id, item)?;1584 },1585 CreateItemData::ReFungible(data) => {1586 let mut owner_list = Vec::new();1587 let value = (10 as u128).pow(collection.decimal_points as u32);1588 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15891590 let item = ReFungibleItemType {1591 owner: owner_list,1592 const_data: data.const_data,1593 variable_data: data.variable_data1594 };15951596 Self::add_refungible_item(collection_id, item)?;1597 }1598 };15991600 // call event1601 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16021603 Ok(())1604 }16051606 fn add_fungible_item(collection_id: CollectionId, item: FungibleItemType<T::AccountId>) -> DispatchResult {1607 let current_index = <ItemListIndex>::get(collection_id)1608 .checked_add(1)1609 .ok_or(Error::<T>::NumOverflow)?;1610 let itemcopy = item.clone();1611 let owner = item.owner.clone();16121613 Self::add_token_index(collection_id, current_index, owner.clone())?;16141615 <ItemListIndex>::insert(collection_id, current_index);1616 <FungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16171618 // Add current block1619 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1620 <FungibleTransferBasket<T>>::insert(collection_id, current_index, v);1621 1622 // Update balance1623 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1624 .checked_add(item.value)1625 .ok_or(Error::<T>::NumOverflow)?;1626 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16271628 Ok(())1629 }16301631 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1632 let current_index = <ItemListIndex>::get(collection_id)1633 .checked_add(1)1634 .ok_or(Error::<T>::NumOverflow)?;1635 let itemcopy = item.clone();16361637 let value = item.owner.first().unwrap().fraction;1638 let owner = item.owner.first().unwrap().owner.clone();16391640 Self::add_token_index(collection_id, current_index, owner.clone())?;16411642 <ItemListIndex>::insert(collection_id, current_index);1643 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16441645 // Add current block1646 let block_number: T::BlockNumber = 0.into();1647 <ReFungibleTransferBasket<T>>::insert(collection_id, current_index, block_number);16481649 // Update balance1650 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1651 .checked_add(value)1652 .ok_or(Error::<T>::NumOverflow)?;1653 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16541655 Ok(())1656 }16571658 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1659 let current_index = <ItemListIndex>::get(collection_id)1660 .checked_add(1)1661 .ok_or(Error::<T>::NumOverflow)?;16621663 let item_owner = item.owner.clone();1664 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16651666 <ItemListIndex>::insert(collection_id, current_index);1667 <NftItemList<T>>::insert(collection_id, current_index, item);16681669 // Add current block1670 let block_number: T::BlockNumber = 0.into();1671 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);16721673 // Update balance1674 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1675 .checked_add(1)1676 .ok_or(Error::<T>::NumOverflow)?;1677 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16781679 Ok(())1680 }16811682 fn burn_refungible_item(1683 collection_id: CollectionId,1684 item_id: TokenId,1685 owner: T::AccountId,1686 ) -> DispatchResult {1687 ensure!(1688 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1689 Error::<T>::TokenNotFound1690 );1691 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1692 let item = collection1693 .owner1694 .iter()1695 .filter(|&i| i.owner == owner)1696 .next()1697 .unwrap();1698 Self::remove_token_index(collection_id, item_id, owner.clone())?;16991700 // remove approve list1701 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17021703 // update balance1704 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1705 .checked_sub(item.fraction)1706 .ok_or(Error::<T>::NumOverflow)?;1707 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17081709 <ReFungibleItemList<T>>::remove(collection_id, item_id);17101711 Ok(())1712 }17131714 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1715 ensure!(1716 <NftItemList<T>>::contains_key(collection_id, item_id),1717 Error::<T>::TokenNotFound1718 );1719 let item = <NftItemList<T>>::get(collection_id, item_id);1720 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17211722 // remove approve list1723 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17241725 // update balance1726 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1727 .checked_sub(1)1728 .ok_or(Error::<T>::NumOverflow)?;1729 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1730 <NftItemList<T>>::remove(collection_id, item_id);17311732 Ok(())1733 }17341735 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1736 ensure!(1737 <FungibleItemList<T>>::contains_key(collection_id, item_id),1738 Error::<T>::TokenNotFound1739 );1740 let item = <FungibleItemList<T>>::get(collection_id, item_id);1741 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17421743 // remove approve list1744 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17451746 // update balance1747 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1748 .checked_sub(item.value)1749 .ok_or(Error::<T>::NumOverflow)?;1750 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17511752 <FungibleItemList<T>>::remove(collection_id, item_id);17531754 Ok(())1755 }17561757 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1758 ensure!(1759 <Collection<T>>::contains_key(collection_id),1760 Error::<T>::CollectionNotFound1761 );1762 Ok(())1763 }17641765 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1766 Self::collection_exists(collection_id)?;17671768 let target_collection = <Collection<T>>::get(collection_id);1769 ensure!(1770 subject == target_collection.owner,1771 Error::<T>::NoPermission1772 );17731774 Ok(())1775 }17761777 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1778 let target_collection = <Collection<T>>::get(collection_id);1779 let mut result: bool = subject == target_collection.owner;1780 let exists = <AdminList<T>>::contains_key(collection_id);17811782 if !result & exists {1783 if <AdminList<T>>::get(collection_id).contains(&subject) {1784 result = true1785 }1786 }17871788 result1789 }17901791 fn check_owner_or_admin_permissions(1792 collection_id: CollectionId,1793 subject: T::AccountId,1794 ) -> DispatchResult {1795 Self::collection_exists(collection_id)?;1796 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17971798 ensure!(1799 result,1800 Error::<T>::NoPermission1801 );1802 Ok(())1803 }18041805 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1806 let target_collection = <Collection<T>>::get(collection_id);18071808 match target_collection.mode {1809 CollectionMode::NFT => {1810 <NftItemList<T>>::get(collection_id, item_id).owner == subject1811 }1812 CollectionMode::Fungible(_) => {1813 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1814 }1815 CollectionMode::ReFungible(_) => {1816 <ReFungibleItemList<T>>::get(collection_id, item_id)1817 .owner1818 .iter()1819 .any(|i| i.owner == subject)1820 }1821 CollectionMode::Invalid => false,1822 }1823 }18241825 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1826 let mes = Error::<T>::AddresNotInWhiteList;1827 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1828 let wl = <WhiteList<T>>::get(collection_id);1829 ensure!(wl.contains(address), mes);18301831 Ok(())1832 }18331834 fn transfer_fungible(1835 collection_id: CollectionId,1836 item_id: TokenId,1837 value: u128,1838 owner: T::AccountId,1839 new_owner: T::AccountId,1840 ) -> DispatchResult {1841 ensure!(1842 <FungibleItemList<T>>::contains_key(collection_id, item_id),1843 Error::<T>::TokenNotFound1844 );18451846 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1847 let amount = full_item.value;18481849 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18501851 // update balance1852 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1853 .checked_sub(value)1854 .ok_or(Error::<T>::NumOverflow)?;1855 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);18561857 let mut new_owner_account_id = 0;1858 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1859 if new_owner_items.len() > 0 {1860 new_owner_account_id = new_owner_items[0];1861 }18621863 // transfer1864 if amount == value && new_owner_account_id == 0 {1865 // change owner1866 // new owner do not have account1867 let mut new_full_item = full_item.clone();1868 new_full_item.owner = new_owner.clone();1869 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18701871 // update balance1872 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1873 .checked_add(value)1874 .ok_or(Error::<T>::NumOverflow)?;1875 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);18761877 // update index collection1878 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1879 } else {1880 let mut new_full_item = full_item.clone();1881 new_full_item.value -= value;18821883 // separate amount1884 if new_owner_account_id > 0 {1885 // new owner has account1886 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1887 item.value += value;18881889 // update balance1890 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1891 .checked_add(value)1892 .ok_or(Error::<T>::NumOverflow)?;1893 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);18941895 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1896 } else {1897 // new owner do not have account1898 let item = FungibleItemType {1899 owner: new_owner.clone(),1900 value1901 };19021903 Self::add_fungible_item(collection_id, item)?;1904 }19051906 if amount == value {1907 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19081909 // remove approve list1910 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1911 <FungibleItemList<T>>::remove(collection_id, item_id);1912 }19131914 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1915 }19161917 Ok(())1918 }19191920 fn transfer_refungible(1921 collection_id: CollectionId,1922 item_id: TokenId,1923 value: u128,1924 owner: T::AccountId,1925 new_owner: T::AccountId,1926 ) -> DispatchResult {1927 ensure!(1928 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1929 Error::<T>::TokenNotFound1930 );19311932 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1933 let item = full_item1934 .owner1935 .iter()1936 .filter(|i| i.owner == owner)1937 .next()1938 .ok_or(Error::<T>::NumOverflow)?;1939 let amount = item.fraction;19401941 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19421943 // update balance1944 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1945 .checked_sub(value)1946 .ok_or(Error::<T>::NumOverflow)?;1947 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19481949 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1950 .checked_add(value)1951 .ok_or(Error::<T>::NumOverflow)?;1952 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19531954 let old_owner = item.owner.clone();1955 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19561957 // transfer1958 if amount == value && !new_owner_has_account {1959 // change owner1960 // new owner do not have account1961 let mut new_full_item = full_item.clone();1962 new_full_item1963 .owner1964 .iter_mut()1965 .find(|i| i.owner == owner)1966 .unwrap()1967 .owner = new_owner.clone();1968 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19691970 // update index collection1971 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1972 } else {1973 let mut new_full_item = full_item.clone();1974 new_full_item1975 .owner1976 .iter_mut()1977 .find(|i| i.owner == owner)1978 .unwrap()1979 .fraction -= value;19801981 // separate amount1982 if new_owner_has_account {1983 // new owner has account1984 new_full_item1985 .owner1986 .iter_mut()1987 .find(|i| i.owner == new_owner)1988 .unwrap()1989 .fraction += value;1990 } else {1991 // new owner do not have account1992 new_full_item.owner.push(Ownership {1993 owner: new_owner.clone(),1994 fraction: value,1995 });1996 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1997 }19981999 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2000 }20012002 Ok(())2003 }20042005 fn transfer_nft(2006 collection_id: CollectionId,2007 item_id: TokenId,2008 sender: T::AccountId,2009 new_owner: T::AccountId,2010 ) -> DispatchResult {2011 ensure!(2012 <NftItemList<T>>::contains_key(collection_id, item_id),2013 Error::<T>::TokenNotFound2014 );20152016 let mut item = <NftItemList<T>>::get(collection_id, item_id);20172018 ensure!(2019 sender == item.owner,2020 Error::<T>::MustBeTokenOwner2021 );20222023 // update balance2024 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2025 .checked_sub(1)2026 .ok_or(Error::<T>::NumOverflow)?;2027 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20282029 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2030 .checked_add(1)2031 .ok_or(Error::<T>::NumOverflow)?;2032 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20332034 // change owner2035 let old_owner = item.owner.clone();2036 item.owner = new_owner.clone();2037 <NftItemList<T>>::insert(collection_id, item_id, item);20382039 // update index collection2040 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20412042 // reset approved list2043 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2044 Ok(())2045 }2046 2047 fn item_exists(2048 collection_id: CollectionId,2049 item_id: TokenId,2050 mode: &CollectionMode2051 ) -> DispatchResult {2052 match mode {2053 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2054 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2055 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2056 _ => ()2057 };2058 2059 Ok(())2060 }20612062 fn set_re_fungible_variable_data(2063 collection_id: CollectionId,2064 item_id: TokenId,2065 data: Vec<u8>2066 ) -> DispatchResult {2067 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20682069 item.variable_data = data;20702071 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20722073 Ok(())2074 }20752076 fn set_nft_variable_data(2077 collection_id: CollectionId,2078 item_id: TokenId,2079 data: Vec<u8>2080 ) -> DispatchResult {2081 let mut item = <NftItemList<T>>::get(collection_id, item_id);2082 2083 item.variable_data = data;20842085 <NftItemList<T>>::insert(collection_id, item_id, item);2086 2087 Ok(())2088 }20892090 fn init_collection(item: &CollectionType<T::AccountId>) {2091 // check params2092 assert!(2093 item.decimal_points <= MAX_DECIMAL_POINTS,2094 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2095 );2096 assert!(2097 item.name.len() <= 64,2098 "Collection name can not be longer than 63 char"2099 );2100 assert!(2101 item.name.len() <= 256,2102 "Collection description can not be longer than 255 char"2103 );2104 assert!(2105 item.token_prefix.len() <= 16,2106 "Token prefix can not be longer than 15 char"2107 );21082109 // Generate next collection ID2110 let next_id = CreatedCollectionCount::get()2111 .checked_add(1)2112 .unwrap();21132114 CreatedCollectionCount::put(next_id);2115 }21162117 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2118 let current_index = <ItemListIndex>::get(collection_id)2119 .checked_add(1)2120 .unwrap();21212122 let item_owner = item.owner.clone();2123 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();21242125 <ItemListIndex>::insert(collection_id, current_index);21262127 // Update balance2128 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2129 .checked_add(1)2130 .unwrap();2131 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2132 }21332134 fn init_fungible_token(collection_id: CollectionId, item: &FungibleItemType<T::AccountId>) {2135 let current_index = <ItemListIndex>::get(collection_id)2136 .checked_add(1)2137 .unwrap();2138 let owner = item.owner.clone();21392140 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();21412142 <ItemListIndex>::insert(collection_id, current_index);21432144 // Update balance2145 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2146 .checked_add(item.value)2147 .unwrap();2148 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2149 }21502151 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2152 let current_index = <ItemListIndex>::get(collection_id)2153 .checked_add(1)2154 .unwrap();21552156 let value = item.owner.first().unwrap().fraction;2157 let owner = item.owner.first().unwrap().owner.clone();21582159 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();21602161 <ItemListIndex>::insert(collection_id, current_index);21622163 // Update balance2164 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2165 .checked_add(value)2166 .unwrap();2167 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2168 }21692170 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21712172 // add to account limit2173 if <AccountItemCount<T>>::contains_key(owner.clone()) {21742175 // bound Owned tokens by a single address2176 let count = <AccountItemCount<T>>::get(owner.clone());2177 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21782179 <AccountItemCount<T>>::insert(owner.clone(), count2180 .checked_add(1)2181 .ok_or(Error::<T>::NumOverflow)?);2182 }2183 else {2184 <AccountItemCount<T>>::insert(owner.clone(), 1);2185 }21862187 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2188 if list_exists {2189 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2190 let item_contains = list.contains(&item_index.clone());21912192 if !item_contains {2193 list.push(item_index.clone());2194 }21952196 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2197 } else {2198 let mut itm = Vec::new();2199 itm.push(item_index.clone());2200 <AddressTokens<T>>::insert(collection_id, owner, itm);2201 2202 }22032204 Ok(())2205 }22062207 fn remove_token_index(2208 collection_id: CollectionId,2209 item_index: TokenId,2210 owner: T::AccountId,2211 ) -> DispatchResult {22122213 // update counter2214 <AccountItemCount<T>>::insert(owner.clone(), 2215 <AccountItemCount<T>>::get(owner.clone())2216 .checked_sub(1)2217 .ok_or(Error::<T>::NumOverflow)?);221822192220 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2221 if list_exists {2222 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2223 let item_contains = list.contains(&item_index.clone());22242225 if item_contains {2226 list.retain(|&item| item != item_index);2227 <AddressTokens<T>>::insert(collection_id, owner, list);2228 }2229 }22302231 Ok(())2232 }22332234 fn move_token_index(2235 collection_id: CollectionId,2236 item_index: TokenId,2237 old_owner: T::AccountId,2238 new_owner: T::AccountId,2239 ) -> DispatchResult {2240 Self::remove_token_index(collection_id, item_index, old_owner)?;2241 Self::add_token_index(collection_id, item_index, new_owner)?;22422243 Ok(())2244 }2245}22462247////////////////////////////////////////////////////////////////////////////////////////////////////2248// Economic models2249// #region22502251/// Fee multiplier.2252pub type Multiplier = FixedU128;22532254type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2255 <T as system::Trait>::AccountId,2256>>::Balance;2257type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2258 <T as system::Trait>::AccountId,2259>>::NegativeImbalance;22602261/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2262/// in the queue.2263#[derive(Encode, Decode, Clone, Eq, PartialEq)]2264pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2265 #[codec(compact)] BalanceOf<T>2266);22672268impl<T: Trait + Send + Sync> sp_std::fmt::Debug2269 for ChargeTransactionPayment<T>2270{2271 #[cfg(feature = "std")]2272 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2273 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2274 }2275 #[cfg(not(feature = "std"))]2276 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2277 Ok(())2278 }2279}22802281impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2282where2283 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2284 BalanceOf<T>: Send + Sync + FixedPointOperand,2285{2286 /// utility constructor. Used only in client/factory code.2287 pub fn from(fee: BalanceOf<T>) -> Self {2288 Self(fee)2289 }22902291 pub fn traditional_fee(2292 len: usize,2293 info: &DispatchInfoOf<T::Call>,2294 tip: BalanceOf<T>,2295 ) -> BalanceOf<T>2296 where2297 T::Call: Dispatchable<Info = DispatchInfo>,2298 {2299 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2300 }23012302 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2303 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2304 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2305 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2306 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2307 }23082309 fn withdraw_fee(2310 &self,2311 who: &T::AccountId,2312 call: &T::Call,2313 info: &DispatchInfoOf<T::Call>,2314 len: usize,2315 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2316 let tip = self.0;23172318 // Set fee based on call type. Creating collection costs 1 Unique.2319 // All other transactions have traditional fees so far2320 // let fee = match call.is_sub_type() {2321 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2322 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2323 // // _ => <BalanceOf<T>>::from(100)2324 // };2325 let fee = Self::traditional_fee(len, info, tip);23262327 // Only mess with balances if fee is not zero.2328 if fee.is_zero() {2329 return Ok((fee, None));2330 }23312332 // Determine who is paying transaction fee based on ecnomic model2333 // Parse call to extract collection ID and access collection sponsor2334 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2335 Some(Call::create_item(collection_id, _owner, _properties)) => {23362337 // check free create limit2338 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2339 (<Collection<T>>::get(collection_id).sponsor_confirmed)2340 {2341 <Collection<T>>::get(collection_id).sponsor2342 } else {2343 T::AccountId::default()2344 }2345 }2346 Some(Call::transfer(new_owner, collection_id, item_id, _value)) => {2347 2348 let mut sponsor_transfer = false;2349 if <Collection<T>>::get(collection_id).sponsor_confirmed {23502351 let collection_limits = <Collection<T>>::get(collection_id).limits;2352 let collection_mode = <Collection<T>>::get(collection_id).mode;2353 2354 // sponsor timeout2355 sponsor_transfer = match collection_mode {2356 CollectionMode::NFT => {2357 2358 // get correct limit2359 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2360 collection_limits.sponsor_transfer_timeout2361 } else {2362 ChainLimit::get().nft_sponsor_transfer_timeout2363 };2364 2365 let basket = <NftTransferBasket<T>>::get(collection_id, item_id);2366 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2367 let limit_time = basket + limit.into();2368 if block_number >= limit_time {2369 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2370 true2371 }2372 else {2373 false2374 }2375 }2376 CollectionMode::Fungible(_) => {2377 2378 // get correct limit2379 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2380 collection_limits.sponsor_transfer_timeout2381 } else {2382 ChainLimit::get().fungible_sponsor_transfer_timeout2383 };2384 2385 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, item_id);2386 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2387 if basket.iter().any(|i| i.address == new_owner.clone())2388 {2389 let item = basket.iter_mut().find(|i| i.address == new_owner.clone()).unwrap().clone();2390 let limit_time = item.start_block + limit.into();2391 if block_number >= limit_time {2392 basket.retain(|x| x.address == item.address);2393 basket.push(BasketItem { start_block: block_number, address: new_owner.clone() });2394 <FungibleTransferBasket<T>>::insert(collection_id, item_id, basket);2395 true2396 }2397 else {2398 false2399 }2400 }2401 else {2402 basket.push(BasketItem { start_block: block_number, address: new_owner.clone()});2403 true2404 }2405 }2406 CollectionMode::ReFungible(_) => {2407 2408 // get correct limit2409 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2410 collection_limits.sponsor_transfer_timeout2411 } else {2412 ChainLimit::get().refungible_sponsor_transfer_timeout2413 };2414 2415 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2416 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2417 let limit_time = basket + limit.into();2418 if block_number >= limit_time {2419 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2420 true2421 } else {2422 false2423 }2424 }2425 _ => {2426 false2427 },2428 };2429 }24302431 if !sponsor_transfer {2432 T::AccountId::default()2433 } else {2434 <Collection<T>>::get(collection_id).sponsor2435 }2436 }24372438 _ => T::AccountId::default(),2439 };24402441 // Sponsor smart contracts2442 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24432444 // On instantiation: set the contract owner2445 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24462447 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2448 code_hash,2449 &data,2450 &who,2451 );2452 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24532454 T::AccountId::default()2455 },24562457 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2458 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24592460 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24612462 let mut sponsor_transfer = false;2463 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2464 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2465 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2466 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2467 let limit_time = last_tx_block + rate_limit;24682469 if block_number >= limit_time {2470 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2471 sponsor_transfer = true;2472 }2473 } else {2474 sponsor_transfer = false;2475 }2476 2477 2478 let mut sp = T::AccountId::default();2479 if sponsor_transfer {2480 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2481 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2482 sp = called_contract;2483 }2484 }2485 }24862487 sp2488 },24892490 _ => sponsor,2491 };24922493 let mut who_pays_fee: T::AccountId = sponsor.clone();2494 if sponsor == T::AccountId::default() {2495 who_pays_fee = who.clone();2496 }24972498 match <T as transaction_payment::Trait>::Currency::withdraw(2499 &who_pays_fee,2500 fee,2501 if tip.is_zero() {2502 WithdrawReason::TransactionPayment.into()2503 } else {2504 WithdrawReason::TransactionPayment | WithdrawReason::Tip2505 },2506 ExistenceRequirement::KeepAlive,2507 ) {2508 Ok(imbalance) => Ok((fee, Some(imbalance))),2509 Err(_) => Err(InvalidTransaction::Payment.into()),2510 }2511 }2512}251325142515impl<T: Trait + Send + Sync> SignedExtension2516 for ChargeTransactionPayment<T>2517where2518 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2519 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2520{2521 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2522 type AccountId = T::AccountId;2523 type Call = T::Call;2524 type AdditionalSigned = ();2525 type Pre = (2526 BalanceOf<T>,2527 Self::AccountId,2528 Option<NegativeImbalanceOf<T>>,2529 BalanceOf<T>,2530 );2531 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2532 Ok(())2533 }25342535 fn validate(2536 &self,2537 who: &Self::AccountId,2538 call: &Self::Call,2539 info: &DispatchInfoOf<Self::Call>,2540 len: usize,2541 ) -> TransactionValidity {2542 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2543 Ok(ValidTransaction {2544 priority: Self::get_priority(len, info, fee),2545 ..Default::default()2546 })2547 }25482549 fn pre_dispatch(2550 self,2551 who: &Self::AccountId,2552 call: &Self::Call,2553 info: &DispatchInfoOf<Self::Call>,2554 len: usize,2555 ) -> Result<Self::Pre, TransactionValidityError> {2556 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2557 Ok((self.0, who.clone(), imbalance, fee))2558 }25592560 fn post_dispatch(2561 pre: Self::Pre,2562 info: &DispatchInfoOf<Self::Call>,2563 post_info: &PostDispatchInfoOf<Self::Call>,2564 len: usize,2565 _result: &DispatchResult,2566 ) -> Result<(), TransactionValidityError> {2567 let (tip, who, imbalance, fee) = pre;2568 if let Some(payed) = imbalance {2569 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2570 len as u32, info, post_info, tip,2571 );2572 let refund = fee.saturating_sub(actual_fee);2573 let actual_payment =2574 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2575 &who, refund,2576 ) {2577 Ok(refund_imbalance) => {2578 // The refund cannot be larger than the up front payed max weight.2579 // `PostDispatchInfo::calc_unspent` guards against such a case.2580 match payed.offset(refund_imbalance) {2581 Ok(actual_payment) => actual_payment,2582 Err(_) => return Err(InvalidTransaction::Payment.into()),2583 }2584 }2585 // We do not recreate the account using the refund. The up front payment2586 // is gone in that case.2587 Err(_) => payed,2588 };2589 let imbalances = actual_payment.split(tip);2590 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2591 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2592 );2593 }2594 Ok(())2595 }2596}25972598// #endregion1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, WithdrawReason,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial,29 },30 IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69 Invalid,70 NFT,71 // decimal points72 Fungible(DecimalPoints),73 // decimal points74 ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78 fn into(self) -> u8 {79 match self {80 CollectionMode::Invalid => 0,81 CollectionMode::NFT => 1,82 CollectionMode::Fungible(_) => 2,83 CollectionMode::ReFungible(_) => 3,84 }85 }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91 Normal,92 WhiteList,93}94impl Default for AccessMode {95 fn default() -> Self {96 Self::Normal97 }98}99100impl Default for CollectionMode {101 fn default() -> Self {102 Self::Invalid103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub owner: AccountId,149 pub const_data: Vec<u8>,150 pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType {156 pub value: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct ReFungibleItemType<AccountId> {162 pub owner: Vec<Ownership<AccountId>>,163 pub const_data: Vec<u8>,164 pub variable_data: Vec<u8>,165}166167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169// pub struct VestingItem<AccountId, Moment> {170// pub sender: AccountId,171// pub recipient: AccountId,172// pub collection_id: CollectionId,173// pub item_id: TokenId,174// pub amount: u64,175// pub vesting_date: Moment,176// }177178#[derive(Encode, Decode, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct CollectionLimits {181 pub account_token_ownership_limit: u32,182 pub sponsored_data_size: u32,183 pub token_limit: u32,184185 // Timeouts for item types in passed blocks186 pub sponsor_transfer_timeout: u32,187}188189impl Default for CollectionLimits {190 fn default() -> CollectionLimits {191 CollectionLimits { 192 account_token_ownership_limit: 10_000_000, 193 token_limit: u32::max_value(),194 sponsored_data_size: u32::max_value(), 195 sponsor_transfer_timeout: 14400 }196 }197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]201pub struct ChainLimits {202 pub collection_numbers_limit: u32,203 pub account_token_ownership_limit: u32,204 pub collections_admins_limit: u64,205 pub custom_data_limit: u32,206207 // Timeouts for item types in passed blocks208 pub nft_sponsor_transfer_timeout: u32,209 pub fungible_sponsor_transfer_timeout: u32,210 pub refungible_sponsor_transfer_timeout: u32,211}212213pub trait WeightInfo {214 fn create_collection() -> Weight;215 fn destroy_collection() -> Weight;216 fn add_to_white_list() -> Weight;217 fn remove_from_white_list() -> Weight;218 fn set_public_access_mode() -> Weight;219 fn set_mint_permission() -> Weight;220 fn change_collection_owner() -> Weight;221 fn add_collection_admin() -> Weight;222 fn remove_collection_admin() -> Weight;223 fn set_collection_sponsor() -> Weight;224 fn confirm_sponsorship() -> Weight;225 fn remove_collection_sponsor() -> Weight;226 fn create_item(s: usize) -> Weight;227 fn burn_item() -> Weight;228 fn transfer() -> Weight;229 fn approve() -> Weight;230 fn transfer_from() -> Weight;231 fn set_offchain_schema() -> Weight;232 fn set_const_on_chain_schema() -> Weight;233 fn set_variable_on_chain_schema() -> Weight;234 fn set_variable_meta_data() -> Weight;235 fn enable_contract_sponsoring() -> Weight;236}237238#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]239#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]240pub struct CreateNftData {241 pub const_data: Vec<u8>,242 pub variable_data: Vec<u8>,243}244245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]247pub struct CreateFungibleData {248 pub value: u128,249}250251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]252#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]253pub struct CreateReFungibleData {254 pub const_data: Vec<u8>,255 pub variable_data: Vec<u8>,256}257258#[derive(Encode, Decode, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub enum CreateItemData {261 NFT(CreateNftData),262 Fungible(CreateFungibleData),263 ReFungible(CreateReFungibleData),264}265266impl CreateItemData {267 pub fn len(&self) -> usize {268 let len = match self {269 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),270 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),271 _ => 0272 };273 274 return len;275 }276}277278impl From<CreateNftData> for CreateItemData {279 fn from(item: CreateNftData) -> Self {280 CreateItemData::NFT(item)281 }282}283284impl From<CreateReFungibleData> for CreateItemData {285 fn from(item: CreateReFungibleData) -> Self {286 CreateItemData::ReFungible(item)287 }288}289290impl From<CreateFungibleData> for CreateItemData {291 fn from(item: CreateFungibleData) -> Self {292 CreateItemData::Fungible(item)293 }294}295296297decl_error! {298 /// Error for non-fungible-token module.299 pub enum Error for Module<T: Trait> {300 /// Total collections bound exceeded.301 TotalCollectionsLimitExceeded,302 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.303 CollectionDecimalPointLimitExceeded, 304 /// Collection name can not be longer than 63 char.305 CollectionNameLimitExceeded, 306 /// Collection description can not be longer than 255 char.307 CollectionDescriptionLimitExceeded, 308 /// Token prefix can not be longer than 15 char.309 CollectionTokenPrefixLimitExceeded,310 /// This collection does not exist.311 CollectionNotFound,312 /// Item not exists.313 TokenNotFound,314 /// Arithmetic calculation overflow.315 NumOverflow, 316 /// Account already has admin role.317 AlreadyAdmin, 318 /// You do not own this collection.319 NoPermission,320 /// This address is not set as sponsor, use setCollectionSponsor first.321 ConfirmUnsetSponsorFail,322 /// Collection is not in mint mode.323 PublicMintingNotAllowed,324 /// Sender parameter and item owner must be equal.325 MustBeTokenOwner,326 /// Item balance not enough.327 TokenValueTooLow,328 /// Size of item is too large.329 NftSizeLimitExceeded,330 /// No approve found331 ApproveNotFound,332 /// Requested value more than approved.333 TokenValueNotEnough,334 /// Only approved addresses can call this method.335 ApproveRequired,336 /// Address is not in white list.337 AddresNotInWhiteList,338 /// Number of collection admins bound exceeded.339 CollectionAdminsLimitExceeded,340 /// Owned tokens by a single address bound exceeded.341 AddressOwnershipLimitExceeded,342 /// Length of items properties must be greater than 0.343 EmptyArgument,344 /// const_data exceeded data limit.345 TokenConstDataLimitExceeded,346 /// variable_data exceeded data limit.347 TokenVariableDataLimitExceeded,348 /// Not NFT item data used to mint in NFT collection.349 NotNftDataUsedToMintNftCollectionToken,350 /// Not Fungible item data used to mint in Fungible collection.351 NotFungibleDataUsedToMintFungibleCollectionToken,352 /// Not Re Fungible item data used to mint in Re Fungible collection.353 NotReFungibleDataUsedToMintReFungibleCollectionToken,354 /// Unexpected collection type.355 UnexpectedCollectionType,356 /// Can't store metadata in fungible tokens.357 CantStoreMetadataInFungibleTokens,358 /// Collection token limit exceeded359 CollectionTokenLimitExceeded,360 /// Account token limit exceeded per collection361 AccountTokenLimitExceeded,362 /// Collection limit bounds per collection exceeded363 CollectionLimitBoundsExceeded364 }365}366367pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {368 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;369370 /// Weight information for extrinsics in this pallet.371 type WeightInfo: WeightInfo;372}373374#[cfg(feature = "runtime-benchmarks")]375mod benchmarking;376377// #endregion378379decl_storage! {380 trait Store for Module<T: Trait> as Nft {381382 // Private members383 NextCollectionID: CollectionId;384 CreatedCollectionCount: u32;385 ChainVersion: u64;386 ItemListIndex: map hasher(identity) CollectionId => TokenId;387388 // Chain limits struct389 pub ChainLimit get(fn chain_limit) config(): ChainLimits;390391 // Bound counters392 CollectionCount: u32;393 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;394395 // Basic collections396 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;397 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;398 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;399400 /// Balance owner per collection map401 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;402403 /// second parameter: item id + owner account id + spender account id404 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;405406 /// Item collections407 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;408 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;409 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;410411 /// Index list412 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;413414 /// Tokens transfer baskets415 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;416 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;417 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;418419 // Contract Sponsorship and Ownership420 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;421 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;422 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;423 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;424 }425 add_extra_genesis {426 build(|config: &GenesisConfig<T>| {427 // Modification of storage428 for (_num, _c) in &config.collection {429 <Module<T>>::init_collection(_c);430 }431432 for (_num, _c, _i) in &config.nft_item_id {433 <Module<T>>::init_nft_token(*_c, _i);434 }435436 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {437 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);438 }439440 for (_num, _c, _i) in &config.refungible_item_id {441 <Module<T>>::init_refungible_token(*_c, _i);442 }443 })444 }445}446447decl_event!(448 pub enum Event<T>449 where450 AccountId = <T as system::Trait>::AccountId,451 {452 /// New collection was created453 /// 454 /// # Arguments455 /// 456 /// * collection_id: Globally unique identifier of newly created collection.457 /// 458 /// * mode: [CollectionMode] converted into u8.459 /// 460 /// * account_id: Collection owner.461 Created(CollectionId, u8, AccountId),462463 /// New item was created.464 /// 465 /// # Arguments466 /// 467 /// * collection_id: Id of the collection where item was created.468 /// 469 /// * item_id: Id of an item. Unique within the collection.470 ItemCreated(CollectionId, TokenId),471472 /// Collection item was burned.473 /// 474 /// # Arguments475 /// 476 /// collection_id.477 /// 478 /// item_id: Identifier of burned NFT.479 ItemDestroyed(CollectionId, TokenId),480 }481);482483decl_module! {484 pub struct Module<T: Trait> for enum Call where origin: T::Origin {485486 fn deposit_event() = default;487 type Error = Error<T>;488489 fn on_initialize(now: T::BlockNumber) -> Weight {490491 if ChainVersion::get() < 2492 {493 let value = NextCollectionID::get();494 CreatedCollectionCount::put(value);495 ChainVersion::put(2);496 }497498 0499 }500501 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.502 /// 503 /// # Permissions504 /// 505 /// * Anyone.506 /// 507 /// # Arguments508 /// 509 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.510 /// 511 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.512 /// 513 /// * token_prefix: UTF-8 string with token prefix.514 /// 515 /// * mode: [CollectionMode] collection type and type dependent data.516 // returns collection ID517 #[weight = T::WeightInfo::create_collection()]518 pub fn create_collection(origin,519 collection_name: Vec<u16>,520 collection_description: Vec<u16>,521 token_prefix: Vec<u8>,522 mode: CollectionMode) -> DispatchResult {523524 // Anyone can create a collection525 let who = ensure_signed(origin)?;526527 let decimal_points = match mode {528 CollectionMode::Fungible(points) => points,529 CollectionMode::ReFungible(points) => points,530 _ => 0531 };532533 // bound Total number of collections534 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);535536 // check params537 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);538539 let mut name = collection_name.to_vec();540 name.push(0);541 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);542543 let mut description = collection_description.to_vec();544 description.push(0);545 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);546547 let mut prefix = token_prefix.to_vec();548 prefix.push(0);549 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);550551 // Generate next collection ID552 let next_id = CreatedCollectionCount::get()553 .checked_add(1)554 .ok_or(Error::<T>::NumOverflow)?;555556 // bound counter557 let total = CollectionCount::get()558 .checked_add(1)559 .ok_or(Error::<T>::NumOverflow)?;560561 CreatedCollectionCount::put(next_id);562 CollectionCount::put(total);563564 // Create new collection565 let new_collection = CollectionType {566 owner: who.clone(),567 name: name,568 mode: mode.clone(),569 mint_mode: false,570 access: AccessMode::Normal,571 description: description,572 decimal_points: decimal_points,573 token_prefix: prefix,574 offchain_schema: Vec::new(),575 schema_version: SchemaVersion::ImageURL,576 sponsor: T::AccountId::default(),577 sponsor_confirmed: false,578 variable_on_chain_schema: Vec::new(),579 const_on_chain_schema: Vec::new(),580 limits: CollectionLimits::default(),581 };582583 // Add new collection to map584 <Collection<T>>::insert(next_id, new_collection);585586 // call event587 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));588589 Ok(())590 }591592 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.593 /// 594 /// # Permissions595 /// 596 /// * Collection Owner.597 /// 598 /// # Arguments599 /// 600 /// * collection_id: collection to destroy.601 #[weight = T::WeightInfo::destroy_collection()]602 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {603604 let sender = ensure_signed(origin)?;605 Self::check_owner_permissions(collection_id, sender)?;606607 <AddressTokens<T>>::remove_prefix(collection_id);608 <Allowances<T>>::remove_prefix(collection_id);609 <Balance<T>>::remove_prefix(collection_id);610 <ItemListIndex>::remove(collection_id);611 <AdminList<T>>::remove(collection_id);612 <Collection<T>>::remove(collection_id);613 <WhiteList<T>>::remove(collection_id);614615 <NftItemList<T>>::remove_prefix(collection_id);616 <FungibleItemList<T>>::remove_prefix(collection_id);617 <ReFungibleItemList<T>>::remove_prefix(collection_id);618619 <NftTransferBasket<T>>::remove_prefix(collection_id);620 <FungibleTransferBasket<T>>::remove_prefix(collection_id);621 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);622623 if CollectionCount::get() > 0624 {625 // bound couter626 let total = CollectionCount::get()627 .checked_sub(1)628 .ok_or(Error::<T>::NumOverflow)?;629630 CollectionCount::put(total);631 }632633 Ok(())634 }635636 /// Add an address to white list.637 /// 638 /// # Permissions639 /// 640 /// * Collection Owner641 /// * Collection Admin642 /// 643 /// # Arguments644 /// 645 /// * collection_id.646 /// 647 /// * address.648 #[weight = T::WeightInfo::add_to_white_list()]649 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{650651 let sender = ensure_signed(origin)?;652 Self::check_owner_or_admin_permissions(collection_id, sender)?;653654 let mut white_list_collection: Vec<T::AccountId>;655 if <WhiteList<T>>::contains_key(collection_id) {656 white_list_collection = <WhiteList<T>>::get(collection_id);657 if !white_list_collection.contains(&address.clone())658 {659 white_list_collection.push(address.clone());660 }661 }662 else {663 white_list_collection = Vec::new();664 white_list_collection.push(address.clone());665 }666667 <WhiteList<T>>::insert(collection_id, white_list_collection);668 Ok(())669 }670671 /// Remove an address from white list.672 /// 673 /// # Permissions674 /// 675 /// * Collection Owner676 /// * Collection Admin677 /// 678 /// # Arguments679 /// 680 /// * collection_id.681 /// 682 /// * address.683 #[weight = T::WeightInfo::remove_from_white_list()]684 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{685686 let sender = ensure_signed(origin)?;687 Self::check_owner_or_admin_permissions(collection_id, sender)?;688689 if <WhiteList<T>>::contains_key(collection_id) {690 let mut white_list_collection = <WhiteList<T>>::get(collection_id);691 if white_list_collection.contains(&address.clone())692 {693 white_list_collection.retain(|i| *i != address.clone());694 <WhiteList<T>>::insert(collection_id, white_list_collection);695 }696 }697698 Ok(())699 }700701 /// Toggle between normal and white list access for the methods with access for `Anyone`.702 /// 703 /// # Permissions704 /// 705 /// * Collection Owner.706 /// 707 /// # Arguments708 /// 709 /// * collection_id.710 /// 711 /// * mode: [AccessMode]712 #[weight = T::WeightInfo::set_public_access_mode()]713 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult714 {715 let sender = ensure_signed(origin)?;716717 Self::check_owner_permissions(collection_id, sender)?;718 let mut target_collection = <Collection<T>>::get(collection_id);719 target_collection.access = mode;720 <Collection<T>>::insert(collection_id, target_collection);721722 Ok(())723 }724725 /// Allows Anyone to create tokens if:726 /// * White List is enabled, and727 /// * Address is added to white list, and728 /// * This method was called with True parameter729 /// 730 /// # Permissions731 /// * Collection Owner732 ///733 /// # Arguments734 /// 735 /// * collection_id.736 /// 737 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.738 #[weight = T::WeightInfo::set_mint_permission()]739 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult740 {741 let sender = ensure_signed(origin)?;742743 Self::check_owner_permissions(collection_id, sender)?;744 let mut target_collection = <Collection<T>>::get(collection_id);745 target_collection.mint_mode = mint_permission;746 <Collection<T>>::insert(collection_id, target_collection);747748 Ok(())749 }750751 /// Change the owner of the collection.752 /// 753 /// # Permissions754 /// 755 /// * Collection Owner.756 /// 757 /// # Arguments758 /// 759 /// * collection_id.760 /// 761 /// * new_owner.762 #[weight = T::WeightInfo::change_collection_owner()]763 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {764765 let sender = ensure_signed(origin)?;766 Self::check_owner_permissions(collection_id, sender)?;767 let mut target_collection = <Collection<T>>::get(collection_id);768 target_collection.owner = new_owner;769 <Collection<T>>::insert(collection_id, target_collection);770771 Ok(())772 }773774 /// Adds an admin of the Collection.775 /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 776 /// 777 /// # Permissions778 /// 779 /// * Collection Owner.780 /// * Collection Admin.781 /// 782 /// # Arguments783 /// 784 /// * collection_id: ID of the Collection to add admin for.785 /// 786 /// * new_admin_id: Address of new admin to add.787 #[weight = T::WeightInfo::add_collection_admin()]788 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {789790 let sender = ensure_signed(origin)?;791 Self::check_owner_or_admin_permissions(collection_id, sender)?;792 let mut admin_arr: Vec<T::AccountId> = Vec::new();793794 if <AdminList<T>>::contains_key(collection_id)795 {796 admin_arr = <AdminList<T>>::get(collection_id);797 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);798 }799800 // Number of collection admins801 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);802803 admin_arr.push(new_admin_id);804 <AdminList<T>>::insert(collection_id, admin_arr);805806 Ok(())807 }808809 /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.810 ///811 /// # Permissions812 /// 813 /// * Collection Owner.814 /// * Collection Admin.815 /// 816 /// # Arguments817 /// 818 /// * collection_id: ID of the Collection to remove admin for.819 /// 820 /// * account_id: Address of admin to remove.821 #[weight = T::WeightInfo::remove_collection_admin()]822 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {823824 let sender = ensure_signed(origin)?;825 Self::check_owner_or_admin_permissions(collection_id, sender)?;826827 if <AdminList<T>>::contains_key(collection_id)828 {829 let mut admin_arr = <AdminList<T>>::get(collection_id);830 admin_arr.retain(|i| *i != account_id);831 <AdminList<T>>::insert(collection_id, admin_arr);832 }833834 Ok(())835 }836837 /// # Permissions838 /// 839 /// * Collection Owner840 /// 841 /// # Arguments842 /// 843 /// * collection_id.844 /// 845 /// * new_sponsor.846 #[weight = T::WeightInfo::set_collection_sponsor()]847 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {848849 let sender = ensure_signed(origin)?;850 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);851852 let mut target_collection = <Collection<T>>::get(collection_id);853 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);854855 target_collection.sponsor = new_sponsor;856 target_collection.sponsor_confirmed = false;857 <Collection<T>>::insert(collection_id, target_collection);858859 Ok(())860 }861862 /// # Permissions863 /// 864 /// * Sponsor.865 /// 866 /// # Arguments867 /// 868 /// * collection_id.869 #[weight = T::WeightInfo::confirm_sponsorship()]870 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {871872 let sender = ensure_signed(origin)?;873 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);874875 let mut target_collection = <Collection<T>>::get(collection_id);876 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);877878 target_collection.sponsor_confirmed = true;879 <Collection<T>>::insert(collection_id, target_collection);880881 Ok(())882 }883884 /// Switch back to pay-per-own-transaction model.885 ///886 /// # Permissions887 ///888 /// * Collection owner.889 /// 890 /// # Arguments891 /// 892 /// * collection_id.893 #[weight = T::WeightInfo::remove_collection_sponsor()]894 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {895896 let sender = ensure_signed(origin)?;897 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);898899 let mut target_collection = <Collection<T>>::get(collection_id);900 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);901902 target_collection.sponsor = T::AccountId::default();903 target_collection.sponsor_confirmed = false;904 <Collection<T>>::insert(collection_id, target_collection);905906 Ok(())907 }908909 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.910 /// 911 /// # Permissions912 /// 913 /// * Collection Owner.914 /// * Collection Admin.915 /// * Anyone if916 /// * White List is enabled, and917 /// * Address is added to white list, and918 /// * MintPermission is enabled (see SetMintPermission method)919 /// 920 /// # Arguments921 /// 922 /// * collection_id: ID of the collection.923 /// 924 /// * owner: Address, initial owner of the NFT.925 ///926 /// * data: Token data to store on chain.927 // #[weight =928 // (130_000_000 as Weight)929 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))930 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))931 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]932933 #[weight = T::WeightInfo::create_item(data.len())]934 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {935936 let sender = ensure_signed(origin)?;937938 Self::collection_exists(collection_id)?;939940 let target_collection = <Collection<T>>::get(collection_id);941942 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;943 Self::validate_create_item_args(&target_collection, &data)?;944 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;945946 Ok(())947 }948949 /// This method creates multiple instances of NFT Collection created with CreateCollection method.950 /// 951 /// # Permissions952 /// 953 /// * Collection Owner.954 /// * Collection Admin.955 /// * Anyone if956 /// * White List is enabled, and957 /// * Address is added to white list, and958 /// * MintPermission is enabled (see SetMintPermission method)959 /// 960 /// # Arguments961 /// 962 /// * collection_id: ID of the collection.963 /// 964 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].965 /// 966 /// * owner: Address, initial owner of the NFT.967 #[weight = T::WeightInfo::create_item(items_data.into_iter()968 .map(|data| { data.len() })969 .sum())]970 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {971972 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);973 let sender = ensure_signed(origin)?;974975 Self::collection_exists(collection_id)?;976 let target_collection = <Collection<T>>::get(collection_id);977978 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;979980 for data in &items_data {981 Self::validate_create_item_args(&target_collection, data)?;982 }983 for data in &items_data {984 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;985 }986987 Ok(())988 }989990 /// Destroys a concrete instance of NFT.991 /// 992 /// # Permissions993 /// 994 /// * Collection Owner.995 /// * Collection Admin.996 /// * Current NFT Owner.997 /// 998 /// # Arguments999 /// 1000 /// * collection_id: ID of the collection.1001 /// 1002 /// * item_id: ID of NFT to burn.1003 #[weight = T::WeightInfo::burn_item()]1004 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10051006 let sender = ensure_signed(origin)?;1007 Self::collection_exists(collection_id)?;10081009 // Transfer permissions check1010 let target_collection = <Collection<T>>::get(collection_id);1011 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1012 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1013 Error::<T>::NoPermission);10141015 if target_collection.access == AccessMode::WhiteList {1016 Self::check_white_list(collection_id, &sender)?;1017 }10181019 match target_collection.mode1020 {1021 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1022 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1023 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1024 _ => ()1025 };10261027 // call event1028 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10291030 Ok(())1031 }10321033 /// Change ownership of the token.1034 /// 1035 /// # Permissions1036 /// 1037 /// * Collection Owner1038 /// * Collection Admin1039 /// * Current NFT owner1040 ///1041 /// # Arguments1042 /// 1043 /// * recipient: Address of token recipient.1044 /// 1045 /// * collection_id.1046 /// 1047 /// * item_id: ID of the item1048 /// * Non-Fungible Mode: Required.1049 /// * Fungible Mode: Ignored.1050 /// * Re-Fungible Mode: Required.1051 /// 1052 /// * value: Amount to transfer.1053 /// * Non-Fungible Mode: Ignored1054 /// * Fungible Mode: Must specify transferred amount1055 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1056 #[weight = T::WeightInfo::transfer()]1057 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10581059 let sender = ensure_signed(origin)?;1060 let target_collection = <Collection<T>>::get(collection_id);10611062 // Limits check1063 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10641065 // Transfer permissions check1066 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1067 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1068 Error::<T>::NoPermission);10691070 if target_collection.access == AccessMode::WhiteList {1071 Self::check_white_list(collection_id, &sender)?;1072 Self::check_white_list(collection_id, &recipient)?;1073 }10741075 match target_collection.mode1076 {1077 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1078 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1079 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1080 _ => ()1081 };10821083 Ok(())1084 }10851086 /// Set, change, or remove approved address to transfer the ownership of the NFT.1087 /// 1088 /// # Permissions1089 /// 1090 /// * Collection Owner1091 /// * Collection Admin1092 /// * Current NFT owner1093 /// 1094 /// # Arguments1095 /// 1096 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1097 /// 1098 /// * collection_id.1099 /// 1100 /// * item_id: ID of the item.1101 #[weight = T::WeightInfo::approve()]1102 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {11031104 let sender = ensure_signed(origin)?;11051106 // Transfer permissions check1107 let target_collection = <Collection<T>>::get(collection_id);1108 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1109 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1110 Error::<T>::NoPermission);11111112 if target_collection.access == AccessMode::WhiteList {1113 Self::check_white_list(collection_id, &sender)?;1114 Self::check_white_list(collection_id, &spender)?;1115 }11161117 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1118 let mut allowance: u128 = amount;1119 if allowance_exists {1120 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1121 }1122 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11231124 Ok(())1125 }1126 1127 /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1128 /// 1129 /// # Permissions1130 /// * Collection Owner1131 /// * Collection Admin1132 /// * Current NFT owner1133 /// * Address approved by current NFT owner1134 /// 1135 /// # Arguments1136 /// 1137 /// * from: Address that owns token.1138 /// 1139 /// * recipient: Address of token recipient.1140 /// 1141 /// * collection_id.1142 /// 1143 /// * item_id: ID of the item.1144 /// 1145 /// * value: Amount to transfer.1146 #[weight = T::WeightInfo::transfer_from()]1147 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11481149 let sender = ensure_signed(origin)?;1150 let mut appoved_transfer = false;11511152 // Check approval1153 let mut approval: u128 = 0;1154 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1155 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1156 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1157 appoved_transfer = true;1158 }11591160 let target_collection = <Collection<T>>::get(collection_id);11611162 // Limits check1163 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11641165 // Transfer permissions check 1166 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1167 Error::<T>::NoPermission);11681169 if target_collection.access == AccessMode::WhiteList {1170 Self::check_white_list(collection_id, &sender)?;1171 Self::check_white_list(collection_id, &recipient)?;1172 }11731174 // Reduce approval by transferred amount or remove if remaining approval drops to 01175 if approval - value > 0 {1176 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1177 }1178 else {1179 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1180 }11811182 match target_collection.mode1183 {1184 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1185 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1186 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1187 _ => ()1188 };11891190 Ok(())1191 }11921193 #[weight = 0]1194 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11951196 // let no_perm_mes = "You do not have permissions to modify this collection";1197 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1198 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1199 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12001201 // // on_nft_received call12021203 // Self::transfer(origin, collection_id, item_id, new_owner)?;12041205 Ok(())1206 }12071208 /// Set off-chain data schema.1209 /// 1210 /// # Permissions1211 /// 1212 /// * Collection Owner1213 /// * Collection Admin1214 /// 1215 /// # Arguments1216 /// 1217 /// * collection_id.1218 /// 1219 /// * schema: String representing the offchain data schema.1220 #[weight = T::WeightInfo::set_variable_meta_data()]1221 pub fn set_variable_meta_data (1222 origin,1223 collection_id: CollectionId,1224 item_id: TokenId,1225 data: Vec<u8>1226 ) -> DispatchResult {1227 let sender = ensure_signed(origin)?;1228 1229 Self::collection_exists(collection_id)?;1230 1231 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12321233 // Modify permissions check1234 let target_collection = <Collection<T>>::get(collection_id);1235 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1236 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1237 Error::<T>::NoPermission);12381239 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12401241 match target_collection.mode1242 {1243 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1244 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1245 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1246 _ => fail!(Error::<T>::UnexpectedCollectionType)1247 };12481249 Ok(())1250 }1251 1252 /// Set schema standard1253 /// ImageURL1254 /// Unique1255 /// 1256 /// # Permissions1257 /// 1258 /// * Collection Owner1259 /// * Collection Admin1260 /// 1261 /// # Arguments1262 /// 1263 /// * collection_id.1264 /// 1265 /// * schema: SchemaVersion: enum1266 #[weight = 0]1267 pub fn set_schema_version(1268 origin,1269 collection_id: CollectionId,1270 version: SchemaVersion1271 ) -> DispatchResult {1272 let sender = ensure_signed(origin)?;1273 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1274 let mut target_collection = <Collection<T>>::get(collection_id);1275 target_collection.schema_version = version;1276 <Collection<T>>::insert(collection_id, target_collection);12771278 Ok(())1279 }12801281 /// Set off-chain data schema.1282 /// 1283 /// # Permissions1284 /// 1285 /// * Collection Owner1286 /// * Collection Admin1287 /// 1288 /// # Arguments1289 /// 1290 /// * collection_id.1291 /// 1292 /// * schema: String representing the offchain data schema.1293 #[weight = T::WeightInfo::set_offchain_schema()]1294 pub fn set_offchain_schema(1295 origin,1296 collection_id: CollectionId,1297 schema: Vec<u8>1298 ) -> DispatchResult {1299 let sender = ensure_signed(origin)?;1300 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13011302 let mut target_collection = <Collection<T>>::get(collection_id);1303 target_collection.offchain_schema = schema;1304 <Collection<T>>::insert(collection_id, target_collection);13051306 Ok(())1307 }13081309 /// Set const on-chain data schema.1310 /// 1311 /// # Permissions1312 /// 1313 /// * Collection Owner1314 /// * Collection Admin1315 /// 1316 /// # Arguments1317 /// 1318 /// * collection_id.1319 /// 1320 /// * schema: String representing the const on-chain data schema.1321 #[weight = T::WeightInfo::set_const_on_chain_schema()]1322 pub fn set_const_on_chain_schema (1323 origin,1324 collection_id: CollectionId,1325 schema: Vec<u8>1326 ) -> DispatchResult {1327 let sender = ensure_signed(origin)?;1328 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13291330 let mut target_collection = <Collection<T>>::get(collection_id);1331 target_collection.const_on_chain_schema = schema;1332 <Collection<T>>::insert(collection_id, target_collection);13331334 Ok(())1335 }13361337 /// Set variable on-chain data schema.1338 /// 1339 /// # Permissions1340 /// 1341 /// * Collection Owner1342 /// * Collection Admin1343 /// 1344 /// # Arguments1345 /// 1346 /// * collection_id.1347 /// 1348 /// * schema: String representing the variable on-chain data schema.1349 #[weight = T::WeightInfo::set_const_on_chain_schema()]1350 pub fn set_variable_on_chain_schema (1351 origin,1352 collection_id: CollectionId,1353 schema: Vec<u8>1354 ) -> DispatchResult {1355 let sender = ensure_signed(origin)?;1356 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13571358 let mut target_collection = <Collection<T>>::get(collection_id);1359 target_collection.variable_on_chain_schema = schema;1360 <Collection<T>>::insert(collection_id, target_collection);13611362 Ok(())1363 }13641365 // Sudo permissions function1366 #[weight = 0]1367 pub fn set_chain_limits(1368 origin,1369 limits: ChainLimits1370 ) -> DispatchResult {1371 ensure_root(origin)?;1372 <ChainLimit>::put(limits);1373 Ok(())1374 }13751376 /// Enable smart contract self-sponsoring.1377 /// 1378 /// # Permissions1379 /// 1380 /// * Contract Owner1381 /// 1382 /// # Arguments1383 /// 1384 /// * contract address1385 /// * enable flag1386 /// 1387 #[weight = T::WeightInfo::enable_contract_sponsoring()]1388 pub fn enable_contract_sponsoring(1389 origin,1390 contract_address: T::AccountId,1391 enable: bool1392 ) -> DispatchResult {13931394 let sender = ensure_signed(origin)?;13951396 #[cfg(feature = "runtime-benchmarks")]1397 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13981399 let mut is_owner = false;1400 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1401 let owner = <ContractOwner<T>>::get(&contract_address);1402 is_owner = sender == owner;1403 }1404 ensure!(is_owner, Error::<T>::NoPermission);14051406 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1407 Ok(())1408 }14091410 /// Set the rate limit for contract sponsoring to specified number of blocks.1411 /// 1412 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1413 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1414 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1415 /// from contract endowment if there are at least B blocks between such transactions. 1416 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1417 /// 1418 /// # Permissions1419 /// 1420 /// * Contract Owner1421 /// 1422 /// # Arguments1423 /// 1424 /// -`contract_address`: Address of the contract to sponsor1425 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1426 /// 1427 #[weight = 0]1428 pub fn set_contract_sponsoring_rate_limit(1429 origin,1430 contract_address: T::AccountId,1431 rate_limit: T::BlockNumber1432 ) -> DispatchResult {1433 let sender = ensure_signed(origin)?;1434 let mut is_owner = false;1435 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1436 let owner = <ContractOwner<T>>::get(&contract_address);1437 is_owner = sender == owner;1438 }1439 ensure!(is_owner, Error::<T>::NoPermission);14401441 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1442 Ok(())1443 }14441445 #[weight = 0]1446 pub fn set_collection_limits(1447 origin,1448 collection_id: u32,1449 limits: CollectionLimits,1450 ) -> DispatchResult {1451 let sender = ensure_signed(origin)?;1452 Self::check_owner_permissions(collection_id, sender.clone())?;1453 let mut target_collection = <Collection<T>>::get(collection_id);1454 let chain_limits = ChainLimit::get();1455 let climits = target_collection.limits;14561457 // collection bounds1458 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1459 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1460 Error::<T>::CollectionLimitBoundsExceeded);14611462 // token_limit check prev1463 ensure!(climits.token_limit > limits.token_limit && 1464 limits.token_limit <= chain_limits.account_token_ownership_limit, 1465 Error::<T>::AccountTokenLimitExceeded);14661467 target_collection.limits = limits;1468 <Collection<T>>::insert(collection_id, target_collection);14691470 Ok(())1471 } 1472 }1473}14741475impl<T: Trait> Module<T> {14761477 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {14781479 // check token limit and account token limit1480 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1481 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1482 1483 Ok(())1484 }14851486 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {14871488 // check token limit and account token limit1489 let total_items: u32 = ItemListIndex::get(collection_id);1490 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1491 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1492 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);14931494 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1495 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1496 Self::check_white_list(collection_id, owner)?;1497 Self::check_white_list(collection_id, sender)?;1498 }14991500 Ok(())1501 }15021503 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1504 match target_collection.mode1505 {1506 CollectionMode::NFT => {1507 if let CreateItemData::NFT(data) = data {1508 // check sizes1509 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1510 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1511 } else {1512 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1513 }1514 },1515 CollectionMode::Fungible(_) => {1516 if let CreateItemData::Fungible(_) = data {1517 } else {1518 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1519 }1520 },1521 CollectionMode::ReFungible(_) => {1522 if let CreateItemData::ReFungible(data) = data {15231524 // check sizes1525 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1526 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1527 } else {1528 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1529 }1530 },1531 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1532 };15331534 Ok(())1535 }15361537 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1538 match data1539 {1540 CreateItemData::NFT(data) => {1541 let item = NftItemType {1542 owner,1543 const_data: data.const_data,1544 variable_data: data.variable_data1545 };15461547 Self::add_nft_item(collection_id, item)?;1548 },1549 CreateItemData::Fungible(data) => {1550 Self::add_fungible_item(collection_id, &owner, data.value)?;1551 },1552 CreateItemData::ReFungible(data) => {1553 let mut owner_list = Vec::new();1554 let value = (10 as u128).pow(collection.decimal_points as u32);1555 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15561557 let item = ReFungibleItemType {1558 owner: owner_list,1559 const_data: data.const_data,1560 variable_data: data.variable_data1561 };15621563 Self::add_refungible_item(collection_id, item)?;1564 }1565 };15661567 // call event1568 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));15691570 Ok(())1571 }15721573 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {15741575 // Does new owner already have an account?1576 let mut balance: u128 = 0;1577 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1578 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1579 } 15801581 // Mint 1582 let item = FungibleItemType {1583 value: balance + value1584 };1585 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);15861587 // Update balance1588 let new_balance = <Balance<T>>::get(collection_id, owner)1589 .checked_add(value)1590 .ok_or(Error::<T>::NumOverflow)?;1591 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);15921593 Ok(())1594 }15951596 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1597 let current_index = <ItemListIndex>::get(collection_id)1598 .checked_add(1)1599 .ok_or(Error::<T>::NumOverflow)?;1600 let itemcopy = item.clone();16011602 let value = item.owner.first().unwrap().fraction;1603 let owner = item.owner.first().unwrap().owner.clone();16041605 Self::add_token_index(collection_id, current_index, owner.clone())?;16061607 <ItemListIndex>::insert(collection_id, current_index);1608 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16091610 // Update balance1611 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1612 .checked_add(value)1613 .ok_or(Error::<T>::NumOverflow)?;1614 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16151616 Ok(())1617 }16181619 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1620 let current_index = <ItemListIndex>::get(collection_id)1621 .checked_add(1)1622 .ok_or(Error::<T>::NumOverflow)?;16231624 let item_owner = item.owner.clone();1625 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16261627 <ItemListIndex>::insert(collection_id, current_index);1628 <NftItemList<T>>::insert(collection_id, current_index, item);16291630 // Update balance1631 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1632 .checked_add(1)1633 .ok_or(Error::<T>::NumOverflow)?;1634 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16351636 Ok(())1637 }16381639 fn burn_refungible_item(1640 collection_id: CollectionId,1641 item_id: TokenId,1642 owner: T::AccountId,1643 ) -> DispatchResult {1644 ensure!(1645 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1646 Error::<T>::TokenNotFound1647 );1648 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1649 let item = collection1650 .owner1651 .iter()1652 .filter(|&i| i.owner == owner)1653 .next()1654 .unwrap();1655 Self::remove_token_index(collection_id, item_id, owner.clone())?;16561657 // update balance1658 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1659 .checked_sub(item.fraction)1660 .ok_or(Error::<T>::NumOverflow)?;1661 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);16621663 <ReFungibleItemList<T>>::remove(collection_id, item_id);16641665 Ok(())1666 }16671668 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1669 ensure!(1670 <NftItemList<T>>::contains_key(collection_id, item_id),1671 Error::<T>::TokenNotFound1672 );1673 let item = <NftItemList<T>>::get(collection_id, item_id);1674 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;16751676 // update balance1677 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1678 .checked_sub(1)1679 .ok_or(Error::<T>::NumOverflow)?;1680 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1681 <NftItemList<T>>::remove(collection_id, item_id);16821683 Ok(())1684 }16851686 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1687 ensure!(1688 <FungibleItemList<T>>::contains_key(collection_id, owner),1689 Error::<T>::TokenNotFound1690 );1691 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1692 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);16931694 // update balance1695 let new_balance = <Balance<T>>::get(collection_id, owner)1696 .checked_sub(value)1697 .ok_or(Error::<T>::NumOverflow)?;1698 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16991700 if balance.value - value > 0 {1701 balance.value -= value;1702 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1703 }1704 else {1705 <FungibleItemList<T>>::remove(collection_id, owner);1706 }17071708 Ok(())1709 }17101711 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1712 ensure!(1713 <Collection<T>>::contains_key(collection_id),1714 Error::<T>::CollectionNotFound1715 );1716 Ok(())1717 }17181719 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1720 Self::collection_exists(collection_id)?;17211722 let target_collection = <Collection<T>>::get(collection_id);1723 ensure!(1724 subject == target_collection.owner,1725 Error::<T>::NoPermission1726 );17271728 Ok(())1729 }17301731 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1732 let target_collection = <Collection<T>>::get(collection_id);1733 let mut result: bool = subject == target_collection.owner;1734 let exists = <AdminList<T>>::contains_key(collection_id);17351736 if !result & exists {1737 if <AdminList<T>>::get(collection_id).contains(&subject) {1738 result = true1739 }1740 }17411742 result1743 }17441745 fn check_owner_or_admin_permissions(1746 collection_id: CollectionId,1747 subject: T::AccountId,1748 ) -> DispatchResult {1749 Self::collection_exists(collection_id)?;1750 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17511752 ensure!(1753 result,1754 Error::<T>::NoPermission1755 );1756 Ok(())1757 }17581759 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1760 let target_collection = <Collection<T>>::get(collection_id);17611762 match target_collection.mode {1763 CollectionMode::NFT => {1764 <NftItemList<T>>::get(collection_id, item_id).owner == subject1765 }1766 CollectionMode::Fungible(_) => {1767 <FungibleItemList<T>>::contains_key(collection_id, &subject)1768 }1769 CollectionMode::ReFungible(_) => {1770 <ReFungibleItemList<T>>::get(collection_id, item_id)1771 .owner1772 .iter()1773 .any(|i| i.owner == subject)1774 }1775 CollectionMode::Invalid => false,1776 }1777 }17781779 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1780 let mes = Error::<T>::AddresNotInWhiteList;1781 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1782 let wl = <WhiteList<T>>::get(collection_id);1783 ensure!(wl.contains(address), mes);17841785 Ok(())1786 }17871788 fn transfer_fungible(1789 collection_id: CollectionId,1790 value: u128,1791 owner: &T::AccountId,1792 recipient: &T::AccountId,1793 ) -> DispatchResult {1794 ensure!(1795 <FungibleItemList<T>>::contains_key(collection_id, owner),1796 Error::<T>::TokenNotFound1797 );17981799 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1800 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18011802 // Send balance to recipient (updates balanceOf of recipient)1803 Self::add_fungible_item(collection_id, recipient, value)?;18041805 // update balanceOf of sender1806 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18071808 // Reduce or remove sender1809 if balance.value == value {1810 <FungibleItemList<T>>::remove(collection_id, owner);1811 }1812 else {1813 balance.value -= value;1814 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1815 }18161817 Ok(())1818 }18191820 fn transfer_refungible(1821 collection_id: CollectionId,1822 item_id: TokenId,1823 value: u128,1824 owner: T::AccountId,1825 new_owner: T::AccountId,1826 ) -> DispatchResult {1827 ensure!(1828 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1829 Error::<T>::TokenNotFound1830 );18311832 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1833 let item = full_item1834 .owner1835 .iter()1836 .filter(|i| i.owner == owner)1837 .next()1838 .ok_or(Error::<T>::NumOverflow)?;1839 let amount = item.fraction;18401841 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18421843 // update balance1844 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1845 .checked_sub(value)1846 .ok_or(Error::<T>::NumOverflow)?;1847 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18481849 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1850 .checked_add(value)1851 .ok_or(Error::<T>::NumOverflow)?;1852 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);18531854 let old_owner = item.owner.clone();1855 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);18561857 // transfer1858 if amount == value && !new_owner_has_account {1859 // change owner1860 // new owner do not have account1861 let mut new_full_item = full_item.clone();1862 new_full_item1863 .owner1864 .iter_mut()1865 .find(|i| i.owner == owner)1866 .unwrap()1867 .owner = new_owner.clone();1868 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18691870 // update index collection1871 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1872 } else {1873 let mut new_full_item = full_item.clone();1874 new_full_item1875 .owner1876 .iter_mut()1877 .find(|i| i.owner == owner)1878 .unwrap()1879 .fraction -= value;18801881 // separate amount1882 if new_owner_has_account {1883 // new owner has account1884 new_full_item1885 .owner1886 .iter_mut()1887 .find(|i| i.owner == new_owner)1888 .unwrap()1889 .fraction += value;1890 } else {1891 // new owner do not have account1892 new_full_item.owner.push(Ownership {1893 owner: new_owner.clone(),1894 fraction: value,1895 });1896 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1897 }18981899 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1900 }19011902 Ok(())1903 }19041905 fn transfer_nft(1906 collection_id: CollectionId,1907 item_id: TokenId,1908 sender: T::AccountId,1909 new_owner: T::AccountId,1910 ) -> DispatchResult {1911 ensure!(1912 <NftItemList<T>>::contains_key(collection_id, item_id),1913 Error::<T>::TokenNotFound1914 );19151916 let mut item = <NftItemList<T>>::get(collection_id, item_id);19171918 ensure!(1919 sender == item.owner,1920 Error::<T>::MustBeTokenOwner1921 );19221923 // update balance1924 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1925 .checked_sub(1)1926 .ok_or(Error::<T>::NumOverflow)?;1927 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19281929 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1930 .checked_add(1)1931 .ok_or(Error::<T>::NumOverflow)?;1932 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19331934 // change owner1935 let old_owner = item.owner.clone();1936 item.owner = new_owner.clone();1937 <NftItemList<T>>::insert(collection_id, item_id, item);19381939 // update index collection1940 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19411942 Ok(())1943 }1944 1945 fn item_exists(1946 collection_id: CollectionId,1947 item_id: TokenId,1948 mode: &CollectionMode1949 ) -> DispatchResult {1950 match mode {1951 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1952 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1953 _ => ()1954 };1955 1956 Ok(())1957 }19581959 fn set_re_fungible_variable_data(1960 collection_id: CollectionId,1961 item_id: TokenId,1962 data: Vec<u8>1963 ) -> DispatchResult {1964 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);19651966 item.variable_data = data;19671968 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);19691970 Ok(())1971 }19721973 fn set_nft_variable_data(1974 collection_id: CollectionId,1975 item_id: TokenId,1976 data: Vec<u8>1977 ) -> DispatchResult {1978 let mut item = <NftItemList<T>>::get(collection_id, item_id);1979 1980 item.variable_data = data;19811982 <NftItemList<T>>::insert(collection_id, item_id, item);1983 1984 Ok(())1985 }19861987 fn init_collection(item: &CollectionType<T::AccountId>) {1988 // check params1989 assert!(1990 item.decimal_points <= MAX_DECIMAL_POINTS,1991 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"1992 );1993 assert!(1994 item.name.len() <= 64,1995 "Collection name can not be longer than 63 char"1996 );1997 assert!(1998 item.name.len() <= 256,1999 "Collection description can not be longer than 255 char"2000 );2001 assert!(2002 item.token_prefix.len() <= 16,2003 "Token prefix can not be longer than 15 char"2004 );20052006 // Generate next collection ID2007 let next_id = CreatedCollectionCount::get()2008 .checked_add(1)2009 .unwrap();20102011 CreatedCollectionCount::put(next_id);2012 }20132014 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2015 let current_index = <ItemListIndex>::get(collection_id)2016 .checked_add(1)2017 .unwrap();20182019 let item_owner = item.owner.clone();2020 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20212022 <ItemListIndex>::insert(collection_id, current_index);20232024 // Update balance2025 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2026 .checked_add(1)2027 .unwrap();2028 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2029 }20302031 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2032 let current_index = <ItemListIndex>::get(collection_id)2033 .checked_add(1)2034 .unwrap();20352036 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();20372038 <ItemListIndex>::insert(collection_id, current_index);20392040 // Update balance2041 let new_balance = <Balance<T>>::get(collection_id, owner)2042 .checked_add(item.value)2043 .unwrap();2044 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2045 }20462047 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2048 let current_index = <ItemListIndex>::get(collection_id)2049 .checked_add(1)2050 .unwrap();20512052 let value = item.owner.first().unwrap().fraction;2053 let owner = item.owner.first().unwrap().owner.clone();20542055 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();20562057 <ItemListIndex>::insert(collection_id, current_index);20582059 // Update balance2060 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2061 .checked_add(value)2062 .unwrap();2063 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2064 }20652066 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {20672068 // add to account limit2069 if <AccountItemCount<T>>::contains_key(owner.clone()) {20702071 // bound Owned tokens by a single address2072 let count = <AccountItemCount<T>>::get(owner.clone());2073 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);20742075 <AccountItemCount<T>>::insert(owner.clone(), count2076 .checked_add(1)2077 .ok_or(Error::<T>::NumOverflow)?);2078 }2079 else {2080 <AccountItemCount<T>>::insert(owner.clone(), 1);2081 }20822083 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2084 if list_exists {2085 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2086 let item_contains = list.contains(&item_index.clone());20872088 if !item_contains {2089 list.push(item_index.clone());2090 }20912092 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2093 } else {2094 let mut itm = Vec::new();2095 itm.push(item_index.clone());2096 <AddressTokens<T>>::insert(collection_id, owner, itm);2097 2098 }20992100 Ok(())2101 }21022103 fn remove_token_index(2104 collection_id: CollectionId,2105 item_index: TokenId,2106 owner: T::AccountId,2107 ) -> DispatchResult {21082109 // update counter2110 <AccountItemCount<T>>::insert(owner.clone(), 2111 <AccountItemCount<T>>::get(owner.clone())2112 .checked_sub(1)2113 .ok_or(Error::<T>::NumOverflow)?);211421152116 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2117 if list_exists {2118 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2119 let item_contains = list.contains(&item_index.clone());21202121 if item_contains {2122 list.retain(|&item| item != item_index);2123 <AddressTokens<T>>::insert(collection_id, owner, list);2124 }2125 }21262127 Ok(())2128 }21292130 fn move_token_index(2131 collection_id: CollectionId,2132 item_index: TokenId,2133 old_owner: T::AccountId,2134 new_owner: T::AccountId,2135 ) -> DispatchResult {2136 Self::remove_token_index(collection_id, item_index, old_owner)?;2137 Self::add_token_index(collection_id, item_index, new_owner)?;21382139 Ok(())2140 }2141}21422143////////////////////////////////////////////////////////////////////////////////////////////////////2144// Economic models2145// #region21462147/// Fee multiplier.2148pub type Multiplier = FixedU128;21492150type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2151 <T as system::Trait>::AccountId,2152>>::Balance;2153type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2154 <T as system::Trait>::AccountId,2155>>::NegativeImbalance;21562157/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2158/// in the queue.2159#[derive(Encode, Decode, Clone, Eq, PartialEq)]2160pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2161 #[codec(compact)] BalanceOf<T>2162);21632164impl<T: Trait + Send + Sync> sp_std::fmt::Debug2165 for ChargeTransactionPayment<T>2166{2167 #[cfg(feature = "std")]2168 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2169 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2170 }2171 #[cfg(not(feature = "std"))]2172 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2173 Ok(())2174 }2175}21762177impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2178where2179 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2180 BalanceOf<T>: Send + Sync + FixedPointOperand,2181{2182 /// utility constructor. Used only in client/factory code.2183 pub fn from(fee: BalanceOf<T>) -> Self {2184 Self(fee)2185 }21862187 pub fn traditional_fee(2188 len: usize,2189 info: &DispatchInfoOf<T::Call>,2190 tip: BalanceOf<T>,2191 ) -> BalanceOf<T>2192 where2193 T::Call: Dispatchable<Info = DispatchInfo>,2194 {2195 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2196 }21972198 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2199 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2200 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2201 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2202 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2203 }22042205 fn withdraw_fee(2206 &self,2207 who: &T::AccountId,2208 call: &T::Call,2209 info: &DispatchInfoOf<T::Call>,2210 len: usize,2211 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2212 let tip = self.0;22132214 // Set fee based on call type. Creating collection costs 1 Unique.2215 // All other transactions have traditional fees so far2216 // let fee = match call.is_sub_type() {2217 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2218 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2219 // // _ => <BalanceOf<T>>::from(100)2220 // };2221 let fee = Self::traditional_fee(len, info, tip);22222223 // Only mess with balances if fee is not zero.2224 if fee.is_zero() {2225 return Ok((fee, None));2226 }22272228 // Determine who is paying transaction fee based on ecnomic model2229 // Parse call to extract collection ID and access collection sponsor2230 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2231 Some(Call::create_item(collection_id, _owner, _properties)) => {22322233 // check free create limit2234 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2235 (<Collection<T>>::get(collection_id).sponsor_confirmed)2236 {2237 <Collection<T>>::get(collection_id).sponsor2238 } else {2239 T::AccountId::default()2240 }2241 }2242 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2243 2244 let mut sponsor_transfer = false;2245 if <Collection<T>>::get(collection_id).sponsor_confirmed {22462247 let collection_limits = <Collection<T>>::get(collection_id).limits;2248 let collection_mode = <Collection<T>>::get(collection_id).mode;2249 2250 // sponsor timeout2251 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2252 sponsor_transfer = match collection_mode {2253 CollectionMode::NFT => {2254 2255 // get correct limit2256 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2257 collection_limits.sponsor_transfer_timeout2258 } else {2259 ChainLimit::get().nft_sponsor_transfer_timeout2260 };2261 2262 let mut sponsored = true;2263 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2264 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2265 let limit_time = last_tx_block + limit.into();2266 if block_number <= limit_time {2267 sponsored = false;2268 }2269 }2270 if sponsored {2271 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2272 }22732274 sponsored2275 }2276 CollectionMode::Fungible(_) => {2277 2278 // get correct limit2279 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2280 collection_limits.sponsor_transfer_timeout2281 } else {2282 ChainLimit::get().fungible_sponsor_transfer_timeout2283 };2284 2285 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2286 let mut sponsored = true;2287 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2288 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2289 let limit_time = last_tx_block + limit.into();2290 if block_number <= limit_time {2291 sponsored = false;2292 }2293 }2294 if sponsored {2295 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2296 }22972298 sponsored2299 }2300 CollectionMode::ReFungible(_) => {2301 2302 // get correct limit2303 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2304 collection_limits.sponsor_transfer_timeout2305 } else {2306 ChainLimit::get().refungible_sponsor_transfer_timeout2307 };2308 2309 let mut sponsored = true;2310 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2311 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2312 let limit_time = last_tx_block + limit.into();2313 if block_number <= limit_time {2314 sponsored = false;2315 }2316 }2317 if sponsored {2318 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2319 }23202321 sponsored2322 }2323 _ => {2324 false2325 },2326 };2327 }23282329 if !sponsor_transfer {2330 T::AccountId::default()2331 } else {2332 <Collection<T>>::get(collection_id).sponsor2333 }2334 }23352336 _ => T::AccountId::default(),2337 };23382339 // Sponsor smart contracts2340 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {23412342 // On instantiation: set the contract owner2343 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {23442345 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2346 code_hash,2347 &data,2348 &who,2349 );2350 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());23512352 T::AccountId::default()2353 },23542355 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2356 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {23572358 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());23592360 let mut sponsor_transfer = false;2361 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2362 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2363 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2364 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2365 let limit_time = last_tx_block + rate_limit;23662367 if block_number >= limit_time {2368 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2369 sponsor_transfer = true;2370 }2371 } else {2372 sponsor_transfer = false;2373 }2374 2375 2376 let mut sp = T::AccountId::default();2377 if sponsor_transfer {2378 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2379 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2380 sp = called_contract;2381 }2382 }2383 }23842385 sp2386 },23872388 _ => sponsor,2389 };23902391 let mut who_pays_fee: T::AccountId = sponsor.clone();2392 if sponsor == T::AccountId::default() {2393 who_pays_fee = who.clone();2394 }23952396 match <T as transaction_payment::Trait>::Currency::withdraw(2397 &who_pays_fee,2398 fee,2399 if tip.is_zero() {2400 WithdrawReason::TransactionPayment.into()2401 } else {2402 WithdrawReason::TransactionPayment | WithdrawReason::Tip2403 },2404 ExistenceRequirement::KeepAlive,2405 ) {2406 Ok(imbalance) => Ok((fee, Some(imbalance))),2407 Err(_) => Err(InvalidTransaction::Payment.into()),2408 }2409 }2410}241124122413impl<T: Trait + Send + Sync> SignedExtension2414 for ChargeTransactionPayment<T>2415where2416 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2417 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2418{2419 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2420 type AccountId = T::AccountId;2421 type Call = T::Call;2422 type AdditionalSigned = ();2423 type Pre = (2424 BalanceOf<T>,2425 Self::AccountId,2426 Option<NegativeImbalanceOf<T>>,2427 BalanceOf<T>,2428 );2429 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2430 Ok(())2431 }24322433 fn validate(2434 &self,2435 who: &Self::AccountId,2436 call: &Self::Call,2437 info: &DispatchInfoOf<Self::Call>,2438 len: usize,2439 ) -> TransactionValidity {2440 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2441 Ok(ValidTransaction {2442 priority: Self::get_priority(len, info, fee),2443 ..Default::default()2444 })2445 }24462447 fn pre_dispatch(2448 self,2449 who: &Self::AccountId,2450 call: &Self::Call,2451 info: &DispatchInfoOf<Self::Call>,2452 len: usize,2453 ) -> Result<Self::Pre, TransactionValidityError> {2454 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2455 Ok((self.0, who.clone(), imbalance, fee))2456 }24572458 fn post_dispatch(2459 pre: Self::Pre,2460 info: &DispatchInfoOf<Self::Call>,2461 post_info: &PostDispatchInfoOf<Self::Call>,2462 len: usize,2463 _result: &DispatchResult,2464 ) -> Result<(), TransactionValidityError> {2465 let (tip, who, imbalance, fee) = pre;2466 if let Some(payed) = imbalance {2467 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2468 len as u32, info, post_info, tip,2469 );2470 let refund = fee.saturating_sub(actual_fee);2471 let actual_payment =2472 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2473 &who, refund,2474 ) {2475 Ok(refund_imbalance) => {2476 // The refund cannot be larger than the up front payed max weight.2477 // `PostDispatchInfo::calc_unspent` guards against such a case.2478 match payed.offset(refund_imbalance) {2479 Ok(actual_payment) => actual_payment,2480 Err(_) => return Err(InvalidTransaction::Payment.into()),2481 }2482 }2483 // We do not recreate the account using the refund. The up front payment2484 // is gone in that case.2485 Err(_) => payed,2486 };2487 let imbalances = actual_payment.split(tip);2488 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2489 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2490 );2491 }2492 Ok(())2493 }2494}24952496// #endregionruntime_types.jsondiffbeforeafterboth--- a/runtime_types.json
+++ b/runtime_types.json
@@ -42,7 +42,6 @@
"Fraction": "u128"
},
"FungibleItemType": {
- "Owner": "AccountId",
"Value": "u128"
},
"NftItemType": {
@@ -72,10 +71,6 @@
"VariableOnChainSchema": "Vec<u8>",
"ConstOnChainSchema": "Vec<u8>"
},
- "ApprovePermissions": {
- "Approved": "AccountId",
- "Amount": "u128"
- },
"RawData": "Vec<u8>",
"Address": "AccountId",
"LookupSource": "AccountId",
@@ -84,7 +79,9 @@
"const_data": "Vec<u8>",
"variable_data": "Vec<u8>"
},
- "CreateFungibleData": {},
+ "CreateFungibleData": {
+ "value": "u128"
+ },
"CreateReFungibleData": {
"const_data": "Vec<u8>",
"variable_data": "Vec<u8>"
@@ -104,10 +101,6 @@
},
"CollectionId": "u32",
"TokenId": "u32",
- "BasketItem": {
- "Address": "AccountId",
- "start_block": "BlockNumber"
- },
"ChainLimits": {
"collection_numbers_limit": "u32",
"account_token_ownership_limit": "u32",
tests/src/confirmSponsorship.test.tsdiffbeforeafterboth--- a/tests/src/confirmSponsorship.test.ts
+++ b/tests/src/confirmSponsorship.test.ts
@@ -16,6 +16,7 @@
createItemExpectSuccess,
findUnusedAddress,
getGenericResult,
+ enableWhiteListExpectSuccess,
} from "./util/helpers";
import { Keyring } from "@polkadot/api";
import { IKeyringPair } from "@polkadot/types/types";
@@ -72,7 +73,7 @@
// Mint token for unused address
const itemId = await createItemExpectSuccess(collectionId, 'NFT', zeroBalance.address, '//Alice');
- // Transfer this token from unused address to Alice
+ // Transfer this tokens from unused address to Alice
const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);
const events = await submitTransactionAsync(zeroBalance, zeroToAlice);
const result = getGenericResult(events);
@@ -86,30 +87,198 @@
});
it('Fungible: Transfer fees are paid by the sponsor after confirmation', async () => {
- expect(false).to.be.true;
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for unused address
+ const itemId = await createItemExpectSuccess(collectionId, 'Fungible', zeroBalance.address, '//Alice');
+
+ // Transfer this tokens from unused address to Alice
+ const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+ const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result1 = getGenericResult(events1);
+
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ expect(result1.success).to.be.true;
+ expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+ });
});
it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async () => {
- expect(false).to.be.true;
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for unused address
+ const itemId = await createItemExpectSuccess(collectionId, 'ReFungible', zeroBalance.address, '//Alice');
+
+ // Transfer this tokens from unused address to Alice
+ const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+ const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result1 = getGenericResult(events1);
+
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ expect(result1.success).to.be.true;
+ expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+ });
});
- it.skip('CreateItem fees are paid by the sponsor after confirmation', async () => {
- // const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
- // await setCollectionSponsorExpectSuccess(collectionId, bob.address);
- // await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
- expect(false).to.be.true;
+ it.only('CreateItem fees are paid by the sponsor after confirmation', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ // Enable collection white list
+ await enableWhiteListExpectSuccess(collectionId);
+
+ // Enable public minting
+
+ // Create Item
+
+
+
});
it('NFT: Sponsoring is rate limited', async () => {
- expect(false).to.be.true;
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for alice
+ const itemId = await createItemExpectSuccess(collectionId, 'NFT', alice.address, '//Alice');
+
+ // Transfer this token from Alice to unused address and back
+ // Alice to Zero gets sponsored
+ const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);
+ const events1 = await submitTransactionAsync(alice, aliceToZero);
+ const result1 = getGenericResult(events1);
+
+ // Second transfer should fail
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+ const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 0);
+ const badTransaction = async function () {
+ console.log = function () {};
+ console.error = function () {};
+ await submitTransactionAsync(zeroBalance, zeroToAlice);
+ delete console.log;
+ delete console.error;
+ };
+ await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Try again after Zero gets some balance - now it should succeed
+ const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+ await submitTransactionAsync(alice, balancetx);
+ const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result2 = getGenericResult(events2);
+
+ expect(result1.success).to.be.true;
+ expect(result2.success).to.be.true;
+ expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+ });
});
it('Fungible: Sponsoring is rate limited', async () => {
- expect(false).to.be.true;
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for unused address
+ const itemId = await createItemExpectSuccess(collectionId, 'Fungible', zeroBalance.address, '//Alice');
+
+ // Transfer this tokens in parts from unused address to Alice
+ const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+ const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result1 = getGenericResult(events1);
+
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ const badTransaction = async function () {
+ console.log = function () {};
+ console.error = function () {};
+ await submitTransactionAsync(zeroBalance, zeroToAlice);
+ delete console.log;
+ delete console.error;
+ };
+
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Try again after Zero gets some balance - now it should succeed
+ const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+ await submitTransactionAsync(alice, balancetx);
+ const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result2 = getGenericResult(events2);
+
+ expect(result1.success).to.be.true;
+ expect(result2.success).to.be.true;
+ expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+ });
});
it('ReFungible: Sponsoring is rate limited', async () => {
- expect(false).to.be.true;
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+ await usingApi(async (api) => {
+ // Find unused address
+ const zeroBalance = await findUnusedAddress(api);
+
+ // Mint token for alice
+ const itemId = await createItemExpectSuccess(collectionId, 'ReFungible', alice.address, '//Alice');
+
+ // Transfer this token from Alice to unused address and back
+ // Alice to Zero gets sponsored
+ const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+ const events1 = await submitTransactionAsync(alice, aliceToZero);
+ const result1 = getGenericResult(events1);
+
+ // Second transfer should fail
+ const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+ const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 1);
+ const badTransaction = async function () {
+ console.log = function () {};
+ console.error = function () {};
+ await submitTransactionAsync(zeroBalance, zeroToAlice);
+ delete console.log;
+ delete console.error;
+ };
+ await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
+ const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+ // Try again after Zero gets some balance - now it should succeed
+ const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+ await submitTransactionAsync(alice, balancetx);
+ const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+ const result2 = getGenericResult(events2);
+
+ expect(result1.success).to.be.true;
+ expect(result2.success).to.be.true;
+ expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+ });
});
});
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -13,6 +13,8 @@
import { strToUTF16, utf16ToStr, hexToStr } from '../util/util';
import { IKeyringPair } from "@polkadot/types/types";
import { BigNumber } from 'bignumber.js';
+import { Struct, Enum } from '@polkadot/types/codec';
+import { u128 } from '@polkadot/types/primitive';
chai.use(chaiAsPromised);
const expect = chai.expect;
@@ -258,25 +260,73 @@
});
}
+export interface CreateFungibleData extends Struct {
+ readonly value: u128;
+};
+
+export interface CreateReFungibleData extends Struct {};
+export interface CreateNftData extends Struct {};
+
+export interface CreateItemData extends Enum {
+ NFT: CreateNftData,
+ Fungible: CreateFungibleData,
+ ReFungible: CreateReFungibleData
+};
+
export async function createItemExpectSuccess(collectionId: number, createMode: string, owner: string = '', senderSeed: string = '//Alice') {
let newItemId: number = 0;
await usingApi(async (api) => {
const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+ const Aitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();
+ const AItemBalance = new BigNumber(Aitem.Value);
const sender = privateKey(senderSeed);
if (owner === '') owner = sender.address;
- const tx = api.tx.nft.createItem(collectionId, owner, createMode);
+
+ let tx;
+ if (createMode == 'Fungible') {
+ let createData = {fungible: {value: 10}};
+ tx = api.tx.nft.createItem(collectionId, owner, createData);
+ }
+ else {
+ tx = api.tx.nft.createItem(collectionId, owner, createMode);
+ }
const events = await submitTransactionAsync(sender, tx);
const result = getCreateItemResult(events);
-
+
const BItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+ const Bitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();
+ const BItemBalance = new BigNumber(Bitem.Value);
// What to expect
expect(result.success).to.be.true;
- expect(BItemCount).to.be.equal(AItemCount+1);
+ if (createMode == 'Fungible') {
+ expect(BItemBalance.minus(AItemBalance).toNumber()).to.be.equal(10);
+ }
+ else {
+ expect(BItemCount).to.be.equal(AItemCount+1);
+ }
expect(collectionId).to.be.equal(result.collectionId);
expect(BItemCount).to.be.equal(result.itemId);
newItemId = result.itemId;
});
return newItemId;
}
+
+export async function enableWhiteListExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
+ await usingApi(async (api) => {
+
+ // Run the transaction
+ const sender = privateKey(senderSeed);
+ const tx = api.tx.nft.setPublicAccessMode(collectionId, 'WhiteList');
+ const events = await submitTransactionAsync(sender, tx);
+ const result = getGenericResult(events);
+
+ // Get the collection
+ const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+ // What to expect
+ expect(result.success).to.be.true;
+ expect(collection.Access).to.be.equal('WhiteList');
+ });
+}