difftreelog
test(Scheduler) EVM test + amendments
in: master
5 files changed
pallets/scheduler/src/lib.rsdiffbeforeafterboth440 Err(err) => Err(err.error),440 Err(err) => Err(err.error),441 },441 },442 Err(_) => {442 Err(_) => {443 log::info!(443 log::error!(444 target: "runtime::scheduler",444 target: "runtime::scheduler",445 "Warning: Scheduler has failed to execute a post-dispatch transaction. \445 "Warning: Scheduler has failed to execute a post-dispatch transaction. \446 This block might have become invalid.");446 This block might have become invalid.");tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -64,6 +64,7 @@
"testSetVariableMetadataSponsoringRateLimit": "mocha --timeout 9999999 -r ts-node/register ./**/setVariableMetadataSponsoringRateLimit.test.ts",
"testInflation": "mocha --timeout 9999999 -r ts-node/register ./**/inflation.test.ts",
"testScheduler": "mocha --timeout 9999999 -r ts-node/register ./**/scheduler.test.ts",
+ "testSchedulingEVM": "mocha --timeout 9999999 -r ts-node/register ./**/eth/scheduling.test.ts",
"testXcmTransfer": "mocha --timeout 9999999 -r ts-node/register ./**/xcmTransfer.test.ts",
"testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts",
"testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts",
tests/src/eth/scheduling.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/eth/scheduling.test.ts
@@ -0,0 +1,55 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {expect} from 'chai';
+import {createEthAccountWithBalance, deployFlipper, GAS_ARGS, itWeb3, subToEth, transferBalanceToEth} from './util/helpers';
+import {scheduleExpectSuccess, waitNewBlocks} from '../util/helpers';
+import privateKey from '../substrate/privateKey';
+
+describe('Scheduing EVM smart contracts', () => {
+ itWeb3('Successfully schedules and periodically executes an EVM contract', async ({api, web3}) => {
+ const deployer = await createEthAccountWithBalance(api, web3);
+ const flipper = await deployFlipper(web3, deployer);
+ const initialValue = await flipper.methods.getValue().call();
+ const alice = privateKey('//Alice');
+ await transferBalanceToEth(api, alice, subToEth(alice.address));
+
+ {
+ const tx = api.tx.evm.call(
+ subToEth(alice.address),
+ flipper.options.address,
+ flipper.methods.flip().encodeABI(),
+ '0',
+ GAS_ARGS.gas,
+ await web3.eth.getGasPrice(),
+ null,
+ null,
+ [],
+ );
+ const waitForBlocks = 4;
+ const periodBlocks = 2;
+
+ await scheduleExpectSuccess(tx, alice, waitForBlocks, '0x' + '0'.repeat(32), periodBlocks, 2);
+ expect(await flipper.methods.getValue().call()).to.be.equal(initialValue);
+
+ await waitNewBlocks(waitForBlocks - 1);
+ expect(await flipper.methods.getValue().call()).to.be.not.equal(initialValue);
+
+ await waitNewBlocks(periodBlocks);
+ expect(await flipper.methods.getValue().call()).to.be.equal(initialValue);
+ }
+ });
+});
\ No newline at end of file
tests/src/scheduler.test.tsdiffbeforeafterboth--- a/tests/src/scheduler.test.ts
+++ b/tests/src/scheduler.test.ts
@@ -20,7 +20,6 @@
import {
default as usingApi,
submitTransactionAsync,
- submitTransactionExpectFailAsync,
} from './substrate/substrate-api';
import {
createItemExpectSuccess,
@@ -40,49 +39,58 @@
scheduleTransferFundsPeriodicExpectSuccess,
getFreeBalance,
confirmSponsorshipByKeyExpectSuccess,
+ scheduleExpectFailure,
} from './util/helpers';
import {IKeyringPair} from '@polkadot/types/types';
-import {getBalanceSingle} from './substrate/get-balance';
chai.use(chaiAsPromised);
describe('Scheduling token and balance transfers', () => {
let alice: IKeyringPair;
let bob: IKeyringPair;
+ let scheduledIdBase: string;
+ let scheduledIdSlider: number;
before(async() => {
await usingApi(async () => {
alice = privateKey('//Alice');
bob = privateKey('//Bob');
});
+
+ scheduledIdBase = '0x' + '0'.repeat(31);
+ scheduledIdSlider = 0;
});
+ // Loop scheduledId around 10. Unless there are concurrent tasks with long periods/repetitions, tests' tasks' ids shouldn't ovelap.
+ function makeScheduledId(): string {
+ return scheduledIdBase + ((scheduledIdSlider++) % 10);
+ }
+
it('Can schedule a transfer of an owned token with delay', async () => {
await usingApi(async () => {
- // nft
const nftCollectionId = await createCollectionExpectSuccess();
const newNftTokenId = await createItemExpectSuccess(alice, nftCollectionId, 'NFT');
await setCollectionSponsorExpectSuccess(nftCollectionId, alice.address);
await confirmSponsorshipExpectSuccess(nftCollectionId);
- await scheduleTransferAndWaitExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 4);
+ await scheduleTransferAndWaitExpectSuccess(nftCollectionId, newNftTokenId, alice, bob, 1, 4, makeScheduledId());
});
});
it('Can transfer funds periodically', async () => {
- await usingApi(async (api) => {
+ await usingApi(async () => {
const waitForBlocks = 4;
const period = 2;
- await scheduleTransferFundsPeriodicExpectSuccess(1n * UNIQUE, alice, bob, waitForBlocks, period, 2);
- const bobsBalanceBefore = await getBalanceSingle(api, bob.address);
+ await scheduleTransferFundsPeriodicExpectSuccess(1n * UNIQUE, alice, bob, waitForBlocks, makeScheduledId(), period, 2);
+ const bobsBalanceBefore = await getFreeBalance(bob);
// discounting already waited-for operations
await waitNewBlocks(waitForBlocks - 2);
- const bobsBalanceAfterFirst = await getBalanceSingle(api, bob.address);
+ const bobsBalanceAfterFirst = await getFreeBalance(bob);
expect(bobsBalanceAfterFirst > bobsBalanceBefore).to.be.true;
await waitNewBlocks(period);
- const bobsBalanceAfterSecond = await getBalanceSingle(api, bob.address);
+ const bobsBalanceAfterSecond = await getFreeBalance(bob);
expect(bobsBalanceAfterSecond > bobsBalanceAfterFirst).to.be.true;
});
});
@@ -95,28 +103,18 @@
await usingApi(async () => {
const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
- const aliceBalanceBefore = await getFreeBalance(alice);
+ const bobBalanceBefore = await getFreeBalance(bob);
+ const waitForBlocks = 4;
// no need to wait to check, fees must be deducted on scheduling, immediately
- await scheduleTransferExpectSuccess(collectionId, tokenId, alice, bob, 0, 4);
- const aliceBalanceAfter = await getFreeBalance(alice);
- expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;
+ await scheduleTransferExpectSuccess(collectionId, tokenId, alice, bob, 0, waitForBlocks, makeScheduledId());
+ const bobBalanceAfter = await getFreeBalance(bob);
+ // expect(aliceBalanceAfter == aliceBalanceBefore).to.be.true;
+ expect(bobBalanceAfter < bobBalanceBefore).to.be.true;
+ // wait for sequentiality matters
+ await waitNewBlocks(waitForBlocks - 1);
});
});
- /*it('Can\'t schedule a transaction with no funds', async () => {
- await usingApi(async (api) => {
- // Find an empty, unused account
- const zeroBalance = await findUnusedAddress(api);
-
- const collectionId = await createCollectionExpectSuccess();
- const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT');
-
- await transferExpectSuccess(collectionId, tokenId, alice, zeroBalance);
-
- await scheduleTransferAndWaitExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, 4);
- });
- });*/
-
it('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => {
await usingApi(async (api) => {
// Find an empty, unused account
@@ -137,13 +135,16 @@
// Schedule transfer of the NFT a few blocks ahead
const waitForBlocks = 5;
- await scheduleTransferExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks);
+ await scheduleTransferExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, makeScheduledId());
// Get rid of the account's funds before the scheduled transaction takes place
- const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?
+ const balanceTx2 = api.tx.balances.transfer(alice.address, UNIQUE * 68n / 100n);
+ const events = await submitTransactionAsync(zeroBalance, balanceTx2);
+ expect(getGenericResult(events).success).to.be.true;
+ /*const emptyBalanceTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0); // do not null reserved?
const sudoTx = api.tx.sudo.sudo(emptyBalanceTx as any);
const events = await submitTransactionAsync(alice, sudoTx);
- expect(getGenericResult(events).success).to.be.true;
+ expect(getGenericResult(events).success).to.be.true;*/
// Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions
await waitNewBlocks(waitForBlocks - 3);
@@ -157,11 +158,6 @@
await usingApi(async (api) => {
const zeroBalance = await findUnusedAddress(api);
-
- /*await setCollectionLimitsExpectSuccess(alice, nftCollectionId, {
- sponsoredDataRateLimit: 2,
- });*/
- //console.log(await getDetailedCollectionInfo(api, nftCollectionId));
const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);
await submitTransactionAsync(alice, balanceTx);
@@ -170,7 +166,8 @@
const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
- await scheduleTransferExpectSuccess(collectionId, tokenId, alice, zeroBalance, 1, 5);
+ const waitForBlocks = 5;
+ await scheduleTransferExpectSuccess(collectionId, tokenId, alice, zeroBalance, 1, waitForBlocks, makeScheduledId());
const emptyBalanceSponsorTx = api.tx.balances.setBalance(zeroBalance.address, 0, 0);
const sudoTx = api.tx.sudo.sudo(emptyBalanceSponsorTx as any);
@@ -178,61 +175,36 @@
expect(getGenericResult(events).success).to.be.true;
// Wait for a certain number of blocks, save for the ones that already happened while accepting the late transactions
- await waitNewBlocks(2);
+ await waitNewBlocks(waitForBlocks - 3);
expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address));
});
});
-});
-describe.skip('Scheduling EVM smart contracts', () => {
- let alice: IKeyringPair;
- let bob: IKeyringPair;
-
- before(async() => {
- await usingApi(async () => {
- alice = privateKey('//Alice');
- bob = privateKey('//Bob');
- });
- });
-
- // todo contract testing
- it.skip('NFT: Sponsoring of transfers is rate limited', async () => {
+ it.skip('Exceeding sponsor rate limit without having enough funds prevents scheduling a periodic transaction', async () => {
const collectionId = await createCollectionExpectSuccess();
await setCollectionSponsorExpectSuccess(collectionId, bob.address);
await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
await usingApi(async (api) => {
- // Find unused address
const zeroBalance = await findUnusedAddress(api);
- // Mint token for alice
- const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+ await enablePublicMintingExpectSuccess(alice, collectionId);
+ await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address);
- // Transfer this token from Alice to unused address and back
- // Alice to Zero gets sponsored
- const aliceToZero = api.tx.unique.transfer(normalizeAccountId(zeroBalance.address), collectionId, itemId, 0);
- const events1 = await submitTransactionAsync(alice, aliceToZero);
- const result1 = getGenericResult(events1);
+ const bobBalanceBefore = await getFreeBalance(bob);
+
+ const createData = {nft: {const_data: [], variable_data: []}};
+ const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any);
- // Second transfer should fail
- const sponsorBalanceBefore = (await api.query.system.account(bob.address)).data.free.toBigInt();
- const zeroToAlice = api.tx.unique.transfer(normalizeAccountId(alice.address), collectionId, itemId, 0);
- const badTransaction = async function () {
+ /*const badTransaction = async function () {
await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
};
- await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');
- const sponsorBalanceAfter = (await api.query.system.account(bob.address)).data.free.toBigInt();
+ await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/
- // Try again after Zero gets some balance - now it should succeed
- const balancetx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE);
- await submitTransactionAsync(alice, balancetx);
- const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
- const result2 = getGenericResult(events2);
+ await scheduleExpectFailure(creationTx, zeroBalance, 3, makeScheduledId(), 1, 3);
- expect(result1.success).to.be.true;
- expect(result2.success).to.be.true;
- expect(sponsorBalanceAfter).to.be.equal(sponsorBalanceBefore);
+ expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore);
});
});
});
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -846,6 +846,61 @@
}
export async function
+scheduleExpectSuccess(
+ operationTx: any,
+ sender: IKeyringPair,
+ blockSchedule: number,
+ scheduledId: string,
+ period = 1,
+ repetitions = 1,
+) {
+ await usingApi(async (api: ApiPromise) => {
+ const blockNumber: number | undefined = await getBlockNumber(api);
+ const expectedBlockNumber = blockNumber + blockSchedule;
+
+ expect(blockNumber).to.be.greaterThan(0);
+ const scheduleTx = api.tx.scheduler.scheduleNamed( // schedule
+ scheduledId,
+ expectedBlockNumber,
+ repetitions > 1 ? [period, repetitions] : null,
+ 0,
+ {value: operationTx as any},
+ );
+
+ const events = await submitTransactionAsync(sender, scheduleTx);
+ expect(getGenericResult(events).success).to.be.true;
+ });
+}
+
+export async function
+scheduleExpectFailure(
+ operationTx: any,
+ sender: IKeyringPair,
+ blockSchedule: number,
+ scheduledId: string,
+ period = 1,
+ repetitions = 1,
+) {
+ await usingApi(async (api: ApiPromise) => {
+ const blockNumber: number | undefined = await getBlockNumber(api);
+ const expectedBlockNumber = blockNumber + blockSchedule;
+
+ expect(blockNumber).to.be.greaterThan(0);
+ const scheduleTx = api.tx.scheduler.scheduleNamed( // schedule
+ scheduledId,
+ expectedBlockNumber,
+ repetitions <= 1 ? null : [period, repetitions],
+ 0,
+ {value: operationTx as any},
+ );
+
+ //const events =
+ await expect(submitTransactionExpectFailAsync(sender, scheduleTx)).to.be.rejected;
+ //expect(getGenericResult(events).success).to.be.false;
+ });
+}
+
+export async function
scheduleTransferAndWaitExpectSuccess(
collectionId: number,
tokenId: number,
@@ -853,12 +908,12 @@
recipient: IKeyringPair,
value: number | bigint = 1,
blockSchedule: number,
+ scheduledId: string,
) {
await usingApi(async (api: ApiPromise) => {
- await scheduleTransferExpectSuccess(collectionId, tokenId, sender, recipient, value, blockSchedule);
+ await scheduleTransferExpectSuccess(collectionId, tokenId, sender, recipient, value, blockSchedule, scheduledId);
const recipientBalanceBefore = (await api.query.system.account(recipient.address)).data.free.toBigInt();
- console.log(await getFreeBalance(sender));
// sleep for n + 1 blocks
await waitNewBlocks(blockSchedule + 1);
@@ -878,17 +933,12 @@
recipient: IKeyringPair,
value: number | bigint = 1,
blockSchedule: number,
+ scheduledId: string,
) {
await usingApi(async (api: ApiPromise) => {
- const blockNumber: number | undefined = await getBlockNumber(api);
- const expectedBlockNumber = blockNumber + blockSchedule;
-
- expect(blockNumber).to.be.greaterThan(0);
const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value);
- const scheduleTx = api.tx.scheduler.schedule(expectedBlockNumber, null, 0, transferTx as any);
- const events = await submitTransactionAsync(sender, scheduleTx);
- expect(getGenericResult(events).success).to.be.true;
+ await scheduleExpectSuccess(transferTx, sender, blockSchedule, scheduledId);
expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(sender.address));
});
@@ -900,26 +950,20 @@
sender: IKeyringPair,
recipient: IKeyringPair,
blockSchedule: number,
+ scheduledId: string,
period: number,
repetitions: number,
) {
await usingApi(async (api: ApiPromise) => {
- const blockNumber: number | undefined = await getBlockNumber(api);
- const expectedBlockNumber = blockNumber + blockSchedule;
+ const transferTx = api.tx.balances.transfer(recipient.address, amount);
const balanceBefore = await getFreeBalance(recipient);
- expect(blockNumber).to.be.greaterThan(0);
- const transferTx = api.tx.balances.transfer(recipient.address, amount);
- const scheduleTx = api.tx.scheduler.schedule(expectedBlockNumber, [period, repetitions], 0, transferTx as any);
+ await scheduleExpectSuccess(transferTx, sender, blockSchedule, scheduledId, period, repetitions);
- const events = await submitTransactionAsync(sender, scheduleTx);
- expect(getGenericResult(events).success).to.be.true;
-
expect(await getFreeBalance(recipient)).to.be.equal(balanceBefore);
});
}
-
export async function
transferExpectSuccess(