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}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,