--- a/tests/package.json +++ b/tests/package.json @@ -82,7 +82,7 @@ "testSetOffchainSchema": "mocha --timeout 9999999 -r ts-node/register ./**/setOffchainSchema.test.ts", "testNextSponsoring": "mocha --timeout 9999999 -r ts-node/register ./**/nextSponsoring.test.ts", "testOverflow": "mocha --timeout 9999999 -r ts-node/register ./**/overflow.test.ts", - "testMaintenance": "mocha --timeout 9999999 -r ts-node/register ./**/maintenanceMode.seqtest.ts", + "testMaintenance": "mocha --timeout 9999999 -r ts-node/register ./**/maintenance.seqtest.ts", "testInflation": "mocha --timeout 9999999 -r ts-node/register ./**/inflation.seqtest.ts", "testScheduler": "mocha --timeout 9999999 -r ts-node/register ./**/scheduler.seqtest.ts", "testSchedulingEVM": "mocha --timeout 9999999 -r ts-node/register ./**/eth/scheduling.test.ts", --- /dev/null +++ b/tests/src/maintenance.seqtest.ts @@ -0,0 +1,338 @@ +// 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 . + +import {IKeyringPair} from '@polkadot/types/types'; +import {ApiPromise} from '@polkadot/api'; +import {expect, itSched, itSub, Pallets, requirePalletsOrSkip, usingPlaygrounds} from './util'; +import {itEth} from './eth/util'; + +async function maintenanceEnabled(api: ApiPromise): Promise { + return (await api.query.maintenance.enabled()).toJSON() as boolean; +} + +describe('Integration Test: Maintenance Functionality', () => { + let superuser: IKeyringPair; + let donor: IKeyringPair; + let bob: IKeyringPair; + + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.Maintenance]); + superuser = await privateKey('//Alice'); + donor = await privateKey({filename: __filename}); + [bob] = await helper.arrange.createAccounts([10000n], donor); + + }); + }); + + describe('Maintenance Mode', () => { + before(async function() { + await usingPlaygrounds(async (helper) => { + if (await maintenanceEnabled(helper.getApi())) { + console.warn('\tMaintenance mode was left enabled BEFORE the test suite! Disabling it now.'); + await expect(helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', [])).to.be.fulfilled; + } + }); + }); + + itSub('Allows superuser to enable and disable maintenance mode - and disallows anyone else', async ({helper}) => { + // Make sure non-sudo can't enable maintenance mode + await expect(helper.executeExtrinsic(superuser, 'api.tx.maintenance.enable', []), 'on commoner enabling MM') + .to.be.rejectedWith(/BadOrigin/); + + // Set maintenance mode + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; + + // Make sure non-sudo can't disable maintenance mode + await expect(helper.executeExtrinsic(bob, 'api.tx.maintenance.disable', []), 'on commoner disabling MM') + .to.be.rejectedWith(/BadOrigin/); + + // Disable maintenance mode + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; + }); + + itSub('MM blocks unique pallet calls', async ({helper}) => { + // Can create an NFT collection before enabling the MM + const nftCollection = await helper.nft.mintCollection(bob, { + tokenPropertyPermissions: [{key: 'test', permission: { + collectionAdmin: true, + tokenOwner: true, + mutable: true, + }}], + }); + + // Can mint an NFT before enabling the MM + const nft = await nftCollection.mintToken(bob); + + // Can create an FT collection before enabling the MM + const ftCollection = await helper.ft.mintCollection(superuser); + + // Can mint an FT before enabling the MM + await expect(ftCollection.mint(superuser)).to.be.fulfilled; + + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; + + // Unable to create a collection when the MM is enabled + await expect(helper.nft.mintCollection(superuser), 'cudo forbidden stuff') + .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); + + // Unable to set token properties when the MM is enabled + await expect(nft.setProperties( + bob, + [{key: 'test', value: 'test-val'}], + )).to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); + + // Unable to mint an NFT when the MM is enabled + await expect(nftCollection.mintToken(superuser)) + .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); + + // Unable to mint an FT when the MM is enabled + await expect(ftCollection.mint(superuser)) + .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); + + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; + + // Can create a collection after disabling the MM + await expect(helper.nft.mintCollection(bob), 'MM is disabled, the collection should be created').to.be.fulfilled; + + // Can set token properties after disabling the MM + await nft.setProperties(bob, [{key: 'test', value: 'test-val'}]); + + // Can mint an NFT after disabling the MM + await nftCollection.mintToken(bob); + + // Can mint an FT after disabling the MM + await ftCollection.mint(superuser); + }); + + itSub.ifWithPallets('MM blocks unique pallet calls (Re-Fungible)', [Pallets.ReFungible], async ({helper}) => { + // Can create an RFT collection before enabling the MM + const rftCollection = await helper.rft.mintCollection(superuser); + + // Can mint an RFT before enabling the MM + await expect(rftCollection.mintToken(superuser)).to.be.fulfilled; + + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; + + // Unable to mint an RFT when the MM is enabled + await expect(rftCollection.mintToken(superuser)) + .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); + + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; + + // Can mint an RFT after disabling the MM + await rftCollection.mintToken(superuser); + }); + + itSub('MM allows native token transfers and RPC calls', async ({helper}) => { + // We can use RPC before the MM is enabled + const totalCount = await helper.collection.getTotalCount(); + + // We can transfer funds before the MM is enabled + await expect(helper.balance.transferToSubstrate(superuser, bob.address, 2n)).to.be.fulfilled; + + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; + + // RPCs work while in maintenance + expect(await helper.collection.getTotalCount()).to.be.deep.equal(totalCount); + + // We still able to transfer funds + await expect(helper.balance.transferToSubstrate(bob, superuser.address, 1n)).to.be.fulfilled; + + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; + + // RPCs work after maintenance + expect(await helper.collection.getTotalCount()).to.be.deep.equal(totalCount); + + // Transfers work after maintenance + await expect(helper.balance.transferToSubstrate(bob, superuser.address, 1n)).to.be.fulfilled; + }); + + itSched.ifWithPallets('MM blocks scheduled calls and the scheduler itself', [Pallets.Scheduler], async (scheduleKind, {helper}) => { + const collection = await helper.nft.mintCollection(bob); + + const nftBeforeMM = await collection.mintToken(bob); + const nftDuringMM = await collection.mintToken(bob); + const nftAfterMM = await collection.mintToken(bob); + + const [ + scheduledIdBeforeMM, + scheduledIdDuringMM, + scheduledIdBunkerThroughMM, + scheduledIdAttemptDuringMM, + scheduledIdAfterMM, + ] = scheduleKind == 'named' + ? helper.arrange.makeScheduledIds(5) + : new Array(5); + + const blocksToWait = 6; + + // Scheduling works before the maintenance + await nftBeforeMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdBeforeMM}) + .transfer(bob, {Substrate: superuser.address}); + + await helper.wait.newBlocks(blocksToWait + 1); + expect(await nftBeforeMM.getOwner()).to.be.deep.equal({Substrate: superuser.address}); + + // Schedule a transaction that should occur *during* the maintenance + await nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdDuringMM}) + .transfer(bob, {Substrate: superuser.address}); + + // Schedule a transaction that should occur *after* the maintenance + await nftDuringMM.scheduleAfter(blocksToWait * 2, {scheduledId: scheduledIdBunkerThroughMM}) + .transfer(bob, {Substrate: superuser.address}); + + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; + + await helper.wait.newBlocks(blocksToWait + 1); + // The owner should NOT change since the scheduled transaction should be rejected + expect(await nftDuringMM.getOwner()).to.be.deep.equal({Substrate: bob.address}); + + // Any attempts to schedule a tx during the MM should be rejected + await expect(nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAttemptDuringMM}) + .transfer(bob, {Substrate: superuser.address})) + .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); + + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; + + // Scheduling works after the maintenance + await nftAfterMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAfterMM}) + .transfer(bob, {Substrate: superuser.address}); + + await helper.wait.newBlocks(blocksToWait + 1); + + expect(await nftAfterMM.getOwner()).to.be.deep.equal({Substrate: superuser.address}); + // The owner of the token scheduled for transaction *before* maintenance should now change *after* maintenance + expect(await nftDuringMM.getOwner()).to.be.deep.equal({Substrate: superuser.address}); + }); + + itEth('Disallows Ethereum transactions to execute while in maintenance', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const receiver = helper.eth.createAccount(); + + const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'A', 'B', 'C', ''); + + // Set maintenance mode + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; + + const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + const tokenId = await contract.methods.nextTokenId().call(); + expect(tokenId).to.be.equal('1'); + + await expect(contract.methods.mintWithTokenURI(receiver, 'Test URI').send()) + .to.be.rejectedWith(/Returned error: unknown error/); + + await expect(contract.methods.ownerOf(tokenId).call()).rejectedWith(/token not found/); + + // Disable maintenance mode + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; + }); + + itSub('Allows to enable and disable MM repeatedly', async ({helper}) => { + // Set maintenance mode + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; + + // Disable maintenance mode + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); + expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; + }); + + afterEach(async () => { + await usingPlaygrounds(async helper => { + if (helper.fetchMissingPalletNames([Pallets.Maintenance]).length != 0) return; + if (await maintenanceEnabled(helper.getApi())) { + console.warn('\tMaintenance mode was left enabled AFTER a test has finished! Be careful. Disabling it now.'); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); + } + expect(await maintenanceEnabled(helper.getApi()), 'Disastrous! Exited the test suite with maintenance mode on.').to.be.false; + }); + }); + }); + + describe('Preimage Execution', () => { + let preimageHash: string; + + before(async function() { + await usingPlaygrounds(async (helper) => { + requirePalletsOrSkip(this, helper, [Pallets.Preimage, Pallets.Maintenance]); + + // create a preimage to be operated with in the tests + const randomAccounts = await helper.arrange.createCrowd(10, 0n, superuser); + const randomIdentities = randomAccounts.map((acc, i) => [ + acc.address, { + deposit: 0n, + judgements: [], + info: { + display: { + raw: `Random Account #${i}`, + }, + }, + }, + ]); + const preimage = helper.constructApiCall('api.tx.identity.forceInsertIdentities', [randomIdentities]).method.toHex(); + const result = await helper.preimage.notePreimage(bob, preimage); + const events = result.result.events.filter(x => x.event.method === 'Noted' && x.event.section === 'preimage'); + preimageHash = events[0].event.data[0].toHuman(); + }); + }); + + itSub('Successfully executes call in a preimage', async ({helper}) => { + const result = await expect(helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.executePreimage', [preimageHash])) + .to.be.fulfilled; + + // preimage is executed, and an appropriate event is present + const events = result.result.events.filter((x: any) => x.event.method === 'IdentitiesInserted' && x.event.section === 'identity'); + expect(events.length).to.be.equal(1); + + // the preimage goes back to being unrequested + expect(await helper.preimage.getPreimageInfo(preimageHash)).to.have.property('unrequested'); + }); + + itSub('Does not allow preimage execution with non-root', async ({helper}) => { + await expect(helper.executeExtrinsic(bob, 'api.tx.maintenance.executePreimage', [preimageHash])) + .to.be.rejectedWith(/BadOrigin/); + }); + + itSub('Does not allow execution of non-existent preimages', async ({helper}) => { + await expect(helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.executePreimage', [ + '0x1010101010101010101010101010101010101010101010101010101010101010', + ])).to.be.rejectedWith(/Unavailable/); + }); + + after(async function() { + await usingPlaygrounds(async (helper) => { + if (helper.fetchMissingPalletNames([Pallets.Preimage, Pallets.Maintenance]).length != 0) return; + + await helper.preimage.unnotePreimage(bob, preimageHash); + }); + }); + }); +}); --- a/tests/src/maintenanceMode.seqtest.ts +++ /dev/null @@ -1,270 +0,0 @@ -// 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 . - -import {IKeyringPair} from '@polkadot/types/types'; -import {ApiPromise} from '@polkadot/api'; -import {expect, itSched, itSub, Pallets, usingPlaygrounds} from './util'; -import {itEth} from './eth/util'; - -async function maintenanceEnabled(api: ApiPromise): Promise { - return (await api.query.maintenance.enabled()).toJSON() as boolean; -} - -describe('Integration Test: Maintenance Mode', () => { - let superuser: IKeyringPair; - let donor: IKeyringPair; - let bob: IKeyringPair; - - before(async () => { - await usingPlaygrounds(async (helper, privateKey) => { - superuser = await privateKey('//Alice'); - donor = await privateKey({filename: __filename}); - [bob] = await helper.arrange.createAccounts([100n], donor); - - if (await maintenanceEnabled(helper.getApi())) { - console.warn('\tMaintenance mode was left enabled BEFORE the test suite! Disabling it now.'); - await expect(helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', [])).to.be.fulfilled; - } - }); - }); - - itSub('Allows superuser to enable and disable maintenance mode - and disallows anyone else', async ({helper}) => { - // Make sure non-sudo can't enable maintenance mode - await expect(helper.executeExtrinsic(superuser, 'api.tx.maintenance.enable', []), 'on commoner enabling MM') - .to.be.rejectedWith(/BadOrigin/); - - // Set maintenance mode - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; - - // Make sure non-sudo can't disable maintenance mode - await expect(helper.executeExtrinsic(bob, 'api.tx.maintenance.disable', []), 'on commoner disabling MM') - .to.be.rejectedWith(/BadOrigin/); - - // Disable maintenance mode - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; - }); - - itSub('MM blocks unique pallet calls', async ({helper}) => { - // Can create an NFT collection before enabling the MM - const nftCollection = await helper.nft.mintCollection(bob, { - tokenPropertyPermissions: [{key: 'test', permission: { - collectionAdmin: true, - tokenOwner: true, - mutable: true, - }}], - }); - - // Can mint an NFT before enabling the MM - const nft = await nftCollection.mintToken(bob); - - // Can create an FT collection before enabling the MM - const ftCollection = await helper.ft.mintCollection(superuser); - - // Can mint an FT before enabling the MM - await expect(ftCollection.mint(superuser)).to.be.fulfilled; - - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; - - // Unable to create a collection when the MM is enabled - await expect(helper.nft.mintCollection(superuser), 'cudo forbidden stuff') - .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); - - // Unable to set token properties when the MM is enabled - await expect(nft.setProperties( - bob, - [{key: 'test', value: 'test-val'}], - )).to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); - - // Unable to mint an NFT when the MM is enabled - await expect(nftCollection.mintToken(superuser)) - .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); - - // Unable to mint an FT when the MM is enabled - await expect(ftCollection.mint(superuser)) - .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); - - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; - - // Can create a collection after disabling the MM - await expect(helper.nft.mintCollection(bob), 'MM is disabled, the collection should be created').to.be.fulfilled; - - // Can set token properties after disabling the MM - await nft.setProperties(bob, [{key: 'test', value: 'test-val'}]); - - // Can mint an NFT after disabling the MM - await nftCollection.mintToken(bob); - - // Can mint an FT after disabling the MM - await ftCollection.mint(superuser); - }); - - itSub.ifWithPallets('MM blocks unique pallet calls (Re-Fungible)', [Pallets.ReFungible], async ({helper}) => { - // Can create an RFT collection before enabling the MM - const rftCollection = await helper.rft.mintCollection(superuser); - - // Can mint an RFT before enabling the MM - await expect(rftCollection.mintToken(superuser)).to.be.fulfilled; - - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; - - // Unable to mint an RFT when the MM is enabled - await expect(rftCollection.mintToken(superuser)) - .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); - - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; - - // Can mint an RFT after disabling the MM - await rftCollection.mintToken(superuser); - }); - - itSub('MM allows native token transfers and RPC calls', async ({helper}) => { - // We can use RPC before the MM is enabled - const totalCount = await helper.collection.getTotalCount(); - - // We can transfer funds before the MM is enabled - await expect(helper.balance.transferToSubstrate(superuser, bob.address, 2n)).to.be.fulfilled; - - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; - - // RPCs work while in maintenance - expect(await helper.collection.getTotalCount()).to.be.deep.equal(totalCount); - - // We still able to transfer funds - await expect(helper.balance.transferToSubstrate(bob, superuser.address, 1n)).to.be.fulfilled; - - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; - - // RPCs work after maintenance - expect(await helper.collection.getTotalCount()).to.be.deep.equal(totalCount); - - // Transfers work after maintenance - await expect(helper.balance.transferToSubstrate(bob, superuser.address, 1n)).to.be.fulfilled; - }); - - itSched.ifWithPallets('MM blocks scheduled calls and the scheduler itself', [Pallets.Scheduler], async (scheduleKind, {helper}) => { - const collection = await helper.nft.mintCollection(bob); - - const nftBeforeMM = await collection.mintToken(bob); - const nftDuringMM = await collection.mintToken(bob); - const nftAfterMM = await collection.mintToken(bob); - - const [ - scheduledIdBeforeMM, - scheduledIdDuringMM, - scheduledIdBunkerThroughMM, - scheduledIdAttemptDuringMM, - scheduledIdAfterMM, - ] = scheduleKind == 'named' - ? helper.arrange.makeScheduledIds(5) - : new Array(5); - - const blocksToWait = 6; - - // Scheduling works before the maintenance - await nftBeforeMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdBeforeMM}) - .transfer(bob, {Substrate: superuser.address}); - - await helper.wait.newBlocks(blocksToWait + 1); - expect(await nftBeforeMM.getOwner()).to.be.deep.equal({Substrate: superuser.address}); - - // Schedule a transaction that should occur *during* the maintenance - await nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdDuringMM}) - .transfer(bob, {Substrate: superuser.address}); - - // Schedule a transaction that should occur *after* the maintenance - await nftDuringMM.scheduleAfter(blocksToWait * 2, {scheduledId: scheduledIdBunkerThroughMM}) - .transfer(bob, {Substrate: superuser.address}); - - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; - - await helper.wait.newBlocks(blocksToWait + 1); - // The owner should NOT change since the scheduled transaction should be rejected - expect(await nftDuringMM.getOwner()).to.be.deep.equal({Substrate: bob.address}); - - // Any attempts to schedule a tx during the MM should be rejected - await expect(nftDuringMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAttemptDuringMM}) - .transfer(bob, {Substrate: superuser.address})) - .to.be.rejectedWith(/Invalid Transaction: Transaction call is not expected/); - - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; - - // Scheduling works after the maintenance - await nftAfterMM.scheduleAfter(blocksToWait, {scheduledId: scheduledIdAfterMM}) - .transfer(bob, {Substrate: superuser.address}); - - await helper.wait.newBlocks(blocksToWait + 1); - - expect(await nftAfterMM.getOwner()).to.be.deep.equal({Substrate: superuser.address}); - // The owner of the token scheduled for transaction *before* maintenance should now change *after* maintenance - expect(await nftDuringMM.getOwner()).to.be.deep.equal({Substrate: superuser.address}); - }); - - itEth('Disallows Ethereum transactions to execute while in maintenance', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const receiver = helper.eth.createAccount(); - - const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'A', 'B', 'C', ''); - - // Set maintenance mode - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; - - const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - const tokenId = await contract.methods.nextTokenId().call(); - expect(tokenId).to.be.equal('1'); - - await expect(contract.methods.mintWithTokenURI(receiver, 'Test URI').send()) - .to.be.rejectedWith(/Returned error: unknown error/); - - await expect(contract.methods.ownerOf(tokenId).call()).rejectedWith(/token not found/); - - // Disable maintenance mode - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; - }); - - itSub('Allows to enable and disable MM repeatedly', async ({helper}) => { - // Set maintenance mode - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.enable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is OFF when it should be ON').to.be.true; - - // Disable maintenance mode - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); - expect(await maintenanceEnabled(helper.getApi()), 'MM is ON when it should be OFF').to.be.false; - }); - - afterEach(async () => { - await usingPlaygrounds(async helper => { - if (await maintenanceEnabled(helper.getApi())) { - console.warn('\tMaintenance mode was left enabled AFTER a test has finished! Be careful. Disabling it now.'); - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.maintenance.disable', []); - } - expect(await maintenanceEnabled(helper.getApi()), 'Disastrous! Exited the test suite with maintenance mode on.').to.be.false; - }); - }); -}); --- a/tests/src/pallet-presence.test.ts +++ b/tests/src/pallet-presence.test.ts @@ -66,6 +66,7 @@ const foreignAssets = 'foreignassets'; const appPromotion = 'apppromotion'; const collatorSelection = ['authorship', 'session', 'collatorselection', 'identity']; + const governance = ['preimage']; const testUtils = 'testutils'; if (chain.eq('OPAL by UNIQUE')) { @@ -75,6 +76,7 @@ appPromotion, testUtils, ...collatorSelection, + ...governance, ); } else if (chain.eq('QUARTZ by UNIQUE') || chain.eq('SAPPHIRE by UNIQUE')) { requiredPallets.push( @@ -82,6 +84,7 @@ appPromotion, foreignAssets, ...collatorSelection, + ...governance, ); } else if (chain.eq('UNIQUE')) { // Insert Unique additional pallets here --- a/tests/src/util/index.ts +++ b/tests/src/util/index.ts @@ -114,6 +114,8 @@ CollatorSelection = 'collatorselection', Session = 'session', Identity = 'identity', + Preimage = 'preimage', + Maintenance = 'maintenance', TestUtils = 'testutils', } --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -2869,6 +2869,55 @@ } } +class PreimageGroup extends HelperGroup { + async getPreimageInfo(h256: string) { + return (await this.helper.callRpc('api.query.preimage.statusFor', [h256])).toJSON(); + } + + /** + * Create a preimage with a hex or a byte array. + * @param signer keyring of the signer. + * @param bytes preimage encoded in hex or a byte array, e.g. an extrinsic call. + * @example await notePreimage(preimageMaker, + * helper.constructApiCall('api.tx.identity.forceInsertIdentities', [identitiesToAdd]).method.toHex() + * ); + * @returns promise of extrinsic execution. + */ + notePreimage(signer: TSigner, bytes: string | Uint8Array) { + return this.helper.executeExtrinsic(signer, 'api.tx.preimage.notePreimage', [bytes]); + } + + /** + * Delete an existing preimage and return the deposit. + * @param signer keyring of the signer - either the owner or the preimage manager (sudo). + * @param h256 hash of the preimage. + * @returns promise of extrinsic execution. + */ + unnotePreimage(signer: TSigner, h256: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unnotePreimage', [h256]); + } + + /** + * Request a preimage be uploaded to the chain without paying any fees or deposits. + * @param signer keyring of the signer - either the owner or the preimage manager (sudo). + * @param h256 hash of the preimage. + * @returns promise of extrinsic execution. + */ + requestPreimage(signer: TSigner, h256: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.preimage.requestPreimage', [h256]); + } + + /** + * Clear a previously made request for a preimage. + * @param signer keyring of the signer - either the owner or the preimage manager (sudo). + * @param h256 hash of the preimage. + * @returns promise of extrinsic execution. + */ + unrequestPreimage(signer: TSigner, h256: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.preimage.unrequestPreimage', [h256]); + } +} + class ForeignAssetsGroup extends HelperGroup { async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) { await this.helper.executeExtrinsic( @@ -3094,6 +3143,7 @@ staking: StakingGroup; scheduler: SchedulerGroup; collatorSelection: CollatorSelectionGroup; + preimage: PreimageGroup; foreignAssets: ForeignAssetsGroup; xcm: XcmGroup; xTokens: XTokensGroup; @@ -3110,6 +3160,7 @@ this.staking = new StakingGroup(this); this.scheduler = new SchedulerGroup(this); this.collatorSelection = new CollatorSelectionGroup(this); + this.preimage = new PreimageGroup(this); this.foreignAssets = new ForeignAssetsGroup(this); this.xcm = new XcmGroup(this, 'polkadotXcm'); this.xTokens = new XTokensGroup(this);