difftreelog
NFTPAR-280. Rate limit for CreateItem
in: master
1 file changed
pallets/nft/src/lib.rsdiffbeforeafterboth1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18 construct_runtime, decl_event, decl_module, decl_storage, decl_error,19 dispatch::DispatchResult,20 ensure, fail, parameter_types,21 traits::{22 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23 Randomness, WithdrawReason,24 },25 weights::{26 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28 WeightToFeePolynomial,29 },30 IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36 traits::{37 DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38 },39 transaction_validity::{40 TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41 },42 FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69 Invalid,70 NFT,71 // decimal points72 Fungible(DecimalPoints),73 // decimal points74 ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78 fn into(self) -> u8 {79 match self {80 CollectionMode::Invalid => 0,81 CollectionMode::NFT => 1,82 CollectionMode::Fungible(_) => 2,83 CollectionMode::ReFungible(_) => 3,84 }85 }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91 Normal,92 WhiteList,93}94impl Default for AccessMode {95 fn default() -> Self {96 Self::Normal97 }98}99100impl Default for CollectionMode {101 fn default() -> Self {102 Self::Invalid103 }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109 ImageURL,110 Unique,111}112impl Default for SchemaVersion {113 fn default() -> Self {114 Self::ImageURL115 }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121 pub owner: AccountId,122 pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128 pub owner: AccountId,129 pub mode: CollectionMode,130 pub access: AccessMode,131 pub decimal_points: DecimalPoints,132 pub name: Vec<u16>, // 64 include null escape char133 pub description: Vec<u16>, // 256 include null escape char134 pub token_prefix: Vec<u8>, // 16 include null escape char135 pub mint_mode: bool,136 pub offchain_schema: Vec<u8>,137 pub schema_version: SchemaVersion,138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140 pub limits: CollectionLimits, // Collection private restrictions 141 pub variable_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148 pub owner: AccountId,149 pub const_data: Vec<u8>,150 pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType {156 pub value: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct ReFungibleItemType<AccountId> {162 pub owner: Vec<Ownership<AccountId>>,163 pub const_data: Vec<u8>,164 pub variable_data: Vec<u8>,165}166167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169// pub struct VestingItem<AccountId, Moment> {170// pub sender: AccountId,171// pub recipient: AccountId,172// pub collection_id: CollectionId,173// pub item_id: TokenId,174// pub amount: u64,175// pub vesting_date: Moment,176// }177178#[derive(Encode, Decode, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct CollectionLimits {181 pub account_token_ownership_limit: u32,182 pub sponsored_data_size: u32,183 pub token_limit: u32,184185 // Timeouts for item types in passed blocks186 pub sponsor_transfer_timeout: u32,187}188189impl Default for CollectionLimits {190 fn default() -> CollectionLimits {191 CollectionLimits { 192 account_token_ownership_limit: 10_000_000, 193 token_limit: u32::max_value(),194 sponsored_data_size: u32::max_value(), 195 sponsor_transfer_timeout: 14400 }196 }197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]201pub struct ChainLimits {202 pub collection_numbers_limit: u32,203 pub account_token_ownership_limit: u32,204 pub collections_admins_limit: u64,205 pub custom_data_limit: u32,206207 // Timeouts for item types in passed blocks208 pub nft_sponsor_transfer_timeout: u32,209 pub fungible_sponsor_transfer_timeout: u32,210 pub refungible_sponsor_transfer_timeout: u32,211}212213pub trait WeightInfo {214 fn create_collection() -> Weight;215 fn destroy_collection() -> Weight;216 fn add_to_white_list() -> Weight;217 fn remove_from_white_list() -> Weight;218 fn set_public_access_mode() -> Weight;219 fn set_mint_permission() -> Weight;220 fn change_collection_owner() -> Weight;221 fn add_collection_admin() -> Weight;222 fn remove_collection_admin() -> Weight;223 fn set_collection_sponsor() -> Weight;224 fn confirm_sponsorship() -> Weight;225 fn remove_collection_sponsor() -> Weight;226 fn create_item(s: usize) -> Weight;227 fn burn_item() -> Weight;228 fn transfer() -> Weight;229 fn approve() -> Weight;230 fn transfer_from() -> Weight;231 fn set_offchain_schema() -> Weight;232 fn set_const_on_chain_schema() -> Weight;233 fn set_variable_on_chain_schema() -> Weight;234 fn set_variable_meta_data() -> Weight;235 fn enable_contract_sponsoring() -> Weight;236 fn set_schema_version() -> Weight;237 fn set_chain_limits() -> Weight;238 fn set_contract_sponsoring_rate_limit() -> Weight;239 fn toggle_contract_white_list() -> Weight;240 fn add_to_contract_white_list() -> Weight;241 fn remove_from_contract_white_list() -> Weight;242 fn set_collection_limits() -> Weight;243}244245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]247pub struct CreateNftData {248 pub const_data: Vec<u8>,249 pub variable_data: Vec<u8>,250}251252#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]253#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]254pub struct CreateFungibleData {255 pub value: u128,256}257258#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub struct CreateReFungibleData {261 pub const_data: Vec<u8>,262 pub variable_data: Vec<u8>,263}264265#[derive(Encode, Decode, Debug, Clone, PartialEq)]266#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]267pub enum CreateItemData {268 NFT(CreateNftData),269 Fungible(CreateFungibleData),270 ReFungible(CreateReFungibleData),271}272273impl CreateItemData {274 pub fn len(&self) -> usize {275 let len = match self {276 CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),277 CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),278 _ => 0279 };280 281 return len;282 }283}284285impl From<CreateNftData> for CreateItemData {286 fn from(item: CreateNftData) -> Self {287 CreateItemData::NFT(item)288 }289}290291impl From<CreateReFungibleData> for CreateItemData {292 fn from(item: CreateReFungibleData) -> Self {293 CreateItemData::ReFungible(item)294 }295}296297impl From<CreateFungibleData> for CreateItemData {298 fn from(item: CreateFungibleData) -> Self {299 CreateItemData::Fungible(item)300 }301}302303304decl_error! {305 /// Error for non-fungible-token module.306 pub enum Error for Module<T: Trait> {307 /// Total collections bound exceeded.308 TotalCollectionsLimitExceeded,309 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.310 CollectionDecimalPointLimitExceeded, 311 /// Collection name can not be longer than 63 char.312 CollectionNameLimitExceeded, 313 /// Collection description can not be longer than 255 char.314 CollectionDescriptionLimitExceeded, 315 /// Token prefix can not be longer than 15 char.316 CollectionTokenPrefixLimitExceeded,317 /// This collection does not exist.318 CollectionNotFound,319 /// Item not exists.320 TokenNotFound,321 /// Arithmetic calculation overflow.322 NumOverflow, 323 /// Account already has admin role.324 AlreadyAdmin, 325 /// You do not own this collection.326 NoPermission,327 /// This address is not set as sponsor, use setCollectionSponsor first.328 ConfirmUnsetSponsorFail,329 /// Collection is not in mint mode.330 PublicMintingNotAllowed,331 /// Sender parameter and item owner must be equal.332 MustBeTokenOwner,333 /// Item balance not enough.334 TokenValueTooLow,335 /// Size of item is too large.336 NftSizeLimitExceeded,337 /// No approve found338 ApproveNotFound,339 /// Requested value more than approved.340 TokenValueNotEnough,341 /// Only approved addresses can call this method.342 ApproveRequired,343 /// Address is not in white list.344 AddresNotInWhiteList,345 /// Number of collection admins bound exceeded.346 CollectionAdminsLimitExceeded,347 /// Owned tokens by a single address bound exceeded.348 AddressOwnershipLimitExceeded,349 /// Length of items properties must be greater than 0.350 EmptyArgument,351 /// const_data exceeded data limit.352 TokenConstDataLimitExceeded,353 /// variable_data exceeded data limit.354 TokenVariableDataLimitExceeded,355 /// Not NFT item data used to mint in NFT collection.356 NotNftDataUsedToMintNftCollectionToken,357 /// Not Fungible item data used to mint in Fungible collection.358 NotFungibleDataUsedToMintFungibleCollectionToken,359 /// Not Re Fungible item data used to mint in Re Fungible collection.360 NotReFungibleDataUsedToMintReFungibleCollectionToken,361 /// Unexpected collection type.362 UnexpectedCollectionType,363 /// Can't store metadata in fungible tokens.364 CantStoreMetadataInFungibleTokens,365 /// Collection token limit exceeded366 CollectionTokenLimitExceeded,367 /// Account token limit exceeded per collection368 AccountTokenLimitExceeded,369 /// Collection limit bounds per collection exceeded370 CollectionLimitBoundsExceeded371 }372}373374pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {375 type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;376377 /// Weight information for extrinsics in this pallet.378 type WeightInfo: WeightInfo;379}380381#[cfg(feature = "runtime-benchmarks")]382mod benchmarking;383384// #endregion385386decl_storage! {387 trait Store for Module<T: Trait> as Nft {388389 // Private members390 NextCollectionID: CollectionId;391 CreatedCollectionCount: u32;392 ChainVersion: u64;393 ItemListIndex: map hasher(identity) CollectionId => TokenId;394395 // Chain limits struct396 pub ChainLimit get(fn chain_limit) config(): ChainLimits;397398 // Bound counters399 CollectionCount: u32;400 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;401402 // Basic collections403 pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;404 pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;405 pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;406407 /// Balance owner per collection map408 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;409410 /// second parameter: item id + owner account id + spender account id411 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;412413 /// Item collections414 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;415 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;416 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;417418 /// Index list419 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;420421 /// Tokens transfer baskets422 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;423 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;424 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;425426 // Contract Sponsorship and Ownership427 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;428 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;429 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;430 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;431 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 432 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 433 }434 add_extra_genesis {435 build(|config: &GenesisConfig<T>| {436 // Modification of storage437 for (_num, _c) in &config.collection {438 <Module<T>>::init_collection(_c);439 }440441 for (_num, _c, _i) in &config.nft_item_id {442 <Module<T>>::init_nft_token(*_c, _i);443 }444445 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {446 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);447 }448449 for (_num, _c, _i) in &config.refungible_item_id {450 <Module<T>>::init_refungible_token(*_c, _i);451 }452 })453 }454}455456decl_event!(457 pub enum Event<T>458 where459 AccountId = <T as system::Trait>::AccountId,460 {461 /// New collection was created462 /// 463 /// # Arguments464 /// 465 /// * collection_id: Globally unique identifier of newly created collection.466 /// 467 /// * mode: [CollectionMode] converted into u8.468 /// 469 /// * account_id: Collection owner.470 Created(CollectionId, u8, AccountId),471472 /// New item was created.473 /// 474 /// # Arguments475 /// 476 /// * collection_id: Id of the collection where item was created.477 /// 478 /// * item_id: Id of an item. Unique within the collection.479 ItemCreated(CollectionId, TokenId),480481 /// Collection item was burned.482 /// 483 /// # Arguments484 /// 485 /// collection_id.486 /// 487 /// item_id: Identifier of burned NFT.488 ItemDestroyed(CollectionId, TokenId),489 }490);491492decl_module! {493 pub struct Module<T: Trait> for enum Call where origin: T::Origin {494495 fn deposit_event() = default;496 type Error = Error<T>;497498 fn on_initialize(now: T::BlockNumber) -> Weight {499500 if ChainVersion::get() < 2501 {502 let value = NextCollectionID::get();503 CreatedCollectionCount::put(value);504 ChainVersion::put(2);505 }506507 0508 }509510 /// 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.511 /// 512 /// # Permissions513 /// 514 /// * Anyone.515 /// 516 /// # Arguments517 /// 518 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.519 /// 520 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.521 /// 522 /// * token_prefix: UTF-8 string with token prefix.523 /// 524 /// * mode: [CollectionMode] collection type and type dependent data.525 // returns collection ID526 #[weight = T::WeightInfo::create_collection()]527 pub fn create_collection(origin,528 collection_name: Vec<u16>,529 collection_description: Vec<u16>,530 token_prefix: Vec<u8>,531 mode: CollectionMode) -> DispatchResult {532533 // Anyone can create a collection534 let who = ensure_signed(origin)?;535536 let decimal_points = match mode {537 CollectionMode::Fungible(points) => points,538 CollectionMode::ReFungible(points) => points,539 _ => 0540 };541542 // bound Total number of collections543 ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);544545 // check params546 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);547 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);548 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);549 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);550551 // Generate next collection ID552 let next_id = CreatedCollectionCount::get()553 .checked_add(1)554 .ok_or(Error::<T>::NumOverflow)?;555556 // bound counter557 let total = CollectionCount::get()558 .checked_add(1)559 .ok_or(Error::<T>::NumOverflow)?;560561 CreatedCollectionCount::put(next_id);562 CollectionCount::put(total);563564 // Create new collection565 let new_collection = CollectionType {566 owner: who.clone(),567 name: collection_name,568 mode: mode.clone(),569 mint_mode: false,570 access: AccessMode::Normal,571 description: collection_description,572 decimal_points: decimal_points,573 token_prefix: token_prefix,574 offchain_schema: Vec::new(),575 schema_version: SchemaVersion::ImageURL,576 sponsor: T::AccountId::default(),577 sponsor_confirmed: false,578 variable_on_chain_schema: Vec::new(),579 const_on_chain_schema: Vec::new(),580 limits: CollectionLimits::default(),581 };582583 // Add new collection to map584 <Collection<T>>::insert(next_id, new_collection);585586 // call event587 Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));588589 Ok(())590 }591592 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.593 /// 594 /// # Permissions595 /// 596 /// * Collection Owner.597 /// 598 /// # Arguments599 /// 600 /// * collection_id: collection to destroy.601 #[weight = T::WeightInfo::destroy_collection()]602 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {603604 let sender = ensure_signed(origin)?;605 Self::check_owner_permissions(collection_id, sender)?;606607 <AddressTokens<T>>::remove_prefix(collection_id);608 <Allowances<T>>::remove_prefix(collection_id);609 <Balance<T>>::remove_prefix(collection_id);610 <ItemListIndex>::remove(collection_id);611 <AdminList<T>>::remove(collection_id);612 <Collection<T>>::remove(collection_id);613 <WhiteList<T>>::remove_prefix(collection_id);614615 <NftItemList<T>>::remove_prefix(collection_id);616 <FungibleItemList<T>>::remove_prefix(collection_id);617 <ReFungibleItemList<T>>::remove_prefix(collection_id);618619 <NftTransferBasket<T>>::remove_prefix(collection_id);620 <FungibleTransferBasket<T>>::remove_prefix(collection_id);621 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);622623 if CollectionCount::get() > 0624 {625 // bound couter626 let total = CollectionCount::get()627 .checked_sub(1)628 .ok_or(Error::<T>::NumOverflow)?;629630 CollectionCount::put(total);631 }632633 Ok(())634 }635636 /// Add an address to white list.637 /// 638 /// # Permissions639 /// 640 /// * Collection Owner641 /// * Collection Admin642 /// 643 /// # Arguments644 /// 645 /// * collection_id.646 /// 647 /// * address.648 #[weight = T::WeightInfo::add_to_white_list()]649 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{650651 let sender = ensure_signed(origin)?;652 Self::check_owner_or_admin_permissions(collection_id, sender)?;653654 <WhiteList<T>>::insert(collection_id, address, true);655 656 Ok(())657 }658659 /// Remove an address from white list.660 /// 661 /// # Permissions662 /// 663 /// * Collection Owner664 /// * Collection Admin665 /// 666 /// # Arguments667 /// 668 /// * collection_id.669 /// 670 /// * address.671 #[weight = T::WeightInfo::remove_from_white_list()]672 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{673674 let sender = ensure_signed(origin)?;675 Self::check_owner_or_admin_permissions(collection_id, sender)?;676677 <WhiteList<T>>::remove(collection_id, address);678679 Ok(())680 }681682 /// Toggle between normal and white list access for the methods with access for `Anyone`.683 /// 684 /// # Permissions685 /// 686 /// * Collection Owner.687 /// 688 /// # Arguments689 /// 690 /// * collection_id.691 /// 692 /// * mode: [AccessMode]693 #[weight = T::WeightInfo::set_public_access_mode()]694 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult695 {696 let sender = ensure_signed(origin)?;697698 Self::check_owner_permissions(collection_id, sender)?;699 let mut target_collection = <Collection<T>>::get(collection_id);700 target_collection.access = mode;701 <Collection<T>>::insert(collection_id, target_collection);702703 Ok(())704 }705706 /// Allows Anyone to create tokens if:707 /// * White List is enabled, and708 /// * Address is added to white list, and709 /// * This method was called with True parameter710 /// 711 /// # Permissions712 /// * Collection Owner713 ///714 /// # Arguments715 /// 716 /// * collection_id.717 /// 718 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.719 #[weight = T::WeightInfo::set_mint_permission()]720 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult721 {722 let sender = ensure_signed(origin)?;723724 Self::check_owner_permissions(collection_id, sender)?;725 let mut target_collection = <Collection<T>>::get(collection_id);726 target_collection.mint_mode = mint_permission;727 <Collection<T>>::insert(collection_id, target_collection);728729 Ok(())730 }731732 /// Change the owner of the collection.733 /// 734 /// # Permissions735 /// 736 /// * Collection Owner.737 /// 738 /// # Arguments739 /// 740 /// * collection_id.741 /// 742 /// * new_owner.743 #[weight = T::WeightInfo::change_collection_owner()]744 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {745746 let sender = ensure_signed(origin)?;747 Self::check_owner_permissions(collection_id, sender)?;748 let mut target_collection = <Collection<T>>::get(collection_id);749 target_collection.owner = new_owner;750 <Collection<T>>::insert(collection_id, target_collection);751752 Ok(())753 }754755 /// Adds an admin of the Collection.756 /// 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. 757 /// 758 /// # Permissions759 /// 760 /// * Collection Owner.761 /// * Collection Admin.762 /// 763 /// # Arguments764 /// 765 /// * collection_id: ID of the Collection to add admin for.766 /// 767 /// * new_admin_id: Address of new admin to add.768 #[weight = T::WeightInfo::add_collection_admin()]769 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {770771 let sender = ensure_signed(origin)?;772 Self::check_owner_or_admin_permissions(collection_id, sender)?;773 let mut admin_arr: Vec<T::AccountId> = Vec::new();774775 if <AdminList<T>>::contains_key(collection_id)776 {777 admin_arr = <AdminList<T>>::get(collection_id);778 ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);779 }780781 // Number of collection admins782 ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);783784 admin_arr.push(new_admin_id);785 <AdminList<T>>::insert(collection_id, admin_arr);786787 Ok(())788 }789790 /// 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.791 ///792 /// # Permissions793 /// 794 /// * Collection Owner.795 /// * Collection Admin.796 /// 797 /// # Arguments798 /// 799 /// * collection_id: ID of the Collection to remove admin for.800 /// 801 /// * account_id: Address of admin to remove.802 #[weight = T::WeightInfo::remove_collection_admin()]803 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {804805 let sender = ensure_signed(origin)?;806 Self::check_owner_or_admin_permissions(collection_id, sender)?;807808 if <AdminList<T>>::contains_key(collection_id)809 {810 let mut admin_arr = <AdminList<T>>::get(collection_id);811 admin_arr.retain(|i| *i != account_id);812 <AdminList<T>>::insert(collection_id, admin_arr);813 }814815 Ok(())816 }817818 /// # Permissions819 /// 820 /// * Collection Owner821 /// 822 /// # Arguments823 /// 824 /// * collection_id.825 /// 826 /// * new_sponsor.827 #[weight = T::WeightInfo::set_collection_sponsor()]828 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {829830 let sender = ensure_signed(origin)?;831 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);832833 let mut target_collection = <Collection<T>>::get(collection_id);834 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);835836 target_collection.sponsor = new_sponsor;837 target_collection.sponsor_confirmed = false;838 <Collection<T>>::insert(collection_id, target_collection);839840 Ok(())841 }842843 /// # Permissions844 /// 845 /// * Sponsor.846 /// 847 /// # Arguments848 /// 849 /// * collection_id.850 #[weight = T::WeightInfo::confirm_sponsorship()]851 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {852853 let sender = ensure_signed(origin)?;854 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);855856 let mut target_collection = <Collection<T>>::get(collection_id);857 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);858859 target_collection.sponsor_confirmed = true;860 <Collection<T>>::insert(collection_id, target_collection);861862 Ok(())863 }864865 /// Switch back to pay-per-own-transaction model.866 ///867 /// # Permissions868 ///869 /// * Collection owner.870 /// 871 /// # Arguments872 /// 873 /// * collection_id.874 #[weight = T::WeightInfo::remove_collection_sponsor()]875 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {876877 let sender = ensure_signed(origin)?;878 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);879880 let mut target_collection = <Collection<T>>::get(collection_id);881 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);882883 target_collection.sponsor = T::AccountId::default();884 target_collection.sponsor_confirmed = false;885 <Collection<T>>::insert(collection_id, target_collection);886887 Ok(())888 }889890 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.891 /// 892 /// # Permissions893 /// 894 /// * Collection Owner.895 /// * Collection Admin.896 /// * Anyone if897 /// * White List is enabled, and898 /// * Address is added to white list, and899 /// * MintPermission is enabled (see SetMintPermission method)900 /// 901 /// # Arguments902 /// 903 /// * collection_id: ID of the collection.904 /// 905 /// * owner: Address, initial owner of the NFT.906 ///907 /// * data: Token data to store on chain.908 // #[weight =909 // (130_000_000 as Weight)910 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))911 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))912 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]913914 #[weight = T::WeightInfo::create_item(data.len())]915 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {916917 let sender = ensure_signed(origin)?;918919 Self::collection_exists(collection_id)?;920921 let target_collection = <Collection<T>>::get(collection_id);922923 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;924 Self::validate_create_item_args(&target_collection, &data)?;925 Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;926927 Ok(())928 }929930 /// This method creates multiple instances of NFT Collection created with CreateCollection method.931 /// 932 /// # Permissions933 /// 934 /// * Collection Owner.935 /// * Collection Admin.936 /// * Anyone if937 /// * White List is enabled, and938 /// * Address is added to white list, and939 /// * MintPermission is enabled (see SetMintPermission method)940 /// 941 /// # Arguments942 /// 943 /// * collection_id: ID of the collection.944 /// 945 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].946 /// 947 /// * owner: Address, initial owner of the NFT.948 #[weight = T::WeightInfo::create_item(items_data.into_iter()949 .map(|data| { data.len() })950 .sum())]951 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {952953 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);954 let sender = ensure_signed(origin)?;955956 Self::collection_exists(collection_id)?;957 let target_collection = <Collection<T>>::get(collection_id);958959 Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;960961 for data in &items_data {962 Self::validate_create_item_args(&target_collection, data)?;963 }964 for data in &items_data {965 Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;966 }967968 Ok(())969 }970971 /// Destroys a concrete instance of NFT.972 /// 973 /// # Permissions974 /// 975 /// * Collection Owner.976 /// * Collection Admin.977 /// * Current NFT Owner.978 /// 979 /// # Arguments980 /// 981 /// * collection_id: ID of the collection.982 /// 983 /// * item_id: ID of NFT to burn.984 #[weight = T::WeightInfo::burn_item()]985 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {986987 let sender = ensure_signed(origin)?;988 Self::collection_exists(collection_id)?;989990 // Transfer permissions check991 let target_collection = <Collection<T>>::get(collection_id);992 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||993 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),994 Error::<T>::NoPermission);995996 if target_collection.access == AccessMode::WhiteList {997 Self::check_white_list(collection_id, &sender)?;998 }9991000 match target_collection.mode1001 {1002 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1003 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,1004 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1005 _ => ()1006 };10071008 // call event1009 Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10101011 Ok(())1012 }10131014 /// Change ownership of the token.1015 /// 1016 /// # Permissions1017 /// 1018 /// * Collection Owner1019 /// * Collection Admin1020 /// * Current NFT owner1021 ///1022 /// # Arguments1023 /// 1024 /// * recipient: Address of token recipient.1025 /// 1026 /// * collection_id.1027 /// 1028 /// * item_id: ID of the item1029 /// * Non-Fungible Mode: Required.1030 /// * Fungible Mode: Ignored.1031 /// * Re-Fungible Mode: Required.1032 /// 1033 /// * value: Amount to transfer.1034 /// * Non-Fungible Mode: Ignored1035 /// * Fungible Mode: Must specify transferred amount1036 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1037 #[weight = T::WeightInfo::transfer()]1038 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10391040 let sender = ensure_signed(origin)?;1041 let target_collection = <Collection<T>>::get(collection_id);10421043 // Limits check1044 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10451046 // Transfer permissions check1047 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1048 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1049 Error::<T>::NoPermission);10501051 if target_collection.access == AccessMode::WhiteList {1052 Self::check_white_list(collection_id, &sender)?;1053 Self::check_white_list(collection_id, &recipient)?;1054 }10551056 match target_collection.mode1057 {1058 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1059 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1060 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1061 _ => ()1062 };10631064 Ok(())1065 }10661067 /// Set, change, or remove approved address to transfer the ownership of the NFT.1068 /// 1069 /// # Permissions1070 /// 1071 /// * Collection Owner1072 /// * Collection Admin1073 /// * Current NFT owner1074 /// 1075 /// # Arguments1076 /// 1077 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1078 /// 1079 /// * collection_id.1080 /// 1081 /// * item_id: ID of the item.1082 #[weight = T::WeightInfo::approve()]1083 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10841085 let sender = ensure_signed(origin)?;10861087 // Transfer permissions check1088 let target_collection = <Collection<T>>::get(collection_id);1089 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1090 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1091 Error::<T>::NoPermission);10921093 if target_collection.access == AccessMode::WhiteList {1094 Self::check_white_list(collection_id, &sender)?;1095 Self::check_white_list(collection_id, &spender)?;1096 }10971098 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1099 let mut allowance: u128 = amount;1100 if allowance_exists {1101 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1102 }1103 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11041105 Ok(())1106 }1107 1108 /// 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.1109 /// 1110 /// # Permissions1111 /// * Collection Owner1112 /// * Collection Admin1113 /// * Current NFT owner1114 /// * Address approved by current NFT owner1115 /// 1116 /// # Arguments1117 /// 1118 /// * from: Address that owns token.1119 /// 1120 /// * recipient: Address of token recipient.1121 /// 1122 /// * collection_id.1123 /// 1124 /// * item_id: ID of the item.1125 /// 1126 /// * value: Amount to transfer.1127 #[weight = T::WeightInfo::transfer_from()]1128 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11291130 let sender = ensure_signed(origin)?;1131 let mut appoved_transfer = false;11321133 // Check approval1134 let mut approval: u128 = 0;1135 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1136 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1137 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1138 appoved_transfer = true;1139 }11401141 let target_collection = <Collection<T>>::get(collection_id);11421143 // Limits check1144 Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11451146 // Transfer permissions check 1147 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1148 Error::<T>::NoPermission);11491150 if target_collection.access == AccessMode::WhiteList {1151 Self::check_white_list(collection_id, &sender)?;1152 Self::check_white_list(collection_id, &recipient)?;1153 }11541155 // Reduce approval by transferred amount or remove if remaining approval drops to 01156 if approval - value > 0 {1157 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1158 }1159 else {1160 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1161 }11621163 match target_collection.mode1164 {1165 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1166 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1167 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1168 _ => ()1169 };11701171 Ok(())1172 }11731174 #[weight = 0]1175 pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11761177 // let no_perm_mes = "You do not have permissions to modify this collection";1178 // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1179 // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1180 // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11811182 // // on_nft_received call11831184 // Self::transfer(origin, collection_id, item_id, new_owner)?;11851186 Ok(())1187 }11881189 /// Set off-chain data schema.1190 /// 1191 /// # Permissions1192 /// 1193 /// * Collection Owner1194 /// * Collection Admin1195 /// 1196 /// # Arguments1197 /// 1198 /// * collection_id.1199 /// 1200 /// * schema: String representing the offchain data schema.1201 #[weight = T::WeightInfo::set_variable_meta_data()]1202 pub fn set_variable_meta_data (1203 origin,1204 collection_id: CollectionId,1205 item_id: TokenId,1206 data: Vec<u8>1207 ) -> DispatchResult {1208 let sender = ensure_signed(origin)?;1209 1210 Self::collection_exists(collection_id)?;1211 1212 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12131214 // Modify permissions check1215 let target_collection = <Collection<T>>::get(collection_id);1216 ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1217 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1218 Error::<T>::NoPermission);12191220 Self::item_exists(collection_id, item_id, &target_collection.mode)?;12211222 match target_collection.mode1223 {1224 CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1225 CollectionMode::ReFungible(_) => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1226 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1227 _ => fail!(Error::<T>::UnexpectedCollectionType)1228 };12291230 Ok(())1231 }1232 1233 /// Set schema standard1234 /// ImageURL1235 /// Unique1236 /// 1237 /// # Permissions1238 /// 1239 /// * Collection Owner1240 /// * Collection Admin1241 /// 1242 /// # Arguments1243 /// 1244 /// * collection_id.1245 /// 1246 /// * schema: SchemaVersion: enum1247 #[weight = T::WeightInfo::set_schema_version()]1248 pub fn set_schema_version(1249 origin,1250 collection_id: CollectionId,1251 version: SchemaVersion1252 ) -> DispatchResult {1253 let sender = ensure_signed(origin)?;1254 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1255 let mut target_collection = <Collection<T>>::get(collection_id);1256 target_collection.schema_version = version;1257 <Collection<T>>::insert(collection_id, target_collection);12581259 Ok(())1260 }12611262 /// Set off-chain data schema.1263 /// 1264 /// # Permissions1265 /// 1266 /// * Collection Owner1267 /// * Collection Admin1268 /// 1269 /// # Arguments1270 /// 1271 /// * collection_id.1272 /// 1273 /// * schema: String representing the offchain data schema.1274 #[weight = T::WeightInfo::set_offchain_schema()]1275 pub fn set_offchain_schema(1276 origin,1277 collection_id: CollectionId,1278 schema: Vec<u8>1279 ) -> DispatchResult {1280 let sender = ensure_signed(origin)?;1281 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12821283 let mut target_collection = <Collection<T>>::get(collection_id);1284 target_collection.offchain_schema = schema;1285 <Collection<T>>::insert(collection_id, target_collection);12861287 Ok(())1288 }12891290 /// Set const on-chain data schema.1291 /// 1292 /// # Permissions1293 /// 1294 /// * Collection Owner1295 /// * Collection Admin1296 /// 1297 /// # Arguments1298 /// 1299 /// * collection_id.1300 /// 1301 /// * schema: String representing the const on-chain data schema.1302 #[weight = T::WeightInfo::set_const_on_chain_schema()]1303 pub fn set_const_on_chain_schema (1304 origin,1305 collection_id: CollectionId,1306 schema: Vec<u8>1307 ) -> DispatchResult {1308 let sender = ensure_signed(origin)?;1309 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13101311 let mut target_collection = <Collection<T>>::get(collection_id);1312 target_collection.const_on_chain_schema = schema;1313 <Collection<T>>::insert(collection_id, target_collection);13141315 Ok(())1316 }13171318 /// Set variable on-chain data schema.1319 /// 1320 /// # Permissions1321 /// 1322 /// * Collection Owner1323 /// * Collection Admin1324 /// 1325 /// # Arguments1326 /// 1327 /// * collection_id.1328 /// 1329 /// * schema: String representing the variable on-chain data schema.1330 #[weight = T::WeightInfo::set_const_on_chain_schema()]1331 pub fn set_variable_on_chain_schema (1332 origin,1333 collection_id: CollectionId,1334 schema: Vec<u8>1335 ) -> DispatchResult {1336 let sender = ensure_signed(origin)?;1337 Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13381339 let mut target_collection = <Collection<T>>::get(collection_id);1340 target_collection.variable_on_chain_schema = schema;1341 <Collection<T>>::insert(collection_id, target_collection);13421343 Ok(())1344 }13451346 // Sudo permissions function1347 #[weight = T::WeightInfo::set_chain_limits()]1348 pub fn set_chain_limits(1349 origin,1350 limits: ChainLimits1351 ) -> DispatchResult {13521353 #[cfg(not(feature = "runtime-benchmarks"))]1354 ensure_root(origin)?;13551356 <ChainLimit>::put(limits);1357 Ok(())1358 }13591360 /// Enable smart contract self-sponsoring.1361 /// 1362 /// # Permissions1363 /// 1364 /// * Contract Owner1365 /// 1366 /// # Arguments1367 /// 1368 /// * contract address1369 /// * enable flag1370 /// 1371 #[weight = T::WeightInfo::enable_contract_sponsoring()]1372 pub fn enable_contract_sponsoring(1373 origin,1374 contract_address: T::AccountId,1375 enable: bool1376 ) -> DispatchResult {13771378 let sender = ensure_signed(origin)?;13791380 #[cfg(feature = "runtime-benchmarks")]1381 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13821383 Self::ensure_contract_owned(sender, &contract_address)?;13841385 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1386 Ok(())1387 }13881389 /// Set the rate limit for contract sponsoring to specified number of blocks.1390 /// 1391 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1392 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1393 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1394 /// from contract endowment if there are at least B blocks between such transactions. 1395 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1396 /// 1397 /// # Permissions1398 /// 1399 /// * Contract Owner1400 /// 1401 /// # Arguments1402 /// 1403 /// -`contract_address`: Address of the contract to sponsor1404 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1405 /// 1406 #[weight = T::WeightInfo::set_contract_sponsoring_rate_limit()]1407 pub fn set_contract_sponsoring_rate_limit(1408 origin,1409 contract_address: T::AccountId,1410 rate_limit: T::BlockNumber1411 ) -> DispatchResult {1412 let sender = ensure_signed(origin)?;14131414 #[cfg(feature = "runtime-benchmarks")]1415 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14161417 Self::ensure_contract_owned(sender, &contract_address)?;1418 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1419 Ok(())1420 }14211422 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1423 /// 1424 /// # Permissions1425 /// 1426 /// * Address that deployed smart contract.1427 /// 1428 /// # Arguments1429 /// 1430 /// -`contract_address`: Address of the contract.1431 /// 1432 /// - `enable`: . 1433 #[weight = T::WeightInfo::toggle_contract_white_list()]1434 pub fn toggle_contract_white_list(1435 origin,1436 contract_address: T::AccountId,1437 enable: bool1438 ) -> DispatchResult {1439 let sender = ensure_signed(origin)?;14401441 #[cfg(feature = "runtime-benchmarks")]1442 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14431444 Self::ensure_contract_owned(sender, &contract_address)?;1445 <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1446 Ok(())1447 }1448 1449 /// Add an address to smart contract white list.1450 /// 1451 /// # Permissions1452 /// 1453 /// * Address that deployed smart contract.1454 /// 1455 /// # Arguments1456 /// 1457 /// -`contract_address`: Address of the contract.1458 ///1459 /// -`account_address`: Address to add.1460 #[weight = T::WeightInfo::add_to_contract_white_list()]1461 pub fn add_to_contract_white_list(1462 origin,1463 contract_address: T::AccountId,1464 account_address: T::AccountId1465 ) -> DispatchResult {1466 let sender = ensure_signed(origin)?;14671468 #[cfg(feature = "runtime-benchmarks")]1469 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1470 1471 Self::ensure_contract_owned(sender, &contract_address)?; 1472 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1473 Ok(())1474 }14751476 #[weight = T::WeightInfo::add_to_contract_white_list()]1477 pub fn wasm_dummy_method(1478 origin1479 ) -> DispatchResult {1480 let sender = ensure_signed(origin)?;1481 Ok(())1482 }14831484 /// Remove an address from smart contract white list.1485 /// 1486 /// # Permissions1487 /// 1488 /// * Address that deployed smart contract.1489 /// 1490 /// # Arguments1491 /// 1492 /// -`contract_address`: Address of the contract.1493 ///1494 /// -`account_address`: Address to remove.1495 #[weight = T::WeightInfo::remove_from_contract_white_list()]1496 pub fn remove_from_contract_white_list(1497 origin,1498 contract_address: T::AccountId,1499 account_address: T::AccountId1500 ) -> DispatchResult {1501 let sender = ensure_signed(origin)?;15021503 #[cfg(feature = "runtime-benchmarks")]1504 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());15051506 Self::ensure_contract_owned(sender, &contract_address)?;1507 <ContractWhiteList<T>>::remove(contract_address, account_address);1508 Ok(())1509 }15101511 #[weight = T::WeightInfo::set_collection_limits()]1512 pub fn set_collection_limits(1513 origin,1514 collection_id: u32,1515 limits: CollectionLimits,1516 ) -> DispatchResult {1517 let sender = ensure_signed(origin)?;1518 Self::check_owner_permissions(collection_id, sender.clone())?;1519 let mut target_collection = <Collection<T>>::get(collection_id);1520 let chain_limits = ChainLimit::get();1521 let climits = target_collection.limits;15221523 // collection bounds1524 ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1525 limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP, 1526 Error::<T>::CollectionLimitBoundsExceeded);15271528 // token_limit check prev1529 ensure!(climits.token_limit > limits.token_limit && 1530 limits.token_limit <= chain_limits.account_token_ownership_limit, 1531 Error::<T>::AccountTokenLimitExceeded);15321533 target_collection.limits = limits;1534 <Collection<T>>::insert(collection_id, target_collection);15351536 Ok(())1537 } 1538 }1539}15401541impl<T: Trait> Module<T> {15421543 fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15441545 // check token limit and account token limit1546 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1547 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1548 1549 Ok(())1550 }15511552 fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15531554 // check token limit and account token limit1555 let total_items: u32 = ItemListIndex::get(collection_id);1556 let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1557 ensure!(collection.limits.token_limit > total_items, Error::<T>::CollectionTokenLimitExceeded);1558 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);15591560 if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1561 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1562 Self::check_white_list(collection_id, owner)?;1563 Self::check_white_list(collection_id, sender)?;1564 }15651566 Ok(())1567 }15681569 fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1570 match target_collection.mode1571 {1572 CollectionMode::NFT => {1573 if let CreateItemData::NFT(data) = data {1574 // check sizes1575 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1576 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1577 } else {1578 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1579 }1580 },1581 CollectionMode::Fungible(_) => {1582 if let CreateItemData::Fungible(_) = data {1583 } else {1584 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1585 }1586 },1587 CollectionMode::ReFungible(_) => {1588 if let CreateItemData::ReFungible(data) = data {15891590 // check sizes1591 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1592 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1593 } else {1594 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1595 }1596 },1597 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1598 };15991600 Ok(())1601 }16021603 fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1604 match data1605 {1606 CreateItemData::NFT(data) => {1607 let item = NftItemType {1608 owner,1609 const_data: data.const_data,1610 variable_data: data.variable_data1611 };16121613 Self::add_nft_item(collection_id, item)?;1614 },1615 CreateItemData::Fungible(data) => {1616 Self::add_fungible_item(collection_id, &owner, data.value)?;1617 },1618 CreateItemData::ReFungible(data) => {1619 let mut owner_list = Vec::new();1620 let value = (10 as u128).pow(collection.decimal_points as u32);1621 owner_list.push(Ownership {owner: owner.clone(), fraction: value});16221623 let item = ReFungibleItemType {1624 owner: owner_list,1625 const_data: data.const_data,1626 variable_data: data.variable_data1627 };16281629 Self::add_refungible_item(collection_id, item)?;1630 }1631 };16321633 // call event1634 Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16351636 Ok(())1637 }16381639 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16401641 // Does new owner already have an account?1642 let mut balance: u128 = 0;1643 if <FungibleItemList<T>>::contains_key(collection_id, owner) {1644 balance = <FungibleItemList<T>>::get(collection_id, owner).value;1645 } 16461647 // Mint 1648 let item = FungibleItemType {1649 value: balance + value1650 };1651 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16521653 // Update balance1654 let new_balance = <Balance<T>>::get(collection_id, owner)1655 .checked_add(value)1656 .ok_or(Error::<T>::NumOverflow)?;1657 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16581659 Ok(())1660 }16611662 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1663 let current_index = <ItemListIndex>::get(collection_id)1664 .checked_add(1)1665 .ok_or(Error::<T>::NumOverflow)?;1666 let itemcopy = item.clone();16671668 let value = item.owner.first().unwrap().fraction;1669 let owner = item.owner.first().unwrap().owner.clone();16701671 Self::add_token_index(collection_id, current_index, owner.clone())?;16721673 <ItemListIndex>::insert(collection_id, current_index);1674 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16751676 // Update balance1677 let new_balance = <Balance<T>>::get(collection_id, owner.clone())1678 .checked_add(value)1679 .ok_or(Error::<T>::NumOverflow)?;1680 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16811682 Ok(())1683 }16841685 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1686 let current_index = <ItemListIndex>::get(collection_id)1687 .checked_add(1)1688 .ok_or(Error::<T>::NumOverflow)?;16891690 let item_owner = item.owner.clone();1691 Self::add_token_index(collection_id, current_index, item.owner.clone())?;16921693 <ItemListIndex>::insert(collection_id, current_index);1694 <NftItemList<T>>::insert(collection_id, current_index, item);16951696 // Update balance1697 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1698 .checked_add(1)1699 .ok_or(Error::<T>::NumOverflow)?;1700 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17011702 Ok(())1703 }17041705 fn burn_refungible_item(1706 collection_id: CollectionId,1707 item_id: TokenId,1708 owner: T::AccountId,1709 ) -> DispatchResult {1710 ensure!(1711 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1712 Error::<T>::TokenNotFound1713 );1714 let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1715 let item = collection1716 .owner1717 .iter()1718 .filter(|&i| i.owner == owner)1719 .next()1720 .unwrap();1721 Self::remove_token_index(collection_id, item_id, owner.clone())?;17221723 // update balance1724 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1725 .checked_sub(item.fraction)1726 .ok_or(Error::<T>::NumOverflow)?;1727 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17281729 <ReFungibleItemList<T>>::remove(collection_id, item_id);17301731 Ok(())1732 }17331734 fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1735 ensure!(1736 <NftItemList<T>>::contains_key(collection_id, item_id),1737 Error::<T>::TokenNotFound1738 );1739 let item = <NftItemList<T>>::get(collection_id, item_id);1740 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17411742 // update balance1743 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1744 .checked_sub(1)1745 .ok_or(Error::<T>::NumOverflow)?;1746 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1747 <NftItemList<T>>::remove(collection_id, item_id);17481749 Ok(())1750 }17511752 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1753 ensure!(1754 <FungibleItemList<T>>::contains_key(collection_id, owner),1755 Error::<T>::TokenNotFound1756 );1757 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1758 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17591760 // update balance1761 let new_balance = <Balance<T>>::get(collection_id, owner)1762 .checked_sub(value)1763 .ok_or(Error::<T>::NumOverflow)?;1764 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17651766 if balance.value - value > 0 {1767 balance.value -= value;1768 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1769 }1770 else {1771 <FungibleItemList<T>>::remove(collection_id, owner);1772 }17731774 Ok(())1775 }17761777 fn collection_exists(collection_id: CollectionId) -> DispatchResult {1778 ensure!(1779 <Collection<T>>::contains_key(collection_id),1780 Error::<T>::CollectionNotFound1781 );1782 Ok(())1783 }17841785 fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1786 Self::collection_exists(collection_id)?;17871788 let target_collection = <Collection<T>>::get(collection_id);1789 ensure!(1790 subject == target_collection.owner,1791 Error::<T>::NoPermission1792 );17931794 Ok(())1795 }17961797 fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1798 let target_collection = <Collection<T>>::get(collection_id);1799 let mut result: bool = subject == target_collection.owner;1800 let exists = <AdminList<T>>::contains_key(collection_id);18011802 if !result & exists {1803 if <AdminList<T>>::get(collection_id).contains(&subject) {1804 result = true1805 }1806 }18071808 result1809 }18101811 fn check_owner_or_admin_permissions(1812 collection_id: CollectionId,1813 subject: T::AccountId,1814 ) -> DispatchResult {1815 Self::collection_exists(collection_id)?;1816 let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18171818 ensure!(1819 result,1820 Error::<T>::NoPermission1821 );1822 Ok(())1823 }18241825 fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1826 let target_collection = <Collection<T>>::get(collection_id);18271828 match target_collection.mode {1829 CollectionMode::NFT => {1830 <NftItemList<T>>::get(collection_id, item_id).owner == subject1831 }1832 CollectionMode::Fungible(_) => {1833 <FungibleItemList<T>>::contains_key(collection_id, &subject)1834 }1835 CollectionMode::ReFungible(_) => {1836 <ReFungibleItemList<T>>::get(collection_id, item_id)1837 .owner1838 .iter()1839 .any(|i| i.owner == subject)1840 }1841 CollectionMode::Invalid => false,1842 }1843 }18441845 fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1846 let mes = Error::<T>::AddresNotInWhiteList;1847 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18481849 Ok(())1850 }18511852 fn transfer_fungible(1853 collection_id: CollectionId,1854 value: u128,1855 owner: &T::AccountId,1856 recipient: &T::AccountId,1857 ) -> DispatchResult {1858 ensure!(1859 <FungibleItemList<T>>::contains_key(collection_id, owner),1860 Error::<T>::TokenNotFound1861 );18621863 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1864 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18651866 // Send balance to recipient (updates balanceOf of recipient)1867 Self::add_fungible_item(collection_id, recipient, value)?;18681869 // update balanceOf of sender1870 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18711872 // Reduce or remove sender1873 if balance.value == value {1874 <FungibleItemList<T>>::remove(collection_id, owner);1875 }1876 else {1877 balance.value -= value;1878 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1879 }18801881 Ok(())1882 }18831884 fn transfer_refungible(1885 collection_id: CollectionId,1886 item_id: TokenId,1887 value: u128,1888 owner: T::AccountId,1889 new_owner: T::AccountId,1890 ) -> DispatchResult {1891 ensure!(1892 <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1893 Error::<T>::TokenNotFound1894 );18951896 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1897 let item = full_item1898 .owner1899 .iter()1900 .filter(|i| i.owner == owner)1901 .next()1902 .ok_or(Error::<T>::NumOverflow)?;1903 let amount = item.fraction;19041905 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19061907 // update balance1908 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1909 .checked_sub(value)1910 .ok_or(Error::<T>::NumOverflow)?;1911 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19121913 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1914 .checked_add(value)1915 .ok_or(Error::<T>::NumOverflow)?;1916 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19171918 let old_owner = item.owner.clone();1919 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19201921 // transfer1922 if amount == value && !new_owner_has_account {1923 // change owner1924 // new owner do not have account1925 let mut new_full_item = full_item.clone();1926 new_full_item1927 .owner1928 .iter_mut()1929 .find(|i| i.owner == owner)1930 .unwrap()1931 .owner = new_owner.clone();1932 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19331934 // update index collection1935 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1936 } else {1937 let mut new_full_item = full_item.clone();1938 new_full_item1939 .owner1940 .iter_mut()1941 .find(|i| i.owner == owner)1942 .unwrap()1943 .fraction -= value;19441945 // separate amount1946 if new_owner_has_account {1947 // new owner has account1948 new_full_item1949 .owner1950 .iter_mut()1951 .find(|i| i.owner == new_owner)1952 .unwrap()1953 .fraction += value;1954 } else {1955 // new owner do not have account1956 new_full_item.owner.push(Ownership {1957 owner: new_owner.clone(),1958 fraction: value,1959 });1960 Self::add_token_index(collection_id, item_id, new_owner.clone())?;1961 }19621963 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1964 }19651966 Ok(())1967 }19681969 fn transfer_nft(1970 collection_id: CollectionId,1971 item_id: TokenId,1972 sender: T::AccountId,1973 new_owner: T::AccountId,1974 ) -> DispatchResult {1975 ensure!(1976 <NftItemList<T>>::contains_key(collection_id, item_id),1977 Error::<T>::TokenNotFound1978 );19791980 let mut item = <NftItemList<T>>::get(collection_id, item_id);19811982 ensure!(1983 sender == item.owner,1984 Error::<T>::MustBeTokenOwner1985 );19861987 // update balance1988 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1989 .checked_sub(1)1990 .ok_or(Error::<T>::NumOverflow)?;1991 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19921993 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1994 .checked_add(1)1995 .ok_or(Error::<T>::NumOverflow)?;1996 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19971998 // change owner1999 let old_owner = item.owner.clone();2000 item.owner = new_owner.clone();2001 <NftItemList<T>>::insert(collection_id, item_id, item);20022003 // update index collection2004 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20052006 Ok(())2007 }2008 2009 fn item_exists(2010 collection_id: CollectionId,2011 item_id: TokenId,2012 mode: &CollectionMode2013 ) -> DispatchResult {2014 match mode {2015 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2016 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2017 _ => ()2018 };2019 2020 Ok(())2021 }20222023 fn set_re_fungible_variable_data(2024 collection_id: CollectionId,2025 item_id: TokenId,2026 data: Vec<u8>2027 ) -> DispatchResult {2028 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20292030 item.variable_data = data;20312032 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20332034 Ok(())2035 }20362037 fn set_nft_variable_data(2038 collection_id: CollectionId,2039 item_id: TokenId,2040 data: Vec<u8>2041 ) -> DispatchResult {2042 let mut item = <NftItemList<T>>::get(collection_id, item_id);2043 2044 item.variable_data = data;20452046 <NftItemList<T>>::insert(collection_id, item_id, item);2047 2048 Ok(())2049 }20502051 fn init_collection(item: &CollectionType<T::AccountId>) {2052 // check params2053 assert!(2054 item.decimal_points <= MAX_DECIMAL_POINTS,2055 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2056 );2057 assert!(2058 item.name.len() <= 64,2059 "Collection name can not be longer than 63 char"2060 );2061 assert!(2062 item.name.len() <= 256,2063 "Collection description can not be longer than 255 char"2064 );2065 assert!(2066 item.token_prefix.len() <= 16,2067 "Token prefix can not be longer than 15 char"2068 );20692070 // Generate next collection ID2071 let next_id = CreatedCollectionCount::get()2072 .checked_add(1)2073 .unwrap();20742075 CreatedCollectionCount::put(next_id);2076 }20772078 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2079 let current_index = <ItemListIndex>::get(collection_id)2080 .checked_add(1)2081 .unwrap();20822083 let item_owner = item.owner.clone();2084 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20852086 <ItemListIndex>::insert(collection_id, current_index);20872088 // Update balance2089 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2090 .checked_add(1)2091 .unwrap();2092 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2093 }20942095 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2096 let current_index = <ItemListIndex>::get(collection_id)2097 .checked_add(1)2098 .unwrap();20992100 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();21012102 <ItemListIndex>::insert(collection_id, current_index);21032104 // Update balance2105 let new_balance = <Balance<T>>::get(collection_id, owner)2106 .checked_add(item.value)2107 .unwrap();2108 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2109 }21102111 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2112 let current_index = <ItemListIndex>::get(collection_id)2113 .checked_add(1)2114 .unwrap();21152116 let value = item.owner.first().unwrap().fraction;2117 let owner = item.owner.first().unwrap().owner.clone();21182119 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();21202121 <ItemListIndex>::insert(collection_id, current_index);21222123 // Update balance2124 let new_balance = <Balance<T>>::get(collection_id, owner.clone())2125 .checked_add(value)2126 .unwrap();2127 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2128 }21292130 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21312132 // add to account limit2133 if <AccountItemCount<T>>::contains_key(owner.clone()) {21342135 // bound Owned tokens by a single address2136 let count = <AccountItemCount<T>>::get(owner.clone());2137 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21382139 <AccountItemCount<T>>::insert(owner.clone(), count2140 .checked_add(1)2141 .ok_or(Error::<T>::NumOverflow)?);2142 }2143 else {2144 <AccountItemCount<T>>::insert(owner.clone(), 1);2145 }21462147 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2148 if list_exists {2149 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2150 let item_contains = list.contains(&item_index.clone());21512152 if !item_contains {2153 list.push(item_index.clone());2154 }21552156 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2157 } else {2158 let mut itm = Vec::new();2159 itm.push(item_index.clone());2160 <AddressTokens<T>>::insert(collection_id, owner, itm);2161 2162 }21632164 Ok(())2165 }21662167 fn remove_token_index(2168 collection_id: CollectionId,2169 item_index: TokenId,2170 owner: T::AccountId,2171 ) -> DispatchResult {21722173 // update counter2174 <AccountItemCount<T>>::insert(owner.clone(), 2175 <AccountItemCount<T>>::get(owner.clone())2176 .checked_sub(1)2177 .ok_or(Error::<T>::NumOverflow)?);217821792180 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2181 if list_exists {2182 let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2183 let item_contains = list.contains(&item_index.clone());21842185 if item_contains {2186 list.retain(|&item| item != item_index);2187 <AddressTokens<T>>::insert(collection_id, owner, list);2188 }2189 }21902191 Ok(())2192 }21932194 fn move_token_index(2195 collection_id: CollectionId,2196 item_index: TokenId,2197 old_owner: T::AccountId,2198 new_owner: T::AccountId,2199 ) -> DispatchResult {2200 Self::remove_token_index(collection_id, item_index, old_owner)?;2201 Self::add_token_index(collection_id, item_index, new_owner)?;22022203 Ok(())2204 }2205 2206 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2207 if <ContractOwner<T>>::contains_key(contract.clone()) {2208 let owner = <ContractOwner<T>>::get(contract);2209 ensure!(account == owner, Error::<T>::NoPermission);2210 } else {2211 fail!(Error::<T>::NoPermission);2212 }22132214 Ok(())2215 }2216}22172218////////////////////////////////////////////////////////////////////////////////////////////////////2219// Economic models2220// #region22212222/// Fee multiplier.2223pub type Multiplier = FixedU128;22242225type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2226 <T as system::Trait>::AccountId,2227>>::Balance;2228type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2229 <T as system::Trait>::AccountId,2230>>::NegativeImbalance;22312232/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2233/// in the queue.2234#[derive(Encode, Decode, Clone, Eq, PartialEq)]2235pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2236 #[codec(compact)] BalanceOf<T>2237);22382239impl<T: Trait + Send + Sync> sp_std::fmt::Debug2240 for ChargeTransactionPayment<T>2241{2242 #[cfg(feature = "std")]2243 fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2244 write!(f, "ChargeTransactionPayment<{:?}>", self.0)2245 }2246 #[cfg(not(feature = "std"))]2247 fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2248 Ok(())2249 }2250}22512252impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2253where2254 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2255 BalanceOf<T>: Send + Sync + FixedPointOperand,2256{2257 /// utility constructor. Used only in client/factory code.2258 pub fn from(fee: BalanceOf<T>) -> Self {2259 Self(fee)2260 }22612262 pub fn traditional_fee(2263 len: usize,2264 info: &DispatchInfoOf<T::Call>,2265 tip: BalanceOf<T>,2266 ) -> BalanceOf<T>2267 where2268 T::Call: Dispatchable<Info = DispatchInfo>,2269 {2270 <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2271 }22722273 fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2274 let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2275 let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2276 let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2277 final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2278 }22792280 fn withdraw_fee(2281 &self,2282 who: &T::AccountId,2283 call: &T::Call,2284 info: &DispatchInfoOf<T::Call>,2285 len: usize,2286 ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2287 let tip = self.0;22882289 // Set fee based on call type. Creating collection costs 1 Unique.2290 // All other transactions have traditional fees so far2291 // let fee = match call.is_sub_type() {2292 // Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2293 // _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2294 // // _ => <BalanceOf<T>>::from(100)2295 // };2296 let fee = Self::traditional_fee(len, info, tip);22972298 // Only mess with balances if fee is not zero.2299 if fee.is_zero() {2300 return Ok((fee, None));2301 }23022303 // Determine who is paying transaction fee based on ecnomic model2304 // Parse call to extract collection ID and access collection sponsor2305 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2306 Some(Call::create_item(collection_id, _owner, _properties)) => {23072308 // check free create limit2309 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2310 (<Collection<T>>::get(collection_id).sponsor_confirmed)2311 {2312 <Collection<T>>::get(collection_id).sponsor2313 } else {2314 T::AccountId::default()2315 }2316 }2317 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2318 2319 let mut sponsor_transfer = false;2320 if <Collection<T>>::get(collection_id).sponsor_confirmed {23212322 let collection_limits = <Collection<T>>::get(collection_id).limits;2323 let collection_mode = <Collection<T>>::get(collection_id).mode;2324 2325 // sponsor timeout2326 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2327 sponsor_transfer = match collection_mode {2328 CollectionMode::NFT => {2329 2330 // get correct limit2331 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2332 collection_limits.sponsor_transfer_timeout2333 } else {2334 ChainLimit::get().nft_sponsor_transfer_timeout2335 };2336 2337 let mut sponsored = true;2338 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2339 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2340 let limit_time = last_tx_block + limit.into();2341 if block_number <= limit_time {2342 sponsored = false;2343 }2344 }2345 if sponsored {2346 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2347 }23482349 sponsored2350 }2351 CollectionMode::Fungible(_) => {2352 2353 // get correct limit2354 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2355 collection_limits.sponsor_transfer_timeout2356 } else {2357 ChainLimit::get().fungible_sponsor_transfer_timeout2358 };2359 2360 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2361 let mut sponsored = true;2362 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2363 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2364 let limit_time = last_tx_block + limit.into();2365 if block_number <= limit_time {2366 sponsored = false;2367 }2368 }2369 if sponsored {2370 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2371 }23722373 sponsored2374 }2375 CollectionMode::ReFungible(_) => {2376 2377 // get correct limit2378 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2379 collection_limits.sponsor_transfer_timeout2380 } else {2381 ChainLimit::get().refungible_sponsor_transfer_timeout2382 };2383 2384 let mut sponsored = true;2385 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2386 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2387 let limit_time = last_tx_block + limit.into();2388 if block_number <= limit_time {2389 sponsored = false;2390 }2391 }2392 if sponsored {2393 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2394 }23952396 sponsored2397 }2398 _ => {2399 false2400 },2401 };2402 }24032404 if !sponsor_transfer {2405 T::AccountId::default()2406 } else {2407 <Collection<T>>::get(collection_id).sponsor2408 }2409 }24102411 _ => T::AccountId::default(),2412 };24132414 // Sponsor smart contracts2415 sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24162417 // On instantiation: set the contract owner2418 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24192420 let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2421 code_hash,2422 &data,2423 &who,2424 );2425 <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24262427 T::AccountId::default()2428 },24292430 // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2431 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24322433 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24342435 let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2436 && <ContractOwner<T>>::get(called_contract.clone()) == *who;2437 let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2438 2439 if !owned_contract && white_list_enabled {2440 if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2441 return Err(InvalidTransaction::Call.into());2442 }2443 }24442445 let mut sponsor_transfer = false;2446 if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2447 let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2448 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2449 let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2450 let limit_time = last_tx_block + rate_limit;24512452 if block_number >= limit_time {2453 <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2454 sponsor_transfer = true;2455 }2456 } else {2457 sponsor_transfer = false;2458 }2459 2460 2461 let mut sp = T::AccountId::default();2462 if sponsor_transfer {2463 if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2464 if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2465 sp = called_contract;2466 }2467 }2468 }24692470 sp2471 },24722473 _ => sponsor,2474 };24752476 let mut who_pays_fee: T::AccountId = sponsor.clone();2477 if sponsor == T::AccountId::default() {2478 who_pays_fee = who.clone();2479 }24802481 match <T as transaction_payment::Trait>::Currency::withdraw(2482 &who_pays_fee,2483 fee,2484 if tip.is_zero() {2485 WithdrawReason::TransactionPayment.into()2486 } else {2487 WithdrawReason::TransactionPayment | WithdrawReason::Tip2488 },2489 ExistenceRequirement::KeepAlive,2490 ) {2491 Ok(imbalance) => Ok((fee, Some(imbalance))),2492 Err(_) => Err(InvalidTransaction::Payment.into()),2493 }2494 }2495}249624972498impl<T: Trait + Send + Sync> SignedExtension2499 for ChargeTransactionPayment<T>2500where2501 BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2502 T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2503{2504 const IDENTIFIER: &'static str = "ChargeTransactionPayment";2505 type AccountId = T::AccountId;2506 type Call = T::Call;2507 type AdditionalSigned = ();2508 type Pre = (2509 BalanceOf<T>,2510 Self::AccountId,2511 Option<NegativeImbalanceOf<T>>,2512 BalanceOf<T>,2513 );2514 fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2515 Ok(())2516 }25172518 fn validate(2519 &self,2520 who: &Self::AccountId,2521 call: &Self::Call,2522 info: &DispatchInfoOf<Self::Call>,2523 len: usize,2524 ) -> TransactionValidity {2525 let (fee, _) = self.withdraw_fee(who, call, info, len)?;2526 Ok(ValidTransaction {2527 priority: Self::get_priority(len, info, fee),2528 ..Default::default()2529 })2530 }25312532 fn pre_dispatch(2533 self,2534 who: &Self::AccountId,2535 call: &Self::Call,2536 info: &DispatchInfoOf<Self::Call>,2537 len: usize,2538 ) -> Result<Self::Pre, TransactionValidityError> {2539 let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2540 Ok((self.0, who.clone(), imbalance, fee))2541 }25422543 fn post_dispatch(2544 pre: Self::Pre,2545 info: &DispatchInfoOf<Self::Call>,2546 post_info: &PostDispatchInfoOf<Self::Call>,2547 len: usize,2548 _result: &DispatchResult,2549 ) -> Result<(), TransactionValidityError> {2550 let (tip, who, imbalance, fee) = pre;2551 if let Some(payed) = imbalance {2552 let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2553 len as u32, info, post_info, tip,2554 );2555 let refund = fee.saturating_sub(actual_fee);2556 let actual_payment =2557 match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2558 &who, refund,2559 ) {2560 Ok(refund_imbalance) => {2561 // The refund cannot be larger than the up front payed max weight.2562 // `PostDispatchInfo::calc_unspent` guards against such a case.2563 match payed.offset(refund_imbalance) {2564 Ok(actual_payment) => actual_payment,2565 Err(_) => return Err(InvalidTransaction::Payment.into()),2566 }2567 }2568 // We do not recreate the account using the refund. The up front payment2569 // is gone in that case.2570 Err(_) => payed,2571 };2572 let imbalances = actual_payment.split(tip);2573 <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2574 Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2575 );2576 }2577 Ok(())2578 }2579}25802581// #endregion