difftreelog
Integration tests for setCollectionSponsor
in: master
6 files changed
node/src/chain_spec.rsdiffbeforeafterboth--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -193,7 +193,7 @@
offchain_schema: vec![],
schema_version: SchemaVersion::default(),
sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
- unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
+ sponsor_confirmed: true,
const_on_chain_schema: vec![],
variable_on_chain_schema: vec![],
limits: CollectionLimits::default()
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 unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship140 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 collection: CollectionId,149 pub owner: AccountId,150 pub const_data: Vec<u8>,151 pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType<AccountId> {157 pub collection: CollectionId,158 pub owner: AccountId,159 pub value: u128,160}161162#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]163#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]164pub struct ReFungibleItemType<AccountId> {165 pub collection: CollectionId,166 pub owner: Vec<Ownership<AccountId>>,167 pub const_data: Vec<u8>,168 pub variable_data: Vec<u8>,169}170171#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]172#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]173pub struct ApprovePermissions<AccountId> {174 pub approved: AccountId,175 pub amount: u128,176}177178#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct VestingItem<AccountId, Moment> {181 pub sender: AccountId,182 pub recipient: AccountId,183 pub collection_id: CollectionId,184 pub item_id: TokenId,185 pub amount: u64,186 pub vesting_date: Moment,187}188189#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]190#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]191pub struct BasketItem<AccountId, BlockNumber> {192 pub address: AccountId,193 pub start_block: BlockNumber,194}195196#[derive(Encode, Decode, Debug, Clone, PartialEq)]197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]198pub struct CollectionLimits {199 pub account_token_ownership_limit: u32,200 pub sponsored_data_size: u32,201 pub token_limit: u32,202203 // Timeouts for item types in passed blocks204 pub sponsor_transfer_timeout: u32,205}206207impl Default for CollectionLimits {208 fn default() -> CollectionLimits {209 CollectionLimits { 210 account_token_ownership_limit: 10_000_000, 211 token_limit: u32::max_value(),212 sponsored_data_size: u32::max_value(), 213 sponsor_transfer_timeout: 14400 }214 }215}216217#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]218#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]219pub struct ChainLimits {220 pub collection_numbers_limit: u32,221 pub account_token_ownership_limit: u32,222 pub collections_admins_limit: u64,223 pub custom_data_limit: u32,224225 // Timeouts for item types in passed blocks226 pub nft_sponsor_transfer_timeout: u32,227 pub fungible_sponsor_transfer_timeout: u32,228 pub refungible_sponsor_transfer_timeout: u32,229}230231pub trait WeightInfo {232 fn create_collection() -> Weight;233 fn destroy_collection() -> Weight;234 fn add_to_white_list() -> Weight;235 fn remove_from_white_list() -> Weight;236 fn set_public_access_mode() -> Weight;237 fn set_mint_permission() -> Weight;238 fn change_collection_owner() -> Weight;239 fn add_collection_admin() -> Weight;240 fn remove_collection_admin() -> Weight;241 fn set_collection_sponsor() -> Weight;242 fn confirm_sponsorship() -> Weight;243 fn remove_collection_sponsor() -> Weight;244 fn create_item(s: usize) -> Weight;245 fn burn_item() -> Weight;246 fn transfer() -> Weight;247 fn approve() -> Weight;248 fn transfer_from() -> Weight;249 fn set_offchain_schema() -> Weight;250 fn set_const_on_chain_schema() -> Weight;251 fn set_variable_on_chain_schema() -> Weight;252 fn set_variable_meta_data() -> Weight;253 fn enable_contract_sponsoring() -> Weight;254}255256#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]257#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]258pub struct CreateNftData {259 pub const_data: Vec<u8>,260 pub variable_data: Vec<u8>,261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateFungibleData {266}267268#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]269#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]270pub struct CreateReFungibleData {271 pub const_data: Vec<u8>,272 pub variable_data: Vec<u8>,273}274275#[derive(Encode, Decode, Debug, Clone, PartialEq)]276#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]277pub enum CreateItemData {278 NFT(CreateNftData),279 Fungible(CreateFungibleData),280 ReFungible(CreateReFungibleData),281}282283impl CreateItemData {284 pub fn len(&self) -> usize {285 let len = match self {286 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),287 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),288 _ => 0289 };290 291 return len;292 }293}294295impl From<CreateNftData> for CreateItemData {296 fn from(item: CreateNftData) -> Self {297 CreateItemData::NFT(item)298 }299}300301impl From<CreateReFungibleData> for CreateItemData {302 fn from(item: CreateReFungibleData) -> Self {303 CreateItemData::ReFungible(item)304 }305}306307impl From<CreateFungibleData> for CreateItemData {308 fn from(item: CreateFungibleData) -> Self {309 CreateItemData::Fungible(item)310 }311}312313314decl_error! {315 /// Error for non-fungible-token module.316 pub enum Error for Module<T: Trait> {317 /// Total collections bound exceeded.318 TotalCollectionsLimitExceeded,319 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.320 CollectionDecimalPointLimitExceeded, 321 /// Collection name can not be longer than 63 char.322 CollectionNameLimitExceeded, 323 /// Collection description can not be longer than 255 char.324 CollectionDescriptionLimitExceeded, 325 /// Token prefix can not be longer than 15 char.326 CollectionTokenPrefixLimitExceeded,327 /// This collection does not exist.328 CollectionNotFound,329 /// Item not exists.330 TokenNotFound,331 /// Arithmetic calculation overflow.332 NumOverflow, 333 /// Account already has admin role.334 AlreadyAdmin, 335 /// You do not own this collection.336 NoPermission,337 /// This address is not set as sponsor, use setCollectionSponsor first.338 ConfirmUnsetSponsorFail,339 /// Collection is not in mint mode.340 PublicMintingNotAllowed,341 /// Sender parameter and item owner must be equal.342 MustBeTokenOwner,343 /// Item balance not enough.344 TokenValueTooLow,345 /// Size of item is too large.346 NftSizeLimitExceeded,347 /// No approve found348 ApproveNotFound,349 /// Requested value more than approved.350 TokenValueNotEnough,351 /// Only approved addresses can call this method.352 ApproveRequired,353 /// Address is not in white list.354 AddresNotInWhiteList,355 /// Number of collection admins bound exceeded.356 CollectionAdminsLimitExceeded,357 /// Owned tokens by a single address bound exceeded.358 AddressOwnershipLimitExceeded,359 /// Length of items properties must be greater than 0.360 EmptyArgument,361 /// const_data exceeded data limit.362 TokenConstDataLimitExceeded,363 /// variable_data exceeded data limit.364 TokenVariableDataLimitExceeded,365 /// Not NFT item data used to mint in NFT collection.366 NotNftDataUsedToMintNftCollectionToken,367 /// Not Fungible item data used to mint in Fungible collection.368 NotFungibleDataUsedToMintFungibleCollectionToken,369 /// Not Re Fungible item data used to mint in Re Fungible collection.370 NotReFungibleDataUsedToMintReFungibleCollectionToken,371 /// Unexpected collection type.372 UnexpectedCollectionType,373 /// Can't store metadata in fungible tokens.374 CantStoreMetadataInFungibleTokens,375 /// Collection token limit exceeded376 CollectionTokenLimitExceeded,377 /// Account token limit exceeded per collection378 AccountTokenLimitExceeded,379 /// Collection limit bounds per collection exceeded380 CollectionLimitBoundsExceeded381 }382}383384pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {385 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;386387 /// Weight information for extrinsics in this pallet.388 type WeightInfo: WeightInfo;389}390391#[cfg(feature = "runtime-benchmarks")]392mod benchmarking;393394// #endregion395396decl_storage! {397 trait Store for Module<T: Trait> as Nft {398399 // Private members400 NextCollectionID: CollectionId;401 CreatedCollectionCount: u32;402 ChainVersion: u64;403 ItemListIndex: map hasher(identity) CollectionId => TokenId;404405 // Chain limits struct406 pub ChainLimit get(fn chain_limit) config(): ChainLimits;407408 // Bound counters409 CollectionCount: u32;410 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;411412 // Basic collections413 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;414 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;415 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;416417 /// Balance owner per collection map418 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;419420 /// second parameter: item id + owner account id421 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;422423 /// Item collections424 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;425 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;426 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;427428 /// Index list429 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;430431 /// Tokens transfer baskets432 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;434 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;435436 // Contract Sponsorship and Ownership437 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;438 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;439 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;440 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;441 }442 add_extra_genesis {443 build(|config: &GenesisConfig<T>| {444 // Modification of storage445 for (_num, _c) in &config.collection {446 <Module<T>>::init_collection(_c);447 }448449 for (_num, _q, _i) in &config.nft_item_id {450 <Module<T>>::init_nft_token(_i);451 }452453 for (_num, _q, _i) in &config.fungible_item_id {454 <Module<T>>::init_fungible_token(_i);455 }456457 for (_num, _q, _i) in &config.refungible_item_id {458 <Module<T>>::init_refungible_token(_i);459 }460 })461 }462}463464decl_event!(465 pub enum Event<T>466 where467 AccountId = <T as system::Trait>::AccountId,468 {469 /// New collection was created470 /// 471 /// # Arguments472 /// 473 /// * collection_id: Globally unique identifier of newly created collection.474 /// 475 /// * mode: [CollectionMode] converted into u8.476 /// 477 /// * account_id: Collection owner.478 Created(CollectionId, u8, AccountId),479480 /// New item was created.481 /// 482 /// # Arguments483 /// 484 /// * collection_id: Id of the collection where item was created.485 /// 486 /// * item_id: Id of an item. Unique within the collection.487 ItemCreated(CollectionId, TokenId),488489 /// Collection item was burned.490 /// 491 /// # Arguments492 /// 493 /// collection_id.494 /// 495 /// item_id: Identifier of burned NFT.496 ItemDestroyed(CollectionId, TokenId),497 }498);499500decl_module! {501 pub struct Module<T: Trait> for enum Call where origin: T::Origin {502503 fn deposit_event() = default;504 type Error = Error<T>;505506 fn on_initialize(now: T::BlockNumber) -> Weight {507508 if ChainVersion::get() < 2509 {510 let value = NextCollectionID::get();511 CreatedCollectionCount::put(value);512 ChainVersion::put(2);513 }514515 0516 }517518 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.519 /// 520 /// # Permissions521 /// 522 /// * Anyone.523 /// 524 /// # Arguments525 /// 526 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.527 /// 528 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.529 /// 530 /// * token_prefix: UTF-8 string with token prefix.531 /// 532 /// * mode: [CollectionMode] collection type and type dependent data.533 // returns collection ID534 #[weight = T::WeightInfo::create_collection()]535 pub fn create_collection(origin,536 collection_name: Vec<u16>,537 collection_description: Vec<u16>,538 token_prefix: Vec<u8>,539 mode: CollectionMode) -> DispatchResult {540541 // Anyone can create a collection542 let who = ensure_signed(origin)?;543544 let decimal_points = match mode {545 CollectionMode::Fungible(points) => points,546 CollectionMode::ReFungible(points) => points,547 _ => 0548 };549550 // bound Total number of collections551 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);552553 // check params554 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);555556 let mut name = collection_name.to_vec();557 name.push(0);558 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);559560 let mut description = collection_description.to_vec();561 description.push(0);562 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);563564 let mut prefix = token_prefix.to_vec();565 prefix.push(0);566 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);567568 // Generate next collection ID569 let next_id = CreatedCollectionCount::get()570 .checked_add(1)571 .ok_or(Error::<T>::NumOverflow)?;572573 // bound counter574 let total = CollectionCount::get()575 .checked_add(1)576 .ok_or(Error::<T>::NumOverflow)?;577578 CreatedCollectionCount::put(next_id);579 CollectionCount::put(total);580581 // Create new collection582 let new_collection = CollectionType {583 owner: who.clone(),584 name: name,585 mode: mode.clone(),586 mint_mode: false,587 access: AccessMode::Normal,588 description: description,589 decimal_points: decimal_points,590 token_prefix: prefix,591 offchain_schema: Vec::new(),592 schema_version: SchemaVersion::ImageURL,593 sponsor: T::AccountId::default(),594 unconfirmed_sponsor: T::AccountId::default(),595 variable_on_chain_schema: Vec::new(),596 const_on_chain_schema: Vec::new(),597 limits: CollectionLimits::default(),598 };599600 // Add new collection to map601 <Collection<T>>::insert(next_id, new_collection);602603 // call event604 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));605606 Ok(())607 }608609 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.610 /// 611 /// # Permissions612 /// 613 /// * Collection Owner.614 /// 615 /// # Arguments616 /// 617 /// * collection_id: collection to destroy.618 #[weight = T::WeightInfo::destroy_collection()]619 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {620621 let sender = ensure_signed(origin)?;622 Self::check_owner_permissions(collection_id, sender)?;623624 <AddressTokens<T>>::remove_prefix(collection_id);625 <ApprovedList<T>>::remove_prefix(collection_id);626 <Balance<T>>::remove_prefix(collection_id);627 <ItemListIndex>::remove(collection_id);628 <AdminList<T>>::remove(collection_id);629 <Collection<T>>::remove(collection_id);630 <WhiteList<T>>::remove(collection_id);631632 <NftItemList<T>>::remove_prefix(collection_id);633 <FungibleItemList<T>>::remove_prefix(collection_id);634 <ReFungibleItemList<T>>::remove_prefix(collection_id);635636 <NftTransferBasket<T>>::remove_prefix(collection_id);637 <FungibleTransferBasket<T>>::remove_prefix(collection_id);638 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);639640 if CollectionCount::get() > 0641 {642 // bound couter643 let total = CollectionCount::get()644 .checked_sub(1)645 .ok_or(Error::<T>::NumOverflow)?;646647 CollectionCount::put(total);648 }649650 Ok(())651 }652653 /// Add an address to white list.654 /// 655 /// # Permissions656 /// 657 /// * Collection Owner658 /// * Collection Admin659 /// 660 /// # Arguments661 /// 662 /// * collection_id.663 /// 664 /// * address.665 #[weight = T::WeightInfo::add_to_white_list()]666 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{667668 let sender = ensure_signed(origin)?;669 Self::check_owner_or_admin_permissions(collection_id, sender)?;670671 let mut white_list_collection: Vec<T::AccountId>;672 if <WhiteList<T>>::contains_key(collection_id) {673 white_list_collection = <WhiteList<T>>::get(collection_id);674 if !white_list_collection.contains(&address.clone())675 {676 white_list_collection.push(address.clone());677 }678 }679 else {680 white_list_collection = Vec::new();681 white_list_collection.push(address.clone());682 }683684 <WhiteList<T>>::insert(collection_id, white_list_collection);685 Ok(())686 }687688 /// Remove an address from white list.689 /// 690 /// # Permissions691 /// 692 /// * Collection Owner693 /// * Collection Admin694 /// 695 /// # Arguments696 /// 697 /// * collection_id.698 /// 699 /// * address.700 #[weight = T::WeightInfo::remove_from_white_list()]701 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{702703 let sender = ensure_signed(origin)?;704 Self::check_owner_or_admin_permissions(collection_id, sender)?;705706 if <WhiteList<T>>::contains_key(collection_id) {707 let mut white_list_collection = <WhiteList<T>>::get(collection_id);708 if white_list_collection.contains(&address.clone())709 {710 white_list_collection.retain(|i| *i != address.clone());711 <WhiteList<T>>::insert(collection_id, white_list_collection);712 }713 }714715 Ok(())716 }717718 /// Toggle between normal and white list access for the methods with access for `Anyone`.719 /// 720 /// # Permissions721 /// 722 /// * Collection Owner.723 /// 724 /// # Arguments725 /// 726 /// * collection_id.727 /// 728 /// * mode: [AccessMode]729 #[weight = T::WeightInfo::set_public_access_mode()]730 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult731 {732 let sender = ensure_signed(origin)?;733734 Self::check_owner_permissions(collection_id, sender)?;735 let mut target_collection = <Collection<T>>::get(collection_id);736 target_collection.access = mode;737 <Collection<T>>::insert(collection_id, target_collection);738739 Ok(())740 }741742 /// Allows Anyone to create tokens if:743 /// * White List is enabled, and744 /// * Address is added to white list, and745 /// * This method was called with True parameter746 /// 747 /// # Permissions748 /// * Collection Owner749 ///750 /// # Arguments751 /// 752 /// * collection_id.753 /// 754 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.755 #[weight = T::WeightInfo::set_mint_permission()]756 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult757 {758 let sender = ensure_signed(origin)?;759760 Self::check_owner_permissions(collection_id, sender)?;761 let mut target_collection = <Collection<T>>::get(collection_id);762 target_collection.mint_mode = mint_permission;763 <Collection<T>>::insert(collection_id, target_collection);764765 Ok(())766 }767768 /// Change the owner of the collection.769 /// 770 /// # Permissions771 /// 772 /// * Collection Owner.773 /// 774 /// # Arguments775 /// 776 /// * collection_id.777 /// 778 /// * new_owner.779 #[weight = T::WeightInfo::change_collection_owner()]780 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {781782 let sender = ensure_signed(origin)?;783 Self::check_owner_permissions(collection_id, sender)?;784 let mut target_collection = <Collection<T>>::get(collection_id);785 target_collection.owner = new_owner;786 <Collection<T>>::insert(collection_id, target_collection);787788 Ok(())789 }790791 /// Adds an admin of the Collection.792 /// 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. 793 /// 794 /// # Permissions795 /// 796 /// * Collection Owner.797 /// * Collection Admin.798 /// 799 /// # Arguments800 /// 801 /// * collection_id: ID of the Collection to add admin for.802 /// 803 /// * new_admin_id: Address of new admin to add.804 #[weight = T::WeightInfo::add_collection_admin()]805 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {806807 let sender = ensure_signed(origin)?;808 Self::check_owner_or_admin_permissions(collection_id, sender)?;809 let mut admin_arr: Vec<T::AccountId> = Vec::new();810811 if <AdminList<T>>::contains_key(collection_id)812 {813 admin_arr = <AdminList<T>>::get(collection_id);814 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);815 }816817 // Number of collection admins818 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);819820 admin_arr.push(new_admin_id);821 <AdminList<T>>::insert(collection_id, admin_arr);822823 Ok(())824 }825826 /// 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.827 ///828 /// # Permissions829 /// 830 /// * Collection Owner.831 /// * Collection Admin.832 /// 833 /// # Arguments834 /// 835 /// * collection_id: ID of the Collection to remove admin for.836 /// 837 /// * account_id: Address of admin to remove.838 #[weight = T::WeightInfo::remove_collection_admin()]839 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {840841 let sender = ensure_signed(origin)?;842 Self::check_owner_or_admin_permissions(collection_id, sender)?;843844 if <AdminList<T>>::contains_key(collection_id)845 {846 let mut admin_arr = <AdminList<T>>::get(collection_id);847 admin_arr.retain(|i| *i != account_id);848 <AdminList<T>>::insert(collection_id, admin_arr);849 }850851 Ok(())852 }853854 /// # Permissions855 /// 856 /// * Collection Owner857 /// 858 /// # Arguments859 /// 860 /// * collection_id.861 /// 862 /// * new_sponsor.863 #[weight = T::WeightInfo::set_collection_sponsor()]864 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {865866 let sender = ensure_signed(origin)?;867 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);868869 let mut target_collection = <Collection<T>>::get(collection_id);870 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);871872 target_collection.unconfirmed_sponsor = new_sponsor;873 <Collection<T>>::insert(collection_id, target_collection);874875 Ok(())876 }877878 /// # Permissions879 /// 880 /// * Sponsor.881 /// 882 /// # Arguments883 /// 884 /// * collection_id.885 #[weight = T::WeightInfo::confirm_sponsorship()]886 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {887888 let sender = ensure_signed(origin)?;889 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);890891 let mut target_collection = <Collection<T>>::get(collection_id);892 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);893894 target_collection.sponsor = target_collection.unconfirmed_sponsor;895 target_collection.unconfirmed_sponsor = T::AccountId::default();896 <Collection<T>>::insert(collection_id, target_collection);897898 Ok(())899 }900901 /// Switch back to pay-per-own-transaction model.902 ///903 /// # Permissions904 ///905 /// * Collection owner.906 /// 907 /// # Arguments908 /// 909 /// * collection_id.910 #[weight = T::WeightInfo::remove_collection_sponsor()]911 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {912913 let sender = ensure_signed(origin)?;914 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);915916 let mut target_collection = <Collection<T>>::get(collection_id);917 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);918919 target_collection.sponsor = T::AccountId::default();920 <Collection<T>>::insert(collection_id, target_collection);921922 Ok(())923 }924925 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.926 /// 927 /// # Permissions928 /// 929 /// * Collection Owner.930 /// * Collection Admin.931 /// * Anyone if932 /// * White List is enabled, and933 /// * Address is added to white list, and934 /// * MintPermission is enabled (see SetMintPermission method)935 /// 936 /// # Arguments937 /// 938 /// * collection_id: ID of the collection.939 /// 940 /// * owner: Address, initial owner of the NFT.941 ///942 /// * data: Token data to store on chain.943 // #[weight =944 // (130_000_000 as Weight)945 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))946 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))947 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]948949 #[weight = T::WeightInfo::create_item(data.len())]950 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {951952 let sender = ensure_signed(origin)?;953954 Self::collection_exists(collection_id)?;955956 let target_collection = <Collection<T>>::get(collection_id);957958 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;959 Self::validate_create_item_args(&target_collection, &data)?;960 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;961962 Ok(())963 }964965 /// This method creates multiple instances of NFT Collection created with CreateCollection method.966 /// 967 /// # Permissions968 /// 969 /// * Collection Owner.970 /// * Collection Admin.971 /// * Anyone if972 /// * White List is enabled, and973 /// * Address is added to white list, and974 /// * MintPermission is enabled (see SetMintPermission method)975 /// 976 /// # Arguments977 /// 978 /// * collection_id: ID of the collection.979 /// 980 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].981 /// 982 /// * owner: Address, initial owner of the NFT.983 #[weight = T::WeightInfo::create_item(items_data.into_iter()984 .map(|data| { data.len() })985 .sum())]986 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {987988 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);989 let sender = ensure_signed(origin)?;990991 Self::collection_exists(collection_id)?;992 let target_collection = <Collection<T>>::get(collection_id);993994 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;995996 for data in &items_data {997 Self::validate_create_item_args(&target_collection, data)?;998 }999 for data in &items_data {1000 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;1001 }10021003 Ok(())1004 }10051006 /// Destroys a concrete instance of NFT.1007 /// 1008 /// # Permissions1009 /// 1010 /// * Collection Owner.1011 /// * Collection Admin.1012 /// * Current NFT Owner.1013 /// 1014 /// # Arguments1015 /// 1016 /// * collection_id: ID of the collection.1017 /// 1018 /// * item_id: ID of NFT to burn.1019 #[weight = T::WeightInfo::burn_item()]1020 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10211022 let sender = ensure_signed(origin)?;1023 Self::collection_exists(collection_id)?;10241025 // Transfer permissions check1026 let target_collection = <Collection<T>>::get(collection_id);1027 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1028 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1029 Error::<T>::NoPermission);10301031 if target_collection.access == AccessMode::WhiteList {1032 Self::check_white_list(collection_id, &sender)?;1033 }10341035 match target_collection.mode1036 {1037 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1038 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1039 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1040 _ => ()1041 };10421043 // call event1044 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10451046 Ok(())1047 }10481049 /// Change ownership of the token.1050 /// 1051 /// # Permissions1052 /// 1053 /// * Collection Owner1054 /// * Collection Admin1055 /// * Current NFT owner1056 ///1057 /// # Arguments1058 /// 1059 /// * recipient: Address of token recipient.1060 /// 1061 /// * collection_id.1062 /// 1063 /// * item_id: ID of the item1064 /// * Non-Fungible Mode: Required.1065 /// * Fungible Mode: Ignored.1066 /// * Re-Fungible Mode: Required.1067 /// 1068 /// * value: Amount to transfer.1069 /// * Non-Fungible Mode: Ignored1070 /// * Fungible Mode: Must specify transferred amount1071 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1072 #[weight = T::WeightInfo::transfer()]1073 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10741075 let sender = ensure_signed(origin)?;1076 let target_collection = <Collection<T>>::get(collection_id);10771078 // Limits check1079 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10801081 // Transfer permissions check1082 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1083 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1084 Error::<T>::NoPermission);10851086 if target_collection.access == AccessMode::WhiteList {1087 Self::check_white_list(collection_id, &sender)?;1088 Self::check_white_list(collection_id, &recipient)?;1089 }10901091 match target_collection.mode1092 {1093 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1094 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1095 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1096 _ => ()1097 };10981099 Ok(())1100 }11011102 /// Set, change, or remove approved address to transfer the ownership of the NFT.1103 /// 1104 /// # Permissions1105 /// 1106 /// * Collection Owner1107 /// * Collection Admin1108 /// * Current NFT owner1109 /// 1110 /// # Arguments1111 /// 1112 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1113 /// 1114 /// * collection_id.1115 /// 1116 /// * item_id: ID of the item.1117 #[weight = T::WeightInfo::approve()]1118 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {11191120 let sender = ensure_signed(origin)?;11211122 // Transfer permissions check1123 let target_collection = <Collection<T>>::get(collection_id);1124 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1125 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1126 Error::<T>::NoPermission);11271128 if target_collection.access == AccessMode::WhiteList {1129 Self::check_white_list(collection_id, &sender)?;1130 Self::check_white_list(collection_id, &approved)?;1131 }11321133 // amount param stub1134 let amount = 100000000;11351136 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1137 if list_exists {11381139 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1140 let item_contains = list.iter().any(|i| i.approved == approved);11411142 if !item_contains {1143 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1144 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1145 }1146 } else {11471148 let mut list = Vec::new();1149 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1150 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1151 }11521153 Ok(())1154 }1155 1156 /// 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.1157 /// 1158 /// # Permissions1159 /// * Collection Owner1160 /// * Collection Admin1161 /// * Current NFT owner1162 /// * Address approved by current NFT owner1163 /// 1164 /// # Arguments1165 /// 1166 /// * from: Address that owns token.1167 /// 1168 /// * recipient: Address of token recipient.1169 /// 1170 /// * collection_id.1171 /// 1172 /// * item_id: ID of the item.1173 /// 1174 /// * value: Amount to transfer.1175 #[weight = T::WeightInfo::transfer_from()]1176 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11771178 let sender = ensure_signed(origin)?;1179 let mut appoved_transfer = false;11801181 // Check approve1182 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1183 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1184 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1185 if opt_item.is_some()1186 {1187 appoved_transfer = true;1188 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1189 }1190 }11911192 let target_collection = <Collection<T>>::get(collection_id);11931194 // Limits check1195 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11961197 // Transfer permissions check 1198 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1199 Error::<T>::NoPermission);12001201 if target_collection.access == AccessMode::WhiteList {1202 Self::check_white_list(collection_id, &sender)?;1203 Self::check_white_list(collection_id, &recipient)?;1204 }12051206 // remove approve1207 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1208 .into_iter().filter(|i| i.approved != sender.clone()).collect();1209 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);121012111212 match target_collection.mode1213 {1214 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1215 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1216 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1217 _ => ()1218 };12191220 Ok(())1221 }12221223 #[weight = 0]1224 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12251226 // let no_perm_mes = "You do not have permissions to modify this collection";1227 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1228 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1229 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12301231 // // on_nft_received call12321233 // Self::transfer(origin, collection_id, item_id, new_owner)?;12341235 Ok(())1236 }12371238 /// Set off-chain data schema.1239 /// 1240 /// # Permissions1241 /// 1242 /// * Collection Owner1243 /// * Collection Admin1244 /// 1245 /// # Arguments1246 /// 1247 /// * collection_id.1248 /// 1249 /// * schema: String representing the offchain data schema.1250 #[weight = T::WeightInfo::set_variable_meta_data()]1251 pub fn set_variable_meta_data (1252 origin,1253 collection_id: CollectionId,1254 item_id: TokenId,1255 data: Vec<u8>1256 ) -> DispatchResult {1257 let sender = ensure_signed(origin)?;1258 1259 Self::collection_exists(collection_id)?;1260 1261 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12621263 // Modify permissions check1264 let target_collection = <Collection<T>>::get(collection_id);1265 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1266 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1267 Error::<T>::NoPermission);12681269 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12701271 match target_collection.mode1272 {1273 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1274 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1275 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1276 _ => fail!(Error::<T>::UnexpectedCollectionType)1277 };12781279 Ok(())1280 }1281 1282 /// Set schema standard1283 /// ImageURL1284 /// Unique1285 /// 1286 /// # Permissions1287 /// 1288 /// * Collection Owner1289 /// * Collection Admin1290 /// 1291 /// # Arguments1292 /// 1293 /// * collection_id.1294 /// 1295 /// * schema: SchemaVersion: enum1296 #[weight = 0]1297 pub fn set_schema_version(1298 origin,1299 collection_id: CollectionId,1300 version: SchemaVersion1301 ) -> DispatchResult {1302 let sender = ensure_signed(origin)?;1303 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1304 let mut target_collection = <Collection<T>>::get(collection_id);1305 target_collection.schema_version = version;1306 <Collection<T>>::insert(collection_id, target_collection);13071308 Ok(())1309 }13101311 /// Set off-chain data schema.1312 /// 1313 /// # Permissions1314 /// 1315 /// * Collection Owner1316 /// * Collection Admin1317 /// 1318 /// # Arguments1319 /// 1320 /// * collection_id.1321 /// 1322 /// * schema: String representing the offchain data schema.1323 #[weight = T::WeightInfo::set_offchain_schema()]1324 pub fn set_offchain_schema(1325 origin,1326 collection_id: CollectionId,1327 schema: Vec<u8>1328 ) -> DispatchResult {1329 let sender = ensure_signed(origin)?;1330 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13311332 let mut target_collection = <Collection<T>>::get(collection_id);1333 target_collection.offchain_schema = schema;1334 <Collection<T>>::insert(collection_id, target_collection);13351336 Ok(())1337 }13381339 /// Set const on-chain data schema.1340 /// 1341 /// # Permissions1342 /// 1343 /// * Collection Owner1344 /// * Collection Admin1345 /// 1346 /// # Arguments1347 /// 1348 /// * collection_id.1349 /// 1350 /// * schema: String representing the const on-chain data schema.1351 #[weight = T::WeightInfo::set_const_on_chain_schema()]1352 pub fn set_const_on_chain_schema (1353 origin,1354 collection_id: CollectionId,1355 schema: Vec<u8>1356 ) -> DispatchResult {1357 let sender = ensure_signed(origin)?;1358 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13591360 let mut target_collection = <Collection<T>>::get(collection_id);1361 target_collection.const_on_chain_schema = schema;1362 <Collection<T>>::insert(collection_id, target_collection);13631364 Ok(())1365 }13661367 /// Set variable on-chain data schema.1368 /// 1369 /// # Permissions1370 /// 1371 /// * Collection Owner1372 /// * Collection Admin1373 /// 1374 /// # Arguments1375 /// 1376 /// * collection_id.1377 /// 1378 /// * schema: String representing the variable on-chain data schema.1379 #[weight = T::WeightInfo::set_const_on_chain_schema()]1380 pub fn set_variable_on_chain_schema (1381 origin,1382 collection_id: CollectionId,1383 schema: Vec<u8>1384 ) -> DispatchResult {1385 let sender = ensure_signed(origin)?;1386 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13871388 let mut target_collection = <Collection<T>>::get(collection_id);1389 target_collection.variable_on_chain_schema = schema;1390 <Collection<T>>::insert(collection_id, target_collection);13911392 Ok(())1393 }13941395 // Sudo permissions function1396 #[weight = 0]1397 pub fn set_chain_limits(1398 origin,1399 limits: ChainLimits1400 ) -> DispatchResult {1401 ensure_root(origin)?;1402 <ChainLimit>::put(limits);1403 Ok(())1404 }14051406 /// Enable smart contract self-sponsoring.1407 /// 1408 /// # Permissions1409 /// 1410 /// * Contract Owner1411 /// 1412 /// # Arguments1413 /// 1414 /// * contract address1415 /// * enable flag1416 /// 1417 #[weight = T::WeightInfo::enable_contract_sponsoring()]1418 pub fn enable_contract_sponsoring(1419 origin,1420 contract_address: T::AccountId,1421 enable: bool1422 ) -> DispatchResult {14231424 let sender = ensure_signed(origin)?;14251426 #[cfg(feature = "runtime-benchmarks")]1427 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14281429 let mut is_owner = false;1430 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1431 let owner = <ContractOwner<T>>::get(&contract_address);1432 is_owner = sender == owner;1433 }1434 ensure!(is_owner, Error::<T>::NoPermission);14351436 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1437 Ok(())1438 }14391440 /// Set the rate limit for contract sponsoring to specified number of blocks.1441 /// 1442 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1443 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1444 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1445 /// from contract endowment if there are at least B blocks between such transactions. 1446 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1447 /// 1448 /// # Permissions1449 /// 1450 /// * Contract Owner1451 /// 1452 /// # Arguments1453 /// 1454 /// -`contract_address`: Address of the contract to sponsor1455 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1456 /// 1457 #[weight = 0]1458 pub fn set_contract_sponsoring_rate_limit(1459 origin,1460 contract_address: T::AccountId,1461 rate_limit: T::BlockNumber1462 ) -> DispatchResult {1463 let sender = ensure_signed(origin)?;1464 let mut is_owner = false;1465 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1466 let owner = <ContractOwner<T>>::get(&contract_address);1467 is_owner = sender == owner;1468 }1469 ensure!(is_owner, Error::<T>::NoPermission);14701471 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1472 Ok(())1473 }14741475 #[weight = 0]1476 pub fn set_collection_limits(1477 origin,1478 collection_id: u32,1479 limits: CollectionLimits,1480 ) -> DispatchResult {1481 let sender = ensure_signed(origin)?;1482 Self::check_owner_permissions(collection_id, sender.clone())?;1483 let mut target_collection = <Collection<T>>::get(collection_id);1484 let chain_limits = ChainLimit::get();1485 let climits = target_collection.limits;14861487 // collection bounds1488 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1489 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1490 Error::<T>::CollectionLimitBoundsExceeded);14911492 // token_limit check prev1493 ensure!(climits.token_limit > limits.token_limit && 1494 limits.token_limit <= chain_limits.account_token_ownership_limit, 1495 Error::<T>::AccountTokenLimitExceeded);14961497 target_collection.limits = limits;1498 <Collection<T>>::insert(collection_id, target_collection);14991500 Ok(())1501 } 1502 }1503}15041505impl<T: Trait> Module<T> {15061507 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15081509 // check token limit and account token limit1510 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1511 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1512 1513 Ok(())1514 }15151516 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15171518 // check token limit and account token limit1519 let total_items: u32 = ItemListIndex::get(collection_id);1520 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1521 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1522 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15231524 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1525 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1526 Self::check_white_list(collection_id, owner)?;1527 Self::check_white_list(collection_id, sender)?;1528 }15291530 Ok(())1531 }15321533 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1534 match target_collection.mode1535 {1536 CollectionMode::NFT => {1537 if let CreateItemData::NFT(data) = data {1538 // check sizes1539 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1540 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1541 } else {1542 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1543 }1544 },1545 CollectionMode::Fungible(_) => {1546 if let CreateItemData::Fungible(_) = data {1547 } else {1548 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1549 }1550 },1551 CollectionMode::ReFungible(_) => {1552 if let CreateItemData::ReFungible(data) = data {15531554 // check sizes1555 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1556 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1557 } else {1558 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1559 }1560 },1561 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1562 };15631564 Ok(())1565 }15661567 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1568 match data1569 {1570 CreateItemData::NFT(data) => {1571 let item = NftItemType {1572 collection: collection_id,1573 owner,1574 const_data: data.const_data,1575 variable_data: data.variable_data1576 };15771578 Self::add_nft_item(item)?;1579 },1580 CreateItemData::Fungible(_) => {1581 let item = FungibleItemType {1582 collection: collection_id,1583 owner,1584 value: (10 as u128).pow(collection.decimal_points as u32)1585 };15861587 Self::add_fungible_item(item)?;1588 },1589 CreateItemData::ReFungible(data) => {1590 let mut owner_list = Vec::new();1591 let value = (10 as u128).pow(collection.decimal_points as u32);1592 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15931594 let item = ReFungibleItemType {1595 collection: collection_id,1596 owner: owner_list,1597 const_data: data.const_data,1598 variable_data: data.variable_data1599 };16001601 Self::add_refungible_item(item)?;1602 }1603 };16041605 // call event1606 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16071608 Ok(())1609 }16101611 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1612 let current_index = <ItemListIndex>::get(item.collection)1613 .checked_add(1)1614 .ok_or(Error::<T>::NumOverflow)?;1615 let itemcopy = item.clone();1616 let owner = item.owner.clone();16171618 Self::add_token_index(item.collection, current_index, owner.clone())?;16191620 <ItemListIndex>::insert(item.collection, current_index);1621 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16221623 // Add current block1624 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1625 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1626 1627 // Update balance1628 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1629 .checked_add(item.value)1630 .ok_or(Error::<T>::NumOverflow)?;1631 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16321633 Ok(())1634 }16351636 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1637 let current_index = <ItemListIndex>::get(item.collection)1638 .checked_add(1)1639 .ok_or(Error::<T>::NumOverflow)?;1640 let itemcopy = item.clone();16411642 let value = item.owner.first().unwrap().fraction;1643 let owner = item.owner.first().unwrap().owner.clone();16441645 Self::add_token_index(item.collection, current_index, owner.clone())?;16461647 <ItemListIndex>::insert(item.collection, current_index);1648 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16491650 // Add current block1651 let block_number: T::BlockNumber = 0.into();1652 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16531654 // Update balance1655 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1656 .checked_add(value)1657 .ok_or(Error::<T>::NumOverflow)?;1658 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16591660 Ok(())1661 }16621663 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1664 let current_index = <ItemListIndex>::get(item.collection)1665 .checked_add(1)1666 .ok_or(Error::<T>::NumOverflow)?;16671668 let item_owner = item.owner.clone();1669 let collection_id = item.collection.clone();1670 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16711672 <ItemListIndex>::insert(collection_id, current_index);1673 <NftItemList<T>>::insert(collection_id, current_index, item);16741675 // Add current block1676 let block_number: T::BlockNumber = 0.into();1677 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);16781679 // Update balance1680 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1681 .checked_add(1)1682 .ok_or(Error::<T>::NumOverflow)?;1683 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16841685 Ok(())1686 }16871688 fn burn_refungible_item(1689 collection_id: CollectionId,1690 item_id: TokenId,1691 owner: T::AccountId,1692 ) -> DispatchResult {1693 ensure!(1694 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1695 Error::<T>::TokenNotFound1696 );1697 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1698 let item = collection1699 .owner1700 .iter()1701 .filter(|&i| i.owner == owner)1702 .next()1703 .unwrap();1704 Self::remove_token_index(collection_id, item_id, owner.clone())?;17051706 // remove approve list1707 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17081709 // update balance1710 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1711 .checked_sub(item.fraction)1712 .ok_or(Error::<T>::NumOverflow)?;1713 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17141715 <ReFungibleItemList<T>>::remove(collection_id, item_id);17161717 Ok(())1718 }17191720 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1721 ensure!(1722 <NftItemList<T>>::contains_key(collection_id, item_id),1723 Error::<T>::TokenNotFound1724 );1725 let item = <NftItemList<T>>::get(collection_id, item_id);1726 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17271728 // remove approve list1729 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17301731 // update balance1732 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1733 .checked_sub(1)1734 .ok_or(Error::<T>::NumOverflow)?;1735 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1736 <NftItemList<T>>::remove(collection_id, item_id);17371738 Ok(())1739 }17401741 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1742 ensure!(1743 <FungibleItemList<T>>::contains_key(collection_id, item_id),1744 Error::<T>::TokenNotFound1745 );1746 let item = <FungibleItemList<T>>::get(collection_id, item_id);1747 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17481749 // remove approve list1750 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17511752 // update balance1753 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1754 .checked_sub(item.value)1755 .ok_or(Error::<T>::NumOverflow)?;1756 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17571758 <FungibleItemList<T>>::remove(collection_id, item_id);17591760 Ok(())1761 }17621763 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1764 ensure!(1765 <Collection<T>>::contains_key(collection_id),1766 Error::<T>::CollectionNotFound1767 );1768 Ok(())1769 }17701771 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1772 Self::collection_exists(collection_id)?;17731774 let target_collection = <Collection<T>>::get(collection_id);1775 ensure!(1776 subject == target_collection.owner,1777 Error::<T>::NoPermission1778 );17791780 Ok(())1781 }17821783 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1784 let target_collection = <Collection<T>>::get(collection_id);1785 let mut result: bool = subject == target_collection.owner;1786 let exists = <AdminList<T>>::contains_key(collection_id);17871788 if !result & exists {1789 if <AdminList<T>>::get(collection_id).contains(&subject) {1790 result = true1791 }1792 }17931794 result1795 }17961797 fn check_owner_or_admin_permissions(1798 collection_id: CollectionId,1799 subject: T::AccountId,1800 ) -> DispatchResult {1801 Self::collection_exists(collection_id)?;1802 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18031804 ensure!(1805 result,1806 Error::<T>::NoPermission1807 );1808 Ok(())1809 }18101811 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1812 let target_collection = <Collection<T>>::get(collection_id);18131814 match target_collection.mode {1815 CollectionMode::NFT => {1816 <NftItemList<T>>::get(collection_id, item_id).owner == subject1817 }1818 CollectionMode::Fungible(_) => {1819 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1820 }1821 CollectionMode::ReFungible(_) => {1822 <ReFungibleItemList<T>>::get(collection_id, item_id)1823 .owner1824 .iter()1825 .any(|i| i.owner == subject)1826 }1827 CollectionMode::Invalid => false,1828 }1829 }18301831 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1832 let mes = Error::<T>::AddresNotInWhiteList;1833 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1834 let wl = <WhiteList<T>>::get(collection_id);1835 ensure!(wl.contains(address), mes);18361837 Ok(())1838 }18391840 fn transfer_fungible(1841 collection_id: CollectionId,1842 item_id: TokenId,1843 value: u128,1844 owner: T::AccountId,1845 new_owner: T::AccountId,1846 ) -> DispatchResult {1847 ensure!(1848 <FungibleItemList<T>>::contains_key(collection_id, item_id),1849 Error::<T>::TokenNotFound1850 );18511852 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1853 let amount = full_item.value;18541855 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18561857 // update balance1858 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1859 .checked_sub(value)1860 .ok_or(Error::<T>::NumOverflow)?;1861 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);18621863 let mut new_owner_account_id = 0;1864 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1865 if new_owner_items.len() > 0 {1866 new_owner_account_id = new_owner_items[0];1867 }18681869 // transfer1870 if amount == value && new_owner_account_id == 0 {1871 // change owner1872 // new owner do not have account1873 let mut new_full_item = full_item.clone();1874 new_full_item.owner = new_owner.clone();1875 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18761877 // update balance1878 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1879 .checked_add(value)1880 .ok_or(Error::<T>::NumOverflow)?;1881 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18821883 // update index collection1884 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1885 } else {1886 let mut new_full_item = full_item.clone();1887 new_full_item.value -= value;18881889 // separate amount1890 if new_owner_account_id > 0 {1891 // new owner has account1892 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1893 item.value += value;18941895 // update balance1896 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1897 .checked_add(value)1898 .ok_or(Error::<T>::NumOverflow)?;1899 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19001901 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1902 } else {1903 // new owner do not have account1904 let item = FungibleItemType {1905 collection: collection_id,1906 owner: new_owner.clone(),1907 value1908 };19091910 Self::add_fungible_item(item)?;1911 }19121913 if amount == value {1914 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19151916 // remove approve list1917 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1918 <FungibleItemList<T>>::remove(collection_id, item_id);1919 }19201921 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1922 }19231924 Ok(())1925 }19261927 fn transfer_refungible(1928 collection_id: CollectionId,1929 item_id: TokenId,1930 value: u128,1931 owner: T::AccountId,1932 new_owner: T::AccountId,1933 ) -> DispatchResult {1934 ensure!(1935 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1936 Error::<T>::TokenNotFound1937 );19381939 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1940 let item = full_item1941 .owner1942 .iter()1943 .filter(|i| i.owner == owner)1944 .next()1945 .ok_or(Error::<T>::NumOverflow)?;1946 let amount = item.fraction;19471948 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19491950 // update balance1951 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1952 .checked_sub(value)1953 .ok_or(Error::<T>::NumOverflow)?;1954 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19551956 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1957 .checked_add(value)1958 .ok_or(Error::<T>::NumOverflow)?;1959 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19601961 let old_owner = item.owner.clone();1962 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19631964 // transfer1965 if amount == value && !new_owner_has_account {1966 // change owner1967 // new owner do not have account1968 let mut new_full_item = full_item.clone();1969 new_full_item1970 .owner1971 .iter_mut()1972 .find(|i| i.owner == owner)1973 .unwrap()1974 .owner = new_owner.clone();1975 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19761977 // update index collection1978 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1979 } else {1980 let mut new_full_item = full_item.clone();1981 new_full_item1982 .owner1983 .iter_mut()1984 .find(|i| i.owner == owner)1985 .unwrap()1986 .fraction -= value;19871988 // separate amount1989 if new_owner_has_account {1990 // new owner has account1991 new_full_item1992 .owner1993 .iter_mut()1994 .find(|i| i.owner == new_owner)1995 .unwrap()1996 .fraction += value;1997 } else {1998 // new owner do not have account1999 new_full_item.owner.push(Ownership {2000 owner: new_owner.clone(),2001 fraction: value,2002 });2003 Self::add_token_index(collection_id, item_id, new_owner.clone())?;2004 }20052006 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2007 }20082009 Ok(())2010 }20112012 fn transfer_nft(2013 collection_id: CollectionId,2014 item_id: TokenId,2015 sender: T::AccountId,2016 new_owner: T::AccountId,2017 ) -> DispatchResult {2018 ensure!(2019 <NftItemList<T>>::contains_key(collection_id, item_id),2020 Error::<T>::TokenNotFound2021 );20222023 let mut item = <NftItemList<T>>::get(collection_id, item_id);20242025 ensure!(2026 sender == item.owner,2027 Error::<T>::MustBeTokenOwner2028 );20292030 // update balance2031 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2032 .checked_sub(1)2033 .ok_or(Error::<T>::NumOverflow)?;2034 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20352036 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2037 .checked_add(1)2038 .ok_or(Error::<T>::NumOverflow)?;2039 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20402041 // change owner2042 let old_owner = item.owner.clone();2043 item.owner = new_owner.clone();2044 <NftItemList<T>>::insert(collection_id, item_id, item);20452046 // update index collection2047 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20482049 // reset approved list2050 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2051 Ok(())2052 }2053 2054 fn item_exists(2055 collection_id: CollectionId,2056 item_id: TokenId,2057 mode: &CollectionMode2058 ) -> DispatchResult {2059 match mode {2060 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2061 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2062 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2063 _ => ()2064 };2065 2066 Ok(())2067 }20682069 fn set_re_fungible_variable_data(2070 collection_id: CollectionId,2071 item_id: TokenId,2072 data: Vec<u8>2073 ) -> DispatchResult {2074 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20752076 item.variable_data = data;20772078 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20792080 Ok(())2081 }20822083 fn set_nft_variable_data(2084 collection_id: CollectionId,2085 item_id: TokenId,2086 data: Vec<u8>2087 ) -> DispatchResult {2088 let mut item = <NftItemList<T>>::get(collection_id, item_id);2089 2090 item.variable_data = data;20912092 <NftItemList<T>>::insert(collection_id, item_id, item);2093 2094 Ok(())2095 }20962097 fn init_collection(item: &CollectionType<T::AccountId>) {2098 // check params2099 assert!(2100 item.decimal_points <= MAX_DECIMAL_POINTS,2101 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2102 );2103 assert!(2104 item.name.len() <= 64,2105 "Collection name can not be longer than 63 char"2106 );2107 assert!(2108 item.name.len() <= 256,2109 "Collection description can not be longer than 255 char"2110 );2111 assert!(2112 item.token_prefix.len() <= 16,2113 "Token prefix can not be longer than 15 char"2114 );21152116 // Generate next collection ID2117 let next_id = CreatedCollectionCount::get()2118 .checked_add(1)2119 .unwrap();21202121 CreatedCollectionCount::put(next_id);2122 }21232124 fn init_nft_token(item: &NftItemType<T::AccountId>) {2125 let current_index = <ItemListIndex>::get(item.collection)2126 .checked_add(1)2127 .unwrap();21282129 let item_owner = item.owner.clone();2130 let collection_id = item.collection.clone();2131 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();21322133 <ItemListIndex>::insert(collection_id, current_index);21342135 // Update balance2136 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2137 .checked_add(1)2138 .unwrap();2139 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2140 }21412142 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2143 let current_index = <ItemListIndex>::get(item.collection)2144 .checked_add(1)2145 .unwrap();2146 let owner = item.owner.clone();21472148 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21492150 <ItemListIndex>::insert(item.collection, current_index);21512152 // Update balance2153 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2154 .checked_add(item.value)2155 .unwrap();2156 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2157 }21582159 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2160 let current_index = <ItemListIndex>::get(item.collection)2161 .checked_add(1)2162 .unwrap();21632164 let value = item.owner.first().unwrap().fraction;2165 let owner = item.owner.first().unwrap().owner.clone();21662167 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21682169 <ItemListIndex>::insert(item.collection, current_index);21702171 // Update balance2172 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2173 .checked_add(value)2174 .unwrap();2175 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2176 }21772178 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21792180 // add to account limit2181 if <AccountItemCount<T>>::contains_key(owner.clone()) {21822183 // bound Owned tokens by a single address2184 let count = <AccountItemCount<T>>::get(owner.clone());2185 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21862187 <AccountItemCount<T>>::insert(owner.clone(), count2188 .checked_add(1)2189 .ok_or(Error::<T>::NumOverflow)?);2190 }2191 else {2192 <AccountItemCount<T>>::insert(owner.clone(), 1);2193 }21942195 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2196 if list_exists {2197 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2198 let item_contains = list.contains(&item_index.clone());21992200 if !item_contains {2201 list.push(item_index.clone());2202 }22032204 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2205 } else {2206 let mut itm = Vec::new();2207 itm.push(item_index.clone());2208 <AddressTokens<T>>::insert(collection_id, owner, itm);2209 2210 }22112212 Ok(())2213 }22142215 fn remove_token_index(2216 collection_id: CollectionId,2217 item_index: TokenId,2218 owner: T::AccountId,2219 ) -> DispatchResult {22202221 // update counter2222 <AccountItemCount<T>>::insert(owner.clone(), 2223 <AccountItemCount<T>>::get(owner.clone())2224 .checked_sub(1)2225 .ok_or(Error::<T>::NumOverflow)?);222622272228 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2229 if list_exists {2230 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2231 let item_contains = list.contains(&item_index.clone());22322233 if item_contains {2234 list.retain(|&item| item != item_index);2235 <AddressTokens<T>>::insert(collection_id, owner, list);2236 }2237 }22382239 Ok(())2240 }22412242 fn move_token_index(2243 collection_id: CollectionId,2244 item_index: TokenId,2245 old_owner: T::AccountId,2246 new_owner: T::AccountId,2247 ) -> DispatchResult {2248 Self::remove_token_index(collection_id, item_index, old_owner)?;2249 Self::add_token_index(collection_id, item_index, new_owner)?;22502251 Ok(())2252 }2253}22542255////////////////////////////////////////////////////////////////////////////////////////////////////2256// Economic models2257// #region22582259/// Fee multiplier.2260pub type Multiplier = FixedU128;22612262type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2263 <T as system::Trait>::AccountId,2264>>::Balance;2265type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2266 <T as system::Trait>::AccountId,2267>>::NegativeImbalance;22682269/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2270/// in the queue.2271#[derive(Encode, Decode, Clone, Eq, PartialEq)]2272pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2273 #[codec(compact)] BalanceOf<T>2274);22752276impl<T: Trait + Send + Sync> sp_std::fmt::Debug2277 for ChargeTransactionPayment<T>2278{2279 #[cfg(feature = "std")]2280 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2281 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2282 }2283 #[cfg(not(feature = "std"))]2284 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2285 Ok(())2286 }2287}22882289impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2290where2291 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2292 BalanceOf<T>: Send + Sync + FixedPointOperand,2293{2294 /// utility constructor. Used only in client/factory code.2295 pub fn from(fee: BalanceOf<T>) -> Self {2296 Self(fee)2297 }22982299 pub fn traditional_fee(2300 len: usize,2301 info: &DispatchInfoOf<T::Call>,2302 tip: BalanceOf<T>,2303 ) -> BalanceOf<T>2304 where2305 T::Call: Dispatchable<Info = DispatchInfo>,2306 {2307 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2308 }23092310 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2311 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2312 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2313 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2314 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2315 }23162317 fn withdraw_fee(2318 &self,2319 who: &T::AccountId,2320 call: &T::Call,2321 info: &DispatchInfoOf<T::Call>,2322 len: usize,2323 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2324 let tip = self.0;23252326 // Set fee based on call type. Creating collection costs 1 Unique.2327 // All other transactions have traditional fees so far2328 // let fee = match call.is_sub_type() {2329 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2330 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2331 // // _ => <BalanceOf<T>>::from(100)2332 // };2333 let fee = Self::traditional_fee(len, info, tip);23342335 // Determine who is paying transaction fee based on ecnomic model2336 // Parse call to extract collection ID and access collection sponsor2337 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2338 Some(Call::create_item(collection_id, _owner, _properties)) => {23392340 // check free create limit2341 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2342 {2343 <Collection<T>>::get(collection_id).sponsor2344 } else {2345 T::AccountId::default()2346 }2347 }2348 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2349 2350 let _collection_limits = <Collection<T>>::get(collection_id).limits;2351 let _collection_mode = <Collection<T>>::get(collection_id).mode;23522353 // sponsor timeout2354 let sponsor_transfer = match _collection_mode {2355 CollectionMode::NFT => {23562357 // get correct limit2358 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2359 _collection_limits.sponsor_transfer_timeout2360 } else {2361 ChainLimit::get().nft_sponsor_transfer_timeout2362 };23632364 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2365 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2366 let limit_time = basket + limit.into();2367 if block_number >= limit_time {2368 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2369 true2370 }2371 else {2372 false2373 }2374 }2375 CollectionMode::Fungible(_) => {23762377 // get correct limit2378 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2379 _collection_limits.sponsor_transfer_timeout2380 } else {2381 ChainLimit::get().fungible_sponsor_transfer_timeout2382 };23832384 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2385 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2386 if basket.iter().any(|i| i.address == _new_owner.clone())2387 {2388 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2389 let limit_time = item.start_block + limit.into();2390 if block_number >= limit_time {2391 basket.retain(|x| x.address == item.address);2392 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2393 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2394 true2395 }2396 else {2397 false2398 }2399 }2400 else {2401 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2402 true2403 }2404 }2405 CollectionMode::ReFungible(_) => {24062407 // get correct limit2408 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2409 _collection_limits.sponsor_transfer_timeout2410 } else {2411 ChainLimit::get().refungible_sponsor_transfer_timeout2412 };24132414 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2415 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2416 let limit_time = basket + limit.into();2417 if block_number >= limit_time {2418 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2419 true2420 } else {2421 false2422 }2423 }2424 _ => {2425 false2426 },2427 };24282429 if !sponsor_transfer {2430 T::AccountId::default()2431 } else {2432 <Collection<T>>::get(collection_id).sponsor2433 }2434 }24352436 _ => T::AccountId::default(),2437 };24382439 // Sponsor smart contracts2440 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24412442 // On instantiation: set the contract owner2443 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24442445 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2446 code_hash,2447 &data,2448 &who,2449 );2450 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24512452 T::AccountId::default()2453 },24542455 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2456 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24572458 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24592460 let mut sponsor_transfer = false;2461 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2462 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2463 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2464 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2465 let limit_time = last_tx_block + rate_limit;24662467 if block_number >= limit_time {2468 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2469 sponsor_transfer = true;2470 }2471 } else {2472 sponsor_transfer = false;2473 }2474 2475 2476 let mut sp = T::AccountId::default();2477 if sponsor_transfer {2478 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2479 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2480 sp = called_contract;2481 }2482 }2483 }24842485 sp2486 },24872488 _ => sponsor,2489 };24902491 let mut who_pays_fee: T::AccountId = sponsor.clone();2492 if sponsor == T::AccountId::default() {2493 who_pays_fee = who.clone();2494 }24952496 // Only mess with balances if fee is not zero.2497 if fee.is_zero() {2498 return Ok((fee, None));2499 }25002501 match <T as transaction_payment::Trait>::Currency::withdraw(2502 &who_pays_fee,2503 fee,2504 if tip.is_zero() {2505 WithdrawReason::TransactionPayment.into()2506 } else {2507 WithdrawReason::TransactionPayment | WithdrawReason::Tip2508 },2509 ExistenceRequirement::KeepAlive,2510 ) {2511 Ok(imbalance) => Ok((fee, Some(imbalance))),2512 Err(_) => Err(InvalidTransaction::Payment.into()),2513 }2514 }2515}251625172518impl<T: Trait + Send + Sync> SignedExtension2519 for ChargeTransactionPayment<T>2520where2521 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2522 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2523{2524 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2525 type AccountId = T::AccountId;2526 type Call = T::Call;2527 type AdditionalSigned = ();2528 type Pre = (2529 BalanceOf<T>,2530 Self::AccountId,2531 Option<NegativeImbalanceOf<T>>,2532 BalanceOf<T>,2533 );2534 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2535 Ok(())2536 }25372538 fn validate(2539 &self,2540 who: &Self::AccountId,2541 call: &Self::Call,2542 info: &DispatchInfoOf<Self::Call>,2543 len: usize,2544 ) -> TransactionValidity {2545 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2546 Ok(ValidTransaction {2547 priority: Self::get_priority(len, info, fee),2548 ..Default::default()2549 })2550 }25512552 fn pre_dispatch(2553 self,2554 who: &Self::AccountId,2555 call: &Self::Call,2556 info: &DispatchInfoOf<Self::Call>,2557 len: usize,2558 ) -> Result<Self::Pre, TransactionValidityError> {2559 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2560 Ok((self.0, who.clone(), imbalance, fee))2561 }25622563 fn post_dispatch(2564 pre: Self::Pre,2565 info: &DispatchInfoOf<Self::Call>,2566 post_info: &PostDispatchInfoOf<Self::Call>,2567 len: usize,2568 _result: &DispatchResult,2569 ) -> Result<(), TransactionValidityError> {2570 let (tip, who, imbalance, fee) = pre;2571 if let Some(payed) = imbalance {2572 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2573 len as u32, info, post_info, tip,2574 );2575 let refund = fee.saturating_sub(actual_fee);2576 let actual_payment =2577 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2578 &who, refund,2579 ) {2580 Ok(refund_imbalance) => {2581 // The refund cannot be larger than the up front payed max weight.2582 // `PostDispatchInfo::calc_unspent` guards against such a case.2583 match payed.offset(refund_imbalance) {2584 Ok(actual_payment) => actual_payment,2585 Err(_) => return Err(InvalidTransaction::Payment.into()),2586 }2587 }2588 // We do not recreate the account using the refund. The up front payment2589 // is gone in that case.2590 Err(_) => payed,2591 };2592 let imbalances = actual_payment.split(tip);2593 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2594 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2595 );2596 }2597 Ok(())2598 }2599}26002601// #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 collection: CollectionId,149 pub owner: AccountId,150 pub const_data: Vec<u8>,151 pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType<AccountId> {157 pub collection: CollectionId,158 pub owner: AccountId,159 pub value: u128,160}161162#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]163#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]164pub struct ReFungibleItemType<AccountId> {165 pub collection: CollectionId,166 pub owner: Vec<Ownership<AccountId>>,167 pub const_data: Vec<u8>,168 pub variable_data: Vec<u8>,169}170171#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]172#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]173pub struct ApprovePermissions<AccountId> {174 pub approved: AccountId,175 pub amount: u128,176}177178#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct VestingItem<AccountId, Moment> {181 pub sender: AccountId,182 pub recipient: AccountId,183 pub collection_id: CollectionId,184 pub item_id: TokenId,185 pub amount: u64,186 pub vesting_date: Moment,187}188189#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]190#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]191pub struct BasketItem<AccountId, BlockNumber> {192 pub address: AccountId,193 pub start_block: BlockNumber,194}195196#[derive(Encode, Decode, Debug, Clone, PartialEq)]197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]198pub struct CollectionLimits {199 pub account_token_ownership_limit: u32,200 pub sponsored_data_size: u32,201 pub token_limit: u32,202203 // Timeouts for item types in passed blocks204 pub sponsor_transfer_timeout: u32,205}206207impl Default for CollectionLimits {208 fn default() -> CollectionLimits {209 CollectionLimits { 210 account_token_ownership_limit: 10_000_000, 211 token_limit: u32::max_value(),212 sponsored_data_size: u32::max_value(), 213 sponsor_transfer_timeout: 14400 }214 }215}216217#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]218#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]219pub struct ChainLimits {220 pub collection_numbers_limit: u32,221 pub account_token_ownership_limit: u32,222 pub collections_admins_limit: u64,223 pub custom_data_limit: u32,224225 // Timeouts for item types in passed blocks226 pub nft_sponsor_transfer_timeout: u32,227 pub fungible_sponsor_transfer_timeout: u32,228 pub refungible_sponsor_transfer_timeout: u32,229}230231pub trait WeightInfo {232 fn create_collection() -> Weight;233 fn destroy_collection() -> Weight;234 fn add_to_white_list() -> Weight;235 fn remove_from_white_list() -> Weight;236 fn set_public_access_mode() -> Weight;237 fn set_mint_permission() -> Weight;238 fn change_collection_owner() -> Weight;239 fn add_collection_admin() -> Weight;240 fn remove_collection_admin() -> Weight;241 fn set_collection_sponsor() -> Weight;242 fn confirm_sponsorship() -> Weight;243 fn remove_collection_sponsor() -> Weight;244 fn create_item(s: usize) -> Weight;245 fn burn_item() -> Weight;246 fn transfer() -> Weight;247 fn approve() -> Weight;248 fn transfer_from() -> Weight;249 fn set_offchain_schema() -> Weight;250 fn set_const_on_chain_schema() -> Weight;251 fn set_variable_on_chain_schema() -> Weight;252 fn set_variable_meta_data() -> Weight;253 fn enable_contract_sponsoring() -> Weight;254}255256#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]257#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]258pub struct CreateNftData {259 pub const_data: Vec<u8>,260 pub variable_data: Vec<u8>,261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateFungibleData {266}267268#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]269#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]270pub struct CreateReFungibleData {271 pub const_data: Vec<u8>,272 pub variable_data: Vec<u8>,273}274275#[derive(Encode, Decode, Debug, Clone, PartialEq)]276#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]277pub enum CreateItemData {278 NFT(CreateNftData),279 Fungible(CreateFungibleData),280 ReFungible(CreateReFungibleData),281}282283impl CreateItemData {284 pub fn len(&self) -> usize {285 let len = match self {286 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),287 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),288 _ => 0289 };290 291 return len;292 }293}294295impl From<CreateNftData> for CreateItemData {296 fn from(item: CreateNftData) -> Self {297 CreateItemData::NFT(item)298 }299}300301impl From<CreateReFungibleData> for CreateItemData {302 fn from(item: CreateReFungibleData) -> Self {303 CreateItemData::ReFungible(item)304 }305}306307impl From<CreateFungibleData> for CreateItemData {308 fn from(item: CreateFungibleData) -> Self {309 CreateItemData::Fungible(item)310 }311}312313314decl_error! {315 /// Error for non-fungible-token module.316 pub enum Error for Module<T: Trait> {317 /// Total collections bound exceeded.318 TotalCollectionsLimitExceeded,319 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.320 CollectionDecimalPointLimitExceeded, 321 /// Collection name can not be longer than 63 char.322 CollectionNameLimitExceeded, 323 /// Collection description can not be longer than 255 char.324 CollectionDescriptionLimitExceeded, 325 /// Token prefix can not be longer than 15 char.326 CollectionTokenPrefixLimitExceeded,327 /// This collection does not exist.328 CollectionNotFound,329 /// Item not exists.330 TokenNotFound,331 /// Arithmetic calculation overflow.332 NumOverflow, 333 /// Account already has admin role.334 AlreadyAdmin, 335 /// You do not own this collection.336 NoPermission,337 /// This address is not set as sponsor, use setCollectionSponsor first.338 ConfirmUnsetSponsorFail,339 /// Collection is not in mint mode.340 PublicMintingNotAllowed,341 /// Sender parameter and item owner must be equal.342 MustBeTokenOwner,343 /// Item balance not enough.344 TokenValueTooLow,345 /// Size of item is too large.346 NftSizeLimitExceeded,347 /// No approve found348 ApproveNotFound,349 /// Requested value more than approved.350 TokenValueNotEnough,351 /// Only approved addresses can call this method.352 ApproveRequired,353 /// Address is not in white list.354 AddresNotInWhiteList,355 /// Number of collection admins bound exceeded.356 CollectionAdminsLimitExceeded,357 /// Owned tokens by a single address bound exceeded.358 AddressOwnershipLimitExceeded,359 /// Length of items properties must be greater than 0.360 EmptyArgument,361 /// const_data exceeded data limit.362 TokenConstDataLimitExceeded,363 /// variable_data exceeded data limit.364 TokenVariableDataLimitExceeded,365 /// Not NFT item data used to mint in NFT collection.366 NotNftDataUsedToMintNftCollectionToken,367 /// Not Fungible item data used to mint in Fungible collection.368 NotFungibleDataUsedToMintFungibleCollectionToken,369 /// Not Re Fungible item data used to mint in Re Fungible collection.370 NotReFungibleDataUsedToMintReFungibleCollectionToken,371 /// Unexpected collection type.372 UnexpectedCollectionType,373 /// Can't store metadata in fungible tokens.374 CantStoreMetadataInFungibleTokens,375 /// Collection token limit exceeded376 CollectionTokenLimitExceeded,377 /// Account token limit exceeded per collection378 AccountTokenLimitExceeded,379 /// Collection limit bounds per collection exceeded380 CollectionLimitBoundsExceeded381 }382}383384pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {385 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;386387 /// Weight information for extrinsics in this pallet.388 type WeightInfo: WeightInfo;389}390391#[cfg(feature = "runtime-benchmarks")]392mod benchmarking;393394// #endregion395396decl_storage! {397 trait Store for Module<T: Trait> as Nft {398399 // Private members400 NextCollectionID: CollectionId;401 CreatedCollectionCount: u32;402 ChainVersion: u64;403 ItemListIndex: map hasher(identity) CollectionId => TokenId;404405 // Chain limits struct406 pub ChainLimit get(fn chain_limit) config(): ChainLimits;407408 // Bound counters409 CollectionCount: u32;410 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;411412 // Basic collections413 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;414 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;415 pub WhiteList get(fn white_list): map hasher(identity) CollectionId => Vec<T::AccountId>;416417 /// Balance owner per collection map418 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;419420 /// second parameter: item id + owner account id421 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;422423 /// Item collections424 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;425 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;426 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;427428 /// Index list429 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;430431 /// Tokens transfer baskets432 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;434 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;435436 // Contract Sponsorship and Ownership437 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;438 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;439 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;440 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;441 }442 add_extra_genesis {443 build(|config: &GenesisConfig<T>| {444 // Modification of storage445 for (_num, _c) in &config.collection {446 <Module<T>>::init_collection(_c);447 }448449 for (_num, _q, _i) in &config.nft_item_id {450 <Module<T>>::init_nft_token(_i);451 }452453 for (_num, _q, _i) in &config.fungible_item_id {454 <Module<T>>::init_fungible_token(_i);455 }456457 for (_num, _q, _i) in &config.refungible_item_id {458 <Module<T>>::init_refungible_token(_i);459 }460 })461 }462}463464decl_event!(465 pub enum Event<T>466 where467 AccountId = <T as system::Trait>::AccountId,468 {469 /// New collection was created470 /// 471 /// # Arguments472 /// 473 /// * collection_id: Globally unique identifier of newly created collection.474 /// 475 /// * mode: [CollectionMode] converted into u8.476 /// 477 /// * account_id: Collection owner.478 Created(CollectionId, u8, AccountId),479480 /// New item was created.481 /// 482 /// # Arguments483 /// 484 /// * collection_id: Id of the collection where item was created.485 /// 486 /// * item_id: Id of an item. Unique within the collection.487 ItemCreated(CollectionId, TokenId),488489 /// Collection item was burned.490 /// 491 /// # Arguments492 /// 493 /// collection_id.494 /// 495 /// item_id: Identifier of burned NFT.496 ItemDestroyed(CollectionId, TokenId),497 }498);499500decl_module! {501 pub struct Module<T: Trait> for enum Call where origin: T::Origin {502503 fn deposit_event() = default;504 type Error = Error<T>;505506 fn on_initialize(now: T::BlockNumber) -> Weight {507508 if ChainVersion::get() < 2509 {510 let value = NextCollectionID::get();511 CreatedCollectionCount::put(value);512 ChainVersion::put(2);513 }514515 0516 }517518 /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.519 /// 520 /// # Permissions521 /// 522 /// * Anyone.523 /// 524 /// # Arguments525 /// 526 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.527 /// 528 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.529 /// 530 /// * token_prefix: UTF-8 string with token prefix.531 /// 532 /// * mode: [CollectionMode] collection type and type dependent data.533 // returns collection ID534 #[weight = T::WeightInfo::create_collection()]535 pub fn create_collection(origin,536 collection_name: Vec<u16>,537 collection_description: Vec<u16>,538 token_prefix: Vec<u8>,539 mode: CollectionMode) -> DispatchResult {540541 // Anyone can create a collection542 let who = ensure_signed(origin)?;543544 let decimal_points = match mode {545 CollectionMode::Fungible(points) => points,546 CollectionMode::ReFungible(points) => points,547 _ => 0548 };549550 // bound Total number of collections551 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);552553 // check params554 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);555556 let mut name = collection_name.to_vec();557 name.push(0);558 ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);559560 let mut description = collection_description.to_vec();561 description.push(0);562 ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);563564 let mut prefix = token_prefix.to_vec();565 prefix.push(0);566 ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);567568 // Generate next collection ID569 let next_id = CreatedCollectionCount::get()570 .checked_add(1)571 .ok_or(Error::<T>::NumOverflow)?;572573 // bound counter574 let total = CollectionCount::get()575 .checked_add(1)576 .ok_or(Error::<T>::NumOverflow)?;577578 CreatedCollectionCount::put(next_id);579 CollectionCount::put(total);580581 // Create new collection582 let new_collection = CollectionType {583 owner: who.clone(),584 name: name,585 mode: mode.clone(),586 mint_mode: false,587 access: AccessMode::Normal,588 description: description,589 decimal_points: decimal_points,590 token_prefix: prefix,591 offchain_schema: Vec::new(),592 schema_version: SchemaVersion::ImageURL,593 sponsor: T::AccountId::default(),594 sponsor_confirmed: false,595 variable_on_chain_schema: Vec::new(),596 const_on_chain_schema: Vec::new(),597 limits: CollectionLimits::default(),598 };599600 // Add new collection to map601 <Collection<T>>::insert(next_id, new_collection);602603 // call event604 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));605606 Ok(())607 }608609 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.610 /// 611 /// # Permissions612 /// 613 /// * Collection Owner.614 /// 615 /// # Arguments616 /// 617 /// * collection_id: collection to destroy.618 #[weight = T::WeightInfo::destroy_collection()]619 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {620621 let sender = ensure_signed(origin)?;622 Self::check_owner_permissions(collection_id, sender)?;623624 <AddressTokens<T>>::remove_prefix(collection_id);625 <ApprovedList<T>>::remove_prefix(collection_id);626 <Balance<T>>::remove_prefix(collection_id);627 <ItemListIndex>::remove(collection_id);628 <AdminList<T>>::remove(collection_id);629 <Collection<T>>::remove(collection_id);630 <WhiteList<T>>::remove(collection_id);631632 <NftItemList<T>>::remove_prefix(collection_id);633 <FungibleItemList<T>>::remove_prefix(collection_id);634 <ReFungibleItemList<T>>::remove_prefix(collection_id);635636 <NftTransferBasket<T>>::remove_prefix(collection_id);637 <FungibleTransferBasket<T>>::remove_prefix(collection_id);638 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);639640 if CollectionCount::get() > 0641 {642 // bound couter643 let total = CollectionCount::get()644 .checked_sub(1)645 .ok_or(Error::<T>::NumOverflow)?;646647 CollectionCount::put(total);648 }649650 Ok(())651 }652653 /// Add an address to white list.654 /// 655 /// # Permissions656 /// 657 /// * Collection Owner658 /// * Collection Admin659 /// 660 /// # Arguments661 /// 662 /// * collection_id.663 /// 664 /// * address.665 #[weight = T::WeightInfo::add_to_white_list()]666 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{667668 let sender = ensure_signed(origin)?;669 Self::check_owner_or_admin_permissions(collection_id, sender)?;670671 let mut white_list_collection: Vec<T::AccountId>;672 if <WhiteList<T>>::contains_key(collection_id) {673 white_list_collection = <WhiteList<T>>::get(collection_id);674 if !white_list_collection.contains(&address.clone())675 {676 white_list_collection.push(address.clone());677 }678 }679 else {680 white_list_collection = Vec::new();681 white_list_collection.push(address.clone());682 }683684 <WhiteList<T>>::insert(collection_id, white_list_collection);685 Ok(())686 }687688 /// Remove an address from white list.689 /// 690 /// # Permissions691 /// 692 /// * Collection Owner693 /// * Collection Admin694 /// 695 /// # Arguments696 /// 697 /// * collection_id.698 /// 699 /// * address.700 #[weight = T::WeightInfo::remove_from_white_list()]701 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{702703 let sender = ensure_signed(origin)?;704 Self::check_owner_or_admin_permissions(collection_id, sender)?;705706 if <WhiteList<T>>::contains_key(collection_id) {707 let mut white_list_collection = <WhiteList<T>>::get(collection_id);708 if white_list_collection.contains(&address.clone())709 {710 white_list_collection.retain(|i| *i != address.clone());711 <WhiteList<T>>::insert(collection_id, white_list_collection);712 }713 }714715 Ok(())716 }717718 /// Toggle between normal and white list access for the methods with access for `Anyone`.719 /// 720 /// # Permissions721 /// 722 /// * Collection Owner.723 /// 724 /// # Arguments725 /// 726 /// * collection_id.727 /// 728 /// * mode: [AccessMode]729 #[weight = T::WeightInfo::set_public_access_mode()]730 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult731 {732 let sender = ensure_signed(origin)?;733734 Self::check_owner_permissions(collection_id, sender)?;735 let mut target_collection = <Collection<T>>::get(collection_id);736 target_collection.access = mode;737 <Collection<T>>::insert(collection_id, target_collection);738739 Ok(())740 }741742 /// Allows Anyone to create tokens if:743 /// * White List is enabled, and744 /// * Address is added to white list, and745 /// * This method was called with True parameter746 /// 747 /// # Permissions748 /// * Collection Owner749 ///750 /// # Arguments751 /// 752 /// * collection_id.753 /// 754 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.755 #[weight = T::WeightInfo::set_mint_permission()]756 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult757 {758 let sender = ensure_signed(origin)?;759760 Self::check_owner_permissions(collection_id, sender)?;761 let mut target_collection = <Collection<T>>::get(collection_id);762 target_collection.mint_mode = mint_permission;763 <Collection<T>>::insert(collection_id, target_collection);764765 Ok(())766 }767768 /// Change the owner of the collection.769 /// 770 /// # Permissions771 /// 772 /// * Collection Owner.773 /// 774 /// # Arguments775 /// 776 /// * collection_id.777 /// 778 /// * new_owner.779 #[weight = T::WeightInfo::change_collection_owner()]780 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {781782 let sender = ensure_signed(origin)?;783 Self::check_owner_permissions(collection_id, sender)?;784 let mut target_collection = <Collection<T>>::get(collection_id);785 target_collection.owner = new_owner;786 <Collection<T>>::insert(collection_id, target_collection);787788 Ok(())789 }790791 /// Adds an admin of the Collection.792 /// 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. 793 /// 794 /// # Permissions795 /// 796 /// * Collection Owner.797 /// * Collection Admin.798 /// 799 /// # Arguments800 /// 801 /// * collection_id: ID of the Collection to add admin for.802 /// 803 /// * new_admin_id: Address of new admin to add.804 #[weight = T::WeightInfo::add_collection_admin()]805 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {806807 let sender = ensure_signed(origin)?;808 Self::check_owner_or_admin_permissions(collection_id, sender)?;809 let mut admin_arr: Vec<T::AccountId> = Vec::new();810811 if <AdminList<T>>::contains_key(collection_id)812 {813 admin_arr = <AdminList<T>>::get(collection_id);814 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);815 }816817 // Number of collection admins818 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);819820 admin_arr.push(new_admin_id);821 <AdminList<T>>::insert(collection_id, admin_arr);822823 Ok(())824 }825826 /// 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.827 ///828 /// # Permissions829 /// 830 /// * Collection Owner.831 /// * Collection Admin.832 /// 833 /// # Arguments834 /// 835 /// * collection_id: ID of the Collection to remove admin for.836 /// 837 /// * account_id: Address of admin to remove.838 #[weight = T::WeightInfo::remove_collection_admin()]839 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {840841 let sender = ensure_signed(origin)?;842 Self::check_owner_or_admin_permissions(collection_id, sender)?;843844 if <AdminList<T>>::contains_key(collection_id)845 {846 let mut admin_arr = <AdminList<T>>::get(collection_id);847 admin_arr.retain(|i| *i != account_id);848 <AdminList<T>>::insert(collection_id, admin_arr);849 }850851 Ok(())852 }853854 /// # Permissions855 /// 856 /// * Collection Owner857 /// 858 /// # Arguments859 /// 860 /// * collection_id.861 /// 862 /// * new_sponsor.863 #[weight = T::WeightInfo::set_collection_sponsor()]864 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {865866 let sender = ensure_signed(origin)?;867 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);868869 let mut target_collection = <Collection<T>>::get(collection_id);870 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);871872 target_collection.sponsor = new_sponsor;873 target_collection.sponsor_confirmed = false;874 <Collection<T>>::insert(collection_id, target_collection);875876 Ok(())877 }878879 /// # Permissions880 /// 881 /// * Sponsor.882 /// 883 /// # Arguments884 /// 885 /// * collection_id.886 #[weight = T::WeightInfo::confirm_sponsorship()]887 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {888889 let sender = ensure_signed(origin)?;890 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);891892 let mut target_collection = <Collection<T>>::get(collection_id);893 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);894895 target_collection.sponsor_confirmed = true;896 <Collection<T>>::insert(collection_id, target_collection);897898 Ok(())899 }900901 /// Switch back to pay-per-own-transaction model.902 ///903 /// # Permissions904 ///905 /// * Collection owner.906 /// 907 /// # Arguments908 /// 909 /// * collection_id.910 #[weight = T::WeightInfo::remove_collection_sponsor()]911 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {912913 let sender = ensure_signed(origin)?;914 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);915916 let mut target_collection = <Collection<T>>::get(collection_id);917 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);918919 target_collection.sponsor = T::AccountId::default();920 target_collection.sponsor_confirmed = false;921 <Collection<T>>::insert(collection_id, target_collection);922923 Ok(())924 }925926 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.927 /// 928 /// # Permissions929 /// 930 /// * Collection Owner.931 /// * Collection Admin.932 /// * Anyone if933 /// * White List is enabled, and934 /// * Address is added to white list, and935 /// * MintPermission is enabled (see SetMintPermission method)936 /// 937 /// # Arguments938 /// 939 /// * collection_id: ID of the collection.940 /// 941 /// * owner: Address, initial owner of the NFT.942 ///943 /// * data: Token data to store on chain.944 // #[weight =945 // (130_000_000 as Weight)946 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))947 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))948 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]949950 #[weight = T::WeightInfo::create_item(data.len())]951 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {952953 let sender = ensure_signed(origin)?;954955 Self::collection_exists(collection_id)?;956957 let target_collection = <Collection<T>>::get(collection_id);958959 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;960 Self::validate_create_item_args(&target_collection, &data)?;961 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;962963 Ok(())964 }965966 /// This method creates multiple instances of NFT Collection created with CreateCollection method.967 /// 968 /// # Permissions969 /// 970 /// * Collection Owner.971 /// * Collection Admin.972 /// * Anyone if973 /// * White List is enabled, and974 /// * Address is added to white list, and975 /// * MintPermission is enabled (see SetMintPermission method)976 /// 977 /// # Arguments978 /// 979 /// * collection_id: ID of the collection.980 /// 981 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].982 /// 983 /// * owner: Address, initial owner of the NFT.984 #[weight = T::WeightInfo::create_item(items_data.into_iter()985 .map(|data| { data.len() })986 .sum())]987 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {988989 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);990 let sender = ensure_signed(origin)?;991992 Self::collection_exists(collection_id)?;993 let target_collection = <Collection<T>>::get(collection_id);994995 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;996997 for data in &items_data {998 Self::validate_create_item_args(&target_collection, data)?;999 }1000 for data in &items_data {1001 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;1002 }10031004 Ok(())1005 }10061007 /// Destroys a concrete instance of NFT.1008 /// 1009 /// # Permissions1010 /// 1011 /// * Collection Owner.1012 /// * Collection Admin.1013 /// * Current NFT Owner.1014 /// 1015 /// # Arguments1016 /// 1017 /// * collection_id: ID of the collection.1018 /// 1019 /// * item_id: ID of NFT to burn.1020 #[weight = T::WeightInfo::burn_item()]1021 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10221023 let sender = ensure_signed(origin)?;1024 Self::collection_exists(collection_id)?;10251026 // Transfer permissions check1027 let target_collection = <Collection<T>>::get(collection_id);1028 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1029 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1030 Error::<T>::NoPermission);10311032 if target_collection.access == AccessMode::WhiteList {1033 Self::check_white_list(collection_id, &sender)?;1034 }10351036 match target_collection.mode1037 {1038 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1039 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1040 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1041 _ => ()1042 };10431044 // call event1045 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10461047 Ok(())1048 }10491050 /// Change ownership of the token.1051 /// 1052 /// # Permissions1053 /// 1054 /// * Collection Owner1055 /// * Collection Admin1056 /// * Current NFT owner1057 ///1058 /// # Arguments1059 /// 1060 /// * recipient: Address of token recipient.1061 /// 1062 /// * collection_id.1063 /// 1064 /// * item_id: ID of the item1065 /// * Non-Fungible Mode: Required.1066 /// * Fungible Mode: Ignored.1067 /// * Re-Fungible Mode: Required.1068 /// 1069 /// * value: Amount to transfer.1070 /// * Non-Fungible Mode: Ignored1071 /// * Fungible Mode: Must specify transferred amount1072 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1073 #[weight = T::WeightInfo::transfer()]1074 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10751076 let sender = ensure_signed(origin)?;1077 let target_collection = <Collection<T>>::get(collection_id);10781079 // Limits check1080 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10811082 // Transfer permissions check1083 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1084 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1085 Error::<T>::NoPermission);10861087 if target_collection.access == AccessMode::WhiteList {1088 Self::check_white_list(collection_id, &sender)?;1089 Self::check_white_list(collection_id, &recipient)?;1090 }10911092 match target_collection.mode1093 {1094 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1095 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1096 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1097 _ => ()1098 };10991100 Ok(())1101 }11021103 /// Set, change, or remove approved address to transfer the ownership of the NFT.1104 /// 1105 /// # Permissions1106 /// 1107 /// * Collection Owner1108 /// * Collection Admin1109 /// * Current NFT owner1110 /// 1111 /// # Arguments1112 /// 1113 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1114 /// 1115 /// * collection_id.1116 /// 1117 /// * item_id: ID of the item.1118 #[weight = T::WeightInfo::approve()]1119 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {11201121 let sender = ensure_signed(origin)?;11221123 // Transfer permissions check1124 let target_collection = <Collection<T>>::get(collection_id);1125 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1126 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1127 Error::<T>::NoPermission);11281129 if target_collection.access == AccessMode::WhiteList {1130 Self::check_white_list(collection_id, &sender)?;1131 Self::check_white_list(collection_id, &approved)?;1132 }11331134 // amount param stub1135 let amount = 100000000;11361137 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1138 if list_exists {11391140 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1141 let item_contains = list.iter().any(|i| i.approved == approved);11421143 if !item_contains {1144 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1145 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1146 }1147 } else {11481149 let mut list = Vec::new();1150 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1151 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1152 }11531154 Ok(())1155 }1156 1157 /// 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.1158 /// 1159 /// # Permissions1160 /// * Collection Owner1161 /// * Collection Admin1162 /// * Current NFT owner1163 /// * Address approved by current NFT owner1164 /// 1165 /// # Arguments1166 /// 1167 /// * from: Address that owns token.1168 /// 1169 /// * recipient: Address of token recipient.1170 /// 1171 /// * collection_id.1172 /// 1173 /// * item_id: ID of the item.1174 /// 1175 /// * value: Amount to transfer.1176 #[weight = T::WeightInfo::transfer_from()]1177 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11781179 let sender = ensure_signed(origin)?;1180 let mut appoved_transfer = false;11811182 // Check approve1183 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1184 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1185 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1186 if opt_item.is_some()1187 {1188 appoved_transfer = true;1189 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1190 }1191 }11921193 let target_collection = <Collection<T>>::get(collection_id);11941195 // Limits check1196 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11971198 // Transfer permissions check 1199 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1200 Error::<T>::NoPermission);12011202 if target_collection.access == AccessMode::WhiteList {1203 Self::check_white_list(collection_id, &sender)?;1204 Self::check_white_list(collection_id, &recipient)?;1205 }12061207 // remove approve1208 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1209 .into_iter().filter(|i| i.approved != sender.clone()).collect();1210 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);121112121213 match target_collection.mode1214 {1215 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1216 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1217 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1218 _ => ()1219 };12201221 Ok(())1222 }12231224 #[weight = 0]1225 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12261227 // let no_perm_mes = "You do not have permissions to modify this collection";1228 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1229 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1230 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12311232 // // on_nft_received call12331234 // Self::transfer(origin, collection_id, item_id, new_owner)?;12351236 Ok(())1237 }12381239 /// Set off-chain data schema.1240 /// 1241 /// # Permissions1242 /// 1243 /// * Collection Owner1244 /// * Collection Admin1245 /// 1246 /// # Arguments1247 /// 1248 /// * collection_id.1249 /// 1250 /// * schema: String representing the offchain data schema.1251 #[weight = T::WeightInfo::set_variable_meta_data()]1252 pub fn set_variable_meta_data (1253 origin,1254 collection_id: CollectionId,1255 item_id: TokenId,1256 data: Vec<u8>1257 ) -> DispatchResult {1258 let sender = ensure_signed(origin)?;1259 1260 Self::collection_exists(collection_id)?;1261 1262 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12631264 // Modify permissions check1265 let target_collection = <Collection<T>>::get(collection_id);1266 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1267 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1268 Error::<T>::NoPermission);12691270 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12711272 match target_collection.mode1273 {1274 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1275 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1276 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1277 _ => fail!(Error::<T>::UnexpectedCollectionType)1278 };12791280 Ok(())1281 }1282 1283 /// Set schema standard1284 /// ImageURL1285 /// Unique1286 /// 1287 /// # Permissions1288 /// 1289 /// * Collection Owner1290 /// * Collection Admin1291 /// 1292 /// # Arguments1293 /// 1294 /// * collection_id.1295 /// 1296 /// * schema: SchemaVersion: enum1297 #[weight = 0]1298 pub fn set_schema_version(1299 origin,1300 collection_id: CollectionId,1301 version: SchemaVersion1302 ) -> DispatchResult {1303 let sender = ensure_signed(origin)?;1304 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1305 let mut target_collection = <Collection<T>>::get(collection_id);1306 target_collection.schema_version = version;1307 <Collection<T>>::insert(collection_id, target_collection);13081309 Ok(())1310 }13111312 /// Set off-chain data schema.1313 /// 1314 /// # Permissions1315 /// 1316 /// * Collection Owner1317 /// * Collection Admin1318 /// 1319 /// # Arguments1320 /// 1321 /// * collection_id.1322 /// 1323 /// * schema: String representing the offchain data schema.1324 #[weight = T::WeightInfo::set_offchain_schema()]1325 pub fn set_offchain_schema(1326 origin,1327 collection_id: CollectionId,1328 schema: Vec<u8>1329 ) -> DispatchResult {1330 let sender = ensure_signed(origin)?;1331 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13321333 let mut target_collection = <Collection<T>>::get(collection_id);1334 target_collection.offchain_schema = schema;1335 <Collection<T>>::insert(collection_id, target_collection);13361337 Ok(())1338 }13391340 /// Set const on-chain data schema.1341 /// 1342 /// # Permissions1343 /// 1344 /// * Collection Owner1345 /// * Collection Admin1346 /// 1347 /// # Arguments1348 /// 1349 /// * collection_id.1350 /// 1351 /// * schema: String representing the const on-chain data schema.1352 #[weight = T::WeightInfo::set_const_on_chain_schema()]1353 pub fn set_const_on_chain_schema (1354 origin,1355 collection_id: CollectionId,1356 schema: Vec<u8>1357 ) -> DispatchResult {1358 let sender = ensure_signed(origin)?;1359 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13601361 let mut target_collection = <Collection<T>>::get(collection_id);1362 target_collection.const_on_chain_schema = schema;1363 <Collection<T>>::insert(collection_id, target_collection);13641365 Ok(())1366 }13671368 /// Set variable on-chain data schema.1369 /// 1370 /// # Permissions1371 /// 1372 /// * Collection Owner1373 /// * Collection Admin1374 /// 1375 /// # Arguments1376 /// 1377 /// * collection_id.1378 /// 1379 /// * schema: String representing the variable on-chain data schema.1380 #[weight = T::WeightInfo::set_const_on_chain_schema()]1381 pub fn set_variable_on_chain_schema (1382 origin,1383 collection_id: CollectionId,1384 schema: Vec<u8>1385 ) -> DispatchResult {1386 let sender = ensure_signed(origin)?;1387 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13881389 let mut target_collection = <Collection<T>>::get(collection_id);1390 target_collection.variable_on_chain_schema = schema;1391 <Collection<T>>::insert(collection_id, target_collection);13921393 Ok(())1394 }13951396 // Sudo permissions function1397 #[weight = 0]1398 pub fn set_chain_limits(1399 origin,1400 limits: ChainLimits1401 ) -> DispatchResult {1402 ensure_root(origin)?;1403 <ChainLimit>::put(limits);1404 Ok(())1405 }14061407 /// Enable smart contract self-sponsoring.1408 /// 1409 /// # Permissions1410 /// 1411 /// * Contract Owner1412 /// 1413 /// # Arguments1414 /// 1415 /// * contract address1416 /// * enable flag1417 /// 1418 #[weight = T::WeightInfo::enable_contract_sponsoring()]1419 pub fn enable_contract_sponsoring(1420 origin,1421 contract_address: T::AccountId,1422 enable: bool1423 ) -> DispatchResult {14241425 let sender = ensure_signed(origin)?;14261427 #[cfg(feature = "runtime-benchmarks")]1428 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14291430 let mut is_owner = false;1431 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1432 let owner = <ContractOwner<T>>::get(&contract_address);1433 is_owner = sender == owner;1434 }1435 ensure!(is_owner, Error::<T>::NoPermission);14361437 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1438 Ok(())1439 }14401441 /// Set the rate limit for contract sponsoring to specified number of blocks.1442 /// 1443 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1444 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1445 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1446 /// from contract endowment if there are at least B blocks between such transactions. 1447 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1448 /// 1449 /// # Permissions1450 /// 1451 /// * Contract Owner1452 /// 1453 /// # Arguments1454 /// 1455 /// -`contract_address`: Address of the contract to sponsor1456 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1457 /// 1458 #[weight = 0]1459 pub fn set_contract_sponsoring_rate_limit(1460 origin,1461 contract_address: T::AccountId,1462 rate_limit: T::BlockNumber1463 ) -> DispatchResult {1464 let sender = ensure_signed(origin)?;1465 let mut is_owner = false;1466 if <ContractOwner<T>>::contains_key(contract_address.clone()) {1467 let owner = <ContractOwner<T>>::get(&contract_address);1468 is_owner = sender == owner;1469 }1470 ensure!(is_owner, Error::<T>::NoPermission);14711472 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1473 Ok(())1474 }14751476 #[weight = 0]1477 pub fn set_collection_limits(1478 origin,1479 collection_id: u32,1480 limits: CollectionLimits,1481 ) -> DispatchResult {1482 let sender = ensure_signed(origin)?;1483 Self::check_owner_permissions(collection_id, sender.clone())?;1484 let mut target_collection = <Collection<T>>::get(collection_id);1485 let chain_limits = ChainLimit::get();1486 let climits = target_collection.limits;14871488 // collection bounds1489 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1490 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1491 Error::<T>::CollectionLimitBoundsExceeded);14921493 // token_limit check prev1494 ensure!(climits.token_limit > limits.token_limit && 1495 limits.token_limit <= chain_limits.account_token_ownership_limit, 1496 Error::<T>::AccountTokenLimitExceeded);14971498 target_collection.limits = limits;1499 <Collection<T>>::insert(collection_id, target_collection);15001501 Ok(())1502 } 1503 }1504}15051506impl<T: Trait> Module<T> {15071508 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15091510 // check token limit and account token limit1511 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1512 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1513 1514 Ok(())1515 }15161517 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15181519 // check token limit and account token limit1520 let total_items: u32 = ItemListIndex::get(collection_id);1521 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1522 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1523 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15241525 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1526 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1527 Self::check_white_list(collection_id, owner)?;1528 Self::check_white_list(collection_id, sender)?;1529 }15301531 Ok(())1532 }15331534 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1535 match target_collection.mode1536 {1537 CollectionMode::NFT => {1538 if let CreateItemData::NFT(data) = data {1539 // check sizes1540 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1541 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1542 } else {1543 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1544 }1545 },1546 CollectionMode::Fungible(_) => {1547 if let CreateItemData::Fungible(_) = data {1548 } else {1549 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1550 }1551 },1552 CollectionMode::ReFungible(_) => {1553 if let CreateItemData::ReFungible(data) = data {15541555 // check sizes1556 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1557 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1558 } else {1559 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1560 }1561 },1562 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1563 };15641565 Ok(())1566 }15671568 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1569 match data1570 {1571 CreateItemData::NFT(data) => {1572 let item = NftItemType {1573 collection: collection_id,1574 owner,1575 const_data: data.const_data,1576 variable_data: data.variable_data1577 };15781579 Self::add_nft_item(item)?;1580 },1581 CreateItemData::Fungible(_) => {1582 let item = FungibleItemType {1583 collection: collection_id,1584 owner,1585 value: (10 as u128).pow(collection.decimal_points as u32)1586 };15871588 Self::add_fungible_item(item)?;1589 },1590 CreateItemData::ReFungible(data) => {1591 let mut owner_list = Vec::new();1592 let value = (10 as u128).pow(collection.decimal_points as u32);1593 owner_list.push(Ownership {owner: owner.clone(), fraction: value});15941595 let item = ReFungibleItemType {1596 collection: collection_id,1597 owner: owner_list,1598 const_data: data.const_data,1599 variable_data: data.variable_data1600 };16011602 Self::add_refungible_item(item)?;1603 }1604 };16051606 // call event1607 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16081609 Ok(())1610 }16111612 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1613 let current_index = <ItemListIndex>::get(item.collection)1614 .checked_add(1)1615 .ok_or(Error::<T>::NumOverflow)?;1616 let itemcopy = item.clone();1617 let owner = item.owner.clone();16181619 Self::add_token_index(item.collection, current_index, owner.clone())?;16201621 <ItemListIndex>::insert(item.collection, current_index);1622 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16231624 // Add current block1625 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1626 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1627 1628 // Update balance1629 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1630 .checked_add(item.value)1631 .ok_or(Error::<T>::NumOverflow)?;1632 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16331634 Ok(())1635 }16361637 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1638 let current_index = <ItemListIndex>::get(item.collection)1639 .checked_add(1)1640 .ok_or(Error::<T>::NumOverflow)?;1641 let itemcopy = item.clone();16421643 let value = item.owner.first().unwrap().fraction;1644 let owner = item.owner.first().unwrap().owner.clone();16451646 Self::add_token_index(item.collection, current_index, owner.clone())?;16471648 <ItemListIndex>::insert(item.collection, current_index);1649 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16501651 // Add current block1652 let block_number: T::BlockNumber = 0.into();1653 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16541655 // Update balance1656 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1657 .checked_add(value)1658 .ok_or(Error::<T>::NumOverflow)?;1659 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16601661 Ok(())1662 }16631664 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1665 let current_index = <ItemListIndex>::get(item.collection)1666 .checked_add(1)1667 .ok_or(Error::<T>::NumOverflow)?;16681669 let item_owner = item.owner.clone();1670 let collection_id = item.collection.clone();1671 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16721673 <ItemListIndex>::insert(collection_id, current_index);1674 <NftItemList<T>>::insert(collection_id, current_index, item);16751676 // Add current block1677 let block_number: T::BlockNumber = 0.into();1678 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);16791680 // Update balance1681 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1682 .checked_add(1)1683 .ok_or(Error::<T>::NumOverflow)?;1684 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16851686 Ok(())1687 }16881689 fn burn_refungible_item(1690 collection_id: CollectionId,1691 item_id: TokenId,1692 owner: T::AccountId,1693 ) -> DispatchResult {1694 ensure!(1695 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1696 Error::<T>::TokenNotFound1697 );1698 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1699 let item = collection1700 .owner1701 .iter()1702 .filter(|&i| i.owner == owner)1703 .next()1704 .unwrap();1705 Self::remove_token_index(collection_id, item_id, owner.clone())?;17061707 // remove approve list1708 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17091710 // update balance1711 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1712 .checked_sub(item.fraction)1713 .ok_or(Error::<T>::NumOverflow)?;1714 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17151716 <ReFungibleItemList<T>>::remove(collection_id, item_id);17171718 Ok(())1719 }17201721 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1722 ensure!(1723 <NftItemList<T>>::contains_key(collection_id, item_id),1724 Error::<T>::TokenNotFound1725 );1726 let item = <NftItemList<T>>::get(collection_id, item_id);1727 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17281729 // remove approve list1730 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17311732 // update balance1733 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1734 .checked_sub(1)1735 .ok_or(Error::<T>::NumOverflow)?;1736 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1737 <NftItemList<T>>::remove(collection_id, item_id);17381739 Ok(())1740 }17411742 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1743 ensure!(1744 <FungibleItemList<T>>::contains_key(collection_id, item_id),1745 Error::<T>::TokenNotFound1746 );1747 let item = <FungibleItemList<T>>::get(collection_id, item_id);1748 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17491750 // remove approve list1751 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17521753 // update balance1754 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1755 .checked_sub(item.value)1756 .ok_or(Error::<T>::NumOverflow)?;1757 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17581759 <FungibleItemList<T>>::remove(collection_id, item_id);17601761 Ok(())1762 }17631764 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1765 ensure!(1766 <Collection<T>>::contains_key(collection_id),1767 Error::<T>::CollectionNotFound1768 );1769 Ok(())1770 }17711772 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1773 Self::collection_exists(collection_id)?;17741775 let target_collection = <Collection<T>>::get(collection_id);1776 ensure!(1777 subject == target_collection.owner,1778 Error::<T>::NoPermission1779 );17801781 Ok(())1782 }17831784 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1785 let target_collection = <Collection<T>>::get(collection_id);1786 let mut result: bool = subject == target_collection.owner;1787 let exists = <AdminList<T>>::contains_key(collection_id);17881789 if !result & exists {1790 if <AdminList<T>>::get(collection_id).contains(&subject) {1791 result = true1792 }1793 }17941795 result1796 }17971798 fn check_owner_or_admin_permissions(1799 collection_id: CollectionId,1800 subject: T::AccountId,1801 ) -> DispatchResult {1802 Self::collection_exists(collection_id)?;1803 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18041805 ensure!(1806 result,1807 Error::<T>::NoPermission1808 );1809 Ok(())1810 }18111812 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1813 let target_collection = <Collection<T>>::get(collection_id);18141815 match target_collection.mode {1816 CollectionMode::NFT => {1817 <NftItemList<T>>::get(collection_id, item_id).owner == subject1818 }1819 CollectionMode::Fungible(_) => {1820 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1821 }1822 CollectionMode::ReFungible(_) => {1823 <ReFungibleItemList<T>>::get(collection_id, item_id)1824 .owner1825 .iter()1826 .any(|i| i.owner == subject)1827 }1828 CollectionMode::Invalid => false,1829 }1830 }18311832 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1833 let mes = Error::<T>::AddresNotInWhiteList;1834 ensure!(<WhiteList<T>>::contains_key(collection_id), mes);1835 let wl = <WhiteList<T>>::get(collection_id);1836 ensure!(wl.contains(address), mes);18371838 Ok(())1839 }18401841 fn transfer_fungible(1842 collection_id: CollectionId,1843 item_id: TokenId,1844 value: u128,1845 owner: T::AccountId,1846 new_owner: T::AccountId,1847 ) -> DispatchResult {1848 ensure!(1849 <FungibleItemList<T>>::contains_key(collection_id, item_id),1850 Error::<T>::TokenNotFound1851 );18521853 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1854 let amount = full_item.value;18551856 ensure!(amount >= value, Error::<T>::TokenValueTooLow);18571858 // update balance1859 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1860 .checked_sub(value)1861 .ok_or(Error::<T>::NumOverflow)?;1862 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);18631864 let mut new_owner_account_id = 0;1865 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1866 if new_owner_items.len() > 0 {1867 new_owner_account_id = new_owner_items[0];1868 }18691870 // transfer1871 if amount == value && new_owner_account_id == 0 {1872 // change owner1873 // new owner do not have account1874 let mut new_full_item = full_item.clone();1875 new_full_item.owner = new_owner.clone();1876 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);18771878 // update balance1879 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1880 .checked_add(value)1881 .ok_or(Error::<T>::NumOverflow)?;1882 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);18831884 // update index collection1885 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1886 } else {1887 let mut new_full_item = full_item.clone();1888 new_full_item.value -= value;18891890 // separate amount1891 if new_owner_account_id > 0 {1892 // new owner has account1893 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1894 item.value += value;18951896 // update balance1897 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1898 .checked_add(value)1899 .ok_or(Error::<T>::NumOverflow)?;1900 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19011902 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1903 } else {1904 // new owner do not have account1905 let item = FungibleItemType {1906 collection: collection_id,1907 owner: new_owner.clone(),1908 value1909 };19101911 Self::add_fungible_item(item)?;1912 }19131914 if amount == value {1915 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19161917 // remove approve list1918 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1919 <FungibleItemList<T>>::remove(collection_id, item_id);1920 }19211922 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1923 }19241925 Ok(())1926 }19271928 fn transfer_refungible(1929 collection_id: CollectionId,1930 item_id: TokenId,1931 value: u128,1932 owner: T::AccountId,1933 new_owner: T::AccountId,1934 ) -> DispatchResult {1935 ensure!(1936 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1937 Error::<T>::TokenNotFound1938 );19391940 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1941 let item = full_item1942 .owner1943 .iter()1944 .filter(|i| i.owner == owner)1945 .next()1946 .ok_or(Error::<T>::NumOverflow)?;1947 let amount = item.fraction;19481949 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19501951 // update balance1952 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1953 .checked_sub(value)1954 .ok_or(Error::<T>::NumOverflow)?;1955 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19561957 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1958 .checked_add(value)1959 .ok_or(Error::<T>::NumOverflow)?;1960 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19611962 let old_owner = item.owner.clone();1963 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19641965 // transfer1966 if amount == value && !new_owner_has_account {1967 // change owner1968 // new owner do not have account1969 let mut new_full_item = full_item.clone();1970 new_full_item1971 .owner1972 .iter_mut()1973 .find(|i| i.owner == owner)1974 .unwrap()1975 .owner = new_owner.clone();1976 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19771978 // update index collection1979 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1980 } else {1981 let mut new_full_item = full_item.clone();1982 new_full_item1983 .owner1984 .iter_mut()1985 .find(|i| i.owner == owner)1986 .unwrap()1987 .fraction -= value;19881989 // separate amount1990 if new_owner_has_account {1991 // new owner has account1992 new_full_item1993 .owner1994 .iter_mut()1995 .find(|i| i.owner == new_owner)1996 .unwrap()1997 .fraction += value;1998 } else {1999 // new owner do not have account2000 new_full_item.owner.push(Ownership {2001 owner: new_owner.clone(),2002 fraction: value,2003 });2004 Self::add_token_index(collection_id, item_id, new_owner.clone())?;2005 }20062007 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2008 }20092010 Ok(())2011 }20122013 fn transfer_nft(2014 collection_id: CollectionId,2015 item_id: TokenId,2016 sender: T::AccountId,2017 new_owner: T::AccountId,2018 ) -> DispatchResult {2019 ensure!(2020 <NftItemList<T>>::contains_key(collection_id, item_id),2021 Error::<T>::TokenNotFound2022 );20232024 let mut item = <NftItemList<T>>::get(collection_id, item_id);20252026 ensure!(2027 sender == item.owner,2028 Error::<T>::MustBeTokenOwner2029 );20302031 // update balance2032 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2033 .checked_sub(1)2034 .ok_or(Error::<T>::NumOverflow)?;2035 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20362037 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2038 .checked_add(1)2039 .ok_or(Error::<T>::NumOverflow)?;2040 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20412042 // change owner2043 let old_owner = item.owner.clone();2044 item.owner = new_owner.clone();2045 <NftItemList<T>>::insert(collection_id, item_id, item);20462047 // update index collection2048 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20492050 // reset approved list2051 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2052 Ok(())2053 }2054 2055 fn item_exists(2056 collection_id: CollectionId,2057 item_id: TokenId,2058 mode: &CollectionMode2059 ) -> DispatchResult {2060 match mode {2061 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2062 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2063 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2064 _ => ()2065 };2066 2067 Ok(())2068 }20692070 fn set_re_fungible_variable_data(2071 collection_id: CollectionId,2072 item_id: TokenId,2073 data: Vec<u8>2074 ) -> DispatchResult {2075 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20762077 item.variable_data = data;20782079 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20802081 Ok(())2082 }20832084 fn set_nft_variable_data(2085 collection_id: CollectionId,2086 item_id: TokenId,2087 data: Vec<u8>2088 ) -> DispatchResult {2089 let mut item = <NftItemList<T>>::get(collection_id, item_id);2090 2091 item.variable_data = data;20922093 <NftItemList<T>>::insert(collection_id, item_id, item);2094 2095 Ok(())2096 }20972098 fn init_collection(item: &CollectionType<T::AccountId>) {2099 // check params2100 assert!(2101 item.decimal_points <= MAX_DECIMAL_POINTS,2102 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2103 );2104 assert!(2105 item.name.len() <= 64,2106 "Collection name can not be longer than 63 char"2107 );2108 assert!(2109 item.name.len() <= 256,2110 "Collection description can not be longer than 255 char"2111 );2112 assert!(2113 item.token_prefix.len() <= 16,2114 "Token prefix can not be longer than 15 char"2115 );21162117 // Generate next collection ID2118 let next_id = CreatedCollectionCount::get()2119 .checked_add(1)2120 .unwrap();21212122 CreatedCollectionCount::put(next_id);2123 }21242125 fn init_nft_token(item: &NftItemType<T::AccountId>) {2126 let current_index = <ItemListIndex>::get(item.collection)2127 .checked_add(1)2128 .unwrap();21292130 let item_owner = item.owner.clone();2131 let collection_id = item.collection.clone();2132 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();21332134 <ItemListIndex>::insert(collection_id, current_index);21352136 // Update balance2137 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2138 .checked_add(1)2139 .unwrap();2140 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2141 }21422143 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2144 let current_index = <ItemListIndex>::get(item.collection)2145 .checked_add(1)2146 .unwrap();2147 let owner = item.owner.clone();21482149 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21502151 <ItemListIndex>::insert(item.collection, current_index);21522153 // Update balance2154 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2155 .checked_add(item.value)2156 .unwrap();2157 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2158 }21592160 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2161 let current_index = <ItemListIndex>::get(item.collection)2162 .checked_add(1)2163 .unwrap();21642165 let value = item.owner.first().unwrap().fraction;2166 let owner = item.owner.first().unwrap().owner.clone();21672168 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21692170 <ItemListIndex>::insert(item.collection, current_index);21712172 // Update balance2173 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2174 .checked_add(value)2175 .unwrap();2176 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2177 }21782179 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21802181 // add to account limit2182 if <AccountItemCount<T>>::contains_key(owner.clone()) {21832184 // bound Owned tokens by a single address2185 let count = <AccountItemCount<T>>::get(owner.clone());2186 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21872188 <AccountItemCount<T>>::insert(owner.clone(), count2189 .checked_add(1)2190 .ok_or(Error::<T>::NumOverflow)?);2191 }2192 else {2193 <AccountItemCount<T>>::insert(owner.clone(), 1);2194 }21952196 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2197 if list_exists {2198 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2199 let item_contains = list.contains(&item_index.clone());22002201 if !item_contains {2202 list.push(item_index.clone());2203 }22042205 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2206 } else {2207 let mut itm = Vec::new();2208 itm.push(item_index.clone());2209 <AddressTokens<T>>::insert(collection_id, owner, itm);2210 2211 }22122213 Ok(())2214 }22152216 fn remove_token_index(2217 collection_id: CollectionId,2218 item_index: TokenId,2219 owner: T::AccountId,2220 ) -> DispatchResult {22212222 // update counter2223 <AccountItemCount<T>>::insert(owner.clone(), 2224 <AccountItemCount<T>>::get(owner.clone())2225 .checked_sub(1)2226 .ok_or(Error::<T>::NumOverflow)?);222722282229 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2230 if list_exists {2231 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2232 let item_contains = list.contains(&item_index.clone());22332234 if item_contains {2235 list.retain(|&item| item != item_index);2236 <AddressTokens<T>>::insert(collection_id, owner, list);2237 }2238 }22392240 Ok(())2241 }22422243 fn move_token_index(2244 collection_id: CollectionId,2245 item_index: TokenId,2246 old_owner: T::AccountId,2247 new_owner: T::AccountId,2248 ) -> DispatchResult {2249 Self::remove_token_index(collection_id, item_index, old_owner)?;2250 Self::add_token_index(collection_id, item_index, new_owner)?;22512252 Ok(())2253 }2254}22552256////////////////////////////////////////////////////////////////////////////////////////////////////2257// Economic models2258// #region22592260/// Fee multiplier.2261pub type Multiplier = FixedU128;22622263type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2264 <T as system::Trait>::AccountId,2265>>::Balance;2266type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2267 <T as system::Trait>::AccountId,2268>>::NegativeImbalance;22692270/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2271/// in the queue.2272#[derive(Encode, Decode, Clone, Eq, PartialEq)]2273pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2274 #[codec(compact)] BalanceOf<T>2275);22762277impl<T: Trait + Send + Sync> sp_std::fmt::Debug2278 for ChargeTransactionPayment<T>2279{2280 #[cfg(feature = "std")]2281 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2282 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2283 }2284 #[cfg(not(feature = "std"))]2285 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2286 Ok(())2287 }2288}22892290impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2291where2292 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2293 BalanceOf<T>: Send + Sync + FixedPointOperand,2294{2295 /// utility constructor. Used only in client/factory code.2296 pub fn from(fee: BalanceOf<T>) -> Self {2297 Self(fee)2298 }22992300 pub fn traditional_fee(2301 len: usize,2302 info: &DispatchInfoOf<T::Call>,2303 tip: BalanceOf<T>,2304 ) -> BalanceOf<T>2305 where2306 T::Call: Dispatchable<Info = DispatchInfo>,2307 {2308 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2309 }23102311 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2312 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2313 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2314 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2315 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2316 }23172318 fn withdraw_fee(2319 &self,2320 who: &T::AccountId,2321 call: &T::Call,2322 info: &DispatchInfoOf<T::Call>,2323 len: usize,2324 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2325 let tip = self.0;23262327 // Set fee based on call type. Creating collection costs 1 Unique.2328 // All other transactions have traditional fees so far2329 // let fee = match call.is_sub_type() {2330 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2331 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2332 // // _ => <BalanceOf<T>>::from(100)2333 // };2334 let fee = Self::traditional_fee(len, info, tip);23352336 // Determine who is paying transaction fee based on ecnomic model2337 // Parse call to extract collection ID and access collection sponsor2338 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2339 Some(Call::create_item(collection_id, _owner, _properties)) => {23402341 // check free create limit2342 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2343 (<Collection<T>>::get(collection_id).sponsor_confirmed)2344 {2345 <Collection<T>>::get(collection_id).sponsor2346 } else {2347 T::AccountId::default()2348 }2349 }2350 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2351 2352 let mut sponsor_transfer = false;2353 if <Collection<T>>::get(collection_id).sponsor_confirmed {2354 let _collection_limits = <Collection<T>>::get(collection_id).limits;2355 let _collection_mode = <Collection<T>>::get(collection_id).mode;2356 2357 // sponsor timeout2358 sponsor_transfer = match _collection_mode {2359 CollectionMode::NFT => {2360 2361 // get correct limit2362 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2363 _collection_limits.sponsor_transfer_timeout2364 } else {2365 ChainLimit::get().nft_sponsor_transfer_timeout2366 };2367 2368 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2369 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2370 let limit_time = basket + limit.into();2371 if block_number >= limit_time {2372 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2373 true2374 }2375 else {2376 false2377 }2378 }2379 CollectionMode::Fungible(_) => {2380 2381 // get correct limit2382 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2383 _collection_limits.sponsor_transfer_timeout2384 } else {2385 ChainLimit::get().fungible_sponsor_transfer_timeout2386 };2387 2388 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2389 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2390 if basket.iter().any(|i| i.address == _new_owner.clone())2391 {2392 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2393 let limit_time = item.start_block + limit.into();2394 if block_number >= limit_time {2395 basket.retain(|x| x.address == item.address);2396 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2397 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2398 true2399 }2400 else {2401 false2402 }2403 }2404 else {2405 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2406 true2407 }2408 }2409 CollectionMode::ReFungible(_) => {2410 2411 // get correct limit2412 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2413 _collection_limits.sponsor_transfer_timeout2414 } else {2415 ChainLimit::get().refungible_sponsor_transfer_timeout2416 };2417 2418 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2419 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2420 let limit_time = basket + limit.into();2421 if block_number >= limit_time {2422 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2423 true2424 } else {2425 false2426 }2427 }2428 _ => {2429 false2430 },2431 };2432 }24332434 if !sponsor_transfer {2435 T::AccountId::default()2436 } else {2437 <Collection<T>>::get(collection_id).sponsor2438 }2439 }24402441 _ => T::AccountId::default(),2442 };24432444 // Sponsor smart contracts2445 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24462447 // On instantiation: set the contract owner2448 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24492450 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2451 code_hash,2452 &data,2453 &who,2454 );2455 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24562457 T::AccountId::default()2458 },24592460 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2461 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24622463 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24642465 let mut sponsor_transfer = false;2466 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2467 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2468 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2469 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2470 let limit_time = last_tx_block + rate_limit;24712472 if block_number >= limit_time {2473 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2474 sponsor_transfer = true;2475 }2476 } else {2477 sponsor_transfer = false;2478 }2479 2480 2481 let mut sp = T::AccountId::default();2482 if sponsor_transfer {2483 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2484 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2485 sp = called_contract;2486 }2487 }2488 }24892490 sp2491 },24922493 _ => sponsor,2494 };24952496 let mut who_pays_fee: T::AccountId = sponsor.clone();2497 if sponsor == T::AccountId::default() {2498 who_pays_fee = who.clone();2499 }25002501 // Only mess with balances if fee is not zero.2502 if fee.is_zero() {2503 return Ok((fee, None));2504 }25052506 match <T as transaction_payment::Trait>::Currency::withdraw(2507 &who_pays_fee,2508 fee,2509 if tip.is_zero() {2510 WithdrawReason::TransactionPayment.into()2511 } else {2512 WithdrawReason::TransactionPayment | WithdrawReason::Tip2513 },2514 ExistenceRequirement::KeepAlive,2515 ) {2516 Ok(imbalance) => Ok((fee, Some(imbalance))),2517 Err(_) => Err(InvalidTransaction::Payment.into()),2518 }2519 }2520}252125222523impl<T: Trait + Send + Sync> SignedExtension2524 for ChargeTransactionPayment<T>2525where2526 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2527 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2528{2529 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2530 type AccountId = T::AccountId;2531 type Call = T::Call;2532 type AdditionalSigned = ();2533 type Pre = (2534 BalanceOf<T>,2535 Self::AccountId,2536 Option<NegativeImbalanceOf<T>>,2537 BalanceOf<T>,2538 );2539 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2540 Ok(())2541 }25422543 fn validate(2544 &self,2545 who: &Self::AccountId,2546 call: &Self::Call,2547 info: &DispatchInfoOf<Self::Call>,2548 len: usize,2549 ) -> TransactionValidity {2550 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2551 Ok(ValidTransaction {2552 priority: Self::get_priority(len, info, fee),2553 ..Default::default()2554 })2555 }25562557 fn pre_dispatch(2558 self,2559 who: &Self::AccountId,2560 call: &Self::Call,2561 info: &DispatchInfoOf<Self::Call>,2562 len: usize,2563 ) -> Result<Self::Pre, TransactionValidityError> {2564 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2565 Ok((self.0, who.clone(), imbalance, fee))2566 }25672568 fn post_dispatch(2569 pre: Self::Pre,2570 info: &DispatchInfoOf<Self::Call>,2571 post_info: &PostDispatchInfoOf<Self::Call>,2572 len: usize,2573 _result: &DispatchResult,2574 ) -> Result<(), TransactionValidityError> {2575 let (tip, who, imbalance, fee) = pre;2576 if let Some(payed) = imbalance {2577 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2578 len as u32, info, post_info, tip,2579 );2580 let refund = fee.saturating_sub(actual_fee);2581 let actual_payment =2582 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2583 &who, refund,2584 ) {2585 Ok(refund_imbalance) => {2586 // The refund cannot be larger than the up front payed max weight.2587 // `PostDispatchInfo::calc_unspent` guards against such a case.2588 match payed.offset(refund_imbalance) {2589 Ok(actual_payment) => actual_payment,2590 Err(_) => return Err(InvalidTransaction::Payment.into()),2591 }2592 }2593 // We do not recreate the account using the refund. The up front payment2594 // is gone in that case.2595 Err(_) => payed,2596 };2597 let imbalances = actual_payment.split(tip);2598 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2599 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2600 );2601 }2602 Ok(())2603 }2604}26052606// #endregionruntime_types.jsondiffbeforeafterboth--- a/runtime_types.json
+++ b/runtime_types.json
@@ -70,7 +70,7 @@
"OffchainSchema": "Vec<u8>",
"SchemaVersion": "SchemaVersion",
"Sponsor": "AccountId",
- "UnconfirmedSponsor": "AccountId",
+ "SponsorConfirmed": "bool",
"Limits": "CollectionLimits",
"VariableOnChainSchema": "Vec<u8>",
"ConstOnChainSchema": "Vec<u8>"
tests/src/destroyCollection.test.tsdiffbeforeafterboth--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -1,55 +1,12 @@
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
-import { createCollectionExpectSuccess, createCollectionExpectFailure } from "./util/helpers";
+import { createCollectionExpectSuccess, createCollectionExpectFailure, destroyCollectionExpectSuccess, destroyCollectionExpectFailure } from "./util/helpers";
import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
import privateKey from './substrate/privateKey';
-import { nullPublicKey } from './accounts';
chai.use(chaiAsPromised);
const expect = chai.expect;
-
-function getDestroyResult(events: EventRecord[]): boolean {
- let success: boolean = false;
- events.forEach(({ phase, event: { data, method, section } }) => {
- // console.log(` ${phase}: ${section}.${method}:: ${data}`);
- if (method == 'ExtrinsicSuccess') {
- success = true;
- }
- });
- return success;
-}
-
-async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
- await usingApi(async (api) => {
- // Run the DestroyCollection transaction
- const alicePrivateKey = privateKey(senderSeed);
- const tx = api.tx.nft.destroyCollection(collectionId);
- const events = await submitTransactionAsync(alicePrivateKey, tx);
- const result = getDestroyResult(events);
-
- // Get the collection
- const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
-
- // What to expect
- expect(result).to.be.true;
- expect(collection).to.be.not.null;
- expect(collection.Owner).to.be.equal(nullPublicKey);
- });
-}
-
-async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
- await usingApi(async (api) => {
- // Run the DestroyCollection transaction
- const alicePrivateKey = privateKey(senderSeed);
- const tx = api.tx.nft.destroyCollection(collectionId);
- const events = await submitTransactionAsync(alicePrivateKey, tx);
- const result = getDestroyResult(events);
-
- // What to expect
- expect(result).to.be.false;
- });
-}
describe('integration test: ext. destroyCollection():', () => {
it('NFT collection can be destroyed', async () => {
tests/src/setCollectionSponsor.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -0,0 +1,78 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi } from "./substrate/substrate-api";
+import { createCollectionExpectSuccess, setCollectionSponsorExpectSuccess, destroyCollectionExpectSuccess, setCollectionSponsorExpectFailure } from "./util/helpers";
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
+import type { AccountId } from '@polkadot/types/interfaces';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let bob: IKeyringPair;
+
+describe('integration test: ext. setCollectionSponsor():', () => {
+
+ before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ bob = keyring.addFromUri(`//Bob`);
+ });
+ });
+
+ it('Set NFT collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+ it('Set Fungible collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+ it('Set ReFungible collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+
+ it('Set the same sponsor repeatedly', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ });
+ it('Replace collection sponsor', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+
+ const keyring = new Keyring({ type: 'sr25519' });
+ const charlie = keyring.addFromUri(`//Charlie`);
+ await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+ await setCollectionSponsorExpectSuccess(collectionId, charlie.address);
+ });
+});
+
+describe('(!negative test!) integration test: ext. setCollectionSponsor():', () => {
+ before(async () => {
+ await usingApi(async (api) => {
+ const keyring = new Keyring({ type: 'sr25519' });
+ bob = keyring.addFromUri(`//Bob`);
+ });
+ });
+
+ it('(!negative test!) Add sponsor to a collection that never existed', async () => {
+ // Find the collection that never existed
+ const collectionId = 0;
+ await usingApi(async (api) => {
+ const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+ });
+
+ await setCollectionSponsorExpectFailure(collectionId, bob.address);
+ });
+ it('(!negative test!) Add sponsor to a collection that was destroyed', async () => {
+ const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+ await destroyCollectionExpectSuccess(collectionId);
+ await setCollectionSponsorExpectFailure(collectionId, bob.address);
+ });
+});
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -9,7 +9,7 @@
import { ApiPromise, Keyring } from "@polkadot/api";
import { default as usingApi, submitTransactionAsync } from "../substrate/substrate-api";
import privateKey from '../substrate/privateKey';
-import { alicesPublicKey } from "../accounts";
+import { alicesPublicKey, nullPublicKey } from "../accounts";
import { strToUTF16, utf16ToStr, hexToStr } from '../util/util';
import { IKeyringPair } from "@polkadot/types/types";
import { BigNumber } from 'bignumber.js';
@@ -121,4 +121,79 @@
bal = new BigNumber((await api.query.system.account(unused.address)).data.free.toString());
} while (bal.toFixed() != '0');
return unused;
-}
\ No newline at end of file
+}
+
+function getDestroyResult(events: EventRecord[]): boolean {
+ let success: boolean = false;
+ events.forEach(({ phase, event: { data, method, section } }) => {
+ // console.log(` ${phase}: ${section}.${method}:: ${data}`);
+ if (method == 'ExtrinsicSuccess') {
+ success = true;
+ }
+ });
+ return success;
+}
+
+export async function setCollectionSponsorExpectSuccess(collectionId: number, sponsor: string) {
+ await usingApi(async (api) => {
+
+ // Run the transaction
+ const alicePrivateKey = privateKey('//Alice');
+ const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor);
+ const events = await submitTransactionAsync(alicePrivateKey, 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.Sponsor.toString()).to.be.equal(sponsor.toString());
+ expect(collection.SponsorConfirmed).to.be.false;
+ });
+}
+
+export async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
+ await usingApi(async (api) => {
+ // Run the DestroyCollection transaction
+ const alicePrivateKey = privateKey(senderSeed);
+ const tx = api.tx.nft.destroyCollection(collectionId);
+ const events = await submitTransactionAsync(alicePrivateKey, tx);
+ const result = getDestroyResult(events);
+
+ // What to expect
+ expect(result).to.be.false;
+ });
+}
+
+export async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
+ await usingApi(async (api) => {
+ // Run the DestroyCollection transaction
+ const alicePrivateKey = privateKey(senderSeed);
+ const tx = api.tx.nft.destroyCollection(collectionId);
+ const events = await submitTransactionAsync(alicePrivateKey, tx);
+ const result = getDestroyResult(events);
+
+ // Get the collection
+ const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+ // What to expect
+ expect(result).to.be.true;
+ expect(collection).to.be.not.null;
+ expect(collection.Owner).to.be.equal(nullPublicKey);
+ });
+}
+
+export async function setCollectionSponsorExpectFailure(collectionId: number, sponsor: string) {
+ await usingApi(async (api) => {
+
+ // Run the transaction
+ const alicePrivateKey = privateKey('//Alice');
+ const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor);
+ const events = await submitTransactionAsync(alicePrivateKey, tx);
+ const result = getGenericResult(events);
+
+ // What to expect
+ expect(result.success).to.be.false;
+ });
+}