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