difftreelog
Scheduler without sponsorship features
in: master
5 files changed
pallets/scheduler/src/lib.rsdiffbeforeafterboth--- a/pallets/scheduler/src/lib.rs
+++ b/pallets/scheduler/src/lib.rs
@@ -361,7 +361,6 @@
} else {
false
};
-
let call = match s.call.as_value().cloned() {
Some(c) => c,
None => {
@@ -419,18 +418,18 @@
)
.unwrap();
- // if call have id and periodic, it was be reserved
- if s.maybe_id.is_some() && s.maybe_periodic.is_some() {
- let _ = T::CallExecutor::pay_for_call(
- s.maybe_id.unwrap(),
- sender.clone(),
- call.clone(),
- );
- }
+ // // if call have id it was be reserved
+ // if s.maybe_id.is_some() {
+ // let _ = T::CallExecutor::pay_for_call(
+ // s.maybe_id.unwrap(),
+ // sender.clone(),
+ // call.clone(),
+ // );
+ // }
let r = T::CallExecutor::dispatch_call(sender, call.clone());
- let mut actual_call_weight: Weight = item_weight; //PostDispatchInfo;
+ let mut actual_call_weight: Weight = item_weight;
let result: Result<_, DispatchError> = match r {
Ok(o) => match o {
Ok(di) => {
@@ -472,7 +471,8 @@
Agenda::<T>::append(wake, Some(s));
}
}
- total_weight
+ 0
+ //total_weight
}
}
@@ -719,18 +719,18 @@
};
// reserve balance for periodic execution
- let sender =
- ensure_signed(<<T as Config>::Origin as From<T::PalletsOrigin>>::from(origin).into())?;
- let repeats = match maybe_periodic {
- Some(p) => p.1,
- None => 0,
- };
- let _ = T::CallExecutor::reserve_balance(
- id.clone(),
- sender,
- call.as_value().unwrap().clone(),
- repeats,
- );
+ // let sender =
+ // ensure_signed(<<T as Config>::Origin as From<T::PalletsOrigin>>::from(origin).into())?;
+ // let repeats = match maybe_periodic {
+ // Some(p) => p.1,
+ // None => 1,
+ // };
+ // let _ = T::CallExecutor::reserve_balance(
+ // id.clone(),
+ // sender,
+ // call.as_value().unwrap().clone(),
+ // repeats,
+ // );
Agenda::<T>::append(when, Some(s));
let index = Agenda::<T>::decode_len(when).unwrap_or(1) as u32 - 1;
@@ -755,13 +755,13 @@
return Err(BadOrigin.into());
}
// release balance reserve
- let sender = ensure_signed(
- <<T as Config>::Origin as From<T::PalletsOrigin>>::from(
- origin.unwrap(),
- )
- .into(),
- )?;
- let _ = T::CallExecutor::cancel_reserve(id, sender);
+ // let sender = ensure_signed(
+ // <<T as Config>::Origin as From<T::PalletsOrigin>>::from(
+ // origin.unwrap(),
+ // )
+ // .into(),
+ // )?;
+ // let _ = T::CallExecutor::cancel_reserve(id, sender);
s.call.ensure_unrequested::<T::PreimageProvider>();
}
runtime/opal/src/lib.rsdiffbeforeafterboth--- a/runtime/opal/src/lib.rs
+++ b/runtime/opal/src/lib.rs
@@ -29,7 +29,7 @@
use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256, U256, H160};
use sp_runtime::DispatchError;
use fp_self_contained::*;
-use sp_runtime::traits::{SignedExtension, Member};
+use sp_runtime::traits::{Member};
// #[cfg(any(feature = "std", test))]
// pub use sp_runtime::BuildStorage;
@@ -78,7 +78,7 @@
traits::{BaseArithmetic, Unsigned},
};
use smallvec::smallvec;
-use scale_info::TypeInfo;
+// use scale_info::TypeInfo;
use codec::{Encode, Decode};
use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, OnMethodCall};
use fp_rpc::TransactionStatus;
@@ -898,7 +898,7 @@
type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
use frame_support::traits::NamedReservableCurrency;
-fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtra {
+fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
(
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckGenesis::<Runtime>::new(),
@@ -907,7 +907,8 @@
from,
)),
frame_system::CheckWeight::<Runtime>::new(),
- pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
+ // sponsoring transaction logic
+ // pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
)
}
@@ -937,13 +938,14 @@
let extrinsic = fp_self_contained::CheckedExtrinsic::<
AccountId,
Call,
- SignedExtra,
+ SignedExtraScheduler,
SelfContainedSignedInfo,
> {
- signed: CheckedSignature::<AccountId, SignedExtra, SelfContainedSignedInfo>::Signed(
- signer.clone().into(),
- get_signed_extras(signer.into()),
- ),
+ signed:
+ CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
+ signer.clone().into(),
+ get_signed_extras(signer.into()),
+ ),
function: call.into(),
};
@@ -957,23 +959,13 @@
count: u32,
) -> Result<(), DispatchError> {
let dispatch_info = call.get_dispatch_info();
- let fee_charger = ChargeTransactionPayment::new(0);
- let pre = match fee_charger.pre_dispatch(
- &sponsor.clone().into(),
- &call.into(),
- &dispatch_info,
- 0,
- ) {
- Ok(p) => p,
- Err(_) => fail!("failed to get pre dispatch info"),
- };
+ let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0)
+ .saturating_mul(count.into());
- let count: u128 = count.into();
- let total_fee: u128 = pre.2.map(|imbalance| imbalance.peek()).unwrap() * count;
<Balances as NamedReservableCurrency<AccountId>>::reserve_named(
&id,
&(sponsor.into()),
- total_fee,
+ weight.into(),
)
}
@@ -983,24 +975,12 @@
call: <T as pallet_unq_scheduler::Config>::Call,
) -> Result<u128, DispatchError> {
let dispatch_info = call.get_dispatch_info();
- let fee_charger = ChargeTransactionPayment::new(0);
- let pre = match fee_charger.pre_dispatch(
- &sponsor.clone().into(),
- &call.into(),
- &dispatch_info,
- 0,
- ) {
- Ok(p) => p,
- Err(_) => fail!("failed to get pre dispatch info"),
- };
-
- let single_fee: u128 = pre.2.map(|imbalance| imbalance.peek()).unwrap();
-
+ let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
Ok(
<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
&id,
&(sponsor.into()),
- single_fee,
+ weight.into(),
),
)
}
@@ -1184,6 +1164,13 @@
ChargeTransactionPayment,
//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
);
+pub type SignedExtraScheduler = (
+ frame_system::CheckSpecVersion<Runtime>,
+ frame_system::CheckGenesis<Runtime>,
+ frame_system::CheckEra<Runtime>,
+ frame_system::CheckNonce<Runtime>,
+ frame_system::CheckWeight<Runtime>,
+);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
runtime/quartz/src/lib.rsdiffbeforeafterboth--- a/runtime/quartz/src/lib.rs
+++ b/runtime/quartz/src/lib.rs
@@ -33,10 +33,7 @@
use sp_runtime::{
Permill, Perbill, Percent, create_runtime_str, generic, impl_opaque_keys,
- traits::{
- AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero, SignedExtension,
- Member,
- },
+ traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion, Zero, Member},
transaction_validity::{TransactionSource, TransactionValidity},
ApplyExtrinsicResult, RuntimeAppPublic,
};
@@ -952,7 +949,7 @@
type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
use frame_support::traits::NamedReservableCurrency;
-fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtra {
+fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
(
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckGenesis::<Runtime>::new(),
@@ -961,7 +958,8 @@
from,
)),
frame_system::CheckWeight::<Runtime>::new(),
- pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
+ // sponsoring transaction logic
+ // pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
)
}
@@ -991,13 +989,14 @@
let extrinsic = fp_self_contained::CheckedExtrinsic::<
AccountId,
Call,
- SignedExtra,
+ SignedExtraScheduler,
SelfContainedSignedInfo,
> {
- signed: CheckedSignature::<AccountId, SignedExtra, SelfContainedSignedInfo>::Signed(
- signer.clone().into(),
- get_signed_extras(signer.into()),
- ),
+ signed:
+ CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
+ signer.clone().into(),
+ get_signed_extras(signer.into()),
+ ),
function: call.into(),
};
@@ -1011,23 +1010,13 @@
count: u32,
) -> Result<(), DispatchError> {
let dispatch_info = call.get_dispatch_info();
- let fee_charger = ChargeTransactionPayment::new(0);
- let pre = match fee_charger.pre_dispatch(
- &sponsor.clone().into(),
- &call.into(),
- &dispatch_info,
- 0,
- ) {
- Ok(p) => p,
- Err(_) => fail!("failed to get pre dispatch info"),
- };
+ let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0)
+ .saturating_mul(count.into());
- let count: u128 = count.into();
- let total_fee: u128 = pre.2.map(|imbalance| imbalance.peek()).unwrap() * count;
<Balances as NamedReservableCurrency<AccountId>>::reserve_named(
&id,
&(sponsor.into()),
- total_fee,
+ weight.into(),
)
}
@@ -1037,24 +1026,12 @@
call: <T as pallet_unq_scheduler::Config>::Call,
) -> Result<u128, DispatchError> {
let dispatch_info = call.get_dispatch_info();
- let fee_charger = ChargeTransactionPayment::new(0);
- let pre = match fee_charger.pre_dispatch(
- &sponsor.clone().into(),
- &call.into(),
- &dispatch_info,
- 0,
- ) {
- Ok(p) => p,
- Err(_) => fail!("failed to get pre dispatch info"),
- };
-
- let single_fee: u128 = pre.2.map(|imbalance| imbalance.peek()).unwrap();
-
+ let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
Ok(
<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
&id,
&(sponsor.into()),
- single_fee,
+ weight.into(),
),
)
}
@@ -1182,6 +1159,15 @@
pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
);
+
+pub type SignedExtraScheduler = (
+ frame_system::CheckSpecVersion<Runtime>,
+ frame_system::CheckGenesis<Runtime>,
+ frame_system::CheckEra<Runtime>,
+ frame_system::CheckNonce<Runtime>,
+ frame_system::CheckWeight<Runtime>,
+ // pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
+);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
runtime/unique/src/lib.rsdiffbeforeafterboth--- a/runtime/unique/src/lib.rs
+++ b/runtime/unique/src/lib.rs
@@ -37,7 +37,7 @@
traits::{
Applyable, BlockNumberProvider, Dispatchable, PostDispatchInfoOf, Saturating,
CheckedConversion, AccountIdLookup, BlakeTwo256, Block as BlockT, AccountIdConversion,
- Zero, Member, SignedExtension,
+ Zero, Member,
},
transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError},
ApplyExtrinsicResult, RuntimeAppPublic, SaturatedConversion, DispatchErrorWithPostInfo,
@@ -918,7 +918,7 @@
type ChargeTransactionPayment = pallet_charge_transaction::ChargeTransactionPayment<Runtime>;
use frame_support::traits::NamedReservableCurrency;
-fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtra {
+fn get_signed_extras(from: <Runtime as frame_system::Config>::AccountId) -> SignedExtraScheduler {
(
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckGenesis::<Runtime>::new(),
@@ -927,7 +927,8 @@
from,
)),
frame_system::CheckWeight::<Runtime>::new(),
- pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
+ // sponsoring transaction logic
+ // pallet_charge_transaction::ChargeTransactionPayment::<Runtime>::new(0),
)
}
@@ -957,13 +958,14 @@
let extrinsic = fp_self_contained::CheckedExtrinsic::<
AccountId,
Call,
- SignedExtra,
+ SignedExtraScheduler,
SelfContainedSignedInfo,
> {
- signed: CheckedSignature::<AccountId, SignedExtra, SelfContainedSignedInfo>::Signed(
- signer.clone().into(),
- get_signed_extras(signer.into()),
- ),
+ signed:
+ CheckedSignature::<AccountId, SignedExtraScheduler, SelfContainedSignedInfo>::Signed(
+ signer.clone().into(),
+ get_signed_extras(signer.into()),
+ ),
function: call.into(),
};
@@ -977,23 +979,13 @@
count: u32,
) -> Result<(), DispatchError> {
let dispatch_info = call.get_dispatch_info();
- let fee_charger = ChargeTransactionPayment::new(0);
- let pre = match fee_charger.pre_dispatch(
- &sponsor.clone().into(),
- &call.into(),
- &dispatch_info,
- 0,
- ) {
- Ok(p) => p,
- Err(_) => fail!("failed to get pre dispatch info"),
- };
+ let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0)
+ .saturating_mul(count.into());
- let count: u128 = count.into();
- let total_fee: u128 = pre.2.map(|imbalance| imbalance.peek()).unwrap() * count;
<Balances as NamedReservableCurrency<AccountId>>::reserve_named(
&id,
&(sponsor.into()),
- total_fee,
+ weight.into(),
)
}
@@ -1003,24 +995,12 @@
call: <T as pallet_unq_scheduler::Config>::Call,
) -> Result<u128, DispatchError> {
let dispatch_info = call.get_dispatch_info();
- let fee_charger = ChargeTransactionPayment::new(0);
- let pre = match fee_charger.pre_dispatch(
- &sponsor.clone().into(),
- &call.into(),
- &dispatch_info,
- 0,
- ) {
- Ok(p) => p,
- Err(_) => fail!("failed to get pre dispatch info"),
- };
-
- let single_fee: u128 = pre.2.map(|imbalance| imbalance.peek()).unwrap();
-
+ let weight: Balance = ChargeTransactionPayment::traditional_fee(0, &dispatch_info, 0);
Ok(
<Balances as NamedReservableCurrency<AccountId>>::unreserve_named(
&id,
&(sponsor.into()),
- single_fee,
+ weight.into(),
),
)
}
@@ -1173,6 +1153,14 @@
pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
//pallet_contract_helpers::ContractHelpersExtension<Runtime>,
);
+pub type SignedExtraScheduler = (
+ frame_system::CheckSpecVersion<Runtime>,
+ frame_system::CheckGenesis<Runtime>,
+ frame_system::CheckEra<Runtime>,
+ frame_system::CheckNonce<Runtime>,
+ frame_system::CheckWeight<Runtime>,
+ // pallet_charge_transaction::ChargeTransactionPayment<Runtime>,
+);
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
fp_self_contained::UncheckedExtrinsic<Address, Call, Signature, SignedExtra>;
tests/src/scheduler.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import chai, {expect} from 'chai';18import chaiAsPromised from 'chai-as-promised';19import privateKey from './substrate/privateKey';20import {21 default as usingApi, 22 submitTransactionAsync,23} from './substrate/substrate-api';24import {25 createItemExpectSuccess,26 createCollectionExpectSuccess,27 scheduleTransferExpectSuccess,28 scheduleTransferAndWaitExpectSuccess,29 setCollectionSponsorExpectSuccess,30 confirmSponsorshipExpectSuccess,31 findUnusedAddress,32 UNIQUE,33 enablePublicMintingExpectSuccess,34 addToAllowListExpectSuccess,35 waitNewBlocks,36 normalizeAccountId,37 getTokenOwner,38 getGenericResult,39 scheduleTransferFundsPeriodicExpectSuccess,40 getFreeBalance,41 confirmSponsorshipByKeyExpectSuccess,42 scheduleExpectFailure,43} from './util/helpers';44import {IKeyringPair} from '@polkadot/types/types';4546chai.use(chaiAsPromised);4748describe('Scheduling token and balance transfers', () => {49 let alice: IKeyringPair;50 let bob: IKeyringPair;51 let scheduledIdBase: string;52 let scheduledIdSlider: number;5354 before(async() => {55 await usingApi(async () => {56 alice = privateKey('//Alice');57 bob = privateKey('//Bob');58 });5960 scheduledIdBase = '0x' + '0'.repeat(31);61 scheduledIdSlider = 0;62 });6364 // Loop scheduledId around 10. Unless there are concurrent tasks with long periods/repetitions, tests' tasks' ids shouldn't ovelap.65 function makeScheduledId(): string {66 return scheduledIdBase + ((scheduledIdSlider++) % 10);67 }6869 it('Can schedule a transfer of an owned token with delay', async () => {70 await usingApi(async () => {71 const nftCollectionId = await createCollectionExpectSuccess();72 const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');73 await setCollectionSponsorExpectSuccess(nftCollectionId, alice.address);74 await confirmSponsorshipExpectSuccess(nftCollectionId);7576 await scheduleTransferAndWaitExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 4, makeScheduledId());77 });78 });7980 it('Can transfer funds periodically', async () => {81 await usingApi(async () => {82 const waitForBlocks = 4;83 const period = 2;84 await scheduleTransferFundsPeriodicExpectSuccess(1n * UNIQUE, alice, bob, waitForBlocks, makeScheduledId(), period, 2);85 const bobsBalanceBefore = await getFreeBalance(bob);8687 // discounting already waited-for operations88 await waitNewBlocks(waitForBlocks - 2);89 const bobsBalanceAfterFirst = await getFreeBalance(bob);90 expect(bobsBalanceAfterFirst > bobsBalanceBefore).to.be.true;9192 await waitNewBlocks(period);93 const bobsBalanceAfterSecond = await getFreeBalance(bob);94 expect(bobsBalanceAfterSecond > bobsBalanceAfterFirst).to.be.true;95 });96 });9798 it('Can sponsor scheduling a transaction', async () => {99 const collectionId = await createCollectionExpectSuccess();100 await setCollectionSponsorExpectSuccess(collectionId, bob.address);101 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');102103 await usingApi(async () => {104 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);105106 const bobBalanceBefore = await getFreeBalance(bob);107 const waitForBlocks = 4;108 // no need to wait to check, fees must be deducted on scheduling, immediately109 await scheduleTransferExpectSuccess(collectionId, tokenId, alice, bob, 0, waitForBlocks, makeScheduledId());110 const bobBalanceAfter = await getFreeBalance(bob);111 // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;112 expect(bobBalanceAfter < bobBalanceBefore).to.be.true;113 // wait for sequentiality matters114 await waitNewBlocks(waitForBlocks - 1);115 });116 });117118 it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {119 await usingApi(async (api) => {120 // Find an empty, unused account121 const zeroBalance = await findUnusedAddress(api);122123 const collectionId = await createCollectionExpectSuccess();124125 // Add zeroBalance address to allow list126 await enablePublicMintingExpectSuccess(alice, collectionId);127 await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);128129 // Grace zeroBalance with money, enough to cover future transactions130 const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);131 await submitTransactionAsync(alice, balanceTx);132133 // Mint a fresh NFT134 const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');135136 // Schedule transfer of the NFT a few blocks ahead137 const waitForBlocks = 5;138 await scheduleTransferExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, makeScheduledId());139140 // Get rid of the account's funds before the scheduled transaction takes place141 const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);142 const events = await submitTransactionAsync(zeroBalance, balanceTx2);143 expect(getGenericResult(events).success).to.be.true;144 /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?145 const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);146 const events = await submitTransactionAsync(alice, sudoTx);147 expect(getGenericResult(events).success).to.be.true;*/148149 // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions150 await waitNewBlocks(waitForBlocks - 3);151152 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));153 });154 });155156 it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {157 const collectionId = await createCollectionExpectSuccess();158159 await usingApi(async (api) => {160 const zeroBalance = await findUnusedAddress(api);161 const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);162 await submitTransactionAsync(alice, balanceTx);163164 await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);165 await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);166167 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);168169 const waitForBlocks = 5;170 await scheduleTransferExpectSuccess(collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, makeScheduledId());171172 const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);173 const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);174 const events = await submitTransactionAsync(alice, sudoTx);175 expect(getGenericResult(events).success).to.be.true;176177 // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions178 await waitNewBlocks(waitForBlocks - 3);179180 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));181 });182 });183184 it.skip('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {185 const collectionId = await createCollectionExpectSuccess();186 await setCollectionSponsorExpectSuccess(collectionId, bob.address);187 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');188189 await usingApi(async (api) => {190 const zeroBalance = await findUnusedAddress(api);191192 await enablePublicMintingExpectSuccess(alice, collectionId);193 await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);194195 const bobBalanceBefore = await getFreeBalance(bob);196197 const createData = {nft: {const_data: [], variable_data: []}};198 const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);199200 /*const badTransaction = async function () {201 await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);202 };203 await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/204205 await scheduleExpectFailure(creationTx, zeroBalance, 3, makeScheduledId(), 1, 3);206207 expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);208 });209 });210});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import chai, {expect} from 'chai';18import chaiAsPromised from 'chai-as-promised';19import privateKey from './substrate/privateKey';20import {21 default as usingApi, 22 submitTransactionAsync,23} from './substrate/substrate-api';24import {25 createItemExpectSuccess,26 createCollectionExpectSuccess,27 scheduleTransferExpectSuccess,28 scheduleTransferAndWaitExpectSuccess,29 setCollectionSponsorExpectSuccess,30 confirmSponsorshipExpectSuccess,31 findUnusedAddress,32 UNIQUE,33 enablePublicMintingExpectSuccess,34 addToAllowListExpectSuccess,35 waitNewBlocks,36 normalizeAccountId,37 getTokenOwner,38 getGenericResult,39 scheduleTransferFundsPeriodicExpectSuccess,40 getFreeBalance,41 confirmSponsorshipByKeyExpectSuccess,42 scheduleExpectFailure,43} from './util/helpers';44import {IKeyringPair} from '@polkadot/types/types';4546chai.use(chaiAsPromised);4748describe.skip('Scheduling token and balance transfers', () => {49 let alice: IKeyringPair;50 let bob: IKeyringPair;51 let scheduledIdBase: string;52 let scheduledIdSlider: number;5354 before(async() => {55 await usingApi(async () => {56 alice = privateKey('//Alice');57 bob = privateKey('//Bob');58 });5960 scheduledIdBase = '0x' + '0'.repeat(31);61 scheduledIdSlider = 0;62 });6364 // Loop scheduledId around 10. Unless there are concurrent tasks with long periods/repetitions, tests' tasks' ids shouldn't ovelap.65 function makeScheduledId(): string {66 return scheduledIdBase + ((scheduledIdSlider++) % 10);67 }6869 it('Can schedule a transfer of an owned token with delay', async () => {70 await usingApi(async () => {71 const nftCollectionId = await createCollectionExpectSuccess();72 const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');73 await setCollectionSponsorExpectSuccess(nftCollectionId, alice.address);74 await confirmSponsorshipExpectSuccess(nftCollectionId);7576 await scheduleTransferAndWaitExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 4, makeScheduledId());77 });78 });7980 it('Can transfer funds periodically', async () => {81 await usingApi(async () => {82 const waitForBlocks = 4;83 const period = 2;84 await scheduleTransferFundsPeriodicExpectSuccess(1n * UNIQUE, alice, bob, waitForBlocks, makeScheduledId(), period, 2);85 const bobsBalanceBefore = await getFreeBalance(bob);8687 // discounting already waited-for operations88 await waitNewBlocks(waitForBlocks - 2);89 const bobsBalanceAfterFirst = await getFreeBalance(bob);90 expect(bobsBalanceAfterFirst > bobsBalanceBefore).to.be.true;9192 await waitNewBlocks(period);93 const bobsBalanceAfterSecond = await getFreeBalance(bob);94 expect(bobsBalanceAfterSecond > bobsBalanceAfterFirst).to.be.true;95 });96 });9798 it('Can sponsor scheduling a transaction', async () => {99 const collectionId = await createCollectionExpectSuccess();100 await setCollectionSponsorExpectSuccess(collectionId, bob.address);101 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');102103 await usingApi(async () => {104 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);105106 const bobBalanceBefore = await getFreeBalance(bob);107 const waitForBlocks = 4;108 // no need to wait to check, fees must be deducted on scheduling, immediately109 await scheduleTransferExpectSuccess(collectionId, tokenId, alice, bob, 0, waitForBlocks, makeScheduledId());110 const bobBalanceAfter = await getFreeBalance(bob);111 // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;112 expect(bobBalanceAfter < bobBalanceBefore).to.be.true;113 // wait for sequentiality matters114 await waitNewBlocks(waitForBlocks - 1);115 });116 });117118 it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {119 await usingApi(async (api) => {120 // Find an empty, unused account121 const zeroBalance = await findUnusedAddress(api);122123 const collectionId = await createCollectionExpectSuccess();124125 // Add zeroBalance address to allow list126 await enablePublicMintingExpectSuccess(alice, collectionId);127 await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);128129 // Grace zeroBalance with money, enough to cover future transactions130 const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);131 await submitTransactionAsync(alice, balanceTx);132133 // Mint a fresh NFT134 const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT');135136 // Schedule transfer of the NFT a few blocks ahead137 const waitForBlocks = 5;138 await scheduleTransferExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, makeScheduledId());139140 // Get rid of the account's funds before the scheduled transaction takes place141 const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);142 const events = await submitTransactionAsync(zeroBalance, balanceTx2);143 expect(getGenericResult(events).success).to.be.true;144 /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?145 const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);146 const events = await submitTransactionAsync(alice, sudoTx);147 expect(getGenericResult(events).success).to.be.true;*/148149 // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions150 await waitNewBlocks(waitForBlocks - 3);151152 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address));153 });154 });155156 it('Sponsor going bankrupt does not impact a scheduled transaction', async () => {157 const collectionId = await createCollectionExpectSuccess();158159 await usingApi(async (api) => {160 const zeroBalance = await findUnusedAddress(api);161 const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);162 await submitTransactionAsync(alice, balanceTx);163164 await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address);165 await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance);166167 const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);168169 const waitForBlocks = 5;170 await scheduleTransferExpectSuccess(collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, makeScheduledId());171172 const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);173 const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);174 const events = await submitTransactionAsync(alice, sudoTx);175 expect(getGenericResult(events).success).to.be.true;176177 // Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions178 await waitNewBlocks(waitForBlocks - 3);179180 expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));181 });182 });183184 it.skip('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {185 const collectionId = await createCollectionExpectSuccess();186 await setCollectionSponsorExpectSuccess(collectionId, bob.address);187 await confirmSponsorshipExpectSuccess(collectionId, '//Bob');188189 await usingApi(async (api) => {190 const zeroBalance = await findUnusedAddress(api);191192 await enablePublicMintingExpectSuccess(alice, collectionId);193 await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);194195 const bobBalanceBefore = await getFreeBalance(bob);196197 const createData = {nft: {const_data: [], variable_data: []}};198 const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);199200 /*const badTransaction = async function () {201 await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);202 };203 await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/204205 await scheduleExpectFailure(creationTx, zeroBalance, 3, makeScheduledId(), 1, 3);206207 expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);208 });209 });210});