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.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#![cfg_attr(not(feature = "std"), no_std)]78#[cfg(feature = "std")]9pub use std::*;1011#[cfg(feature = "std")]12pub use serde::*;1314#[cfg(feature = "runtime-benchmarks")]15mod benchmarking;1617#[cfg(test)]18mod tests;1920use frame_support::{21 decl_error, decl_module, decl_storage,22 traits::{23 IsSubType, 24 },25 weights::{26 DispatchInfo27 }28};29use sp_runtime::traits::StaticLookup;30use sp_runtime::{31 traits::{ 32 Hash, Dispatchable,33 },34};35use pallet_contracts::chain_extension::UncheckedFrom;36use sp_std::prelude::*;37use nft_data_structs::{38 CreateItemData,39 CollectionId, CollectionMode, TokenId40};4142type CodeHash<T> = <T as frame_system::Config>::Hash;4344 pub trait Config: frame_system::Config + pallet_contracts::Config + pallet_transaction_payment::Config + pallet_nft::Config {45 }4647 // Error for non-fungible-token module.48 49 decl_error! {50 /// Error for non-fungible-token module.51 pub enum Error for Module<T: Config> {52 /// No available class ID53 NoAvailableClassId,54 /// No available token ID55 NoAvailableTokenId,56 /// Token(ClassId, TokenId) not found57 TokenNotFound,58 /// Class not found59 CollectionNotFound,60 /// The operator is not the owner of the token and has no permission61 NoPermission,62 /// Arithmetic calculation overflow63 NumOverflow,64 /// Can not destroy class65 /// Total issuance is not 066 CannotDestroyClass,67 }68}6970 decl_storage! {71 trait Store for Module<T: Config> as NftTransactionPayment{7273 }74}7576decl_module! {7778 pub struct Module<T: Config> for enum Call 79 where 80 origin: T::Origin,81 {82 }83}8485impl<T: Config> Module<T> 86{8788 pub fn withdraw_type(89 who: &T::AccountId,90 call: &T::Call91 ) -> Option<T::AccountId> where 92 T::Call: Dispatchable<Info=DispatchInfo>,93 T::Call: IsSubType<pallet_nft::Call<T>>, 94 T::Call: IsSubType<pallet_contracts::Call<T>>,95 T::AccountId: AsRef<[u8]>,96 T::AccountId: UncheckedFrom<T::Hash>97 {9899 let mut sponsor: Option<T::AccountId> = match IsSubType::<pallet_nft::Call<T>>::is_sub_type(call) {100 Some(pallet_nft::Call::create_item(collection_id, _owner, _properties)) => {101102 Self::withdraw_create_item(who, collection_id, &_properties)103 },104 Some(pallet_nft::Call::transfer(_new_owner, collection_id, item_id, _value)) => {105106 Self::withdraw_transfer(who, collection_id, item_id)107 },108 Some(pallet_nft::Call::set_variable_meta_data(collection_id, item_id, data)) => {109110 Self::withdraw_set_variable_meta_data(who, collection_id, item_id, &data)111 },112 _ => None,113 };114115 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {116 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {117118 Self::withdraw_contract_call(who, dest)119 },120 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {121122 Self::withdraw_contract_instantiate(&who, code_hash, salt)123 },124 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {125126 Self::withdraw_contract_instantiate(&who, &T::Hashing::hash(&_code), _salt)127 },128 _ => None,129 });130131 sponsor132 }133134135136 pub fn withdraw_create_item(137 who: &T::AccountId,138 collection_id: &CollectionId,139 _properties: &CreateItemData,140 ) -> Option<T::AccountId> {141 142 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;143144 // sponsor timeout145 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;146147 let limit = collection.limits.sponsor_transfer_timeout;148 if pallet_nft::CreateItemBasket::<T>::contains_key((collection_id, &who)) {149 let last_tx_block = pallet_nft::CreateItemBasket::<T>::get((collection_id, &who));150 let limit_time = last_tx_block + limit.into();151 if block_number <= limit_time {152 return None;153 }154 }155 pallet_nft::CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);156157 // check free create limit158 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {159 collection.sponsorship.sponsor()160 .cloned()161 } else {162 None163 }164 }165166 pub fn withdraw_transfer(167 who: &T::AccountId,168 collection_id: &CollectionId,169 item_id: &TokenId,170 ) -> Option<T::AccountId> {171172 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;173 let limits = pallet_nft::ChainLimit::get();174175 let mut sponsor_transfer = false;176 if collection.sponsorship.confirmed() {177178 let collection_limits = collection.limits.clone();179 let collection_mode = collection.mode.clone();180181 // sponsor timeout182 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;183 sponsor_transfer = match collection_mode {184 CollectionMode::NFT => {185186 // get correct limit187 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {188 collection_limits.sponsor_transfer_timeout189 } else {190 limits.nft_sponsor_transfer_timeout191 };192193 let mut sponsored = true;194 if pallet_nft::NftTransferBasket::<T>::contains_key(collection_id, item_id) {195 let last_tx_block = pallet_nft::NftTransferBasket::<T>::get(collection_id, item_id);196 let limit_time = last_tx_block + limit.into();197 if block_number <= limit_time {198 sponsored = false;199 }200 }201 if sponsored {202 pallet_nft::NftTransferBasket::<T>::insert(collection_id, item_id, block_number);203 }204205 sponsored206 }207 CollectionMode::Fungible(_) => {208209 // get correct limit210 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {211 collection_limits.sponsor_transfer_timeout212 } else {213 limits.fungible_sponsor_transfer_timeout214 };215216 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;217 let mut sponsored = true;218 if pallet_nft::FungibleTransferBasket::<T>::contains_key(collection_id, who) {219 let last_tx_block = pallet_nft::FungibleTransferBasket::<T>::get(collection_id, who);220 let limit_time = last_tx_block + limit.into();221 if block_number <= limit_time {222 sponsored = false;223 }224 }225 if sponsored {226 pallet_nft::FungibleTransferBasket::<T>::insert(collection_id, who, block_number);227 }228229 sponsored230 }231 CollectionMode::ReFungible => {232233 // get correct limit234 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {235 collection_limits.sponsor_transfer_timeout236 } else {237 limits.refungible_sponsor_transfer_timeout238 };239240 let mut sponsored = true;241 if pallet_nft::ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {242 let last_tx_block = pallet_nft::ReFungibleTransferBasket::<T>::get(collection_id, item_id);243 let limit_time = last_tx_block + limit.into();244 if block_number <= limit_time {245 sponsored = false;246 }247 }248 if sponsored {249 pallet_nft::ReFungibleTransferBasket::<T>::insert(collection_id, item_id, block_number);250 }251252 sponsored253 }254 _ => {255 false256 },257 };258 }259260 if !sponsor_transfer {261 None262 } else {263 collection.sponsorship.sponsor()264 .cloned()265 }266 }267 268 pub fn withdraw_set_variable_meta_data(269 who: &T::AccountId,270 collection_id: &CollectionId,271 item_id: &TokenId,272 data: &Vec<u8>,273 ) -> Option<T::AccountId> {274275 let mut sponsor_metadata_changes = false;276277 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;278279 if280 collection.sponsorship.confirmed() &&281 // Can't sponsor fungible collection, this tx will be rejected282 // as invalid283 !matches!(collection.mode, CollectionMode::Fungible(_)) &&284 data.len() <= collection.limits.sponsored_data_size as usize285 {286 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {287 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;288289 if pallet_nft::VariableMetaDataBasket::<T>::get(collection_id, item_id)290 .map(|last_block| block_number - last_block > rate_limit)291 .unwrap_or(true) 292 {293 sponsor_metadata_changes = true;294 pallet_nft::VariableMetaDataBasket::<T>::insert(collection_id, item_id, block_number);295 }296 }297 }298299 if !sponsor_metadata_changes {300 None301 } else {302 collection.sponsorship.sponsor().cloned()303 }304305 }306307 pub fn withdraw_contract_call(308 who: &T::AccountId,309 dest: &<T::Lookup as StaticLookup>::Source310 ) -> Option<T::AccountId> {311312 let called_contract: T::AccountId = T::Lookup::lookup((dest).clone()).unwrap_or(T::AccountId::default());313314 let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);315 let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());316 317 // ???318 if !owned_contract && white_list_enabled {319 if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {320 return Some(who.clone())321 // return Err(InvalidTransaction::Call.into());322 }323 }324325 let mut sponsor_transfer = false;326 if pallet_nft::ContractSponsoringRateLimit::<T>::contains_key(called_contract.clone()) {327 let last_tx_block = pallet_nft::ContractSponsorBasket::<T>::get((&called_contract, &who));328 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;329 let rate_limit = pallet_nft::ContractSponsoringRateLimit::<T>::get(&called_contract);330 let limit_time = last_tx_block + rate_limit;331332 if block_number >= limit_time {333 pallet_nft::ContractSponsorBasket::<T>::insert((called_contract.clone(), who.clone()), block_number);334 sponsor_transfer = true;335 }336 } else {337 sponsor_transfer = false;338 }339 340 if sponsor_transfer {341 if pallet_nft::ContractSelfSponsoring::<T>::contains_key(called_contract.clone()) {342 if pallet_nft::ContractSelfSponsoring::<T>::get(called_contract.clone()) {343 return Some(called_contract);344 }345 }346 }347348 None349 }350351 pub fn withdraw_contract_instantiate(352 who: &T::AccountId,353 code_hash: &CodeHash<T>,354 salt: &[u8],355 ) -> Option<T::AccountId> where356 T::AccountId: AsRef<[u8]>,357 T::AccountId: UncheckedFrom<T::Hash>358 {359360 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(361 &who,362 code_hash,363 salt,364 );365 pallet_nft::ContractOwner::<T>::insert(new_contract_address.clone(), who.clone());366367 None368 }369}370371// type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;372373// /// Require the transactor pay for themselves and maybe include a tip to gain additional priority374// /// in the queue.375// #[derive(Encode, Decode, Clone, Eq, PartialEq)]376// pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);377378// impl<T: Config + Send + Sync> sp_std::fmt::Debug 379// for ChargeTransactionPayment<T>380// {381// #[cfg(feature = "std")]382// fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {383// write!(f, "ChargeTransactionPayment<{:?}>", self.0)384// }385// #[cfg(not(feature = "std"))]386// fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {387// Ok(())388// }389// }390391// impl<T: Config> ChargeTransactionPayment<T>392// where393// T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<pallet_nft::Call<T>> + IsSubType<pallet_contracts::Call<T>>,394// BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,395// T::AccountId: AsRef<[u8]>,396// T::AccountId: UncheckedFrom<T::Hash>,397// {398// fn traditional_fee(399// len: usize,400// info: &DispatchInfoOf<T::Call>,401// tip: BalanceOf<T>,402// ) -> BalanceOf<T>403// where404// T::Call: Dispatchable<Info = DispatchInfo>,405// {406// <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)407// }408409// fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {410// let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);411// let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);412// let len_saturation = max_block_length as u64 / (len as u64).max(1);413// let coefficient: BalanceOf<T> = weight_saturation414// .min(len_saturation)415// .saturated_into::<BalanceOf<T>>();416// final_fee417// .saturating_mul(coefficient)418// .saturated_into::<TransactionPriority>()419// }420421// fn withdraw_fee(422// &self,423// who: &T::AccountId,424// call: &T::Call,425// info: &DispatchInfoOf<T::Call>,426// len: usize,427// ) -> Result<428// (429// BalanceOf<T>,430// <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,431// ),432// TransactionValidityError,433// > {434// let tip = self.0;435436// let fee = Self::traditional_fee(len, info, tip);437438// // Only mess with balances if fee is not zero.439// if fee.is_zero() {440// return <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)441// .map(|i| (fee, i));442// }443444// // Determine who is paying transaction fee based on ecnomic model445// // Parse call to extract collection ID and access collection sponsor446 447// let sponsor = pallet_nft_transaction_payment::<T>::withdraw_type(who, call);448// //let sponsor = Self::Module::<T>::withdraw_type(who, call);;449// // <pallet_nft_transaction_payment::Module<T>>::withdraw_type(who, call);450451// let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());452453// <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::withdraw_fee(&who_pays_fee, call, info, fee, tip)454// .map(|i| (fee, i))455// }456// }457458459// impl<T: Config + Send + Sync> SignedExtension460// for ChargeTransactionPayment<T>461// where462// BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,463// T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo> + IsSubType<pallet_nft::Call<T>> + IsSubType<pallet_contracts::Call<T>>,464// T::AccountId: AsRef<[u8]>,465// T::AccountId: UncheckedFrom<T::Hash>,466// {467// const IDENTIFIER: &'static str = "ChargeTransactionPayment";468// type AccountId = T::AccountId;469// type Call = T::Call;470// type AdditionalSigned = ();471// type Pre = (472// // tip473// BalanceOf<T>,474// // who pays fee475// Self::AccountId,476// // imbalance resulting from withdrawing the fee477// <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::LiquidityInfo,478// );479// fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {480// Ok(())481// }482483// fn validate(484// &self,485// who: &Self::AccountId,486// call: &Self::Call,487// info: &DispatchInfoOf<Self::Call>,488// len: usize,489// ) -> TransactionValidity {490// let (fee, _) = self.withdraw_fee(who, call, info, len)?;491// Ok(ValidTransaction {492// priority: Self::get_priority(len, info, fee),493// ..Default::default()494// })495// }496497// fn pre_dispatch(498// self,499// who: &Self::AccountId,500// call: &Self::Call,501// info: &DispatchInfoOf<Self::Call>,502// len: usize,503// ) -> Result<Self::Pre, TransactionValidityError> {504// let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;505// Ok((self.0, who.clone(), imbalance))506// }507508// fn post_dispatch(509// pre: Self::Pre,510// info: &DispatchInfoOf<Self::Call>,511// post_info: &PostDispatchInfoOf<Self::Call>,512// len: usize,513// _result: &DispatchResult,514// ) -> Result<(), TransactionValidityError> {515// let (tip, who, imbalance) = pre;516// let actual_fee = pallet_transaction_payment::Module::<T>::compute_actual_fee(517// len as u32,518// info,519// post_info,520// tip,521// );522// <T as pallet_transaction_payment::Config>::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;523// Ok(())524// }525// }1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![cfg_attr(not(feature = "std"), no_std)]78#[cfg(feature = "std")]9pub use std::*;1011#[cfg(feature = "std")]12pub use serde::*;1314#[cfg(feature = "runtime-benchmarks")]15mod benchmarking;1617#[cfg(test)]18mod tests;1920use frame_support::{21 decl_error, decl_module, decl_storage,22 traits::{23 IsSubType, 24 },25 weights::{26 DispatchInfo27 }28};29use sp_runtime::traits::StaticLookup;30use sp_runtime::{31 traits::{ 32 Hash, Dispatchable,33 },34 transaction_validity::{35 InvalidTransaction, TransactionValidityError,36 },37};38use pallet_contracts::chain_extension::UncheckedFrom;39use sp_std::prelude::*;40use nft_data_structs::{41 CreateItemData,42 CollectionId, CollectionMode, TokenId43};4445type CodeHash<T> = <T as frame_system::Config>::Hash;4647 pub trait Config: frame_system::Config + pallet_contracts::Config + pallet_transaction_payment::Config + pallet_nft::Config {48 }4950 // Error for non-fungible-token module.51 52 decl_error! {53 /// Error for non-fungible-token module.54 pub enum Error for Module<T: Config> {55 /// No available class ID56 NoAvailableClassId,57 /// No available token ID58 NoAvailableTokenId,59 /// Token(ClassId, TokenId) not found60 TokenNotFound,61 /// Class not found62 CollectionNotFound,63 /// The operator is not the owner of the token and has no permission64 NoPermission,65 /// Arithmetic calculation overflow66 NumOverflow,67 /// Can not destroy class68 /// Total issuance is not 069 CannotDestroyClass,70 }71}7273 decl_storage! {74 trait Store for Module<T: Config> as NftTransactionPayment{7576 }77}7879decl_module! {8081 pub struct Module<T: Config> for enum Call 82 where 83 origin: T::Origin,84 {85 }86}8788impl<T: Config> Module<T> 89{9091 pub fn check_error(92 who: &T::AccountId,93 call: &T::Call94 ) -> Result<bool, TransactionValidityError> where 95 T::Call: Dispatchable<Info=DispatchInfo>,96 T::Call: IsSubType<pallet_nft::Call<T>>, 97 T::Call: IsSubType<pallet_contracts::Call<T>>,98 T::AccountId: AsRef<[u8]>,99 T::AccountId: UncheckedFrom<T::Hash>100 {101102 match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {103 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {104 105 let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());106 107 let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);108 let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());109 110 if !owned_contract && white_list_enabled {111 if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {112 return Err(InvalidTransaction::Call.into());113 }114 }115 Ok(true)116 },117 _ => { Ok(true) },118 }119 }120121 pub fn withdraw_type(122 who: &T::AccountId,123 call: &T::Call124 ) -> Option<T::AccountId> where 125 T::Call: Dispatchable<Info=DispatchInfo>,126 T::Call: IsSubType<pallet_nft::Call<T>>, 127 T::Call: IsSubType<pallet_contracts::Call<T>>,128 T::AccountId: AsRef<[u8]>,129 T::AccountId: UncheckedFrom<T::Hash>130 {131132 let mut sponsor: Option<T::AccountId> = match IsSubType::<pallet_nft::Call<T>>::is_sub_type(call) {133 Some(pallet_nft::Call::create_item(collection_id, _owner, _properties)) => {134135 Self::withdraw_create_item(who, collection_id, &_properties)136 },137 Some(pallet_nft::Call::transfer(_new_owner, collection_id, item_id, _value)) => {138139 Self::withdraw_transfer(who, collection_id, item_id)140 },141 Some(pallet_nft::Call::set_variable_meta_data(collection_id, item_id, data)) => {142143 Self::withdraw_set_variable_meta_data(who, collection_id, item_id, &data)144 },145 _ => None,146 };147148 sponsor = sponsor.or_else(|| match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {149 Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {150151 Self::withdraw_contract_call(who, dest)152 },153 Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, _data, salt)) => {154155 Self::withdraw_contract_instantiate(&who, code_hash, salt)156 },157 Some(pallet_contracts::Call::instantiate_with_code(_endowment, _gas_limit, _code, _data, _salt)) => {158159 Self::withdraw_contract_instantiate(&who, &T::Hashing::hash(&_code), _salt)160 },161 _ => None,162 });163164 sponsor165 }166167168169 pub fn withdraw_create_item(170 who: &T::AccountId,171 collection_id: &CollectionId,172 _properties: &CreateItemData,173 ) -> Option<T::AccountId> {174 175 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;176177 // sponsor timeout178 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;179180 let limit = collection.limits.sponsor_transfer_timeout;181 if pallet_nft::CreateItemBasket::<T>::contains_key((collection_id, &who)) {182 let last_tx_block = pallet_nft::CreateItemBasket::<T>::get((collection_id, &who));183 let limit_time = last_tx_block + limit.into();184 if block_number <= limit_time {185 return None;186 }187 }188 pallet_nft::CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);189190 // check free create limit191 if collection.limits.sponsored_data_size >= (_properties.len() as u32) {192 collection.sponsorship.sponsor()193 .cloned()194 } else {195 None196 }197 }198199 pub fn withdraw_transfer(200 who: &T::AccountId,201 collection_id: &CollectionId,202 item_id: &TokenId,203 ) -> Option<T::AccountId> {204205 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;206 let limits = pallet_nft::ChainLimit::get();207208 let mut sponsor_transfer = false;209 if collection.sponsorship.confirmed() {210211 let collection_limits = collection.limits.clone();212 let collection_mode = collection.mode.clone();213214 // sponsor timeout215 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;216 sponsor_transfer = match collection_mode {217 CollectionMode::NFT => {218219 // get correct limit220 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {221 collection_limits.sponsor_transfer_timeout222 } else {223 limits.nft_sponsor_transfer_timeout224 };225226 let mut sponsored = true;227 if pallet_nft::NftTransferBasket::<T>::contains_key(collection_id, item_id) {228 let last_tx_block = pallet_nft::NftTransferBasket::<T>::get(collection_id, item_id);229 let limit_time = last_tx_block + limit.into();230 if block_number <= limit_time {231 sponsored = false;232 }233 }234 if sponsored {235 pallet_nft::NftTransferBasket::<T>::insert(collection_id, item_id, block_number);236 }237238 sponsored239 }240 CollectionMode::Fungible(_) => {241242 // get correct limit243 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {244 collection_limits.sponsor_transfer_timeout245 } else {246 limits.fungible_sponsor_transfer_timeout247 };248249 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;250 let mut sponsored = true;251 if pallet_nft::FungibleTransferBasket::<T>::contains_key(collection_id, who) {252 let last_tx_block = pallet_nft::FungibleTransferBasket::<T>::get(collection_id, who);253 let limit_time = last_tx_block + limit.into();254 if block_number <= limit_time {255 sponsored = false;256 }257 }258 if sponsored {259 pallet_nft::FungibleTransferBasket::<T>::insert(collection_id, who, block_number);260 }261262 sponsored263 }264 CollectionMode::ReFungible => {265266 // get correct limit267 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {268 collection_limits.sponsor_transfer_timeout269 } else {270 limits.refungible_sponsor_transfer_timeout271 };272273 let mut sponsored = true;274 if pallet_nft::ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {275 let last_tx_block = pallet_nft::ReFungibleTransferBasket::<T>::get(collection_id, item_id);276 let limit_time = last_tx_block + limit.into();277 if block_number <= limit_time {278 sponsored = false;279 }280 }281 if sponsored {282 pallet_nft::ReFungibleTransferBasket::<T>::insert(collection_id, item_id, block_number);283 }284285 sponsored286 }287 _ => {288 false289 },290 };291 }292293 if !sponsor_transfer {294 None295 } else {296 collection.sponsorship.sponsor()297 .cloned()298 }299 }300 301 pub fn withdraw_set_variable_meta_data(302 who: &T::AccountId,303 collection_id: &CollectionId,304 item_id: &TokenId,305 data: &Vec<u8>,306 ) -> Option<T::AccountId> {307308 let mut sponsor_metadata_changes = false;309310 let collection = pallet_nft::CollectionById::<T>::get(collection_id)?;311312 if313 collection.sponsorship.confirmed() &&314 // Can't sponsor fungible collection, this tx will be rejected315 // as invalid316 !matches!(collection.mode, CollectionMode::Fungible(_)) &&317 data.len() <= collection.limits.sponsored_data_size as usize318 {319 if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {320 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;321322 if pallet_nft::VariableMetaDataBasket::<T>::get(collection_id, item_id)323 .map(|last_block| block_number - last_block > rate_limit)324 .unwrap_or(true) 325 {326 sponsor_metadata_changes = true;327 pallet_nft::VariableMetaDataBasket::<T>::insert(collection_id, item_id, block_number);328 }329 }330 }331332 if !sponsor_metadata_changes {333 None334 } else {335 collection.sponsorship.sponsor().cloned()336 }337338 }339340 pub fn withdraw_contract_call(341 who: &T::AccountId,342 dest: &<T::Lookup as StaticLookup>::Source343 ) -> Option<T::AccountId> {344345 let called_contract: T::AccountId = T::Lookup::lookup((dest).clone()).unwrap_or(T::AccountId::default());346347 let owned_contract = pallet_nft::ContractOwner::<T>::get(called_contract.clone()).as_ref() == Some(who);348 let white_list_enabled = pallet_nft::ContractWhiteListEnabled::<T>::contains_key(called_contract.clone());349 350 // ???351 if !owned_contract && white_list_enabled {352 if !pallet_nft::ContractWhiteList::<T>::contains_key(called_contract.clone(), who) {353 return Some(who.clone())354 // return Err(InvalidTransaction::Call.into());355 }356 }357358 let mut sponsor_transfer = false;359 if pallet_nft::ContractSponsoringRateLimit::<T>::contains_key(called_contract.clone()) {360 let last_tx_block = pallet_nft::ContractSponsorBasket::<T>::get((&called_contract, &who));361 let block_number = <frame_system::Module<T>>::block_number() as T::BlockNumber;362 let rate_limit = pallet_nft::ContractSponsoringRateLimit::<T>::get(&called_contract);363 let limit_time = last_tx_block + rate_limit;364365 if block_number >= limit_time {366 pallet_nft::ContractSponsorBasket::<T>::insert((called_contract.clone(), who.clone()), block_number);367 sponsor_transfer = true;368 }369 } else {370 sponsor_transfer = false;371 }372 373 if sponsor_transfer {374 if pallet_nft::ContractSelfSponsoring::<T>::contains_key(called_contract.clone()) {375 if pallet_nft::ContractSelfSponsoring::<T>::get(called_contract.clone()) {376 return Some(called_contract);377 }378 }379 }380381 None382 }383384 pub fn withdraw_contract_instantiate(385 who: &T::AccountId,386 code_hash: &CodeHash<T>,387 salt: &[u8],388 ) -> Option<T::AccountId> where389 T::AccountId: AsRef<[u8]>,390 T::AccountId: UncheckedFrom<T::Hash>391 {392393 let new_contract_address = <pallet_contracts::Module<T>>::contract_address(394 &who,395 code_hash,396 salt,397 );398 pallet_nft::ContractOwner::<T>::insert(new_contract_address.clone(), who.clone());399400 None401 }402}pallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -32,10 +32,11 @@
use frame_system::{self as system, ensure_signed, ensure_root};
use sp_runtime::sp_std::prelude::Vec;
+use core::ops::{Deref, DerefMut};
use nft_data_structs::{
MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,
AccessMode, ChainLimits, Collection, CreateItemData, CollectionLimits,
- CollectionId, CollectionMode, CollectionHandle, TokenId,
+ CollectionId, CollectionMode, TokenId,
SchemaVersion, SponsorshipState, Ownership,
NftItemType, FungibleItemType, ReFungibleItemType
};
@@ -164,7 +165,25 @@
}
}
-// + pallet_transaction_payment::Config + pallet_nft_transaction_payment::Config + pallet_contracts::Config
+pub struct CollectionHandle<T: frame_system::Config> {
+ pub id: CollectionId,
+ pub collection: Collection<T>,
+}
+
+impl<T: frame_system::Config> Deref for CollectionHandle<T> {
+ type Target = Collection<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.collection
+ }
+}
+
+impl<T: frame_system::Config> DerefMut for CollectionHandle<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.collection
+ }
+}
+
pub trait Config: system::Config + Sized {
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,