git.delta.rocks / unique-network / refs/commits / 2a441390261c

difftreelog

Scheduler refactored and fixed

str-mv2021-06-15parent: #eb5b535.patch.diff
in: master

8 files changed

modifiedpallets/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);
addedpallets/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.
modifiedpallets/nft-transaction-payment/src/lib.rsdiffbeforeafterboth
31 traits::{ 31 traits::{
32 Hash, Dispatchable,32 Hash, Dispatchable,
33 },33 },
34 transaction_validity::{
35 InvalidTransaction, TransactionValidityError,
36 },
34};37};
35use pallet_contracts::chain_extension::UncheckedFrom;38use pallet_contracts::chain_extension::UncheckedFrom;
36use sp_std::prelude::*;39use sp_std::prelude::*;
85impl<T: Config> Module<T> 88impl<T: Config> Module<T>
86{89{
90
91 pub fn check_error(
92 who: &T::AccountId,
93 call: &T::Call
94 ) -> 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 {
101
102 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 }
87120
88 pub fn withdraw_type(121 pub fn withdraw_type(
89 who: &T::AccountId,122 who: &T::AccountId,
368 }401 }
369}402}
370
371// type BalanceOf<T> = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as pallet_transaction_payment::OnChargeTransaction<T>>::Balance;
372
373// /// Require the transactor pay for themselves and maybe include a tip to gain additional priority
374// /// in the queue.
375// #[derive(Encode, Decode, Clone, Eq, PartialEq)]
376// pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);
377
378// 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// }
390
391// impl<T: Config> ChargeTransactionPayment<T>
392// where
393// 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// where
404// T::Call: Dispatchable<Info = DispatchInfo>,
405// {
406// <pallet_transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)
407// }
408
409// 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_saturation
414// .min(len_saturation)
415// .saturated_into::<BalanceOf<T>>();
416// final_fee
417// .saturating_mul(coefficient)
418// .saturated_into::<TransactionPriority>()
419// }
420
421// 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;
435
436// let fee = Self::traditional_fee(len, info, tip);
437
438// // 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// }
443
444// // Determine who is paying transaction fee based on ecnomic model
445// // Parse call to extract collection ID and access collection sponsor
446
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);
450
451// let who_pays_fee = sponsor.unwrap_or_else(|| who.clone());
452
453// <<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// }
457
458
459// impl<T: Config + Send + Sync> SignedExtension
460// for ChargeTransactionPayment<T>
461// where
462// 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// // tip
473// BalanceOf<T>,
474// // who pays fee
475// Self::AccountId,
476// // imbalance resulting from withdrawing the fee
477// <<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// }
482
483// 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// }
496
497// 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// }
507
508// 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// }
modifiedpallets/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 {
modifiedpallets/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();
modifiedtests/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
addedtests/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);
+//   });
+// });
modifiedtests/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,