difftreelog
Scheduler refactored and fixed
in: master
8 files changed
pallets/nft-charge-transaction/src/lib.rsdiffbeforeafterboth--- a/pallets/nft-charge-transaction/src/lib.rs
+++ b/pallets/nft-charge-transaction/src/lib.rs
@@ -135,6 +135,13 @@
.map(|i| (fee, i));
}
+ // check errors
+ let error = <pallet_nft_transaction_payment::Module<T>>::check_error(who, call);
+ match error {
+ Err(error) => return Err(error),
+ Ok(error) => {}
+ };
+
// Determine who is paying transaction fee based on ecnomic model
// Parse call to extract collection ID and access collection sponsor
let sponsor = <pallet_nft_transaction_payment::Module<T>>::withdraw_type(who, call);
pallets/nft-transaction-payment/README.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/nft-transaction-payment/README.md
@@ -0,0 +1,13 @@
+# Nft Transaction Payment
+
+## Overview
+
+A module containing the sponsoring logic for paying for sponsored collections
+
+**NOTE:** The scheduled calls will be dispatched with the default filter
+for the origin: namely `frame_system::Config::BaseCallFilter` for all origin
+except root which will get no filter. And not the filter contained in origin
+use to call `fn schedule`.
+
+If a call is scheduled using proxy or whatever mecanism which adds filter,
+then those filter will not be used when dispatching the schedule call.
pallets/nft-transaction-payment/src/lib.rsdiffbeforeafterboth--- a/pallets/nft-transaction-payment/src/lib.rs
+++ b/pallets/nft-transaction-payment/src/lib.rs
@@ -31,6 +31,9 @@
traits::{
Hash, Dispatchable,
},
+ transaction_validity::{
+ InvalidTransaction, TransactionValidityError,
+ },
};
use pallet_contracts::chain_extension::UncheckedFrom;
use sp_std::prelude::*;
@@ -85,6 +88,36 @@
impl<T: Config> Module<T>
{
+ pub fn check_error(
+ who: &T::AccountId,
+ call: &T::Call
+ ) -> Result<bool, TransactionValidityError> where
+ T::Call: Dispatchable<Info=DispatchInfo>,
+ T::Call: IsSubType<pallet_nft::Call<T>>,
+ T::Call: IsSubType<pallet_contracts::Call<T>>,
+ T::AccountId: AsRef<[u8]>,
+ T::AccountId: UncheckedFrom<T::Hash>
+ {
+
+ match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {
+ Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {
+
+ let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());
+
+ let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);
+ let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());
+
+ if !owned_contract && white_list_enabled {
+ if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {
+ return Err(InvalidTransaction::Call.into());
+ }
+ }
+ Ok(true)
+ },
+ _ => { Ok(true) },
+ }
+ }
+
pub fn withdraw_type(
who: &T::AccountId,
call: &T::Call
@@ -366,160 +399,4 @@
None
}
-}
-
-// type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;
-
-// /// Require the transactor pay for themselves and maybe include a tip to gain additional priority
-// /// in the queue.
-// #[derive(Encode, Decode, Clone, Eq, PartialEq)]
-// pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);
-
-// impl<T: Config + Send + Sync> sp_std::fmt::Debug
-// for ChargeTransactionPayment<T>
-// {
-// #[cfg(feature = "std")]
-// fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
-// write!(f, "ChargeTransactionPayment<{:?}>", self.0)
-// }
-// #[cfg(not(feature = "std"))]
-// fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
-// Ok(())
-// }
-// }
-
-// impl<T: Config> ChargeTransactionPayment<T>
-// where
-// T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<pallet_nft::Call<T>> + IsSubType<pallet_contracts::Call<T>>,
-// BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,
-// T::AccountId: AsRef<[u8]>,
-// T::AccountId: UncheckedFrom<T::Hash>,
-// {
-// fn traditional_fee(
-// len: usize,
-// info: &DispatchInfoOf<T::Call>,
-// tip: BalanceOf<T>,
-// ) -> BalanceOf<T>
-// where
-// T::Call: Dispatchable<Info = DispatchInfo>,
-// {
-// <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)
-// }
-
-// fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {
-// let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);
-// let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);
-// let len_saturation = max_block_length as u64 / (len as u64).max(1);
-// let coefficient: BalanceOf<T> = weight_saturation
-// .min(len_saturation)
-// .saturated_into::<BalanceOf<T>>();
-// final_fee
-// .saturating_mul(coefficient)
-// .saturated_into::<TransactionPriority>()
-// }
-
-// fn withdraw_fee(
-// &self,
-// who: &T::AccountId,
-// call: &T::Call,
-// info: &DispatchInfoOf<T::Call>,
-// len: usize,
-// ) -> Result<
-// (
-// BalanceOf<T>,
-// <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,
-// ),
-// TransactionValidityError,
-// > {
-// let tip = self.0;
-
-// let fee = Self::traditional_fee(len, info, tip);
-
-// // Only mess with balances if fee is not zero.
-// if fee.is_zero() {
-// return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)
-// .map(|i| (fee, i));
-// }
-
-// // Determine who is paying transaction fee based on ecnomic model
-// // Parse call to extract collection ID and access collection sponsor
-
-// let sponsor = pallet_nft_transaction_payment::<T>::withdraw_type(who, call);
-// //let sponsor = Self::Module::<T>::withdraw_type(who, call);;
-// // <pallet_nft_transaction_payment::Module<T>>::withdraw_type(who, call);
-
-// let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());
-
-// <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)
-// .map(|i| (fee, i))
-// }
-// }
-
-
-// impl<T: Config + Send + Sync> SignedExtension
-// for ChargeTransactionPayment<T>
-// where
-// BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,
-// T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<pallet_nft::Call<T>> + IsSubType<pallet_contracts::Call<T>>,
-// T::AccountId: AsRef<[u8]>,
-// T::AccountId: UncheckedFrom<T::Hash>,
-// {
-// const IDENTIFIER: &'static str = "ChargeTransactionPayment";
-// type AccountId = T::AccountId;
-// type Call = T::Call;
-// type AdditionalSigned = ();
-// type Pre = (
-// // tip
-// BalanceOf<T>,
-// // who pays fee
-// Self::AccountId,
-// // imbalance resulting from withdrawing the fee
-// <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,
-// );
-// fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
-// Ok(())
-// }
-
-// fn validate(
-// &self,
-// who: &Self::AccountId,
-// call: &Self::Call,
-// info: &DispatchInfoOf<Self::Call>,
-// len: usize,
-// ) -> TransactionValidity {
-// let (fee, _) = self.withdraw_fee(who, call, info, len)?;
-// Ok(ValidTransaction {
-// priority: Self::get_priority(len, info, fee),
-// ..Default::default()
-// })
-// }
-
-// fn pre_dispatch(
-// self,
-// who: &Self::AccountId,
-// call: &Self::Call,
-// info: &DispatchInfoOf<Self::Call>,
-// len: usize,
-// ) -> Result<Self::Pre, TransactionValidityError> {
-// let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;
-// Ok((self.0, who.clone(), imbalance))
-// }
-
-// fn post_dispatch(
-// pre: Self::Pre,
-// info: &DispatchInfoOf<Self::Call>,
-// post_info: &PostDispatchInfoOf<Self::Call>,
-// len: usize,
-// _result: &DispatchResult,
-// ) -> Result<(), TransactionValidityError> {
-// let (tip, who, imbalance) = pre;
-// let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(
-// len as u32,
-// info,
-// post_info,
-// tip,
-// );
-// <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;
-// Ok(())
-// }
-// }
\ No newline at end of file
+}
\ No newline at end of file
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::*;1516pub use frame_support::{17 construct_runtime, decl_event, decl_module, decl_storage, decl_error,18 dispatch::DispatchResult,19 ensure, fail, parameter_types,20 traits::{21 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,22 Randomness, IsSubType, WithdrawReasons,23 },24 weights::{25 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},26 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,27 WeightToFeePolynomial, DispatchClass,28 },29 StorageValue,30 transactional,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use nft_data_structs::{36 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,37 AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,38 CollectionId, CollectionMode, CollectionHandle, TokenId, 39 SchemaVersion, SponsorshipState, Ownership,40 NftItemType, FungibleItemType, ReFungibleItemType41};4243#[cfg(test)]44mod mock;4546#[cfg(test)]47mod tests;4849mod default_weights;5051#[cfg(feature = "runtime-benchmarks")]52mod benchmarking;5354pub trait WeightInfo {55 fn create_collection() -> Weight;56 fn destroy_collection() -> Weight;57 fn add_to_white_list() -> Weight;58 fn remove_from_white_list() -> Weight;59 fn set_public_access_mode() -> Weight;60 fn set_mint_permission() -> Weight;61 fn change_collection_owner() -> Weight;62 fn add_collection_admin() -> Weight;63 fn remove_collection_admin() -> Weight;64 fn set_collection_sponsor() -> Weight;65 fn confirm_sponsorship() -> Weight;66 fn remove_collection_sponsor() -> Weight;67 fn create_item(s: usize) -> Weight;68 fn burn_item() -> Weight;69 fn transfer() -> Weight;70 fn approve() -> Weight;71 fn transfer_from() -> Weight;72 fn set_offchain_schema() -> Weight;73 fn set_const_on_chain_schema() -> Weight;74 fn set_variable_on_chain_schema() -> Weight;75 fn set_variable_meta_data() -> Weight;76 fn enable_contract_sponsoring() -> Weight;77 fn set_schema_version() -> Weight;78 fn set_chain_limits() -> Weight;79 fn set_contract_sponsoring_rate_limit() -> Weight;80 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;81 fn toggle_contract_white_list() -> Weight;82 fn add_to_contract_white_list() -> Weight;83 fn remove_from_contract_white_list() -> Weight;84 fn set_collection_limits() -> Weight;85}8687decl_error! {88 /// Error for non-fungible-token module.89 pub enum Error for Module<T: Config> {90 /// Total collections bound exceeded.91 TotalCollectionsLimitExceeded,92 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.93 CollectionDecimalPointLimitExceeded, 94 /// Collection name can not be longer than 63 char.95 CollectionNameLimitExceeded, 96 /// Collection description can not be longer than 255 char.97 CollectionDescriptionLimitExceeded, 98 /// Token prefix can not be longer than 15 char.99 CollectionTokenPrefixLimitExceeded,100 /// This collection does not exist.101 CollectionNotFound,102 /// Item not exists.103 TokenNotFound,104 /// Admin not found105 AdminNotFound,106 /// Arithmetic calculation overflow.107 NumOverflow, 108 /// Account already has admin role.109 AlreadyAdmin, 110 /// You do not own this collection.111 NoPermission,112 /// This address is not set as sponsor, use setCollectionSponsor first.113 ConfirmUnsetSponsorFail,114 /// Collection is not in mint mode.115 PublicMintingNotAllowed,116 /// Sender parameter and item owner must be equal.117 MustBeTokenOwner,118 /// Item balance not enough.119 TokenValueTooLow,120 /// Size of item is too large.121 NftSizeLimitExceeded,122 /// No approve found123 ApproveNotFound,124 /// Requested value more than approved.125 TokenValueNotEnough,126 /// Only approved addresses can call this method.127 ApproveRequired,128 /// Address is not in white list.129 AddresNotInWhiteList,130 /// Number of collection admins bound exceeded.131 CollectionAdminsLimitExceeded,132 /// Owned tokens by a single address bound exceeded.133 AddressOwnershipLimitExceeded,134 /// Length of items properties must be greater than 0.135 EmptyArgument,136 /// const_data exceeded data limit.137 TokenConstDataLimitExceeded,138 /// variable_data exceeded data limit.139 TokenVariableDataLimitExceeded,140 /// Not NFT item data used to mint in NFT collection.141 NotNftDataUsedToMintNftCollectionToken,142 /// Not Fungible item data used to mint in Fungible collection.143 NotFungibleDataUsedToMintFungibleCollectionToken,144 /// Not Re Fungible item data used to mint in Re Fungible collection.145 NotReFungibleDataUsedToMintReFungibleCollectionToken,146 /// Unexpected collection type.147 UnexpectedCollectionType,148 /// Can't store metadata in fungible tokens.149 CantStoreMetadataInFungibleTokens,150 /// Collection token limit exceeded151 CollectionTokenLimitExceeded,152 /// Account token limit exceeded per collection153 AccountTokenLimitExceeded,154 /// Collection limit bounds per collection exceeded155 CollectionLimitBoundsExceeded,156 /// Tried to enable permissions which are only permitted to be disabled157 OwnerPermissionsCantBeReverted,158 /// Schema data size limit bound exceeded159 SchemaDataLimitExceeded,160 /// Maximum refungibility exceeded161 WrongRefungiblePieces,162 /// createRefungible should be called with one owner163 BadCreateRefungibleCall,164 }165}166167// + pallet_transaction_payment::Config + pallet_nft_transaction_payment::Config + pallet_contracts::Config168169170pub trait Config: system::Config + Sized {171 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;172173 /// Weight information for extrinsics in this pallet.174 type WeightInfo: WeightInfo;175176 type Currency: Currency<Self::AccountId>;177 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;178 type TreasuryAccountId: Get<Self::AccountId>;179}180181// # Used definitions182//183// ## User control levels184//185// chain-controlled - key is uncontrolled by user186// i.e autoincrementing index187// can use non-cryptographic hash188// real - key is controlled by user189// but it is hard to generate enough colliding values, i.e owner of signed txs190// can use non-cryptographic hash191// controlled - key is completly controlled by users192// i.e maps with mutable keys193// should use cryptographic hash194//195// ## User control level downgrade reasons196//197// ?1 - chain-controlled -> controlled198// collections/tokens can be destroyed, resulting in massive holes199// ?2 - chain-controlled -> controlled200// same as ?1, but can be only added, resulting in easier exploitation201// ?3 - real -> controlled202// no confirmation required, so addresses can be easily generated203decl_storage! {204 trait Store for Module<T: Config> as Nft {205206 //#region Private members207 /// Id of next collection208 CreatedCollectionCount: u32;209 /// Used for migrations210 ChainVersion: u64;211 /// Id of last collection token212 /// Collection id (controlled?1)213 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;214 //#endregion215216 //#region Chain limits struct217 pub ChainLimit get(fn chain_limit) config(): ChainLimits;218 //#endregion219220 //#region Bound counters221 /// Amount of collections destroyed, used for total amount tracking with222 /// CreatedCollectionCount223 DestroyedCollectionCount: u32;224 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)225 /// Account id (real)226 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;227 //#endregion228229 //#region Basic collections230 /// Collection info231 /// Collection id (controlled?1)232 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;233 /// List of collection admins234 /// Collection id (controlled?2)235 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::AccountId>;236 /// Whitelisted collection users237 /// Collection id (controlled?2), user id (controlled?3)238 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;239 //#endregion240241 /// How many of collection items user have242 /// Collection id (controlled?2), account id (real)243 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;244245 /// Amount of items which spender can transfer out of owners account (via transferFrom)246 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))247 /// TODO: Off chain worker should remove from this map when token gets removed248 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;249250 //#region Item collections251 /// Collection id (controlled?2), token id (controlled?1)252 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::AccountId>>;253 /// Collection id (controlled?2), owner (controlled?2)254 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;255 /// Collection id (controlled?2), token id (controlled?1)256 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::AccountId>>;257 //#endregion258259 //#region Index list260 /// Collection id (controlled?2), tokens owner (controlled?2)261 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;262 //#endregion263264 //#region Tokens transfer rate limit baskets265 /// (Collection id (controlled?2), who created (real))266 /// TODO: Off chain worker should remove from this map when collection gets removed267 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;268 /// Collection id (controlled?2), token id (controlled?2)269 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;270 /// Collection id (controlled?2), owning user (real)271 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;272 /// Collection id (controlled?2), token id (controlled?2)273 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;274 //#endregion275276 /// Variable metadata sponsoring277 /// Collection id (controlled?2), token id (controlled?2)278 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;279 280 //#region Contract Sponsorship and Ownership281 /// Contract address (real)282 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;283 /// Contract address (real)284 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;285 /// (Contract address(real), caller (real))286 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;287 /// Contract address (real)288 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;289 /// Contract address (real)290 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 291 /// Contract address (real) => Whitelisted user (controlled?3)292 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 293 //#endregion294 }295 add_extra_genesis {296 build(|config: &GenesisConfig<T>| {297 // Modification of storage298 for (_num, _c) in &config.collection_id {299 <Module<T>>::init_collection(_c);300 }301302 for (_num, _c, _i) in &config.nft_item_id {303 <Module<T>>::init_nft_token(*_c, _i);304 }305306 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {307 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);308 }309310 for (_num, _c, _i) in &config.refungible_item_id {311 <Module<T>>::init_refungible_token(*_c, _i);312 }313 })314 }315}316317decl_event!(318 pub enum Event<T>319 where320 AccountId = <T as system::Config>::AccountId,321 {322 /// New collection was created323 /// 324 /// # Arguments325 /// 326 /// * collection_id: Globally unique identifier of newly created collection.327 /// 328 /// * mode: [CollectionMode] converted into u8.329 /// 330 /// * account_id: Collection owner.331 CollectionCreated(CollectionId, u8, AccountId),332333 /// New item was created.334 /// 335 /// # Arguments336 /// 337 /// * collection_id: Id of the collection where item was created.338 /// 339 /// * item_id: Id of an item. Unique within the collection.340 ///341 /// * recipient: Owner of newly created item 342 ItemCreated(CollectionId, TokenId, AccountId),343344 /// Collection item was burned.345 /// 346 /// # Arguments347 /// 348 /// collection_id.349 /// 350 /// item_id: Identifier of burned NFT.351 ItemDestroyed(CollectionId, TokenId),352353 /// Item was transferred354 ///355 /// * collection_id: Id of collection to which item is belong356 ///357 /// * item_id: Id of an item358 ///359 /// * sender: Original owner of item360 ///361 /// * recipient: New owner of item362 ///363 /// * amount: Always 1 for NFT364 Transfer(CollectionId, TokenId, AccountId, AccountId, u128),365366 /// * collection_id367 ///368 /// * item_id369 ///370 /// * sender371 ///372 /// * spender373 ///374 /// * amount375 Approved(CollectionId, TokenId, AccountId, AccountId, u128),376 }377);378379decl_module! {380 pub struct Module<T: Config> for enum Call 381 where 382 origin: T::Origin383 {384 fn deposit_event() = default;385 type Error = Error<T>;386387 fn on_initialize(_now: T::BlockNumber) -> Weight {388 0389 }390391 /// 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.392 /// 393 /// # Permissions394 /// 395 /// * Anyone.396 /// 397 /// # Arguments398 /// 399 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.400 /// 401 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.402 /// 403 /// * token_prefix: UTF-8 string with token prefix.404 /// 405 /// * mode: [CollectionMode] collection type and type dependent data.406 // returns collection ID407 #[weight = <T as Config>::WeightInfo::create_collection()]408 #[transactional]409 pub fn create_collection(origin,410 collection_name: Vec<u16>,411 collection_description: Vec<u16>,412 token_prefix: Vec<u8>,413 mode: CollectionMode) -> DispatchResult {414415 // Anyone can create a collection416 let who = ensure_signed(origin)?;417418 // Take a (non-refundable) deposit of collection creation419 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();420 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(421 &T::TreasuryAccountId::get(),422 T::CollectionCreationPrice::get(),423 ));424 <T as Config>::Currency::settle(425 &who,426 imbalance,427 WithdrawReasons::TRANSFER,428 ExistenceRequirement::KeepAlive,429 ).map_err(|_| Error::<T>::NoPermission)?;430431 let decimal_points = match mode {432 CollectionMode::Fungible(points) => points,433 _ => 0434 };435436 let chain_limit = ChainLimit::get();437438 let created_count = CreatedCollectionCount::get();439 let destroyed_count = DestroyedCollectionCount::get();440441 // bound Total number of collections442 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);443444 // check params445 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);446 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);447 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);448 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);449450 // Generate next collection ID451 let next_id = created_count452 .checked_add(1)453 .ok_or(Error::<T>::NumOverflow)?;454455 CreatedCollectionCount::put(next_id);456457 let limits = CollectionLimits {458 sponsored_data_size: chain_limit.custom_data_limit,459 ..Default::default()460 };461462 // Create new collection463 let new_collection = Collection {464 owner: who.clone(),465 name: collection_name,466 mode: mode.clone(),467 mint_mode: false,468 access: AccessMode::Normal,469 description: collection_description,470 decimal_points: decimal_points,471 token_prefix: token_prefix,472 offchain_schema: Vec::new(),473 schema_version: SchemaVersion::ImageURL,474 sponsorship: SponsorshipState::Disabled,475 variable_on_chain_schema: Vec::new(),476 const_on_chain_schema: Vec::new(),477 limits,478 };479480 // Add new collection to map481 <CollectionById<T>>::insert(next_id, new_collection);482483 // call event484 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who.clone()));485486 Ok(())487 }488489 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.490 /// 491 /// # Permissions492 /// 493 /// * Collection Owner.494 /// 495 /// # Arguments496 /// 497 /// * collection_id: collection to destroy.498 #[weight = <T as Config>::WeightInfo::destroy_collection()]499 #[transactional]500 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {501502 let sender = ensure_signed(origin)?;503 let collection = Self::get_collection(collection_id)?;504 Self::check_owner_permissions(&collection, sender)?;505 if !collection.limits.owner_can_destroy {506 fail!(Error::<T>::NoPermission);507 }508509 <AddressTokens<T>>::remove_prefix(collection_id);510 <Allowances<T>>::remove_prefix(collection_id);511 <Balance<T>>::remove_prefix(collection_id);512 <ItemListIndex>::remove(collection_id);513 <AdminList<T>>::remove(collection_id);514 <CollectionById<T>>::remove(collection_id);515 <WhiteList<T>>::remove_prefix(collection_id);516517 <NftItemList<T>>::remove_prefix(collection_id);518 <FungibleItemList<T>>::remove_prefix(collection_id);519 <ReFungibleItemList<T>>::remove_prefix(collection_id);520521 <NftTransferBasket<T>>::remove_prefix(collection_id);522 <FungibleTransferBasket<T>>::remove_prefix(collection_id);523 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);524525 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);526527 DestroyedCollectionCount::put(DestroyedCollectionCount::get()528 .checked_add(1)529 .ok_or(Error::<T>::NumOverflow)?);530531 Ok(())532 }533534 /// Add an address to white list.535 /// 536 /// # Permissions537 /// 538 /// * Collection Owner539 /// * Collection Admin540 /// 541 /// # Arguments542 /// 543 /// * collection_id.544 /// 545 /// * address.546 #[weight = <T as Config>::WeightInfo::add_to_white_list()]547 #[transactional]548 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{549550 let sender = ensure_signed(origin)?;551 let collection = Self::get_collection(collection_id)?;552 Self::check_owner_or_admin_permissions(&collection, sender)?;553554 <WhiteList<T>>::insert(collection_id, address, true);555 556 Ok(())557 }558559 /// Remove an address from white list.560 /// 561 /// # Permissions562 /// 563 /// * Collection Owner564 /// * Collection Admin565 /// 566 /// # Arguments567 /// 568 /// * collection_id.569 /// 570 /// * address.571 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]572 #[transactional]573 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{574575 let sender = ensure_signed(origin)?;576 let collection = Self::get_collection(collection_id)?;577 Self::check_owner_or_admin_permissions(&collection, sender)?;578579 <WhiteList<T>>::remove(collection_id, address);580581 Ok(())582 }583584 /// Toggle between normal and white list access for the methods with access for `Anyone`.585 /// 586 /// # Permissions587 /// 588 /// * Collection Owner.589 /// 590 /// # Arguments591 /// 592 /// * collection_id.593 /// 594 /// * mode: [AccessMode]595 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]596 #[transactional]597 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult598 {599 let sender = ensure_signed(origin)?;600601 let mut target_collection = Self::get_collection(collection_id)?;602 Self::check_owner_permissions(&target_collection, sender)?;603 target_collection.access = mode;604 Self::save_collection(target_collection);605606 Ok(())607 }608609 /// Allows Anyone to create tokens if:610 /// * White List is enabled, and611 /// * Address is added to white list, and612 /// * This method was called with True parameter613 /// 614 /// # Permissions615 /// * Collection Owner616 ///617 /// # Arguments618 /// 619 /// * collection_id.620 /// 621 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.622 #[weight = <T as Config>::WeightInfo::set_mint_permission()]623 #[transactional]624 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult625 {626 let sender = ensure_signed(origin)?;627628 let mut target_collection = Self::get_collection(collection_id)?;629 Self::check_owner_permissions(&target_collection, sender)?;630 target_collection.mint_mode = mint_permission;631 Self::save_collection(target_collection);632633 Ok(())634 }635636 /// Change the owner of the collection.637 /// 638 /// # Permissions639 /// 640 /// * Collection Owner.641 /// 642 /// # Arguments643 /// 644 /// * collection_id.645 /// 646 /// * new_owner.647 #[weight = <T as Config>::WeightInfo::change_collection_owner()]648 #[transactional]649 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {650651 let sender = ensure_signed(origin)?;652 let mut target_collection = Self::get_collection(collection_id)?;653 Self::check_owner_permissions(&target_collection, sender)?;654 target_collection.owner = new_owner;655 Self::save_collection(target_collection);656657 Ok(())658 }659660 /// Adds an admin of the Collection.661 /// 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. 662 /// 663 /// # Permissions664 /// 665 /// * Collection Owner.666 /// * Collection Admin.667 /// 668 /// # Arguments669 /// 670 /// * collection_id: ID of the Collection to add admin for.671 /// 672 /// * new_admin_id: Address of new admin to add.673 #[weight = <T as Config>::WeightInfo::add_collection_admin()]674 #[transactional]675 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {676677 let sender = ensure_signed(origin)?;678 let collection = Self::get_collection(collection_id)?;679 Self::check_owner_or_admin_permissions(&collection, sender)?;680 let mut admin_arr = <AdminList<T>>::get(collection_id);681682 match admin_arr.binary_search(&new_admin_id) {683 Ok(_) => {},684 Err(idx) => {685 let limits = ChainLimit::get();686 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);687 admin_arr.insert(idx, new_admin_id);688 <AdminList<T>>::insert(collection_id, admin_arr);689 }690 }691 Ok(())692 }693694 /// 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.695 ///696 /// # Permissions697 /// 698 /// * Collection Owner.699 /// * Collection Admin.700 /// 701 /// # Arguments702 /// 703 /// * collection_id: ID of the Collection to remove admin for.704 /// 705 /// * account_id: Address of admin to remove.706 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]707 #[transactional]708 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {709710 let sender = ensure_signed(origin)?;711 let collection = Self::get_collection(collection_id)?;712 Self::check_owner_or_admin_permissions(&collection, sender)?;713 let mut admin_arr = <AdminList<T>>::get(collection_id);714715 match admin_arr.binary_search(&account_id) {716 Ok(idx) => {717 admin_arr.remove(idx);718 <AdminList<T>>::insert(collection_id, admin_arr);719 },720 Err(_) => {}721 }722 Ok(())723 }724725 /// # Permissions726 /// 727 /// * Collection Owner728 /// 729 /// # Arguments730 /// 731 /// * collection_id.732 /// 733 /// * new_sponsor.734 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]735 #[transactional]736 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {737738 let sender = ensure_signed(origin)?;739 let mut target_collection = Self::get_collection(collection_id)?;740 Self::check_owner_permissions(&target_collection, sender)?;741742 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);743 Self::save_collection(target_collection);744745 Ok(())746 }747748 /// # Permissions749 /// 750 /// * Sponsor.751 /// 752 /// # Arguments753 /// 754 /// * collection_id.755 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]756 #[transactional]757 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {758759 let sender = ensure_signed(origin)?;760761 let mut target_collection = Self::get_collection(collection_id)?;762 ensure!(763 target_collection.sponsorship.pending_sponsor() == Some(&sender),764 Error::<T>::ConfirmUnsetSponsorFail765 );766767 target_collection.sponsorship = SponsorshipState::Confirmed(sender);768 Self::save_collection(target_collection);769770 Ok(())771 }772773 /// Switch back to pay-per-own-transaction model.774 ///775 /// # Permissions776 ///777 /// * Collection owner.778 /// 779 /// # Arguments780 /// 781 /// * collection_id.782 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]783 #[transactional]784 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {785786 let sender = ensure_signed(origin)?;787788 let mut target_collection = Self::get_collection(collection_id)?;789 Self::check_owner_permissions(&target_collection, sender)?;790791 target_collection.sponsorship = SponsorshipState::Disabled;792 Self::save_collection(target_collection);793794 Ok(())795 }796797 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.798 /// 799 /// # Permissions800 /// 801 /// * Collection Owner.802 /// * Collection Admin.803 /// * Anyone if804 /// * White List is enabled, and805 /// * Address is added to white list, and806 /// * MintPermission is enabled (see SetMintPermission method)807 /// 808 /// # Arguments809 /// 810 /// * collection_id: ID of the collection.811 /// 812 /// * owner: Address, initial owner of the NFT.813 ///814 /// * data: Token data to store on chain.815 // #[weight =816 // (130_000_000 as Weight)817 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))818 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))819 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]820821 #[weight = <T as Config>::WeightInfo::create_item(data.len())]822 #[transactional]823 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {824 let sender = ensure_signed(origin)?;825 Self::create_item_internal(sender, collection_id, owner, data)826 }827828 /// This method creates multiple items in a collection created with CreateCollection method.829 /// 830 /// # Permissions831 /// 832 /// * Collection Owner.833 /// * Collection Admin.834 /// * Anyone if835 /// * White List is enabled, and836 /// * Address is added to white list, and837 /// * MintPermission is enabled (see SetMintPermission method)838 /// 839 /// # Arguments840 /// 841 /// * collection_id: ID of the collection.842 /// 843 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].844 /// 845 /// * owner: Address, initial owner of the NFT.846 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()847 .map(|data| { data.len() })848 .sum())]849 #[transactional]850 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {851852 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);853 let sender = ensure_signed(origin)?;854855 let target_collection = Self::get_collection(collection_id)?;856857 Self::can_create_items_in_collection(&target_collection, &sender, &owner, items_data.len() as u32)?;858859 for data in &items_data {860 Self::validate_create_item_args(&target_collection, data)?;861 }862 for data in &items_data {863 Self::create_item_no_validation(&target_collection, owner.clone(), data.clone())?;864 }865866 Ok(())867 }868869 /// Destroys a concrete instance of NFT.870 /// 871 /// # Permissions872 /// 873 /// * Collection Owner.874 /// * Collection Admin.875 /// * Current NFT Owner.876 /// 877 /// # Arguments878 /// 879 /// * collection_id: ID of the collection.880 /// 881 /// * item_id: ID of NFT to burn.882 #[weight = <T as Config>::WeightInfo::burn_item()]883 #[transactional]884 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {885886 let sender = ensure_signed(origin)?;887888 // Transfer permissions check889 let target_collection = Self::get_collection(collection_id)?;890 ensure!(891 Self::is_item_owner(sender.clone(), &target_collection, item_id) ||892 (893 target_collection.limits.owner_can_transfer &&894 Self::is_owner_or_admin_permissions(&target_collection, sender.clone())895 ),896 Error::<T>::NoPermission897 );898899 if target_collection.access == AccessMode::WhiteList {900 Self::check_white_list(&target_collection, &sender)?;901 }902903 match target_collection.mode904 {905 CollectionMode::NFT => Self::burn_nft_item(&target_collection, item_id)?,906 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &target_collection, value)?,907 CollectionMode::ReFungible => Self::burn_refungible_item(&target_collection, item_id, &sender)?,908 _ => ()909 };910911 // call event912 Self::deposit_event(RawEvent::ItemDestroyed(target_collection.id, item_id));913914 Ok(())915 }916917 /// Change ownership of the token.918 /// 919 /// # Permissions920 /// 921 /// * Collection Owner922 /// * Collection Admin923 /// * Current NFT owner924 ///925 /// # Arguments926 /// 927 /// * recipient: Address of token recipient.928 /// 929 /// * collection_id.930 /// 931 /// * item_id: ID of the item932 /// * Non-Fungible Mode: Required.933 /// * Fungible Mode: Ignored.934 /// * Re-Fungible Mode: Required.935 /// 936 /// * value: Amount to transfer.937 /// * Non-Fungible Mode: Ignored938 /// * Fungible Mode: Must specify transferred amount939 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)940 #[weight = <T as Config>::WeightInfo::transfer()]941 #[transactional]942 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {943 let sender = ensure_signed(origin)?;944 let collection = Self::get_collection(collection_id)?;945946 Self::transfer_internal(sender, recipient, &collection, item_id, value)947 }948949 /// Set, change, or remove approved address to transfer the ownership of the NFT.950 /// 951 /// # Permissions952 /// 953 /// * Collection Owner954 /// * Collection Admin955 /// * Current NFT owner956 /// 957 /// # Arguments958 /// 959 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).960 /// 961 /// * collection_id.962 /// 963 /// * item_id: ID of the item.964 #[weight = <T as Config>::WeightInfo::approve()]965 #[transactional]966 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {967968 let sender = ensure_signed(origin)?;969 let target_collection = Self::get_collection(collection_id)?;970971 Self::token_exists(&target_collection, item_id)?;972973 // Transfer permissions check974 let bypasses_limits = target_collection.limits.owner_can_transfer &&975 Self::is_owner_or_admin_permissions(976 &target_collection,977 sender.clone(),978 );979980 let allowance_limit = if bypasses_limits {981 None982 } else if let Some(amount) = Self::owned_amount(983 sender.clone(),984 &target_collection,985 item_id,986 ) {987 Some(amount)988 } else {989 fail!(Error::<T>::NoPermission);990 };991992 if target_collection.access == AccessMode::WhiteList {993 Self::check_white_list(&target_collection, &sender)?;994 Self::check_white_list(&target_collection, &spender)?;995 }996997 let allowance: u128 = amount998 .checked_add(<Allowances<T>>::get(collection_id, (item_id, &sender, &spender)))999 .ok_or(Error::<T>::NumOverflow)?;1000 if let Some(limit) = allowance_limit {1001 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1002 }1003 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);10041005 Self::deposit_event(RawEvent::Approved(target_collection.id, item_id, sender, spender, allowance));1006 Ok(())1007 }1008 1009 /// 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.1010 /// 1011 /// # Permissions1012 /// * Collection Owner1013 /// * Collection Admin1014 /// * Current NFT owner1015 /// * Address approved by current NFT owner1016 /// 1017 /// # Arguments1018 /// 1019 /// * from: Address that owns token.1020 /// 1021 /// * recipient: Address of token recipient.1022 /// 1023 /// * collection_id.1024 /// 1025 /// * item_id: ID of the item.1026 /// 1027 /// * value: Amount to transfer.1028 #[weight = <T as Config>::WeightInfo::transfer_from()]1029 #[transactional]1030 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {10311032 let sender = ensure_signed(origin)?;1033 let target_collection = Self::get_collection(collection_id)?;10341035 // Check approval1036 let approval: u128 = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));10371038 // Limits check1039 Self::is_correct_transfer(&target_collection, &recipient)?;10401041 // Transfer permissions check 1042 ensure!(1043 approval >= value || 1044 (1045 target_collection.limits.owner_can_transfer &&1046 Self::is_owner_or_admin_permissions(&target_collection, sender.clone())1047 ),1048 Error::<T>::NoPermission1049 );10501051 if target_collection.access == AccessMode::WhiteList {1052 Self::check_white_list(&target_collection, &sender)?;1053 Self::check_white_list(&target_collection, &recipient)?;1054 }10551056 // Reduce approval by transferred amount or remove if remaining approval drops to 01057 if approval.saturating_sub(value) > 0 {1058 <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);1059 }1060 else {1061 <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));1062 }10631064 match target_collection.mode1065 {1066 CollectionMode::NFT => Self::transfer_nft(&target_collection, item_id, from.clone(), recipient.clone())?,1067 CollectionMode::Fungible(_) => Self::transfer_fungible(&target_collection, value, &from, &recipient)?,1068 CollectionMode::ReFungible => Self::transfer_refungible(&target_collection, item_id, value, from.clone(), recipient.clone())?,1069 _ => ()1070 };10711072 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, from, recipient, value));1073 Ok(())1074 }10751076 // #[weight = 0]1077 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {10781079 // // let no_perm_mes = "You do not have permissions to modify this collection";1080 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1081 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1082 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10831084 // // // on_nft_received call10851086 // // Self::transfer(origin, collection_id, item_id, new_owner)?;10871088 // Ok(())1089 // }10901091 /// Set off-chain data schema.1092 /// 1093 /// # Permissions1094 /// 1095 /// * Collection Owner1096 /// * Collection Admin1097 /// 1098 /// # Arguments1099 /// 1100 /// * collection_id.1101 /// 1102 /// * schema: String representing the offchain data schema.1103 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1104 #[transactional]1105 pub fn set_variable_meta_data (1106 origin,1107 collection_id: CollectionId,1108 item_id: TokenId,1109 data: Vec<u8>1110 ) -> DispatchResult {1111 let sender = ensure_signed(origin)?;1112 1113 let target_collection = Self::get_collection(collection_id)?;1114 Self::token_exists(&target_collection, item_id)?;11151116 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);11171118 // Modify permissions check1119 ensure!(Self::is_item_owner(sender.clone(), &target_collection, item_id) ||1120 Self::is_owner_or_admin_permissions(&target_collection, sender.clone()),1121 Error::<T>::NoPermission);11221123 match target_collection.mode1124 {1125 CollectionMode::NFT => Self::set_nft_variable_data(&target_collection, item_id, data)?,1126 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&target_collection, item_id, data)?,1127 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1128 _ => fail!(Error::<T>::UnexpectedCollectionType)1129 };11301131 Ok(())1132 }1133 1134 /// Set schema standard1135 /// ImageURL1136 /// Unique1137 /// 1138 /// # Permissions1139 /// 1140 /// * Collection Owner1141 /// * Collection Admin1142 /// 1143 /// # Arguments1144 /// 1145 /// * collection_id.1146 /// 1147 /// * schema: SchemaVersion: enum1148 #[weight = <T as Config>::WeightInfo::set_schema_version()]1149 #[transactional]1150 pub fn set_schema_version(1151 origin,1152 collection_id: CollectionId,1153 version: SchemaVersion1154 ) -> DispatchResult {1155 let sender = ensure_signed(origin)?;1156 let mut target_collection = Self::get_collection(collection_id)?;1157 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;1158 target_collection.schema_version = version;1159 Self::save_collection(target_collection);11601161 Ok(())1162 }11631164 /// Set off-chain data schema.1165 /// 1166 /// # Permissions1167 /// 1168 /// * Collection Owner1169 /// * Collection Admin1170 /// 1171 /// # Arguments1172 /// 1173 /// * collection_id.1174 /// 1175 /// * schema: String representing the offchain data schema.1176 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1177 #[transactional]1178 pub fn set_offchain_schema(1179 origin,1180 collection_id: CollectionId,1181 schema: Vec<u8>1182 ) -> DispatchResult {1183 let sender = ensure_signed(origin)?;1184 let mut target_collection = Self::get_collection(collection_id)?;1185 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;11861187 // check schema limit1188 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");11891190 target_collection.offchain_schema = schema;1191 Self::save_collection(target_collection);11921193 Ok(())1194 }11951196 /// Set const on-chain data schema.1197 /// 1198 /// # Permissions1199 /// 1200 /// * Collection Owner1201 /// * Collection Admin1202 /// 1203 /// # Arguments1204 /// 1205 /// * collection_id.1206 /// 1207 /// * schema: String representing the const on-chain data schema.1208 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1209 #[transactional]1210 pub fn set_const_on_chain_schema (1211 origin,1212 collection_id: CollectionId,1213 schema: Vec<u8>1214 ) -> DispatchResult {1215 let sender = ensure_signed(origin)?;1216 let mut target_collection = Self::get_collection(collection_id)?;1217 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;12181219 // check schema limit1220 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");12211222 target_collection.const_on_chain_schema = schema;1223 Self::save_collection(target_collection);12241225 Ok(())1226 }12271228 /// Set variable on-chain data schema.1229 /// 1230 /// # Permissions1231 /// 1232 /// * Collection Owner1233 /// * Collection Admin1234 /// 1235 /// # Arguments1236 /// 1237 /// * collection_id.1238 /// 1239 /// * schema: String representing the variable on-chain data schema.1240 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1241 #[transactional]1242 pub fn set_variable_on_chain_schema (1243 origin,1244 collection_id: CollectionId,1245 schema: Vec<u8>1246 ) -> DispatchResult {1247 let sender = ensure_signed(origin)?;1248 let mut target_collection = Self::get_collection(collection_id)?;1249 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;12501251 // check schema limit1252 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12531254 target_collection.variable_on_chain_schema = schema;1255 Self::save_collection(target_collection);12561257 Ok(())1258 }12591260 // Sudo permissions function1261 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1262 #[transactional]1263 pub fn set_chain_limits(1264 origin,1265 limits: ChainLimits1266 ) -> DispatchResult {12671268 #[cfg(not(feature = "runtime-benchmarks"))]1269 ensure_root(origin)?;12701271 <ChainLimit>::put(limits);1272 Ok(())1273 }12741275 /// Enable smart contract self-sponsoring.1276 /// 1277 /// # Permissions1278 /// 1279 /// * Contract Owner1280 /// 1281 /// # Arguments1282 /// 1283 /// * contract address1284 /// * enable flag1285 /// 1286 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1287 #[transactional]1288 pub fn enable_contract_sponsoring(1289 origin,1290 contract_address: T::AccountId,1291 enable: bool1292 ) -> DispatchResult {12931294 let sender = ensure_signed(origin)?;12951296 #[cfg(feature = "runtime-benchmarks")]1297 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());12981299 Self::ensure_contract_owned(sender, &contract_address)?;13001301 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1302 Ok(())1303 }13041305 /// Set the rate limit for contract sponsoring to specified number of blocks.1306 /// 1307 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1308 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1309 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1310 /// from contract endowment if there are at least B blocks between such transactions. 1311 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1312 /// 1313 /// # Permissions1314 /// 1315 /// * Contract Owner1316 /// 1317 /// # Arguments1318 /// 1319 /// -`contract_address`: Address of the contract to sponsor1320 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1321 /// 1322 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1323 #[transactional]1324 pub fn set_contract_sponsoring_rate_limit(1325 origin,1326 contract_address: T::AccountId,1327 rate_limit: T::BlockNumber1328 ) -> DispatchResult {1329 let sender = ensure_signed(origin)?;13301331 #[cfg(feature = "runtime-benchmarks")]1332 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13331334 Self::ensure_contract_owned(sender, &contract_address)?;1335 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1336 Ok(())1337 }13381339 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1340 /// 1341 /// # Permissions1342 /// 1343 /// * Address that deployed smart contract.1344 /// 1345 /// # Arguments1346 /// 1347 /// -`contract_address`: Address of the contract.1348 /// 1349 /// - `enable`: . 1350 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1351 #[transactional]1352 pub fn toggle_contract_white_list(1353 origin,1354 contract_address: T::AccountId,1355 enable: bool1356 ) -> DispatchResult {1357 let sender = ensure_signed(origin)?;13581359 #[cfg(feature = "runtime-benchmarks")]1360 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13611362 Self::ensure_contract_owned(sender, &contract_address)?;1363 if enable {1364 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1365 } else {1366 <ContractWhiteListEnabled<T>>::remove(contract_address);1367 }1368 Ok(())1369 }1370 1371 /// Add an address to smart contract white list.1372 /// 1373 /// # Permissions1374 /// 1375 /// * Address that deployed smart contract.1376 /// 1377 /// # Arguments1378 /// 1379 /// -`contract_address`: Address of the contract.1380 ///1381 /// -`account_address`: Address to add.1382 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1383 #[transactional]1384 pub fn add_to_contract_white_list(1385 origin,1386 contract_address: T::AccountId,1387 account_address: T::AccountId1388 ) -> DispatchResult {1389 let sender = ensure_signed(origin)?;13901391 #[cfg(feature = "runtime-benchmarks")]1392 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1393 1394 Self::ensure_contract_owned(sender, &contract_address)?; 1395 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1396 Ok(())1397 }13981399 /// Remove an address from smart contract white list.1400 /// 1401 /// # Permissions1402 /// 1403 /// * Address that deployed smart contract.1404 /// 1405 /// # Arguments1406 /// 1407 /// -`contract_address`: Address of the contract.1408 ///1409 /// -`account_address`: Address to remove.1410 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1411 #[transactional]1412 pub fn remove_from_contract_white_list(1413 origin,1414 contract_address: T::AccountId,1415 account_address: T::AccountId1416 ) -> DispatchResult {1417 let sender = ensure_signed(origin)?;14181419 #[cfg(feature = "runtime-benchmarks")]1420 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14211422 Self::ensure_contract_owned(sender, &contract_address)?;1423 <ContractWhiteList<T>>::remove(contract_address, account_address);1424 Ok(())1425 }14261427 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1428 #[transactional]1429 pub fn set_collection_limits(1430 origin,1431 collection_id: u32,1432 new_limits: CollectionLimits<T::BlockNumber>,1433 ) -> DispatchResult {1434 let sender = ensure_signed(origin)?;1435 let mut target_collection = Self::get_collection(collection_id)?;1436 Self::check_owner_permissions(&target_collection, sender.clone())?;1437 let old_limits = &target_collection.limits;1438 let chain_limits = ChainLimit::get();14391440 // collection bounds1441 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1442 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1443 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1444 Error::<T>::CollectionLimitBoundsExceeded);14451446 // token_limit check prev1447 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1448 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);14491450 ensure!(1451 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1452 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1453 Error::<T>::OwnerPermissionsCantBeReverted,1454 );14551456 target_collection.limits = new_limits;1457 Self::save_collection(target_collection);14581459 Ok(())1460 } 1461 }1462}14631464impl<T: Config> Module<T> {1465 pub fn create_item_internal(sender: T::AccountId, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1466 let target_collection = Self::get_collection(collection_id)?;14671468 Self::can_create_items_in_collection(&target_collection, &sender, &owner, 1)?;1469 Self::validate_create_item_args(&target_collection, &data)?;1470 Self::create_item_no_validation(&target_collection, owner, data)?;14711472 Ok(())1473 }14741475 pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1476 // Limits check1477 Self::is_correct_transfer(target_collection, &recipient)?;14781479 // Transfer permissions check1480 ensure!(Self::is_item_owner(sender.clone(), target_collection, item_id) ||1481 Self::is_owner_or_admin_permissions(target_collection, sender.clone()),1482 Error::<T>::NoPermission);14831484 if target_collection.access == AccessMode::WhiteList {1485 Self::check_white_list(target_collection, &sender)?;1486 Self::check_white_list(target_collection, &recipient)?;1487 }14881489 match target_collection.mode1490 {1491 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1492 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1493 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1494 _ => ()1495 };14961497 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender, recipient, value));14981499 Ok(())1500 }150115021503 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::AccountId) -> DispatchResult {1504 let collection_id = collection.id;15051506 // check token limit and account token limit1507 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1508 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1509 1510 Ok(())1511 }15121513 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::AccountId, owner: &T::AccountId, amount: u32) -> DispatchResult {1514 let collection_id = collection.id;15151516 // check token limit and account token limit1517 let total_items: u32 = ItemListIndex::get(collection_id)1518 .checked_add(amount)1519 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1520 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner).len() as u32)1521 .checked_add(amount)1522 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1523 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1524 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);15251526 if !Self::is_owner_or_admin_permissions(collection, sender.clone()) {1527 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1528 Self::check_white_list(collection, owner)?;1529 Self::check_white_list(collection, sender)?;1530 }15311532 Ok(())1533 }15341535 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1536 match target_collection.mode1537 {1538 CollectionMode::NFT => {1539 if let CreateItemData::NFT(data) = data {1540 // check sizes1541 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1542 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1543 } else {1544 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1545 }1546 },1547 CollectionMode::Fungible(_) => {1548 if let CreateItemData::Fungible(_) = data {1549 } else {1550 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1551 }1552 },1553 CollectionMode::ReFungible => {1554 if let CreateItemData::ReFungible(data) = data {15551556 // check sizes1557 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1558 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);15591560 // Check refungibility limits1561 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1562 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1563 } else {1564 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1565 }1566 },1567 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1568 };15691570 Ok(())1571 }15721573 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1574 match data1575 {1576 CreateItemData::NFT(data) => {1577 let item = NftItemType {1578 owner: owner.clone(),1579 const_data: data.const_data,1580 variable_data: data.variable_data1581 };15821583 Self::add_nft_item(collection, item)?;1584 },1585 CreateItemData::Fungible(data) => {1586 Self::add_fungible_item(collection, &owner, data.value)?;1587 },1588 CreateItemData::ReFungible(data) => {1589 let mut owner_list = Vec::new();1590 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});15911592 let item = ReFungibleItemType {1593 owner: owner_list,1594 const_data: data.const_data,1595 variable_data: data.variable_data1596 };15971598 Self::add_refungible_item(collection, item)?;1599 }1600 };16011602 Ok(())1603 }16041605 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::AccountId, value: u128) -> DispatchResult {1606 let collection_id = collection.id;16071608 // Does new owner already have an account?1609 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner).value;16101611 // Mint 1612 let item = FungibleItemType {1613 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1614 };1615 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16161617 // Update balance1618 let new_balance = <Balance<T>>::get(collection_id, owner)1619 .checked_add(value)1620 .ok_or(Error::<T>::NumOverflow)?;1621 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16221623 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1624 Ok(())1625 }16261627 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1628 let collection_id = collection.id;16291630 let current_index = <ItemListIndex>::get(collection_id)1631 .checked_add(1)1632 .ok_or(Error::<T>::NumOverflow)?;1633 let itemcopy = item.clone();16341635 ensure!(1636 item.owner.len() == 1,1637 Error::<T>::BadCreateRefungibleCall,1638 );1639 let item_owner = item.owner.first().expect("only one owner is defined");16401641 let value = item_owner.fraction;1642 let owner = item_owner.owner.clone();16431644 Self::add_token_index(collection_id, current_index, &owner)?;16451646 <ItemListIndex>::insert(collection_id, current_index);1647 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16481649 // Update balance1650 let new_balance = <Balance<T>>::get(collection_id, &owner)1651 .checked_add(value)1652 .ok_or(Error::<T>::NumOverflow)?;1653 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16541655 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1656 Ok(())1657 }16581659 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::AccountId>) -> DispatchResult {1660 let collection_id = collection.id;16611662 let current_index = <ItemListIndex>::get(collection_id)1663 .checked_add(1)1664 .ok_or(Error::<T>::NumOverflow)?;16651666 let item_owner = item.owner.clone();1667 Self::add_token_index(collection_id, current_index, &item.owner)?;16681669 <ItemListIndex>::insert(collection_id, current_index);1670 <NftItemList<T>>::insert(collection_id, current_index, item);16711672 // Update balance1673 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1674 .checked_add(1)1675 .ok_or(Error::<T>::NumOverflow)?;1676 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16771678 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1679 Ok(())1680 }16811682 fn burn_refungible_item(1683 collection: &CollectionHandle<T>,1684 item_id: TokenId,1685 owner: &T::AccountId,1686 ) -> DispatchResult {1687 let collection_id = collection.id;16881689 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1690 .ok_or(Error::<T>::TokenNotFound)?;1691 let rft_balance = token1692 .owner1693 .iter()1694 .find(|&i| i.owner == *owner)1695 .ok_or(Error::<T>::TokenNotFound)?;1696 Self::remove_token_index(collection_id, item_id, owner)?;16971698 // update balance1699 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1700 .checked_sub(rft_balance.fraction)1701 .ok_or(Error::<T>::NumOverflow)?;1702 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);17031704 // Re-create owners list with sender removed1705 let index = token1706 .owner1707 .iter()1708 .position(|i| i.owner == *owner)1709 .expect("owned item is exists");1710 token.owner.remove(index);1711 let owner_count = token.owner.len();17121713 // Burn the token completely if this was the last (only) owner1714 if owner_count == 0 {1715 <ReFungibleItemList<T>>::remove(collection_id, item_id);1716 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1717 }1718 else {1719 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1720 }17211722 Ok(())1723 }17241725 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1726 let collection_id = collection.id;17271728 let item = <NftItemList<T>>::get(collection_id, item_id)1729 .ok_or(Error::<T>::TokenNotFound)?;1730 Self::remove_token_index(collection_id, item_id, &item.owner)?;17311732 // update balance1733 let new_balance = <Balance<T>>::get(collection_id, &item.owner)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);1738 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);17391740 Ok(())1741 }17421743 fn burn_fungible_item(owner: &T::AccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {1744 let collection_id = collection.id;17451746 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1747 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17481749 // update balance1750 let new_balance = <Balance<T>>::get(collection_id, owner)1751 .checked_sub(value)1752 .ok_or(Error::<T>::NumOverflow)?;1753 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17541755 if balance.value - value > 0 {1756 balance.value -= value;1757 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1758 }1759 else {1760 <FungibleItemList<T>>::remove(collection_id, owner);1761 }17621763 Ok(())1764 }17651766 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1767 Ok(<CollectionById<T>>::get(collection_id)1768 .map(|collection| CollectionHandle {1769 id: collection_id,1770 collection1771 })1772 .ok_or(Error::<T>::CollectionNotFound)?)1773 }17741775 fn save_collection(collection: CollectionHandle<T>) {1776 <CollectionById<T>>::insert(collection.id, collection.collection);1777 }17781779 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: T::AccountId) -> DispatchResult {1780 ensure!(1781 subject == target_collection.owner,1782 Error::<T>::NoPermission1783 );17841785 Ok(())1786 }17871788 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: T::AccountId) -> bool {1789 subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)1790 }17911792 fn check_owner_or_admin_permissions(1793 collection: &CollectionHandle<T>,1794 subject: T::AccountId,1795 ) -> DispatchResult {1796 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);17971798 Ok(())1799 }18001801 fn owned_amount(1802 subject: T::AccountId,1803 target_collection: &CollectionHandle<T>,1804 item_id: TokenId,1805 ) -> Option<u128> {1806 let collection_id = target_collection.id;18071808 match target_collection.mode {1809 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == subject)1810 .then(|| 1),1811 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject)1812 .value),1813 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1814 .owner1815 .iter()1816 .find(|i| i.owner == subject)1817 .map(|i| i.fraction),1818 CollectionMode::Invalid => None,1819 }1820 }18211822 fn is_item_owner(subject: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {1823 match target_collection.mode {1824 CollectionMode::Fungible(_) => true,1825 _ => Self::owned_amount(subject, target_collection, item_id).is_some(),1826 }1827 }18281829 fn check_white_list(collection: &CollectionHandle<T>, address: &T::AccountId) -> DispatchResult {1830 let collection_id = collection.id;18311832 let mes = Error::<T>::AddresNotInWhiteList;1833 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18341835 Ok(())1836 }18371838 /// Check if token exists. In case of Fungible, check if there is an entry for 1839 /// the owner in fungible balances double map1840 fn token_exists(1841 target_collection: &CollectionHandle<T>,1842 item_id: TokenId,1843 ) -> DispatchResult {1844 let collection_id = target_collection.id;1845 let exists = match target_collection.mode1846 {1847 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1848 CollectionMode::Fungible(_) => true,1849 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1850 _ => false1851 };18521853 ensure!(exists == true, Error::<T>::TokenNotFound);1854 Ok(())1855 }18561857 fn transfer_fungible(1858 collection: &CollectionHandle<T>,1859 value: u128,1860 owner: &T::AccountId,1861 recipient: &T::AccountId,1862 ) -> DispatchResult {1863 let collection_id = collection.id;18641865 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1866 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18671868 // Send balance to recipient (updates balanceOf of recipient)1869 Self::add_fungible_item(collection, recipient, value)?;18701871 // update balanceOf of sender1872 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18731874 // Reduce or remove sender1875 if balance.value == value {1876 <FungibleItemList<T>>::remove(collection_id, owner);1877 }1878 else {1879 balance.value -= value;1880 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1881 }18821883 Ok(())1884 }18851886 fn transfer_refungible(1887 collection: &CollectionHandle<T>,1888 item_id: TokenId,1889 value: u128,1890 owner: T::AccountId,1891 new_owner: T::AccountId,1892 ) -> DispatchResult {1893 let collection_id = collection.id;1894 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)1895 .ok_or(Error::<T>::TokenNotFound)?;18961897 let item = full_item1898 .owner1899 .iter()1900 .filter(|i| i.owner == owner)1901 .next()1902 .ok_or(Error::<T>::TokenNotFound)?;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 balance_new_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(), balance_new_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 .expect("old owner does present in refungible")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, &new_owner)?;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 .expect("old owner does present in refungible")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 .expect("new owner has account")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)?;1961 }19621963 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1964 }19651966 Ok(())1967 }19681969 fn transfer_nft(1970 collection: &CollectionHandle<T>,1971 item_id: TokenId,1972 sender: T::AccountId,1973 new_owner: T::AccountId,1974 ) -> DispatchResult {1975 let collection_id = collection.id;1976 let mut item = <NftItemList<T>>::get(collection_id, item_id)1977 .ok_or(Error::<T>::TokenNotFound)?;19781979 ensure!(1980 sender == item.owner,1981 Error::<T>::MustBeTokenOwner1982 );19831984 // update balance1985 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1986 .checked_sub(1)1987 .ok_or(Error::<T>::NumOverflow)?;1988 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19891990 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1991 .checked_add(1)1992 .ok_or(Error::<T>::NumOverflow)?;1993 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19941995 // change owner1996 let old_owner = item.owner.clone();1997 item.owner = new_owner.clone();1998 <NftItemList<T>>::insert(collection_id, item_id, item);19992000 // update index collection2001 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20022003 Ok(())2004 }2005 2006 fn set_re_fungible_variable_data(2007 collection: &CollectionHandle<T>,2008 item_id: TokenId,2009 data: Vec<u8>2010 ) -> DispatchResult {2011 let collection_id = collection.id;2012 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2013 .ok_or(Error::<T>::TokenNotFound)?;20142015 item.variable_data = data;20162017 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20182019 Ok(())2020 }20212022 fn set_nft_variable_data(2023 collection: &CollectionHandle<T>,2024 item_id: TokenId,2025 data: Vec<u8>2026 ) -> DispatchResult {2027 let collection_id = collection.id;2028 let mut item = <NftItemList<T>>::get(collection_id, item_id)2029 .ok_or(Error::<T>::TokenNotFound)?;2030 2031 item.variable_data = data;20322033 <NftItemList<T>>::insert(collection_id, item_id, item);2034 2035 Ok(())2036 }20372038 fn init_collection(item: &Collection<T>) {2039 // check params2040 assert!(2041 item.decimal_points <= MAX_DECIMAL_POINTS,2042 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2043 );2044 assert!(2045 item.name.len() <= 64,2046 "Collection name can not be longer than 63 char"2047 );2048 assert!(2049 item.name.len() <= 256,2050 "Collection description can not be longer than 255 char"2051 );2052 assert!(2053 item.token_prefix.len() <= 16,2054 "Token prefix can not be longer than 15 char"2055 );20562057 // Generate next collection ID2058 let next_id = CreatedCollectionCount::get()2059 .checked_add(1)2060 .unwrap();20612062 CreatedCollectionCount::put(next_id);2063 }20642065 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2066 let current_index = <ItemListIndex>::get(collection_id)2067 .checked_add(1)2068 .unwrap();20692070 let item_owner = item.owner.clone();2071 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();20722073 <ItemListIndex>::insert(collection_id, current_index);20742075 // Update balance2076 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2077 .checked_add(1)2078 .unwrap();2079 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2080 }20812082 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2083 let current_index = <ItemListIndex>::get(collection_id)2084 .checked_add(1)2085 .unwrap();20862087 Self::add_token_index(collection_id, current_index, owner).unwrap();20882089 <ItemListIndex>::insert(collection_id, current_index);20902091 // Update balance2092 let new_balance = <Balance<T>>::get(collection_id, owner)2093 .checked_add(item.value)2094 .unwrap();2095 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2096 }20972098 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2099 let current_index = <ItemListIndex>::get(collection_id)2100 .checked_add(1)2101 .unwrap();21022103 let value = item.owner.first().unwrap().fraction;2104 let owner = item.owner.first().unwrap().owner.clone();21052106 Self::add_token_index(collection_id, current_index, &owner).unwrap();21072108 <ItemListIndex>::insert(collection_id, current_index);21092110 // Update balance2111 let new_balance = <Balance<T>>::get(collection_id, &owner)2112 .checked_add(value)2113 .unwrap();2114 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2115 }21162117 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {2118 // add to account limit2119 if <AccountItemCount<T>>::contains_key(owner) {21202121 // bound Owned tokens by a single address2122 let count = <AccountItemCount<T>>::get(owner);2123 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21242125 <AccountItemCount<T>>::insert(owner.clone(), count2126 .checked_add(1)2127 .ok_or(Error::<T>::NumOverflow)?);2128 }2129 else {2130 <AccountItemCount<T>>::insert(owner.clone(), 1);2131 }21322133 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2134 if list_exists {2135 let mut list = <AddressTokens<T>>::get(collection_id, owner);2136 let item_contains = list.contains(&item_index.clone());21372138 if !item_contains {2139 list.push(item_index.clone());2140 }21412142 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2143 } else {2144 let mut itm = Vec::new();2145 itm.push(item_index.clone());2146 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2147 }21482149 Ok(())2150 }21512152 fn remove_token_index(2153 collection_id: CollectionId,2154 item_index: TokenId,2155 owner: &T::AccountId,2156 ) -> DispatchResult {21572158 // update counter2159 <AccountItemCount<T>>::insert(owner.clone(), 2160 <AccountItemCount<T>>::get(owner)2161 .checked_sub(1)2162 .ok_or(Error::<T>::NumOverflow)?);216321642165 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2166 if list_exists {2167 let mut list = <AddressTokens<T>>::get(collection_id, owner);2168 let item_contains = list.contains(&item_index.clone());21692170 if item_contains {2171 list.retain(|&item| item != item_index);2172 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2173 }2174 }21752176 Ok(())2177 }21782179 fn move_token_index(2180 collection_id: CollectionId,2181 item_index: TokenId,2182 old_owner: &T::AccountId,2183 new_owner: &T::AccountId,2184 ) -> DispatchResult {2185 Self::remove_token_index(collection_id, item_index, old_owner)?;2186 Self::add_token_index(collection_id, item_index, new_owner)?;21872188 Ok(())2189 }2190 2191 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2192 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);21932194 Ok(())2195 }2196}1//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::*;1516pub use frame_support::{17 construct_runtime, decl_event, decl_module, decl_storage, decl_error,18 dispatch::DispatchResult,19 ensure, fail, parameter_types,20 traits::{21 Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,22 Randomness, IsSubType, WithdrawReasons,23 },24 weights::{25 constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},26 DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,27 WeightToFeePolynomial, DispatchClass,28 },29 StorageValue,30 transactional,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use core::ops::{Deref, DerefMut};36use nft_data_structs::{37 MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,38 AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,39 CollectionId, CollectionMode, TokenId, 40 SchemaVersion, SponsorshipState, Ownership,41 NftItemType, FungibleItemType, ReFungibleItemType42};4344#[cfg(test)]45mod mock;4647#[cfg(test)]48mod tests;4950mod default_weights;5152#[cfg(feature = "runtime-benchmarks")]53mod benchmarking;5455pub trait WeightInfo {56 fn create_collection() -> Weight;57 fn destroy_collection() -> Weight;58 fn add_to_white_list() -> Weight;59 fn remove_from_white_list() -> Weight;60 fn set_public_access_mode() -> Weight;61 fn set_mint_permission() -> Weight;62 fn change_collection_owner() -> Weight;63 fn add_collection_admin() -> Weight;64 fn remove_collection_admin() -> Weight;65 fn set_collection_sponsor() -> Weight;66 fn confirm_sponsorship() -> Weight;67 fn remove_collection_sponsor() -> Weight;68 fn create_item(s: usize) -> Weight;69 fn burn_item() -> Weight;70 fn transfer() -> Weight;71 fn approve() -> Weight;72 fn transfer_from() -> Weight;73 fn set_offchain_schema() -> Weight;74 fn set_const_on_chain_schema() -> Weight;75 fn set_variable_on_chain_schema() -> Weight;76 fn set_variable_meta_data() -> Weight;77 fn enable_contract_sponsoring() -> Weight;78 fn set_schema_version() -> Weight;79 fn set_chain_limits() -> Weight;80 fn set_contract_sponsoring_rate_limit() -> Weight;81 fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;82 fn toggle_contract_white_list() -> Weight;83 fn add_to_contract_white_list() -> Weight;84 fn remove_from_contract_white_list() -> Weight;85 fn set_collection_limits() -> Weight;86}8788decl_error! {89 /// Error for non-fungible-token module.90 pub enum Error for Module<T: Config> {91 /// Total collections bound exceeded.92 TotalCollectionsLimitExceeded,93 /// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.94 CollectionDecimalPointLimitExceeded, 95 /// Collection name can not be longer than 63 char.96 CollectionNameLimitExceeded, 97 /// Collection description can not be longer than 255 char.98 CollectionDescriptionLimitExceeded, 99 /// Token prefix can not be longer than 15 char.100 CollectionTokenPrefixLimitExceeded,101 /// This collection does not exist.102 CollectionNotFound,103 /// Item not exists.104 TokenNotFound,105 /// Admin not found106 AdminNotFound,107 /// Arithmetic calculation overflow.108 NumOverflow, 109 /// Account already has admin role.110 AlreadyAdmin, 111 /// You do not own this collection.112 NoPermission,113 /// This address is not set as sponsor, use setCollectionSponsor first.114 ConfirmUnsetSponsorFail,115 /// Collection is not in mint mode.116 PublicMintingNotAllowed,117 /// Sender parameter and item owner must be equal.118 MustBeTokenOwner,119 /// Item balance not enough.120 TokenValueTooLow,121 /// Size of item is too large.122 NftSizeLimitExceeded,123 /// No approve found124 ApproveNotFound,125 /// Requested value more than approved.126 TokenValueNotEnough,127 /// Only approved addresses can call this method.128 ApproveRequired,129 /// Address is not in white list.130 AddresNotInWhiteList,131 /// Number of collection admins bound exceeded.132 CollectionAdminsLimitExceeded,133 /// Owned tokens by a single address bound exceeded.134 AddressOwnershipLimitExceeded,135 /// Length of items properties must be greater than 0.136 EmptyArgument,137 /// const_data exceeded data limit.138 TokenConstDataLimitExceeded,139 /// variable_data exceeded data limit.140 TokenVariableDataLimitExceeded,141 /// Not NFT item data used to mint in NFT collection.142 NotNftDataUsedToMintNftCollectionToken,143 /// Not Fungible item data used to mint in Fungible collection.144 NotFungibleDataUsedToMintFungibleCollectionToken,145 /// Not Re Fungible item data used to mint in Re Fungible collection.146 NotReFungibleDataUsedToMintReFungibleCollectionToken,147 /// Unexpected collection type.148 UnexpectedCollectionType,149 /// Can't store metadata in fungible tokens.150 CantStoreMetadataInFungibleTokens,151 /// Collection token limit exceeded152 CollectionTokenLimitExceeded,153 /// Account token limit exceeded per collection154 AccountTokenLimitExceeded,155 /// Collection limit bounds per collection exceeded156 CollectionLimitBoundsExceeded,157 /// Tried to enable permissions which are only permitted to be disabled158 OwnerPermissionsCantBeReverted,159 /// Schema data size limit bound exceeded160 SchemaDataLimitExceeded,161 /// Maximum refungibility exceeded162 WrongRefungiblePieces,163 /// createRefungible should be called with one owner164 BadCreateRefungibleCall,165 }166}167168pub struct CollectionHandle<T: frame_system::Config> {169 pub id: CollectionId,170 pub collection: Collection<T>,171}172173impl<T: frame_system::Config> Deref for CollectionHandle<T> {174 type Target = Collection<T>;175176 fn deref(&self) -> &Self::Target {177 &self.collection178 }179}180181impl<T: frame_system::Config> DerefMut for CollectionHandle<T> {182 fn deref_mut(&mut self) -> &mut Self::Target {183 &mut self.collection184 }185}186187188189pub trait Config: system::Config + Sized {190 type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;191192 /// Weight information for extrinsics in this pallet.193 type WeightInfo: WeightInfo;194195 type Currency: Currency<Self::AccountId>;196 type CollectionCreationPrice: Get<<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance>;197 type TreasuryAccountId: Get<Self::AccountId>;198}199200// # Used definitions201//202// ## User control levels203//204// chain-controlled - key is uncontrolled by user205// i.e autoincrementing index206// can use non-cryptographic hash207// real - key is controlled by user208// but it is hard to generate enough colliding values, i.e owner of signed txs209// can use non-cryptographic hash210// controlled - key is completly controlled by users211// i.e maps with mutable keys212// should use cryptographic hash213//214// ## User control level downgrade reasons215//216// ?1 - chain-controlled -> controlled217// collections/tokens can be destroyed, resulting in massive holes218// ?2 - chain-controlled -> controlled219// same as ?1, but can be only added, resulting in easier exploitation220// ?3 - real -> controlled221// no confirmation required, so addresses can be easily generated222decl_storage! {223 trait Store for Module<T: Config> as Nft {224225 //#region Private members226 /// Id of next collection227 CreatedCollectionCount: u32;228 /// Used for migrations229 ChainVersion: u64;230 /// Id of last collection token231 /// Collection id (controlled?1)232 ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;233 //#endregion234235 //#region Chain limits struct236 pub ChainLimit get(fn chain_limit) config(): ChainLimits;237 //#endregion238239 //#region Bound counters240 /// Amount of collections destroyed, used for total amount tracking with241 /// CreatedCollectionCount242 DestroyedCollectionCount: u32;243 /// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)244 /// Account id (real)245 pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;246 //#endregion247248 //#region Basic collections249 /// Collection info250 /// Collection id (controlled?1)251 pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;252 /// List of collection admins253 /// Collection id (controlled?2)254 pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::AccountId>;255 /// Whitelisted collection users256 /// Collection id (controlled?2), user id (controlled?3)257 pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;258 //#endregion259260 /// How many of collection items user have261 /// Collection id (controlled?2), account id (real)262 pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;263264 /// Amount of items which spender can transfer out of owners account (via transferFrom)265 /// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))266 /// TODO: Off chain worker should remove from this map when token gets removed267 pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;268269 //#region Item collections270 /// Collection id (controlled?2), token id (controlled?1)271 pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::AccountId>>;272 /// Collection id (controlled?2), owner (controlled?2)273 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;274 /// Collection id (controlled?2), token id (controlled?1)275 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::AccountId>>;276 //#endregion277278 //#region Index list279 /// Collection id (controlled?2), tokens owner (controlled?2)280 pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;281 //#endregion282283 //#region Tokens transfer rate limit baskets284 /// (Collection id (controlled?2), who created (real))285 /// TODO: Off chain worker should remove from this map when collection gets removed286 pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;287 /// Collection id (controlled?2), token id (controlled?2)288 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;289 /// Collection id (controlled?2), owning user (real)290 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;291 /// Collection id (controlled?2), token id (controlled?2)292 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;293 //#endregion294295 /// Variable metadata sponsoring296 /// Collection id (controlled?2), token id (controlled?2)297 pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;298 299 //#region Contract Sponsorship and Ownership300 /// Contract address (real)301 pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => Option<T::AccountId>;302 /// Contract address (real)303 pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;304 /// (Contract address(real), caller (real))305 pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;306 /// Contract address (real)307 pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;308 /// Contract address (real)309 pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 310 /// Contract address (real) => Whitelisted user (controlled?3)311 pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(blake2_128_concat) T::AccountId => bool; 312 //#endregion313 }314 add_extra_genesis {315 build(|config: &GenesisConfig<T>| {316 // Modification of storage317 for (_num, _c) in &config.collection_id {318 <Module<T>>::init_collection(_c);319 }320321 for (_num, _c, _i) in &config.nft_item_id {322 <Module<T>>::init_nft_token(*_c, _i);323 }324325 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {326 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);327 }328329 for (_num, _c, _i) in &config.refungible_item_id {330 <Module<T>>::init_refungible_token(*_c, _i);331 }332 })333 }334}335336decl_event!(337 pub enum Event<T>338 where339 AccountId = <T as system::Config>::AccountId,340 {341 /// New collection was created342 /// 343 /// # Arguments344 /// 345 /// * collection_id: Globally unique identifier of newly created collection.346 /// 347 /// * mode: [CollectionMode] converted into u8.348 /// 349 /// * account_id: Collection owner.350 CollectionCreated(CollectionId, u8, AccountId),351352 /// New item was created.353 /// 354 /// # Arguments355 /// 356 /// * collection_id: Id of the collection where item was created.357 /// 358 /// * item_id: Id of an item. Unique within the collection.359 ///360 /// * recipient: Owner of newly created item 361 ItemCreated(CollectionId, TokenId, AccountId),362363 /// Collection item was burned.364 /// 365 /// # Arguments366 /// 367 /// collection_id.368 /// 369 /// item_id: Identifier of burned NFT.370 ItemDestroyed(CollectionId, TokenId),371372 /// Item was transferred373 ///374 /// * collection_id: Id of collection to which item is belong375 ///376 /// * item_id: Id of an item377 ///378 /// * sender: Original owner of item379 ///380 /// * recipient: New owner of item381 ///382 /// * amount: Always 1 for NFT383 Transfer(CollectionId, TokenId, AccountId, AccountId, u128),384385 /// * collection_id386 ///387 /// * item_id388 ///389 /// * sender390 ///391 /// * spender392 ///393 /// * amount394 Approved(CollectionId, TokenId, AccountId, AccountId, u128),395 }396);397398decl_module! {399 pub struct Module<T: Config> for enum Call 400 where 401 origin: T::Origin402 {403 fn deposit_event() = default;404 type Error = Error<T>;405406 fn on_initialize(_now: T::BlockNumber) -> Weight {407 0408 }409410 /// 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.411 /// 412 /// # Permissions413 /// 414 /// * Anyone.415 /// 416 /// # Arguments417 /// 418 /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.419 /// 420 /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.421 /// 422 /// * token_prefix: UTF-8 string with token prefix.423 /// 424 /// * mode: [CollectionMode] collection type and type dependent data.425 // returns collection ID426 #[weight = <T as Config>::WeightInfo::create_collection()]427 #[transactional]428 pub fn create_collection(origin,429 collection_name: Vec<u16>,430 collection_description: Vec<u16>,431 token_prefix: Vec<u8>,432 mode: CollectionMode) -> DispatchResult {433434 // Anyone can create a collection435 let who = ensure_signed(origin)?;436437 // Take a (non-refundable) deposit of collection creation438 let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();439 imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(440 &T::TreasuryAccountId::get(),441 T::CollectionCreationPrice::get(),442 ));443 <T as Config>::Currency::settle(444 &who,445 imbalance,446 WithdrawReasons::TRANSFER,447 ExistenceRequirement::KeepAlive,448 ).map_err(|_| Error::<T>::NoPermission)?;449450 let decimal_points = match mode {451 CollectionMode::Fungible(points) => points,452 _ => 0453 };454455 let chain_limit = ChainLimit::get();456457 let created_count = CreatedCollectionCount::get();458 let destroyed_count = DestroyedCollectionCount::get();459460 // bound Total number of collections461 ensure!(created_count - destroyed_count < chain_limit.collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);462463 // check params464 ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);465 ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);466 ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);467 ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);468469 // Generate next collection ID470 let next_id = created_count471 .checked_add(1)472 .ok_or(Error::<T>::NumOverflow)?;473474 CreatedCollectionCount::put(next_id);475476 let limits = CollectionLimits {477 sponsored_data_size: chain_limit.custom_data_limit,478 ..Default::default()479 };480481 // Create new collection482 let new_collection = Collection {483 owner: who.clone(),484 name: collection_name,485 mode: mode.clone(),486 mint_mode: false,487 access: AccessMode::Normal,488 description: collection_description,489 decimal_points: decimal_points,490 token_prefix: token_prefix,491 offchain_schema: Vec::new(),492 schema_version: SchemaVersion::ImageURL,493 sponsorship: SponsorshipState::Disabled,494 variable_on_chain_schema: Vec::new(),495 const_on_chain_schema: Vec::new(),496 limits,497 };498499 // Add new collection to map500 <CollectionById<T>>::insert(next_id, new_collection);501502 // call event503 Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.into(), who.clone()));504505 Ok(())506 }507508 /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.509 /// 510 /// # Permissions511 /// 512 /// * Collection Owner.513 /// 514 /// # Arguments515 /// 516 /// * collection_id: collection to destroy.517 #[weight = <T as Config>::WeightInfo::destroy_collection()]518 #[transactional]519 pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {520521 let sender = ensure_signed(origin)?;522 let collection = Self::get_collection(collection_id)?;523 Self::check_owner_permissions(&collection, sender)?;524 if !collection.limits.owner_can_destroy {525 fail!(Error::<T>::NoPermission);526 }527528 <AddressTokens<T>>::remove_prefix(collection_id);529 <Allowances<T>>::remove_prefix(collection_id);530 <Balance<T>>::remove_prefix(collection_id);531 <ItemListIndex>::remove(collection_id);532 <AdminList<T>>::remove(collection_id);533 <CollectionById<T>>::remove(collection_id);534 <WhiteList<T>>::remove_prefix(collection_id);535536 <NftItemList<T>>::remove_prefix(collection_id);537 <FungibleItemList<T>>::remove_prefix(collection_id);538 <ReFungibleItemList<T>>::remove_prefix(collection_id);539540 <NftTransferBasket<T>>::remove_prefix(collection_id);541 <FungibleTransferBasket<T>>::remove_prefix(collection_id);542 <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);543544 <VariableMetaDataBasket<T>>::remove_prefix(collection_id);545546 DestroyedCollectionCount::put(DestroyedCollectionCount::get()547 .checked_add(1)548 .ok_or(Error::<T>::NumOverflow)?);549550 Ok(())551 }552553 /// Add an address to white list.554 /// 555 /// # Permissions556 /// 557 /// * Collection Owner558 /// * Collection Admin559 /// 560 /// # Arguments561 /// 562 /// * collection_id.563 /// 564 /// * address.565 #[weight = <T as Config>::WeightInfo::add_to_white_list()]566 #[transactional]567 pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{568569 let sender = ensure_signed(origin)?;570 let collection = Self::get_collection(collection_id)?;571 Self::check_owner_or_admin_permissions(&collection, sender)?;572573 <WhiteList<T>>::insert(collection_id, address, true);574 575 Ok(())576 }577578 /// Remove an address from white list.579 /// 580 /// # Permissions581 /// 582 /// * Collection Owner583 /// * Collection Admin584 /// 585 /// # Arguments586 /// 587 /// * collection_id.588 /// 589 /// * address.590 #[weight = <T as Config>::WeightInfo::remove_from_white_list()]591 #[transactional]592 pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{593594 let sender = ensure_signed(origin)?;595 let collection = Self::get_collection(collection_id)?;596 Self::check_owner_or_admin_permissions(&collection, sender)?;597598 <WhiteList<T>>::remove(collection_id, address);599600 Ok(())601 }602603 /// Toggle between normal and white list access for the methods with access for `Anyone`.604 /// 605 /// # Permissions606 /// 607 /// * Collection Owner.608 /// 609 /// # Arguments610 /// 611 /// * collection_id.612 /// 613 /// * mode: [AccessMode]614 #[weight = <T as Config>::WeightInfo::set_public_access_mode()]615 #[transactional]616 pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult617 {618 let sender = ensure_signed(origin)?;619620 let mut target_collection = Self::get_collection(collection_id)?;621 Self::check_owner_permissions(&target_collection, sender)?;622 target_collection.access = mode;623 Self::save_collection(target_collection);624625 Ok(())626 }627628 /// Allows Anyone to create tokens if:629 /// * White List is enabled, and630 /// * Address is added to white list, and631 /// * This method was called with True parameter632 /// 633 /// # Permissions634 /// * Collection Owner635 ///636 /// # Arguments637 /// 638 /// * collection_id.639 /// 640 /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.641 #[weight = <T as Config>::WeightInfo::set_mint_permission()]642 #[transactional]643 pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult644 {645 let sender = ensure_signed(origin)?;646647 let mut target_collection = Self::get_collection(collection_id)?;648 Self::check_owner_permissions(&target_collection, sender)?;649 target_collection.mint_mode = mint_permission;650 Self::save_collection(target_collection);651652 Ok(())653 }654655 /// Change the owner of the collection.656 /// 657 /// # Permissions658 /// 659 /// * Collection Owner.660 /// 661 /// # Arguments662 /// 663 /// * collection_id.664 /// 665 /// * new_owner.666 #[weight = <T as Config>::WeightInfo::change_collection_owner()]667 #[transactional]668 pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {669670 let sender = ensure_signed(origin)?;671 let mut target_collection = Self::get_collection(collection_id)?;672 Self::check_owner_permissions(&target_collection, sender)?;673 target_collection.owner = new_owner;674 Self::save_collection(target_collection);675676 Ok(())677 }678679 /// Adds an admin of the Collection.680 /// 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. 681 /// 682 /// # Permissions683 /// 684 /// * Collection Owner.685 /// * Collection Admin.686 /// 687 /// # Arguments688 /// 689 /// * collection_id: ID of the Collection to add admin for.690 /// 691 /// * new_admin_id: Address of new admin to add.692 #[weight = <T as Config>::WeightInfo::add_collection_admin()]693 #[transactional]694 pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {695696 let sender = ensure_signed(origin)?;697 let collection = Self::get_collection(collection_id)?;698 Self::check_owner_or_admin_permissions(&collection, sender)?;699 let mut admin_arr = <AdminList<T>>::get(collection_id);700701 match admin_arr.binary_search(&new_admin_id) {702 Ok(_) => {},703 Err(idx) => {704 let limits = ChainLimit::get();705 ensure!(admin_arr.len() < limits.collections_admins_limit as usize, Error::<T>::CollectionAdminsLimitExceeded);706 admin_arr.insert(idx, new_admin_id);707 <AdminList<T>>::insert(collection_id, admin_arr);708 }709 }710 Ok(())711 }712713 /// 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.714 ///715 /// # Permissions716 /// 717 /// * Collection Owner.718 /// * Collection Admin.719 /// 720 /// # Arguments721 /// 722 /// * collection_id: ID of the Collection to remove admin for.723 /// 724 /// * account_id: Address of admin to remove.725 #[weight = <T as Config>::WeightInfo::remove_collection_admin()]726 #[transactional]727 pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {728729 let sender = ensure_signed(origin)?;730 let collection = Self::get_collection(collection_id)?;731 Self::check_owner_or_admin_permissions(&collection, sender)?;732 let mut admin_arr = <AdminList<T>>::get(collection_id);733734 match admin_arr.binary_search(&account_id) {735 Ok(idx) => {736 admin_arr.remove(idx);737 <AdminList<T>>::insert(collection_id, admin_arr);738 },739 Err(_) => {}740 }741 Ok(())742 }743744 /// # Permissions745 /// 746 /// * Collection Owner747 /// 748 /// # Arguments749 /// 750 /// * collection_id.751 /// 752 /// * new_sponsor.753 #[weight = <T as Config>::WeightInfo::set_collection_sponsor()]754 #[transactional]755 pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {756757 let sender = ensure_signed(origin)?;758 let mut target_collection = Self::get_collection(collection_id)?;759 Self::check_owner_permissions(&target_collection, sender)?;760761 target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);762 Self::save_collection(target_collection);763764 Ok(())765 }766767 /// # Permissions768 /// 769 /// * Sponsor.770 /// 771 /// # Arguments772 /// 773 /// * collection_id.774 #[weight = <T as Config>::WeightInfo::confirm_sponsorship()]775 #[transactional]776 pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {777778 let sender = ensure_signed(origin)?;779780 let mut target_collection = Self::get_collection(collection_id)?;781 ensure!(782 target_collection.sponsorship.pending_sponsor() == Some(&sender),783 Error::<T>::ConfirmUnsetSponsorFail784 );785786 target_collection.sponsorship = SponsorshipState::Confirmed(sender);787 Self::save_collection(target_collection);788789 Ok(())790 }791792 /// Switch back to pay-per-own-transaction model.793 ///794 /// # Permissions795 ///796 /// * Collection owner.797 /// 798 /// # Arguments799 /// 800 /// * collection_id.801 #[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]802 #[transactional]803 pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {804805 let sender = ensure_signed(origin)?;806807 let mut target_collection = Self::get_collection(collection_id)?;808 Self::check_owner_permissions(&target_collection, sender)?;809810 target_collection.sponsorship = SponsorshipState::Disabled;811 Self::save_collection(target_collection);812813 Ok(())814 }815816 /// This method creates a concrete instance of NFT Collection created with CreateCollection method.817 /// 818 /// # Permissions819 /// 820 /// * Collection Owner.821 /// * Collection Admin.822 /// * Anyone if823 /// * White List is enabled, and824 /// * Address is added to white list, and825 /// * MintPermission is enabled (see SetMintPermission method)826 /// 827 /// # Arguments828 /// 829 /// * collection_id: ID of the collection.830 /// 831 /// * owner: Address, initial owner of the NFT.832 ///833 /// * data: Token data to store on chain.834 // #[weight =835 // (130_000_000 as Weight)836 // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))837 // .saturating_add(RocksDbWeight::get().reads(10 as Weight))838 // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]839840 #[weight = <T as Config>::WeightInfo::create_item(data.len())]841 #[transactional]842 pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {843 let sender = ensure_signed(origin)?;844 Self::create_item_internal(sender, collection_id, owner, data)845 }846847 /// This method creates multiple items in a collection created with CreateCollection method.848 /// 849 /// # Permissions850 /// 851 /// * Collection Owner.852 /// * Collection Admin.853 /// * Anyone if854 /// * White List is enabled, and855 /// * Address is added to white list, and856 /// * MintPermission is enabled (see SetMintPermission method)857 /// 858 /// # Arguments859 /// 860 /// * collection_id: ID of the collection.861 /// 862 /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].863 /// 864 /// * owner: Address, initial owner of the NFT.865 #[weight = <T as Config>::WeightInfo::create_item(items_data.into_iter()866 .map(|data| { data.len() })867 .sum())]868 #[transactional]869 pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {870871 ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);872 let sender = ensure_signed(origin)?;873874 let target_collection = Self::get_collection(collection_id)?;875876 Self::can_create_items_in_collection(&target_collection, &sender, &owner, items_data.len() as u32)?;877878 for data in &items_data {879 Self::validate_create_item_args(&target_collection, data)?;880 }881 for data in &items_data {882 Self::create_item_no_validation(&target_collection, owner.clone(), data.clone())?;883 }884885 Ok(())886 }887888 /// Destroys a concrete instance of NFT.889 /// 890 /// # Permissions891 /// 892 /// * Collection Owner.893 /// * Collection Admin.894 /// * Current NFT Owner.895 /// 896 /// # Arguments897 /// 898 /// * collection_id: ID of the collection.899 /// 900 /// * item_id: ID of NFT to burn.901 #[weight = <T as Config>::WeightInfo::burn_item()]902 #[transactional]903 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {904905 let sender = ensure_signed(origin)?;906907 // Transfer permissions check908 let target_collection = Self::get_collection(collection_id)?;909 ensure!(910 Self::is_item_owner(sender.clone(), &target_collection, item_id) ||911 (912 target_collection.limits.owner_can_transfer &&913 Self::is_owner_or_admin_permissions(&target_collection, sender.clone())914 ),915 Error::<T>::NoPermission916 );917918 if target_collection.access == AccessMode::WhiteList {919 Self::check_white_list(&target_collection, &sender)?;920 }921922 match target_collection.mode923 {924 CollectionMode::NFT => Self::burn_nft_item(&target_collection, item_id)?,925 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, &target_collection, value)?,926 CollectionMode::ReFungible => Self::burn_refungible_item(&target_collection, item_id, &sender)?,927 _ => ()928 };929930 // call event931 Self::deposit_event(RawEvent::ItemDestroyed(target_collection.id, item_id));932933 Ok(())934 }935936 /// Change ownership of the token.937 /// 938 /// # Permissions939 /// 940 /// * Collection Owner941 /// * Collection Admin942 /// * Current NFT owner943 ///944 /// # Arguments945 /// 946 /// * recipient: Address of token recipient.947 /// 948 /// * collection_id.949 /// 950 /// * item_id: ID of the item951 /// * Non-Fungible Mode: Required.952 /// * Fungible Mode: Ignored.953 /// * Re-Fungible Mode: Required.954 /// 955 /// * value: Amount to transfer.956 /// * Non-Fungible Mode: Ignored957 /// * Fungible Mode: Must specify transferred amount958 /// * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)959 #[weight = <T as Config>::WeightInfo::transfer()]960 #[transactional]961 pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {962 let sender = ensure_signed(origin)?;963 let collection = Self::get_collection(collection_id)?;964965 Self::transfer_internal(sender, recipient, &collection, item_id, value)966 }967968 /// Set, change, or remove approved address to transfer the ownership of the NFT.969 /// 970 /// # Permissions971 /// 972 /// * Collection Owner973 /// * Collection Admin974 /// * Current NFT owner975 /// 976 /// # Arguments977 /// 978 /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).979 /// 980 /// * collection_id.981 /// 982 /// * item_id: ID of the item.983 #[weight = <T as Config>::WeightInfo::approve()]984 #[transactional]985 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {986987 let sender = ensure_signed(origin)?;988 let target_collection = Self::get_collection(collection_id)?;989990 Self::token_exists(&target_collection, item_id)?;991992 // Transfer permissions check993 let bypasses_limits = target_collection.limits.owner_can_transfer &&994 Self::is_owner_or_admin_permissions(995 &target_collection,996 sender.clone(),997 );998999 let allowance_limit = if bypasses_limits {1000 None1001 } else if let Some(amount) = Self::owned_amount(1002 sender.clone(),1003 &target_collection,1004 item_id,1005 ) {1006 Some(amount)1007 } else {1008 fail!(Error::<T>::NoPermission);1009 };10101011 if target_collection.access == AccessMode::WhiteList {1012 Self::check_white_list(&target_collection, &sender)?;1013 Self::check_white_list(&target_collection, &spender)?;1014 }10151016 let allowance: u128 = amount1017 .checked_add(<Allowances<T>>::get(collection_id, (item_id, &sender, &spender)))1018 .ok_or(Error::<T>::NumOverflow)?;1019 if let Some(limit) = allowance_limit {1020 ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1021 }1022 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);10231024 Self::deposit_event(RawEvent::Approved(target_collection.id, item_id, sender, spender, allowance));1025 Ok(())1026 }1027 1028 /// 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.1029 /// 1030 /// # Permissions1031 /// * Collection Owner1032 /// * Collection Admin1033 /// * Current NFT owner1034 /// * Address approved by current NFT owner1035 /// 1036 /// # Arguments1037 /// 1038 /// * from: Address that owns token.1039 /// 1040 /// * recipient: Address of token recipient.1041 /// 1042 /// * collection_id.1043 /// 1044 /// * item_id: ID of the item.1045 /// 1046 /// * value: Amount to transfer.1047 #[weight = <T as Config>::WeightInfo::transfer_from()]1048 #[transactional]1049 pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {10501051 let sender = ensure_signed(origin)?;1052 let target_collection = Self::get_collection(collection_id)?;10531054 // Check approval1055 let approval: u128 = <Allowances<T>>::get(collection_id, (item_id, &from, &sender));10561057 // Limits check1058 Self::is_correct_transfer(&target_collection, &recipient)?;10591060 // Transfer permissions check 1061 ensure!(1062 approval >= value || 1063 (1064 target_collection.limits.owner_can_transfer &&1065 Self::is_owner_or_admin_permissions(&target_collection, sender.clone())1066 ),1067 Error::<T>::NoPermission1068 );10691070 if target_collection.access == AccessMode::WhiteList {1071 Self::check_white_list(&target_collection, &sender)?;1072 Self::check_white_list(&target_collection, &recipient)?;1073 }10741075 // Reduce approval by transferred amount or remove if remaining approval drops to 01076 if approval.saturating_sub(value) > 0 {1077 <Allowances<T>>::insert(collection_id, (item_id, &from, &sender), approval - value);1078 }1079 else {1080 <Allowances<T>>::remove(collection_id, (item_id, &from, &sender));1081 }10821083 match target_collection.mode1084 {1085 CollectionMode::NFT => Self::transfer_nft(&target_collection, item_id, from.clone(), recipient.clone())?,1086 CollectionMode::Fungible(_) => Self::transfer_fungible(&target_collection, value, &from, &recipient)?,1087 CollectionMode::ReFungible => Self::transfer_refungible(&target_collection, item_id, value, from.clone(), recipient.clone())?,1088 _ => ()1089 };10901091 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, from, recipient, value));1092 Ok(())1093 }10941095 // #[weight = 0]1096 // pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {10971098 // // let no_perm_mes = "You do not have permissions to modify this collection";1099 // // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1100 // // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1101 // // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11021103 // // // on_nft_received call11041105 // // Self::transfer(origin, collection_id, item_id, new_owner)?;11061107 // Ok(())1108 // }11091110 /// Set off-chain data schema.1111 /// 1112 /// # Permissions1113 /// 1114 /// * Collection Owner1115 /// * Collection Admin1116 /// 1117 /// # Arguments1118 /// 1119 /// * collection_id.1120 /// 1121 /// * schema: String representing the offchain data schema.1122 #[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1123 #[transactional]1124 pub fn set_variable_meta_data (1125 origin,1126 collection_id: CollectionId,1127 item_id: TokenId,1128 data: Vec<u8>1129 ) -> DispatchResult {1130 let sender = ensure_signed(origin)?;1131 1132 let target_collection = Self::get_collection(collection_id)?;1133 Self::token_exists(&target_collection, item_id)?;11341135 ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);11361137 // Modify permissions check1138 ensure!(Self::is_item_owner(sender.clone(), &target_collection, item_id) ||1139 Self::is_owner_or_admin_permissions(&target_collection, sender.clone()),1140 Error::<T>::NoPermission);11411142 match target_collection.mode1143 {1144 CollectionMode::NFT => Self::set_nft_variable_data(&target_collection, item_id, data)?,1145 CollectionMode::ReFungible => Self::set_re_fungible_variable_data(&target_collection, item_id, data)?,1146 CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1147 _ => fail!(Error::<T>::UnexpectedCollectionType)1148 };11491150 Ok(())1151 }1152 1153 /// Set schema standard1154 /// ImageURL1155 /// Unique1156 /// 1157 /// # Permissions1158 /// 1159 /// * Collection Owner1160 /// * Collection Admin1161 /// 1162 /// # Arguments1163 /// 1164 /// * collection_id.1165 /// 1166 /// * schema: SchemaVersion: enum1167 #[weight = <T as Config>::WeightInfo::set_schema_version()]1168 #[transactional]1169 pub fn set_schema_version(1170 origin,1171 collection_id: CollectionId,1172 version: SchemaVersion1173 ) -> DispatchResult {1174 let sender = ensure_signed(origin)?;1175 let mut target_collection = Self::get_collection(collection_id)?;1176 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;1177 target_collection.schema_version = version;1178 Self::save_collection(target_collection);11791180 Ok(())1181 }11821183 /// Set off-chain data schema.1184 /// 1185 /// # Permissions1186 /// 1187 /// * Collection Owner1188 /// * Collection Admin1189 /// 1190 /// # Arguments1191 /// 1192 /// * collection_id.1193 /// 1194 /// * schema: String representing the offchain data schema.1195 #[weight = <T as Config>::WeightInfo::set_offchain_schema()]1196 #[transactional]1197 pub fn set_offchain_schema(1198 origin,1199 collection_id: CollectionId,1200 schema: Vec<u8>1201 ) -> DispatchResult {1202 let sender = ensure_signed(origin)?;1203 let mut target_collection = Self::get_collection(collection_id)?;1204 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;12051206 // check schema limit1207 ensure!(schema.len() as u32 <= ChainLimit::get().offchain_schema_limit, "");12081209 target_collection.offchain_schema = schema;1210 Self::save_collection(target_collection);12111212 Ok(())1213 }12141215 /// Set const on-chain data schema.1216 /// 1217 /// # Permissions1218 /// 1219 /// * Collection Owner1220 /// * Collection Admin1221 /// 1222 /// # Arguments1223 /// 1224 /// * collection_id.1225 /// 1226 /// * schema: String representing the const on-chain data schema.1227 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1228 #[transactional]1229 pub fn set_const_on_chain_schema (1230 origin,1231 collection_id: CollectionId,1232 schema: Vec<u8>1233 ) -> DispatchResult {1234 let sender = ensure_signed(origin)?;1235 let mut target_collection = Self::get_collection(collection_id)?;1236 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;12371238 // check schema limit1239 ensure!(schema.len() as u32 <= ChainLimit::get().const_on_chain_schema_limit, "");12401241 target_collection.const_on_chain_schema = schema;1242 Self::save_collection(target_collection);12431244 Ok(())1245 }12461247 /// Set variable on-chain data schema.1248 /// 1249 /// # Permissions1250 /// 1251 /// * Collection Owner1252 /// * Collection Admin1253 /// 1254 /// # Arguments1255 /// 1256 /// * collection_id.1257 /// 1258 /// * schema: String representing the variable on-chain data schema.1259 #[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1260 #[transactional]1261 pub fn set_variable_on_chain_schema (1262 origin,1263 collection_id: CollectionId,1264 schema: Vec<u8>1265 ) -> DispatchResult {1266 let sender = ensure_signed(origin)?;1267 let mut target_collection = Self::get_collection(collection_id)?;1268 Self::check_owner_or_admin_permissions(&target_collection, sender.clone())?;12691270 // check schema limit1271 ensure!(schema.len() as u32 <= ChainLimit::get().variable_on_chain_schema_limit, "");12721273 target_collection.variable_on_chain_schema = schema;1274 Self::save_collection(target_collection);12751276 Ok(())1277 }12781279 // Sudo permissions function1280 #[weight = <T as Config>::WeightInfo::set_chain_limits()]1281 #[transactional]1282 pub fn set_chain_limits(1283 origin,1284 limits: ChainLimits1285 ) -> DispatchResult {12861287 #[cfg(not(feature = "runtime-benchmarks"))]1288 ensure_root(origin)?;12891290 <ChainLimit>::put(limits);1291 Ok(())1292 }12931294 /// Enable smart contract self-sponsoring.1295 /// 1296 /// # Permissions1297 /// 1298 /// * Contract Owner1299 /// 1300 /// # Arguments1301 /// 1302 /// * contract address1303 /// * enable flag1304 /// 1305 #[weight = <T as Config>::WeightInfo::enable_contract_sponsoring()]1306 #[transactional]1307 pub fn enable_contract_sponsoring(1308 origin,1309 contract_address: T::AccountId,1310 enable: bool1311 ) -> DispatchResult {13121313 let sender = ensure_signed(origin)?;13141315 #[cfg(feature = "runtime-benchmarks")]1316 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13171318 Self::ensure_contract_owned(sender, &contract_address)?;13191320 <ContractSelfSponsoring<T>>::insert(contract_address, enable);1321 Ok(())1322 }13231324 /// Set the rate limit for contract sponsoring to specified number of blocks.1325 /// 1326 /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1327 /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1328 /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1329 /// from contract endowment if there are at least B blocks between such transactions. 1330 /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1331 /// 1332 /// # Permissions1333 /// 1334 /// * Contract Owner1335 /// 1336 /// # Arguments1337 /// 1338 /// -`contract_address`: Address of the contract to sponsor1339 /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1340 /// 1341 #[weight = <T as Config>::WeightInfo::set_contract_sponsoring_rate_limit()]1342 #[transactional]1343 pub fn set_contract_sponsoring_rate_limit(1344 origin,1345 contract_address: T::AccountId,1346 rate_limit: T::BlockNumber1347 ) -> DispatchResult {1348 let sender = ensure_signed(origin)?;13491350 #[cfg(feature = "runtime-benchmarks")]1351 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13521353 Self::ensure_contract_owned(sender, &contract_address)?;1354 <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1355 Ok(())1356 }13571358 /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1359 /// 1360 /// # Permissions1361 /// 1362 /// * Address that deployed smart contract.1363 /// 1364 /// # Arguments1365 /// 1366 /// -`contract_address`: Address of the contract.1367 /// 1368 /// - `enable`: . 1369 #[weight = <T as Config>::WeightInfo::toggle_contract_white_list()]1370 #[transactional]1371 pub fn toggle_contract_white_list(1372 origin,1373 contract_address: T::AccountId,1374 enable: bool1375 ) -> DispatchResult {1376 let sender = ensure_signed(origin)?;13771378 #[cfg(feature = "runtime-benchmarks")]1379 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13801381 Self::ensure_contract_owned(sender, &contract_address)?;1382 if enable {1383 <ContractWhiteListEnabled<T>>::insert(contract_address, true);1384 } else {1385 <ContractWhiteListEnabled<T>>::remove(contract_address);1386 }1387 Ok(())1388 }1389 1390 /// Add an address to smart contract white list.1391 /// 1392 /// # Permissions1393 /// 1394 /// * Address that deployed smart contract.1395 /// 1396 /// # Arguments1397 /// 1398 /// -`contract_address`: Address of the contract.1399 ///1400 /// -`account_address`: Address to add.1401 #[weight = <T as Config>::WeightInfo::add_to_contract_white_list()]1402 #[transactional]1403 pub fn add_to_contract_white_list(1404 origin,1405 contract_address: T::AccountId,1406 account_address: T::AccountId1407 ) -> DispatchResult {1408 let sender = ensure_signed(origin)?;14091410 #[cfg(feature = "runtime-benchmarks")]1411 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());1412 1413 Self::ensure_contract_owned(sender, &contract_address)?; 1414 <ContractWhiteList<T>>::insert(contract_address, account_address, true);1415 Ok(())1416 }14171418 /// Remove an address from smart contract white list.1419 /// 1420 /// # Permissions1421 /// 1422 /// * Address that deployed smart contract.1423 /// 1424 /// # Arguments1425 /// 1426 /// -`contract_address`: Address of the contract.1427 ///1428 /// -`account_address`: Address to remove.1429 #[weight = <T as Config>::WeightInfo::remove_from_contract_white_list()]1430 #[transactional]1431 pub fn remove_from_contract_white_list(1432 origin,1433 contract_address: T::AccountId,1434 account_address: T::AccountId1435 ) -> DispatchResult {1436 let sender = ensure_signed(origin)?;14371438 #[cfg(feature = "runtime-benchmarks")]1439 <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14401441 Self::ensure_contract_owned(sender, &contract_address)?;1442 <ContractWhiteList<T>>::remove(contract_address, account_address);1443 Ok(())1444 }14451446 #[weight = <T as Config>::WeightInfo::set_collection_limits()]1447 #[transactional]1448 pub fn set_collection_limits(1449 origin,1450 collection_id: u32,1451 new_limits: CollectionLimits<T::BlockNumber>,1452 ) -> DispatchResult {1453 let sender = ensure_signed(origin)?;1454 let mut target_collection = Self::get_collection(collection_id)?;1455 Self::check_owner_permissions(&target_collection, sender.clone())?;1456 let old_limits = &target_collection.limits;1457 let chain_limits = ChainLimit::get();14581459 // collection bounds1460 ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1461 new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP && 1462 new_limits.sponsored_data_size <= chain_limits.custom_data_limit,1463 Error::<T>::CollectionLimitBoundsExceeded);14641465 // token_limit check prev1466 ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1467 ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);14681469 ensure!(1470 (old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1471 (old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1472 Error::<T>::OwnerPermissionsCantBeReverted,1473 );14741475 target_collection.limits = new_limits;1476 Self::save_collection(target_collection);14771478 Ok(())1479 } 1480 }1481}14821483impl<T: Config> Module<T> {1484 pub fn create_item_internal(sender: T::AccountId, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1485 let target_collection = Self::get_collection(collection_id)?;14861487 Self::can_create_items_in_collection(&target_collection, &sender, &owner, 1)?;1488 Self::validate_create_item_args(&target_collection, &data)?;1489 Self::create_item_no_validation(&target_collection, owner, data)?;14901491 Ok(())1492 }14931494 pub fn transfer_internal(sender: T::AccountId, recipient: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId, value: u128) -> DispatchResult {1495 // Limits check1496 Self::is_correct_transfer(target_collection, &recipient)?;14971498 // Transfer permissions check1499 ensure!(Self::is_item_owner(sender.clone(), target_collection, item_id) ||1500 Self::is_owner_or_admin_permissions(target_collection, sender.clone()),1501 Error::<T>::NoPermission);15021503 if target_collection.access == AccessMode::WhiteList {1504 Self::check_white_list(target_collection, &sender)?;1505 Self::check_white_list(target_collection, &recipient)?;1506 }15071508 match target_collection.mode1509 {1510 CollectionMode::NFT => Self::transfer_nft(target_collection, item_id, sender.clone(), recipient.clone())?,1511 CollectionMode::Fungible(_) => Self::transfer_fungible(target_collection, value, &sender, &recipient)?,1512 CollectionMode::ReFungible => Self::transfer_refungible(target_collection, item_id, value, sender.clone(), recipient.clone())?,1513 _ => ()1514 };15151516 Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, sender, recipient, value));15171518 Ok(())1519 }152015211522 fn is_correct_transfer(collection: &CollectionHandle<T>, recipient: &T::AccountId) -> DispatchResult {1523 let collection_id = collection.id;15241525 // check token limit and account token limit1526 let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1527 ensure!(collection.limits.account_token_ownership_limit > account_items, Error::<T>::AccountTokenLimitExceeded);1528 1529 Ok(())1530 }15311532 fn can_create_items_in_collection(collection: &CollectionHandle<T>, sender: &T::AccountId, owner: &T::AccountId, amount: u32) -> DispatchResult {1533 let collection_id = collection.id;15341535 // check token limit and account token limit1536 let total_items: u32 = ItemListIndex::get(collection_id)1537 .checked_add(amount)1538 .ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1539 let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner).len() as u32)1540 .checked_add(amount)1541 .ok_or(Error::<T>::AccountTokenLimitExceeded)?;1542 ensure!(collection.limits.token_limit >= total_items, Error::<T>::CollectionTokenLimitExceeded);1543 ensure!(collection.limits.account_token_ownership_limit >= account_items, Error::<T>::AccountTokenLimitExceeded);15441545 if !Self::is_owner_or_admin_permissions(collection, sender.clone()) {1546 ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1547 Self::check_white_list(collection, owner)?;1548 Self::check_white_list(collection, sender)?;1549 }15501551 Ok(())1552 }15531554 fn validate_create_item_args(target_collection: &CollectionHandle<T>, data: &CreateItemData) -> DispatchResult {1555 match target_collection.mode1556 {1557 CollectionMode::NFT => {1558 if let CreateItemData::NFT(data) = data {1559 // check sizes1560 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1561 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1562 } else {1563 fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1564 }1565 },1566 CollectionMode::Fungible(_) => {1567 if let CreateItemData::Fungible(_) = data {1568 } else {1569 fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1570 }1571 },1572 CollectionMode::ReFungible => {1573 if let CreateItemData::ReFungible(data) = data {15741575 // check sizes1576 ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1577 ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);15781579 // Check refungibility limits1580 ensure!(data.pieces <= MAX_REFUNGIBLE_PIECES, Error::<T>::WrongRefungiblePieces);1581 ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1582 } else {1583 fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1584 }1585 },1586 _ => { fail!(Error::<T>::UnexpectedCollectionType); }1587 };15881589 Ok(())1590 }15911592 fn create_item_no_validation(collection: &CollectionHandle<T>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1593 match data1594 {1595 CreateItemData::NFT(data) => {1596 let item = NftItemType {1597 owner: owner.clone(),1598 const_data: data.const_data,1599 variable_data: data.variable_data1600 };16011602 Self::add_nft_item(collection, item)?;1603 },1604 CreateItemData::Fungible(data) => {1605 Self::add_fungible_item(collection, &owner, data.value)?;1606 },1607 CreateItemData::ReFungible(data) => {1608 let mut owner_list = Vec::new();1609 owner_list.push(Ownership {owner: owner.clone(), fraction: data.pieces});16101611 let item = ReFungibleItemType {1612 owner: owner_list,1613 const_data: data.const_data,1614 variable_data: data.variable_data1615 };16161617 Self::add_refungible_item(collection, item)?;1618 }1619 };16201621 Ok(())1622 }16231624 fn add_fungible_item(collection: &CollectionHandle<T>, owner: &T::AccountId, value: u128) -> DispatchResult {1625 let collection_id = collection.id;16261627 // Does new owner already have an account?1628 let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner).value;16291630 // Mint 1631 let item = FungibleItemType {1632 value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1633 };1634 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16351636 // Update balance1637 let new_balance = <Balance<T>>::get(collection_id, owner)1638 .checked_add(value)1639 .ok_or(Error::<T>::NumOverflow)?;1640 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16411642 Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1643 Ok(())1644 }16451646 fn add_refungible_item(collection: &CollectionHandle<T>, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1647 let collection_id = collection.id;16481649 let current_index = <ItemListIndex>::get(collection_id)1650 .checked_add(1)1651 .ok_or(Error::<T>::NumOverflow)?;1652 let itemcopy = item.clone();16531654 ensure!(1655 item.owner.len() == 1,1656 Error::<T>::BadCreateRefungibleCall,1657 );1658 let item_owner = item.owner.first().expect("only one owner is defined");16591660 let value = item_owner.fraction;1661 let owner = item_owner.owner.clone();16621663 Self::add_token_index(collection_id, current_index, &owner)?;16641665 <ItemListIndex>::insert(collection_id, current_index);1666 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16671668 // Update balance1669 let new_balance = <Balance<T>>::get(collection_id, &owner)1670 .checked_add(value)1671 .ok_or(Error::<T>::NumOverflow)?;1672 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16731674 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1675 Ok(())1676 }16771678 fn add_nft_item(collection: &CollectionHandle<T>, item: NftItemType<T::AccountId>) -> DispatchResult {1679 let collection_id = collection.id;16801681 let current_index = <ItemListIndex>::get(collection_id)1682 .checked_add(1)1683 .ok_or(Error::<T>::NumOverflow)?;16841685 let item_owner = item.owner.clone();1686 Self::add_token_index(collection_id, current_index, &item.owner)?;16871688 <ItemListIndex>::insert(collection_id, current_index);1689 <NftItemList<T>>::insert(collection_id, current_index, item);16901691 // Update balance1692 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1693 .checked_add(1)1694 .ok_or(Error::<T>::NumOverflow)?;1695 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16961697 Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, item_owner));1698 Ok(())1699 }17001701 fn burn_refungible_item(1702 collection: &CollectionHandle<T>,1703 item_id: TokenId,1704 owner: &T::AccountId,1705 ) -> DispatchResult {1706 let collection_id = collection.id;17071708 let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1709 .ok_or(Error::<T>::TokenNotFound)?;1710 let rft_balance = token1711 .owner1712 .iter()1713 .find(|&i| i.owner == *owner)1714 .ok_or(Error::<T>::TokenNotFound)?;1715 Self::remove_token_index(collection_id, item_id, owner)?;17161717 // update balance1718 let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.clone())1719 .checked_sub(rft_balance.fraction)1720 .ok_or(Error::<T>::NumOverflow)?;1721 <Balance<T>>::insert(collection_id, rft_balance.owner.clone(), new_balance);17221723 // Re-create owners list with sender removed1724 let index = token1725 .owner1726 .iter()1727 .position(|i| i.owner == *owner)1728 .expect("owned item is exists");1729 token.owner.remove(index);1730 let owner_count = token.owner.len();17311732 // Burn the token completely if this was the last (only) owner1733 if owner_count == 0 {1734 <ReFungibleItemList<T>>::remove(collection_id, item_id);1735 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);1736 }1737 else {1738 <ReFungibleItemList<T>>::insert(collection_id, item_id, token);1739 }17401741 Ok(())1742 }17431744 fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1745 let collection_id = collection.id;17461747 let item = <NftItemList<T>>::get(collection_id, item_id)1748 .ok_or(Error::<T>::TokenNotFound)?;1749 Self::remove_token_index(collection_id, item_id, &item.owner)?;17501751 // update balance1752 let new_balance = <Balance<T>>::get(collection_id, &item.owner)1753 .checked_sub(1)1754 .ok_or(Error::<T>::NumOverflow)?;1755 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1756 <NftItemList<T>>::remove(collection_id, item_id);1757 <VariableMetaDataBasket<T>>::remove(collection_id, item_id);17581759 Ok(())1760 }17611762 fn burn_fungible_item(owner: &T::AccountId, collection: &CollectionHandle<T>, value: u128) -> DispatchResult {1763 let collection_id = collection.id;17641765 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1766 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17671768 // update balance1769 let new_balance = <Balance<T>>::get(collection_id, owner)1770 .checked_sub(value)1771 .ok_or(Error::<T>::NumOverflow)?;1772 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17731774 if balance.value - value > 0 {1775 balance.value -= value;1776 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1777 }1778 else {1779 <FungibleItemList<T>>::remove(collection_id, owner);1780 }17811782 Ok(())1783 }17841785 pub fn get_collection(collection_id: CollectionId) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1786 Ok(<CollectionById<T>>::get(collection_id)1787 .map(|collection| CollectionHandle {1788 id: collection_id,1789 collection1790 })1791 .ok_or(Error::<T>::CollectionNotFound)?)1792 }17931794 fn save_collection(collection: CollectionHandle<T>) {1795 <CollectionById<T>>::insert(collection.id, collection.collection);1796 }17971798 fn check_owner_permissions(target_collection: &CollectionHandle<T>, subject: T::AccountId) -> DispatchResult {1799 ensure!(1800 subject == target_collection.owner,1801 Error::<T>::NoPermission1802 );18031804 Ok(())1805 }18061807 fn is_owner_or_admin_permissions(collection: &CollectionHandle<T>, subject: T::AccountId) -> bool {1808 subject == collection.owner || <AdminList<T>>::get(collection.id).contains(&subject)1809 }18101811 fn check_owner_or_admin_permissions(1812 collection: &CollectionHandle<T>,1813 subject: T::AccountId,1814 ) -> DispatchResult {1815 ensure!(Self::is_owner_or_admin_permissions(collection, subject), Error::<T>::NoPermission);18161817 Ok(())1818 }18191820 fn owned_amount(1821 subject: T::AccountId,1822 target_collection: &CollectionHandle<T>,1823 item_id: TokenId,1824 ) -> Option<u128> {1825 let collection_id = target_collection.id;18261827 match target_collection.mode {1828 CollectionMode::NFT => (<NftItemList<T>>::get(collection_id, item_id)?.owner == subject)1829 .then(|| 1),1830 CollectionMode::Fungible(_) => Some(<FungibleItemList<T>>::get(collection_id, &subject)1831 .value),1832 CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1833 .owner1834 .iter()1835 .find(|i| i.owner == subject)1836 .map(|i| i.fraction),1837 CollectionMode::Invalid => None,1838 }1839 }18401841 fn is_item_owner(subject: T::AccountId, target_collection: &CollectionHandle<T>, item_id: TokenId) -> bool {1842 match target_collection.mode {1843 CollectionMode::Fungible(_) => true,1844 _ => Self::owned_amount(subject, target_collection, item_id).is_some(),1845 }1846 }18471848 fn check_white_list(collection: &CollectionHandle<T>, address: &T::AccountId) -> DispatchResult {1849 let collection_id = collection.id;18501851 let mes = Error::<T>::AddresNotInWhiteList;1852 ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18531854 Ok(())1855 }18561857 /// Check if token exists. In case of Fungible, check if there is an entry for 1858 /// the owner in fungible balances double map1859 fn token_exists(1860 target_collection: &CollectionHandle<T>,1861 item_id: TokenId,1862 ) -> DispatchResult {1863 let collection_id = target_collection.id;1864 let exists = match target_collection.mode1865 {1866 CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1867 CollectionMode::Fungible(_) => true,1868 CollectionMode::ReFungible => <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1869 _ => false1870 };18711872 ensure!(exists == true, Error::<T>::TokenNotFound);1873 Ok(())1874 }18751876 fn transfer_fungible(1877 collection: &CollectionHandle<T>,1878 value: u128,1879 owner: &T::AccountId,1880 recipient: &T::AccountId,1881 ) -> DispatchResult {1882 let collection_id = collection.id;18831884 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1885 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18861887 // Send balance to recipient (updates balanceOf of recipient)1888 Self::add_fungible_item(collection, recipient, value)?;18891890 // update balanceOf of sender1891 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18921893 // Reduce or remove sender1894 if balance.value == value {1895 <FungibleItemList<T>>::remove(collection_id, owner);1896 }1897 else {1898 balance.value -= value;1899 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1900 }19011902 Ok(())1903 }19041905 fn transfer_refungible(1906 collection: &CollectionHandle<T>,1907 item_id: TokenId,1908 value: u128,1909 owner: T::AccountId,1910 new_owner: T::AccountId,1911 ) -> DispatchResult {1912 let collection_id = collection.id;1913 let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)1914 .ok_or(Error::<T>::TokenNotFound)?;19151916 let item = full_item1917 .owner1918 .iter()1919 .filter(|i| i.owner == owner)1920 .next()1921 .ok_or(Error::<T>::TokenNotFound)?;1922 let amount = item.fraction;19231924 ensure!(amount >= value, Error::<T>::TokenValueTooLow);19251926 // update balance1927 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1928 .checked_sub(value)1929 .ok_or(Error::<T>::NumOverflow)?;1930 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19311932 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1933 .checked_add(value)1934 .ok_or(Error::<T>::NumOverflow)?;1935 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19361937 let old_owner = item.owner.clone();1938 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);19391940 // transfer1941 if amount == value && !new_owner_has_account {1942 // change owner1943 // new owner do not have account1944 let mut new_full_item = full_item.clone();1945 new_full_item1946 .owner1947 .iter_mut()1948 .find(|i| i.owner == owner)1949 .expect("old owner does present in refungible")1950 .owner = new_owner.clone();1951 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19521953 // update index collection1954 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;1955 } else {1956 let mut new_full_item = full_item.clone();1957 new_full_item1958 .owner1959 .iter_mut()1960 .find(|i| i.owner == owner)1961 .expect("old owner does present in refungible")1962 .fraction -= value;19631964 // separate amount1965 if new_owner_has_account {1966 // new owner has account1967 new_full_item1968 .owner1969 .iter_mut()1970 .find(|i| i.owner == new_owner)1971 .expect("new owner has account")1972 .fraction += value;1973 } else {1974 // new owner do not have account1975 new_full_item.owner.push(Ownership {1976 owner: new_owner.clone(),1977 fraction: value,1978 });1979 Self::add_token_index(collection_id, item_id, &new_owner)?;1980 }19811982 <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1983 }19841985 Ok(())1986 }19871988 fn transfer_nft(1989 collection: &CollectionHandle<T>,1990 item_id: TokenId,1991 sender: T::AccountId,1992 new_owner: T::AccountId,1993 ) -> DispatchResult {1994 let collection_id = collection.id;1995 let mut item = <NftItemList<T>>::get(collection_id, item_id)1996 .ok_or(Error::<T>::TokenNotFound)?;19971998 ensure!(1999 sender == item.owner,2000 Error::<T>::MustBeTokenOwner2001 );20022003 // update balance2004 let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2005 .checked_sub(1)2006 .ok_or(Error::<T>::NumOverflow)?;2007 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20082009 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())2010 .checked_add(1)2011 .ok_or(Error::<T>::NumOverflow)?;2012 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);20132014 // change owner2015 let old_owner = item.owner.clone();2016 item.owner = new_owner.clone();2017 <NftItemList<T>>::insert(collection_id, item_id, item);20182019 // update index collection2020 Self::move_token_index(collection_id, item_id, &old_owner, &new_owner)?;20212022 Ok(())2023 }2024 2025 fn set_re_fungible_variable_data(2026 collection: &CollectionHandle<T>,2027 item_id: TokenId,2028 data: Vec<u8>2029 ) -> DispatchResult {2030 let collection_id = collection.id;2031 let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2032 .ok_or(Error::<T>::TokenNotFound)?;20332034 item.variable_data = data;20352036 <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20372038 Ok(())2039 }20402041 fn set_nft_variable_data(2042 collection: &CollectionHandle<T>,2043 item_id: TokenId,2044 data: Vec<u8>2045 ) -> DispatchResult {2046 let collection_id = collection.id;2047 let mut item = <NftItemList<T>>::get(collection_id, item_id)2048 .ok_or(Error::<T>::TokenNotFound)?;2049 2050 item.variable_data = data;20512052 <NftItemList<T>>::insert(collection_id, item_id, item);2053 2054 Ok(())2055 }20562057 fn init_collection(item: &Collection<T>) {2058 // check params2059 assert!(2060 item.decimal_points <= MAX_DECIMAL_POINTS,2061 "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2062 );2063 assert!(2064 item.name.len() <= 64,2065 "Collection name can not be longer than 63 char"2066 );2067 assert!(2068 item.name.len() <= 256,2069 "Collection description can not be longer than 255 char"2070 );2071 assert!(2072 item.token_prefix.len() <= 16,2073 "Token prefix can not be longer than 15 char"2074 );20752076 // Generate next collection ID2077 let next_id = CreatedCollectionCount::get()2078 .checked_add(1)2079 .unwrap();20802081 CreatedCollectionCount::put(next_id);2082 }20832084 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2085 let current_index = <ItemListIndex>::get(collection_id)2086 .checked_add(1)2087 .unwrap();20882089 let item_owner = item.owner.clone();2090 Self::add_token_index(collection_id, current_index, &item.owner).unwrap();20912092 <ItemListIndex>::insert(collection_id, current_index);20932094 // Update balance2095 let new_balance = <Balance<T>>::get(collection_id, &item_owner)2096 .checked_add(1)2097 .unwrap();2098 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2099 }21002101 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2102 let current_index = <ItemListIndex>::get(collection_id)2103 .checked_add(1)2104 .unwrap();21052106 Self::add_token_index(collection_id, current_index, owner).unwrap();21072108 <ItemListIndex>::insert(collection_id, current_index);21092110 // Update balance2111 let new_balance = <Balance<T>>::get(collection_id, owner)2112 .checked_add(item.value)2113 .unwrap();2114 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2115 }21162117 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2118 let current_index = <ItemListIndex>::get(collection_id)2119 .checked_add(1)2120 .unwrap();21212122 let value = item.owner.first().unwrap().fraction;2123 let owner = item.owner.first().unwrap().owner.clone();21242125 Self::add_token_index(collection_id, current_index, &owner).unwrap();21262127 <ItemListIndex>::insert(collection_id, current_index);21282129 // Update balance2130 let new_balance = <Balance<T>>::get(collection_id, &owner)2131 .checked_add(value)2132 .unwrap();2133 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2134 }21352136 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: &T::AccountId) -> DispatchResult {2137 // add to account limit2138 if <AccountItemCount<T>>::contains_key(owner) {21392140 // bound Owned tokens by a single address2141 let count = <AccountItemCount<T>>::get(owner);2142 ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21432144 <AccountItemCount<T>>::insert(owner.clone(), count2145 .checked_add(1)2146 .ok_or(Error::<T>::NumOverflow)?);2147 }2148 else {2149 <AccountItemCount<T>>::insert(owner.clone(), 1);2150 }21512152 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2153 if list_exists {2154 let mut list = <AddressTokens<T>>::get(collection_id, owner);2155 let item_contains = list.contains(&item_index.clone());21562157 if !item_contains {2158 list.push(item_index.clone());2159 }21602161 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2162 } else {2163 let mut itm = Vec::new();2164 itm.push(item_index.clone());2165 <AddressTokens<T>>::insert(collection_id, owner.clone(), itm);2166 }21672168 Ok(())2169 }21702171 fn remove_token_index(2172 collection_id: CollectionId,2173 item_index: TokenId,2174 owner: &T::AccountId,2175 ) -> DispatchResult {21762177 // update counter2178 <AccountItemCount<T>>::insert(owner.clone(), 2179 <AccountItemCount<T>>::get(owner)2180 .checked_sub(1)2181 .ok_or(Error::<T>::NumOverflow)?);218221832184 let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner);2185 if list_exists {2186 let mut list = <AddressTokens<T>>::get(collection_id, owner);2187 let item_contains = list.contains(&item_index.clone());21882189 if item_contains {2190 list.retain(|&item| item != item_index);2191 <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2192 }2193 }21942195 Ok(())2196 }21972198 fn move_token_index(2199 collection_id: CollectionId,2200 item_index: TokenId,2201 old_owner: &T::AccountId,2202 new_owner: &T::AccountId,2203 ) -> DispatchResult {2204 Self::remove_token_index(collection_id, item_index, old_owner)?;2205 Self::add_token_index(collection_id, item_index, new_owner)?;22062207 Ok(())2208 }2209 2210 fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2211 ensure!(<ContractOwner<T>>::get(contract) == Some(account), Error::<T>::NoPermission);22122213 Ok(())2214 }2215}pallets/scheduler/src/lib.rsdiffbeforeafterboth--- a/pallets/scheduler/src/lib.rs
+++ b/pallets/scheduler/src/lib.rs
@@ -102,7 +102,7 @@
/// Not strictly enforced, but used for weight estimation.
type MaxScheduledPerBlock: Get<u32>;
- /// TODO
+ /// Sponsoring function
type Sponsoring: SponsoringResolve<Self::AccountId, <Self as Config>::Call>;
/// Weight information for extrinsics in this pallet.
@@ -230,8 +230,7 @@
/// - Write: Agenda
/// - Will use base weight of 25 which should be good for up to 30 scheduled calls
/// # </weight>
- // #[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]
- #[weight = 0]
+ #[weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get())]
fn schedule(origin,
when: T::BlockNumber,
maybe_periodic: Option<schedule::Period<T::BlockNumber>>,
@@ -367,7 +366,7 @@
}
queued.sort_by_key(|(_, s)| s.priority);
let base_weight: Weight = T::DbWeight::get().reads_writes(1, 2); // Agenda + Agenda(next)
- let mut total_weight: Weight = <T as Config>::WeightInfo::schedule(T::MaxScheduledPerBlock::get());
+ let mut total_weight: Weight = 0;
queued.into_iter()
.enumerate()
.scan(base_weight, |cumulative_weight, (order, (index, s))| {
@@ -407,7 +406,7 @@
).into();
let sender = ensure_signed(origin).unwrap_or(T::AccountId::default());
let who_will_pay = T::Sponsoring::resolve(&sender, &s.call.clone()).unwrap_or(
- T::AccountId::default());
+ sender);
let sponsor = T::PalletsOrigin::from(system::RawOrigin::Signed(who_will_pay));
let r = s.call.clone().dispatch(sponsor.into());
let maybe_id = s.maybe_id.clone();
tests/src/pallet-presence.test.tsdiffbeforeafterboth--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -13,7 +13,8 @@
// Pallets that must always be present
const requiredPallets = [
- 'nft', 'inflation', 'balances', 'contracts', 'randomnesscollectiveflip', 'system', 'timestamp', 'transactionpayment', 'treasury', 'vesting'
+ 'nft', 'inflation', 'balances', 'contracts', 'randomnesscollectiveflip', 'system', 'timestamp', 'transactionpayment', 'treasury', 'vesting',
+ 'scheduler', 'nftpayment', 'charging'
];
// Pallets that depend on consensus and governance configuration
tests/src/scheduler.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/scheduler.test.ts
@@ -0,0 +1,94 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { IKeyringPair } from '@polkadot/types/types';
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import usingApi from './substrate/substrate-api';
+import {
+ createItemExpectSuccess,
+ createCollectionExpectSuccess,
+ destroyCollectionExpectSuccess,
+ findNotExistingCollection,
+ queryCollectionExpectSuccess,
+ setOffchainSchemaExpectFailure,
+ setOffchainSchemaExpectSuccess,
+ transferExpectSuccess,
+ scheduleTransferExpectSuccess,
+ setCollectionSponsorExpectSuccess,
+ confirmSponsorshipExpectSuccess
+} from './util/helpers';
+
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+const DATA = [1, 2, 3, 4];
+
+describe('Integration Test scheduler base transaction', () => {
+ let alice: IKeyringPair;
+
+ before(async () => {
+ await usingApi(async () => {
+ alice = privateKey('//Alice');
+ });
+ });
+
+ it('User can transfer owned token with delay (scheduler)', async () => {
+ await usingApi(async (api) => {
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setCollectionSponsorExpectSuccess(nftCollectionId, Alice.address);
+ await confirmSponsorshipExpectSuccess(nftCollectionId);
+
+ await scheduleTransferExpectSuccess(nftCollectionId, newNftTokenId, Alice, Bob, 1, 'NFT');
+ });
+ });
+
+
+});
+
+// describe('Negative Integration Test setOffchainSchema', () => {
+// let alice: IKeyringPair;
+// let bob: IKeyringPair;
+
+// let validCollectionId: number;
+
+// before(async () => {
+// await usingApi(async () => {
+// alice = privateKey('//Alice');
+// bob = privateKey('//Bob');
+
+// validCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+// });
+// });
+
+// it('fails on not existing collection id', async () => {
+// const nonExistingCollectionId = await usingApi(findNotExistingCollection);
+
+// await setOffchainSchemaExpectFailure(alice, nonExistingCollectionId, DATA);
+// });
+
+// it('fails on destroyed collection id', async () => {
+// const destroyedCollectionId = await createCollectionExpectSuccess({ mode: { type: 'NFT' } });
+// await destroyCollectionExpectSuccess(destroyedCollectionId);
+
+// await setOffchainSchemaExpectFailure(alice, destroyedCollectionId, DATA);
+// });
+
+// it('fails on too long data', async () => {
+// const tooLongData = new Array(4097).fill(0xff);
+
+// await setOffchainSchemaExpectFailure(alice, validCollectionId, tooLongData);
+// });
+
+// it('fails on execution by non-owner', async () => {
+// await setOffchainSchemaExpectFailure(bob, validCollectionId, DATA);
+// });
+// });
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -5,7 +5,7 @@
import { ApiPromise, Keyring } from '@polkadot/api';
import { Enum, Struct } from '@polkadot/types/codec';
-import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
+import type { AccountId, BlockNumber, Call, EventRecord } from '@polkadot/types/interfaces';
import { u128 } from '@polkadot/types/primitive';
import { IKeyringPair } from '@polkadot/types/types';
import { BigNumber } from 'bignumber.js';
@@ -17,6 +17,8 @@
import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from '../substrate/substrate-api';
import { ICollectionInterface } from '../types';
import { hexToStr, strToUTF16, utf16ToStr } from './util';
+import { Compact, Option, Raw, Vec } from '@polkadot/types/codec';
+// import { AccountId, AccountIdOf, AccountIndex, Address, AssetId, Balance, BalanceOf, Block, BlockNumber, Call, ChangesTrieConfiguration, Consensus, ConsensusEngineId, Digest, DigestItem, DispatchClass, DispatchInfo, DispatchInfoTo190, EcdsaSignature, Ed25519Signature, Extrinsic, ExtrinsicEra, ExtrinsicPayload, ExtrinsicPayloadUnknown, ExtrinsicPayloadV1, ExtrinsicPayloadV2, ExtrinsicPayloadV3, ExtrinsicPayloadV4, ExtrinsicSignatureV1, ExtrinsicSignatureV2, ExtrinsicSignatureV3, ExtrinsicSignatureV4, ExtrinsicUnknown, ExtrinsicV1, ExtrinsicV2, ExtrinsicV3, ExtrinsicV4, Hash, Header, ImmortalEra, Index, Justification, KeyTypeId, KeyValue, LockIdentifier, LookupSource, LookupTarget, Moment, MortalEra, MultiSignature, Origin, Perbill, Percent, Permill, Perquintill, Phantom, PhantomData, PreRuntime, Seal, SealV0, Signature, SignedBlock, SignerPayload, Sr25519Signature, ValidatorId, Weight, WeightMultiplier } from '@polkadot/types/interfaces/runtime';
chai.use(chaiAsPromised);
const expect = chai.expect;
@@ -702,6 +704,54 @@
});
}
+async function getBlockNumber(api: ApiPromise): Promise<number> {
+ return new Promise<number>(async (resolve, reject) => {
+ const unsubscribe = await api.rpc.chain.subscribeNewHeads((head) => {
+ unsubscribe();
+ resolve(head.number.toNumber());
+ });
+ });
+}
+
+export async function
+scheduleTransferExpectSuccess(collectionId: number,
+ tokenId: number,
+ sender: IKeyringPair,
+ recipient: IKeyringPair,
+ value: number | bigint = 1,
+ type: string = 'NFT') {
+ await usingApi(async (api: ApiPromise) => {
+ let balanceBefore = new BN(0);
+
+
+ let blockNumber: number | undefined = await getBlockNumber(api);
+ let expectedBlockNumber = blockNumber + 2;
+
+ expect(blockNumber).to.be.greaterThan(0);
+ const transferTx = await api.tx.nft.transfer(recipient.address, collectionId, tokenId, value);
+ const scheduleTx = await api.tx.scheduler.schedule(expectedBlockNumber, null, 0, transferTx);
+
+ const events = await submitTransactionAsync(sender, scheduleTx);
+
+ const sponsorBalanceBefore = new BigNumber((await api.query.system.account(sender.address)).data.free.toString());
+ const recipientBalanceBefore = new BigNumber((await api.query.system.account(recipient.address)).data.free.toString());
+
+ const nftItemDataBefore = (await api.query.nft.nftItemList(collectionId, tokenId)).toJSON() as unknown as ITokenDataType;
+ expect(nftItemDataBefore.Owner.toString()).to.be.equal(sender.address);
+
+ // sleep for 2 blocks
+ await new Promise(resolve => setTimeout(resolve, 6000 * 2));
+
+ const sponsorBalanceAfter = new BigNumber((await api.query.system.account(sender.address)).data.free.toString());
+ const recipientBalanceAfter = new BigNumber((await api.query.system.account(recipient.address)).data.free.toString());
+
+ const nftItemData = (await api.query.nft.nftItemList(collectionId, tokenId)).toJSON() as unknown as ITokenDataType;
+ expect(nftItemData.Owner.toString()).to.be.equal(recipient.address);
+ expect(recipientBalanceAfter.toNumber()).to.be.equal(recipientBalanceBefore.toNumber());
+ });
+}
+
+
export async function
transferExpectSuccess(collectionId: number,
tokenId: number,