--- /dev/null +++ b/tests/src/.outdated/addToContractAllowList.test.ts @@ -0,0 +1,99 @@ +// 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 chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import usingApi, {submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api'; +import {deployFlipper} from '../deprecated-helpers/contracthelpers'; +import {getGenericResult} from '../deprecated-helpers/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +// todo:playgrounds skipped ~ postponed +describe.skip('Integration Test addToContractAllowList', () => { + + it('Add an address to a contract allow list', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const bob = privateKeyWrapper('//Bob'); + const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); + + const allowListedBefore = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); + const addTx = api.tx.unique.addToContractAllowList(contract.address, bob.address); + const addEvents = await submitTransactionAsync(deployer, addTx); + const allowListedAfter = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); + + expect(getGenericResult(addEvents).success).to.be.true; + expect(allowListedBefore).to.be.false; + expect(allowListedAfter).to.be.true; + }); + }); + + it('Adding same address to allow list repeatedly should not produce errors', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const bob = privateKeyWrapper('//Bob'); + const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); + + const allowListedBefore = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); + const addTx = api.tx.unique.addToContractAllowList(contract.address, bob.address); + const addEvents = await submitTransactionAsync(deployer, addTx); + const allowListedAfter = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); + const addAgainEvents = await submitTransactionAsync(deployer, addTx); + const allowListedAgainAfter = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); + + expect(getGenericResult(addEvents).success).to.be.true; + expect(allowListedBefore).to.be.false; + expect(allowListedAfter).to.be.true; + expect(getGenericResult(addAgainEvents).success).to.be.true; + expect(allowListedAgainAfter).to.be.true; + }); + }); +}); + +describe.skip('Negative Integration Test addToContractAllowList', () => { + + it('Add an address to a allow list of a non-contract', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const alice = privateKeyWrapper('//Bob'); + const bob = privateKeyWrapper('//Bob'); + const charlieGuineaPig = privateKeyWrapper('//Charlie'); + + const allowListedBefore = (await api.query.unique.contractAllowList(charlieGuineaPig.address, bob.address)).toJSON(); + const addTx = api.tx.unique.addToContractAllowList(charlieGuineaPig.address, bob.address); + await expect(submitTransactionExpectFailAsync(alice, addTx)).to.be.rejected; + const allowListedAfter = (await api.query.unique.contractAllowList(charlieGuineaPig.address, bob.address)).toJSON(); + + expect(allowListedBefore).to.be.false; + expect(allowListedAfter).to.be.false; + }); + }); + + it('Add to a contract allow list using a non-owner address', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const bob = privateKeyWrapper('//Bob'); + const [contract] = await deployFlipper(api, privateKeyWrapper); + + const allowListedBefore = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); + const addTx = api.tx.unique.addToContractAllowList(contract.address, bob.address); + await expect(submitTransactionExpectFailAsync(bob, addTx)).to.be.rejected; + const allowListedAfter = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); + + expect(allowListedBefore).to.be.false; + expect(allowListedAfter).to.be.false; + }); + }); + +}); --- /dev/null +++ b/tests/src/.outdated/balance-transfer-contract/metadata.json @@ -0,0 +1,96 @@ +{ + "registry": { + "strings": [ + "Storage", + "calls", + "__ink_private", + "__ink_storage", + "new", + "balance_transfer", + "dest", + "AccountId", + "value", + "Balance" + ], + "types": [ + { + "id": { + "custom.name": 1, + "custom.namespace": [ + 2, + 2, + 3, + 4 + ], + "custom.params": [] + }, + "def": { + "struct.fields": [] + } + }, + { + "id": { + "array.len": 32, + "array.type": 3 + }, + "def": "builtin" + }, + { + "id": "u8", + "def": "builtin" + }, + { + "id": "u128", + "def": "builtin" + } + ] + }, + "storage": { + "struct.type": 1, + "struct.fields": [] + }, + "contract": { + "name": 2, + "constructors": [ + { + "name": 5, + "selector": "[\"0x5E\",\"0xBD\",\"0x88\",\"0xD6\"]", + "args": [], + "docs": [] + } + ], + "messages": [ + { + "name": 6, + "selector": "[\"0xEC\",\"0x9F\",\"0xA4\",\"0xF0\"]", + "mutates": false, + "args": [ + { + "name": 7, + "type": { + "ty": 2, + "display_name": [ + 8 + ] + } + }, + { + "name": 9, + "type": { + "ty": 4, + "display_name": [ + 10 + ] + } + } + ], + "return_type": null, + "docs": [ + "Dispatches a `transfer` call to the Balances srml module" + ] + } + ], + "events": [], + "docs": [] + } +} \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/collision-tests/admVsOwnerChanges.test.ts @@ -0,0 +1,73 @@ +// 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 . + +/* broken by design +// substrate transactions are sequential, not parallel +// the order of execution is indeterminate + +import { IKeyringPair } from '@polkadot/types/types'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import privateKey from '../substrate/privateKey'; +import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; +import { + createCollectionExpectSuccess, + createItemExpectSuccess, + normalizeAccountId, + waitNewBlocks, +} from '../util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +let Alice: IKeyringPair; +let Bob: IKeyringPair; +let Ferdie: IKeyringPair; + +before(async () => { + await usingApi(async () => { + Alice = privateKey('//Alice'); + Bob = privateKey('//Bob'); + Ferdie = privateKey('//Ferdie'); + }); +}); + +describe('Admin vs Owner changes token: ', () => { + // tslint:disable-next-line: max-line-length + it('The collection admin changes the owner of the token and in the same block the current owner transfers the token to another address ', async () => { + + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const changeAdminTxBob = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); + await submitTransactionAsync(Alice, changeAdminTxBob); + const changeAdminTxFerdie = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Ferdie.address)); + await submitTransactionAsync(Bob, changeAdminTxFerdie); + const itemId = await createItemExpectSuccess(Ferdie, collectionId, 'NFT'); + + const changeOwner = api.tx.unique.transferFrom(normalizeAccountId(Ferdie.address), normalizeAccountId(Bob.address), collectionId, itemId, 1); + const approve = api.tx.unique.approve(normalizeAccountId(Bob.address), collectionId, itemId, 1); + const sendItem = api.tx.unique.transfer(normalizeAccountId(Alice.address), collectionId, itemId, 1); + await Promise.all([ + changeOwner.signAndSend(Alice), + approve.signAndSend(Bob), + sendItem.signAndSend(Ferdie), + ]); + const itemBefore: any = await api.query.unique.nftItemList(collectionId, itemId); + expect(itemBefore.owner).not.to.be.eq(Bob.address); + await waitNewBlocks(2); + }); + }); +}); +*/ \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/collision-tests/admVsOwnerData.test.ts @@ -0,0 +1,69 @@ +// 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 . + +/* broken by design +// substrate transactions are sequential, not parallel +// the order of execution is indeterminate + +import { IKeyringPair } from '@polkadot/types/types'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import privateKey from '../substrate/privateKey'; +import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; +import { + createCollectionExpectSuccess, + createItemExpectSuccess, + normalizeAccountId, + waitNewBlocks, +} from '../util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +let Alice: IKeyringPair; +let Bob: IKeyringPair; + +before(async () => { + await usingApi(async () => { + Alice = privateKey('//Alice'); + Bob = privateKey('//Bob'); + }); +}); + +describe('Admin vs Owner changes the data in the token: ', () => { + it('The collection admin changes the data in the token and in the same block the token owner also changes the data in it ', async () => { + await usingApi(async (api) => { + const AliceData = 1; + const BobData = 2; + const collectionId = await createCollectionExpectSuccess(); + const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); + await submitTransactionAsync(Alice, changeAdminTx); + const itemId = await createItemExpectSuccess(Bob, collectionId, 'NFT'); + // + // tslint:disable-next-line: max-line-length + const AliceTx = api.tx.unique.setVariableMetaData(collectionId, itemId, AliceData.toString()); + // tslint:disable-next-line: max-line-length + const BobTx = api.tx.unique.setVariableMetaData(collectionId, itemId, BobData.toString()); + await Promise.all([ + AliceTx.signAndSend(Alice), + BobTx.signAndSend(Bob), + ]); + const item: any = await api.query.unique.nftItemList(collectionId, itemId); + expect(item.variableData).not.to.be.eq(null); // Pseudo-random selection of one of two values + await waitNewBlocks(2); + }); + }); +}); +*/ \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/collision-tests/admVsOwnerTake.test.ts @@ -0,0 +1,71 @@ +// 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 . + +/* broken by design +// substrate transactions are sequential, not parallel +// the order of execution is indeterminate + +import { IKeyringPair } from '@polkadot/types/types'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import privateKey from '../substrate/privateKey'; +import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; +import { + createCollectionExpectSuccess, + createItemExpectSuccess, + normalizeAccountId, + waitNewBlocks, +} from '../util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +let Alice: IKeyringPair; +let Bob: IKeyringPair; +let Ferdie: IKeyringPair; + +before(async () => { + await usingApi(async () => { + Alice = privateKey('//Alice'); + Bob = privateKey('//Bob'); + Ferdie = privateKey('//Ferdie'); + }); +}); + +describe('Admin vs Owner take token: ', () => { + // tslint:disable-next-line: max-line-length + it('The collection admin burns the token and in the same block the token owner performs a transaction on it ', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); + await submitTransactionAsync(Alice, changeAdminTx); + const itemId = await createItemExpectSuccess(Bob, collectionId, 'NFT'); + // + const sendItem = api.tx.unique.transfer(normalizeAccountId(Ferdie.address), collectionId, itemId, 1); + const burnItem = api.tx.unique.burnItem(collectionId, itemId, 1); + await Promise.all([ + sendItem.signAndSend(Bob), + burnItem.signAndSend(Alice), + ]); + await waitNewBlocks(2); + let itemBurn = false; + itemBurn = (await (api.query.unique.nftItemList(collectionId, itemId))).toJSON() as boolean; + // tslint:disable-next-line: no-unused-expression + expect(itemBurn).to.be.null; + await waitNewBlocks(2); + }); + }); +}); +*/ \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/collision-tests/adminDestroyCollection.test.ts @@ -0,0 +1,70 @@ +// 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 . + +/* broken by design +// substrate transactions are sequential, not parallel +// the order of execution is indeterminate + +import { IKeyringPair } from '@polkadot/types/types'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import privateKey from '../substrate/privateKey'; +import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; +import { + createCollectionExpectSuccess, + normalizeAccountId, + waitNewBlocks, +} from '../util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +let Alice: IKeyringPair; +let Bob: IKeyringPair; +let Ferdie: IKeyringPair; + +before(async () => { + await usingApi(async () => { + Alice = privateKey('//Alice'); + Bob = privateKey('//Bob'); + Ferdie = privateKey('//Ferdie'); + }); +}); + +describe('Deleting a collection while add address to allowlist: ', () => { + // tslint:disable-next-line: max-line-length + it('Adding an address to the collection allowlist in a block by the admin, and deleting the collection by the owner ', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); + await submitTransactionAsync(Alice, changeAdminTx); + await waitNewBlocks(1); + // + const addAllowlistAdm = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(Ferdie.address)); + const destroyCollection = api.tx.unique.destroyCollection(collectionId); + await Promise.all([ + addAllowlistAdm.signAndSend(Bob), + destroyCollection.signAndSend(Alice), + ]); + await waitNewBlocks(1); + let allowList = false; + allowList = (await api.query.unique.allowList(collectionId, Ferdie.address)).toJSON() as boolean; + // tslint:disable-next-line: no-unused-expression + expect(allowList).to.be.false; + await waitNewBlocks(2); + }); + }); +}); +*/ \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/collision-tests/adminLimitsOff.test.ts @@ -0,0 +1,86 @@ +// 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 . + +/* broken by design +// substrate transactions are sequential, not parallel +// the order of execution is indeterminate + +import { IKeyringPair } from '@polkadot/types/types'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import privateKey from '../substrate/privateKey'; +import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from '../substrate/substrate-api'; +import { + createCollectionExpectSuccess, + normalizeAccountId, + waitNewBlocks, +} from '../util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +let Alice: IKeyringPair; +let Bob: IKeyringPair; +let Ferdie: IKeyringPair; +let Charlie: IKeyringPair; +let Eve: IKeyringPair; +let Dave: IKeyringPair; + +before(async () => { + await usingApi(async () => { + Alice = privateKey('//Alice'); + Bob = privateKey('//Bob'); + Ferdie = privateKey('//Ferdie'); + Charlie = privateKey('//Charlie'); + Eve = privateKey('//Eve'); + Dave = privateKey('//Dave'); + }); +}); + +describe('Admin limit exceeded collection: ', () => { + // tslint:disable-next-line: max-line-length + it('In one block, the owner and admin add new admins to the collection more than the limit ', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + + const chainAdminLimit = (api.consts.unique.collectionAdminsLimit as any).toNumber(); + expect(chainAdminLimit).to.be.equal(5); + + const changeAdminTx1 = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Eve.address)); + await submitTransactionAsync(Alice, changeAdminTx1); + const changeAdminTx2 = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Dave.address)); + await submitTransactionAsync(Alice, changeAdminTx2); + const changeAdminTx3 = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); + await submitTransactionAsync(Alice, changeAdminTx3); + + const addAdmOne = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Ferdie.address)); + const addAdmTwo = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Charlie.address)); + await Promise.all([ + addAdmOne.signAndSend(Bob), + addAdmTwo.signAndSend(Alice), + ]); + await waitNewBlocks(2); + const changeAdminTx4 = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Alice.address)); + await expect(submitTransactionExpectFailAsync(Alice, changeAdminTx4)).to.be.rejected; + + const adminListAfterAddAdmin: any = (await api.query.unique.adminList(collectionId)); + expect(adminListAfterAddAdmin).to.be.contains(normalizeAccountId(Eve.address)); + expect(adminListAfterAddAdmin).to.be.contains(normalizeAccountId(Ferdie.address)); + expect(adminListAfterAddAdmin).not.to.be.contains(normalizeAccountId(Alice.address)); + await waitNewBlocks(2); + }); + }); +}); +*/ \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/collision-tests/adminRightsOff.test.ts @@ -0,0 +1,69 @@ +// 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 . + +/* broken by design +// substrate transactions are sequential, not parallel +// the order of execution is indeterminate + +import { IKeyringPair } from '@polkadot/types/types'; +import BN from 'bn.js'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import privateKey from '../substrate/privateKey'; +import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; +import { + createCollectionExpectSuccess, + normalizeAccountId, + waitNewBlocks, +} from '../util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +let Alice: IKeyringPair; +let Bob: IKeyringPair; + +before(async () => { + await usingApi(async () => { + Alice = privateKey('//Alice'); + Bob = privateKey('//Bob'); + }); +}); + +describe('Deprivation of admin rights: ', () => { + // tslint:disable-next-line: max-line-length + it('In the block, the collection admin adds a token or changes data, and the collection owner deprives the admin of rights ', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); + await submitTransactionAsync(Alice, changeAdminTx); + await waitNewBlocks(1); + const args = [{ nft: ['0x31', '0x31'] }, { nft: ['0x32', '0x32'] }, { nft: ['0x33', '0x33'] }]; + const addItemAdm = api.tx.unique.createMultipleItems(collectionId, normalizeAccountId(Bob.address), args); + const removeAdm = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); + await Promise.all([ + addItemAdm.signAndSend(Bob), + removeAdm.signAndSend(Alice), + ]); + await waitNewBlocks(2); + const itemsListIndex = await api.query.unique.itemListIndex(collectionId) as unknown as BN; + expect(itemsListIndex.toNumber()).to.be.equal(0); + const adminList: any = (await api.query.unique.adminList(collectionId)); + expect(adminList).not.to.be.contains(normalizeAccountId(Bob.address)); + await waitNewBlocks(2); + }); + }); +}); +*/ \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/collision-tests/setSponsorNewOwner.test.ts @@ -0,0 +1,67 @@ +// 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 . + +/* broken by design +// substrate transactions are sequential, not parallel +// the order of execution is indeterminate + +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 { + createCollectionExpectSuccess, + setCollectionSponsorExpectSuccess, + waitNewBlocks, +} from '../util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +let Alice: IKeyringPair; +let Bob: IKeyringPair; +let Ferdie: IKeyringPair; + +before(async () => { + await usingApi(async () => { + Alice = privateKey('//Alice'); + Bob = privateKey('//Bob'); + Ferdie = privateKey('//Ferdie'); + }); +}); + +describe('Sponsored with new owner ', () => { + // tslint:disable-next-line: max-line-length + it('Confirmation of sponsorship of a collection in a block with a change in the owner of the collection: ', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + await setCollectionSponsorExpectSuccess(collectionId, Bob.address); + await waitNewBlocks(2); + const confirmSponsorship = api.tx.unique.confirmSponsorship(collectionId); + const changeCollectionOwner = api.tx.unique.changeCollectionOwner(collectionId, Ferdie.address); + await Promise.all([ + confirmSponsorship.signAndSend(Bob), + changeCollectionOwner.signAndSend(Alice), + ]); + await waitNewBlocks(2); + const collection: any = (await api.query.unique.collectionById(collectionId)).toJSON(); + expect(collection.sponsorship.confirmed).to.be.eq(Bob.address); + expect(collection.owner).to.be.eq(Ferdie.address); + await waitNewBlocks(2); + }); + }); +}); +*/ \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/collision-tests/sponsorPayments.test.ts @@ -0,0 +1,76 @@ +// 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 . + +/* broken by design +// substrate transactions are sequential, not parallel +// the order of execution is indeterminate + +import { IKeyringPair } from '@polkadot/types/types'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { alicesPublicKey, bobsPublicKey } from '../accounts'; +import getBalance from '../substrate/get-balance'; +import privateKey from '../substrate/privateKey'; +import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; +import { + confirmSponsorshipExpectSuccess, + createCollectionExpectSuccess, + createItemExpectSuccess, + setCollectionSponsorExpectSuccess, + normalizeAccountId, + waitNewBlocks, +} from '../util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +let Alice: IKeyringPair; +let Bob: IKeyringPair; + +before(async () => { + await usingApi(async () => { + Alice = privateKey('//Alice'); + Bob = privateKey('//Bob'); + }); +}); + +describe('Payment of commission if one block: ', () => { + // tslint:disable-next-line: max-line-length + it('Payment of commission if one block contains transactions for payment from the sponsor\'s balance and his (sponsor\'s) exclusion from the collection ', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + const changeAdminTxBob = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); + await submitTransactionAsync(Alice, changeAdminTxBob); + const itemId = await createItemExpectSuccess(Bob, collectionId, 'NFT'); + await setCollectionSponsorExpectSuccess(collectionId, Bob.address); + await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); + + const [alicesBalanceBefore, bobsBalanceBefore] = await getBalance(api, [alicesPublicKey, bobsPublicKey]); + const sendItem = api.tx.unique.transfer(normalizeAccountId(Alice.address), collectionId, itemId, 1); + const revokeSponsor = api.tx.unique.removeCollectionSponsor(collectionId); + await Promise.all([ + sendItem.signAndSend(Bob), + revokeSponsor.signAndSend(Alice), + ]); + const [alicesBalanceAfter, bobsBalanceAfter] = await getBalance(api, [alicesPublicKey, bobsPublicKey]); + // tslint:disable-next-line:no-unused-expression + expect(alicesBalanceAfter === alicesBalanceBefore).to.be.true; + // tslint:disable-next-line:no-unused-expression + expect(bobsBalanceAfter === bobsBalanceBefore).to.be.true; + await waitNewBlocks(2); + }); + }); +}); +*/ \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/collision-tests/tokenLimitsOff.test.ts @@ -0,0 +1,100 @@ +// 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 . + +/* broken by design +// substrate transactions are sequential, not parallel +// the order of execution is indeterminate + +import { IKeyringPair } from '@polkadot/types/types'; +import BN from 'bn.js'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import privateKey from '../substrate/privateKey'; +import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; +import { + addToAllowListExpectSuccess, + createCollectionExpectSuccess, + getCreateItemResult, + setMintPermissionExpectSuccess, + normalizeAccountId, + waitNewBlocks, +} from '../util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +let Alice: IKeyringPair; +let Bob: IKeyringPair; +let Ferdie: IKeyringPair; + +const accountTokenOwnershipLimit = 4; +const sponsoredMintSize = 4294967295; +const tokenLimit = 4; +const sponsorTimeout = 14400; +const ownerCanTransfer = false; +const ownerCanDestroy = false; + +before(async () => { + await usingApi(async () => { + Alice = privateKey('//Alice'); + Bob = privateKey('//Bob'); + Ferdie = privateKey('//Ferdie'); + }); +}); + +describe('Token limit exceeded collection: ', () => { + // tslint:disable-next-line: max-line-length + it('The number of tokens created in the collection from different addresses exceeds the allowed collection limit ', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + await setMintPermissionExpectSuccess(Alice, collectionId, true); + await addToAllowListExpectSuccess(Alice, collectionId, Ferdie.address); + await addToAllowListExpectSuccess(Alice, collectionId, Bob.address); + const setCollectionLim = api.tx.unique.setCollectionLimits( + collectionId, + { + accountTokenOwnershipLimit, + sponsoredMintSize, + tokenLimit, + // tslint:disable-next-line: object-literal-sort-keys + sponsorTimeout, + ownerCanTransfer, + ownerCanDestroy, + }, + ); + const subTx = await submitTransactionAsync(Alice, setCollectionLim); + const subTxTesult = getCreateItemResult(subTx); + // tslint:disable-next-line:no-unused-expression + expect(subTxTesult.success).to.be.true; + await waitNewBlocks(2); + + const args = [{ nft: ['0x31', '0x31'] }, { nft: ['0x32', '0x32'] }, { nft: ['0x33', '0x33'] }]; + const mintItemOne = api.tx.unique + .createMultipleItems(collectionId, normalizeAccountId(Ferdie.address), args); + const mintItemTwo = api.tx.unique + .createMultipleItems(collectionId, normalizeAccountId(Bob.address), args); + await Promise.all([ + mintItemOne.signAndSend(Ferdie), + mintItemTwo.signAndSend(Bob), + ]); + await waitNewBlocks(2); + const itemsListIndexAfter = await api.query.unique.itemListIndex(collectionId) as unknown as BN; + expect(itemsListIndexAfter.toNumber()).to.be.equal(3); + // TokenLimit = 4. The first transaction is successful. The second should fail. + await waitNewBlocks(2); + }); + }); +}); +*/ --- /dev/null +++ b/tests/src/.outdated/collision-tests/turnsOffMinting.test.ts @@ -0,0 +1,68 @@ +// 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 . + +/* broken by design +// substrate transactions are sequential, not parallel +// the order of execution is indeterminate + +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 { + addToAllowListExpectSuccess, + createCollectionExpectSuccess, + setMintPermissionExpectSuccess, + normalizeAccountId, + waitNewBlocks, +} from '../util/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +let Alice: IKeyringPair; +let Ferdie: IKeyringPair; + +before(async () => { + await usingApi(async () => { + Alice = privateKey('//Alice'); + Ferdie = privateKey('//Ferdie'); + }); +}); + +describe('Turns off minting mode: ', () => { + // tslint:disable-next-line: max-line-length + it('The collection owner turns off minting mode and there are minting transactions in the same block ', async () => { + await usingApi(async (api) => { + const collectionId = await createCollectionExpectSuccess(); + await setMintPermissionExpectSuccess(Alice, collectionId, true); + await addToAllowListExpectSuccess(Alice, collectionId, Ferdie.address); + + const mintItem = api.tx.unique.createItem(collectionId, normalizeAccountId(Ferdie.address), 'NFT'); + const offMinting = api.tx.unique.setMintPermission(collectionId, false); + await Promise.all([ + mintItem.signAndSend(Ferdie), + offMinting.signAndSend(Alice), + ]); + let itemList = false; + itemList = (await (api.query.unique.nftItemList(collectionId, mintItem))).toJSON() as boolean; + // tslint:disable-next-line: no-unused-expression + expect(itemList).to.be.null; + await waitNewBlocks(2); + }); + }); +}); +*/ \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/contracts.test.ts @@ -0,0 +1,236 @@ +// 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 chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import usingApi, {submitTransactionAsync} from '../substrate/substrate-api'; +import fs from 'fs'; +import {Abi, ContractPromise as Contract} from '@polkadot/api-contract'; +import {deployFlipper, getFlipValue, deployTransferContract} from '../deprecated-helpers/contracthelpers'; + +import { + addToAllowListExpectSuccess, + approveExpectSuccess, + createCollectionExpectSuccess, + createItemExpectSuccess, + enablePublicMintingExpectSuccess, + enableAllowListExpectSuccess, + getGenericResult, + normalizeAccountId, + isAllowlisted, + transferFromExpectSuccess, + getTokenOwner, +} from '../deprecated-helpers/helpers'; + + +chai.use(chaiAsPromised); +const expect = chai.expect; + +const value = 0; +const gasLimit = 9000n * 1000000n; +const marketContractAddress = '5CYN9j3YvRkqxewoxeSvRbhAym4465C57uMmX5j4yz99L5H6'; + +// todo:playgrounds skipped ~ postponed +describe.skip('Contracts', () => { + it('Can deploy smart contract Flipper, instantiate it and call it\'s get and flip messages.', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); + const initialGetResponse = await getFlipValue(contract, deployer); + + const bob = privateKeyWrapper('//Bob'); + const flip = contract.tx.flip({value, gasLimit}); + await submitTransactionAsync(bob, flip); + + const afterFlipGetResponse = await getFlipValue(contract, deployer); + expect(afterFlipGetResponse).not.to.be.eq(initialGetResponse, 'Flipping should change value.'); + }); + }); + + it('Can initialize contract instance', async () => { + await usingApi(async (api) => { + const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8')); + const abi = new Abi(metadata); + const newContractInstance = new Contract(api, abi, marketContractAddress); + expect(newContractInstance).to.have.property('address'); + expect(newContractInstance.address.toString()).to.equal(marketContractAddress); + }); + }); +}); + +describe.skip('Chain extensions', () => { + it('Transfer CE', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const alice = privateKeyWrapper('//Alice'); + const bob = privateKeyWrapper('//Bob'); + + // Prep work + const collectionId = await createCollectionExpectSuccess(); + const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT'); + const [contract] = await deployTransferContract(api, privateKeyWrapper); + const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, contract.address); + await submitTransactionAsync(alice, changeAdminTx); + + expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address)); + + // Transfer + const transferTx = contract.tx.transfer({value, gasLimit}, bob.address, collectionId, tokenId, 1); + const events = await submitTransactionAsync(alice, transferTx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(bob.address)); + }); + }); + + it('Mint CE', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const alice = privateKeyWrapper('//Alice'); + const bob = privateKeyWrapper('//Bob'); + + const collectionId = await createCollectionExpectSuccess(); + const [contract] = await deployTransferContract(api, privateKeyWrapper); + await enablePublicMintingExpectSuccess(alice, collectionId); + await enableAllowListExpectSuccess(alice, collectionId); + await addToAllowListExpectSuccess(alice, collectionId, contract.address); + await addToAllowListExpectSuccess(alice, collectionId, bob.address); + + const transferTx = contract.tx.createItem({value, gasLimit}, bob.address, collectionId, {Nft: {const_data: '0x010203'}}); + const events = await submitTransactionAsync(alice, transferTx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + const tokensAfter = (await api.query.unique.nftItemList.entries(collectionId)).map((kv: any) => kv[1].toJSON()); + expect(tokensAfter).to.be.deep.equal([ + { + owner: bob.address, + constData: '0x010203', + }, + ]); + }); + }); + + it('Bulk mint CE', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const alice = privateKeyWrapper('//Alice'); + const bob = privateKeyWrapper('//Bob'); + + const collectionId = await createCollectionExpectSuccess(); + const [contract] = await deployTransferContract(api, privateKeyWrapper); + await enablePublicMintingExpectSuccess(alice, collectionId); + await enableAllowListExpectSuccess(alice, collectionId); + await addToAllowListExpectSuccess(alice, collectionId, contract.address); + await addToAllowListExpectSuccess(alice, collectionId, bob.address); + + const transferTx = contract.tx.createMultipleItems({value, gasLimit}, bob.address, collectionId, [ + {NFT: {/*const_data: '0x010203'*/}}, + {NFT: {/*const_data: '0x010204'*/}}, + {NFT: {/*const_data: '0x010205'*/}}, + ]); + const events = await submitTransactionAsync(alice, transferTx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + const tokensAfter: any = (await api.query.unique.nftItemList.entries(collectionId) as any) + .map((kv: any) => kv[1].toJSON()) + .sort((a: any, b: any) => a.constData.localeCompare(b.constData)); + expect(tokensAfter).to.be.deep.equal([ + { + Owner: bob.address, + //ConstData: '0x010203', + }, + { + Owner: bob.address, + //ConstData: '0x010204', + }, + { + Owner: bob.address, + //ConstData: '0x010205', + }, + ]); + }); + }); + + it('Approve CE', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const alice = privateKeyWrapper('//Alice'); + const bob = privateKeyWrapper('//Bob'); + const charlie = privateKeyWrapper('//Charlie'); + + const collectionId = await createCollectionExpectSuccess(); + const [contract] = await deployTransferContract(api, privateKeyWrapper); + const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', contract.address.toString()); + + const transferTx = contract.tx.approve({value, gasLimit}, bob.address, collectionId, tokenId, 1); + const events = await submitTransactionAsync(alice, transferTx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + await transferFromExpectSuccess(collectionId, tokenId, bob, normalizeAccountId(contract.address.toString()), charlie, 1, 'NFT'); + }); + }); + + it('TransferFrom CE', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const alice = privateKeyWrapper('//Alice'); + const bob = privateKeyWrapper('//Bob'); + const charlie = privateKeyWrapper('//Charlie'); + + const collectionId = await createCollectionExpectSuccess(); + const [contract] = await deployTransferContract(api, privateKeyWrapper); + const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address); + await approveExpectSuccess(collectionId, tokenId, bob, contract.address.toString(), 1); + + const transferTx = contract.tx.transferFrom({value, gasLimit}, bob.address, charlie.address, collectionId, tokenId, 1); + const events = await submitTransactionAsync(alice, transferTx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + const token: any = (await api.query.unique.nftItemList(collectionId, tokenId) as any).unwrap(); + expect(token.owner.toString()).to.be.equal(charlie.address); + }); + }); + + it('ToggleAllowList CE', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const alice = privateKeyWrapper('//Alice'); + const bob = privateKeyWrapper('//Bob'); + + const collectionId = await createCollectionExpectSuccess(); + const [contract] = await deployTransferContract(api, privateKeyWrapper); + const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, contract.address); + await submitTransactionAsync(alice, changeAdminTx); + + expect(await isAllowlisted(api, collectionId, bob.address)).to.be.false; + + { + const transferTx = contract.tx.toggleAllowList({value, gasLimit}, collectionId, bob.address, true); + const events = await submitTransactionAsync(alice, transferTx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + expect(await isAllowlisted(api, collectionId, bob.address)).to.be.true; + } + { + const transferTx = contract.tx.toggleAllowList({value, gasLimit}, collectionId, bob.address, false); + const events = await submitTransactionAsync(alice, transferTx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + expect(await isAllowlisted(api, collectionId, bob.address)).to.be.false; + } + }); + }); +}); --- /dev/null +++ b/tests/src/.outdated/enableContractSponsoring.test.ts @@ -0,0 +1,102 @@ +// 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 chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import usingApi from '../substrate/substrate-api'; +import {deployFlipper, getFlipValue, toggleFlipValueExpectSuccess} from '../deprecated-helpers/contracthelpers'; +import { + enableContractSponsoringExpectFailure, + enableContractSponsoringExpectSuccess, + findUnusedAddress, + setContractSponsoringRateLimitExpectSuccess, +} from '../deprecated-helpers/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +// todo:playgrounds skipped ~ postponed +describe.skip('Integration Test enableContractSponsoring', () => { + it('ensure tx fee is paid from endowment', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const user = await findUnusedAddress(api, privateKeyWrapper); + + const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); + await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); + await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 1); + await toggleFlipValueExpectSuccess(user, flipper); + + expect(await getFlipValue(flipper, deployer)).to.be.false; + }); + }); + + it('ensure it can be enabled twice', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); + + await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); + await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); + }); + }); + + it('ensure it can be disabled twice', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); + + await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); + await enableContractSponsoringExpectSuccess(deployer, flipper.address, false); + await enableContractSponsoringExpectSuccess(deployer, flipper.address, false); + }); + }); + + it('ensure it can be re-enabled', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); + + await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); + await enableContractSponsoringExpectSuccess(deployer, flipper.address, false); + await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); + }); + }); + +}); + +describe.skip('Negative Integration Test enableContractSponsoring', () => { + let alice: IKeyringPair; + + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + }); + }); + + it('fails when called for non-contract address', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const user = await findUnusedAddress(api, privateKeyWrapper); + + await enableContractSponsoringExpectFailure(alice, user.address, true); + }); + }); + + it('fails when called by non-owning user', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [flipper] = await deployFlipper(api, privateKeyWrapper); + + await enableContractSponsoringExpectFailure(alice, flipper.address, true); + }); + }); +}); --- /dev/null +++ b/tests/src/.outdated/eth/scheduling.test.ts @@ -0,0 +1,59 @@ +// 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 {expect} from 'chai'; +import {createEthAccountWithBalance, deployFlipper, GAS_ARGS, itWeb3, subToEth, transferBalanceToEth} from '../../deprecated-helpers/eth/helpers'; +import {scheduleExpectSuccess, waitNewBlocks, requirePallets, Pallets} from '../../deprecated-helpers/helpers'; + +// TODO mrshiposha update this test in #581 +describe.skip('Scheduing EVM smart contracts', () => { + before(async function() { + await requirePallets(this, [Pallets.Scheduler]); + }); + + itWeb3('Successfully schedules and periodically executes an EVM contract', async ({api, web3, privateKeyWrapper}) => { + const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + const flipper = await deployFlipper(web3, deployer); + const initialValue = await flipper.methods.getValue().call(); + const alice = privateKeyWrapper('//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); + } + }); +}); --- /dev/null +++ b/tests/src/.outdated/flipper/metadata.json @@ -0,0 +1,110 @@ +{ + "metadataVersion": "0.1.0", + "source": { + "hash": "0x5b02ceadaacee8408d3c6496394847092c099bcb897221dbe8d22c16d372fa17", + "language": "ink! 3.0.0-rc2", + "compiler": "rustc 1.51.0-nightly" + }, + "contract": { + "name": "flipper", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "spec": { + "constructors": [ + { + "args": [ + { + "name": "init_value", + "type": { + "displayName": [ + "bool" + ], + "type": 1 + } + } + ], + "docs": [ + " Constructor that initializes the `bool` value to the given `init_value`." + ], + "name": [ + "new" + ], + "selector": "0xd183512b" + }, + { + "args": [], + "docs": [ + " Constructor that initializes the `bool` value to `false`.", + "", + " Constructors can delegate to other constructors." + ], + "name": [ + "default" + ], + "selector": "0x6a3712e2" + } + ], + "docs": [], + "events": [], + "messages": [ + { + "args": [], + "docs": [ + " A message that can be called on instantiated contracts.", + " This one flips the value of the stored `bool` from `true`", + " to `false` and vice versa." + ], + "mutates": true, + "name": [ + "flip" + ], + "payable": false, + "returnType": null, + "selector": "0xc096a5f3" + }, + { + "args": [], + "docs": [ + " Simply returns the current value of our `bool`." + ], + "mutates": false, + "name": [ + "get" + ], + "payable": false, + "returnType": { + "displayName": [ + "bool" + ], + "type": 1 + }, + "selector": "0x1e5ca456" + } + ] + }, + "storage": { + "struct": { + "fields": [ + { + "layout": { + "cell": { + "key": "0x0000000000000000000000000000000000000000000000000000000000000000", + "ty": 1 + } + }, + "name": "value" + } + ] + } + }, + "types": [ + { + "def": { + "primitive": "bool" + } + } + ] +} \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/load_test_sc/metadata.json @@ -0,0 +1,125 @@ +{ + "metadataVersion": "0.1.0", + "source": { + "hash": "0x168cc3cba9657ad3950fb506e568751f99b90fb097685107f6101675662a8303", + "language": "ink! 3.0.0-rc2", + "compiler": "rustc 1.49.0-nightly" + }, + "contract": { + "name": "loadtester", + "version": "0.1.0", + "authors": [ + "[your_name] <[your_email]>" + ] + }, + "spec": { + "constructors": [ + { + "args": [], + "docs": [], + "name": [ + "new" + ], + "selector": "0xd183512b" + } + ], + "docs": [], + "events": [], + "messages": [ + { + "args": [ + { + "name": "count", + "type": { + "displayName": [ + "u64" + ], + "type": 2 + } + } + ], + "docs": [], + "mutates": true, + "name": [ + "bloat" + ], + "payable": false, + "returnType": null, + "selector": "0x49891c2a" + }, + { + "args": [], + "docs": [], + "mutates": false, + "name": [ + "get" + ], + "payable": false, + "returnType": { + "displayName": [ + "u128" + ], + "type": 3 + }, + "selector": "0x1e5ca456" + } + ] + }, + "storage": { + "struct": { + "fields": [ + { + "layout": { + "struct": { + "fields": [ + { + "layout": { + "cell": { + "key": "0x0000000000000000000000000000000000000000000000000000000000000000", + "ty": 1 + } + }, + "name": "len" + }, + { + "layout": { + "array": { + "cellsPerElem": 1, + "layout": { + "cell": { + "key": "0x0000000001000000000000000000000000000000000000000000000000000000", + "ty": 2 + } + }, + "len": 4294967295, + "offset": "0x0100000000000000000000000000000000000000000000000000000000000000" + } + }, + "name": "elems" + } + ] + } + }, + "name": "vector" + } + ] + } + }, + "types": [ + { + "def": { + "primitive": "u32" + } + }, + { + "def": { + "primitive": "u64" + } + }, + { + "def": { + "primitive": "u128" + } + } + ] +} \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/overflow.test.ts @@ -0,0 +1,73 @@ +// 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 chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import usingApi from '../substrate/substrate-api'; +import {approveExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, getAllowance, getBalance, transferExpectFailure, transferExpectSuccess, transferFromExpectFail, transferFromExpectSuccess, U128_MAX} from '../deprecated-helpers/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +// todo:playgrounds skipped ~ postponed +describe.skip('Integration Test fungible overflows', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let charlie: IKeyringPair; + + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + bob = privateKeyWrapper('//Bob'); + charlie = privateKeyWrapper('//Charlie'); + }); + }); + + it('fails when overflows on transfer', async () => { + await usingApi(async api => { + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + + await createFungibleItemExpectSuccess(alice, fungibleCollectionId, {Value: U128_MAX}); + await transferExpectSuccess(fungibleCollectionId, 0, alice, bob, U128_MAX, 'Fungible'); + + await createFungibleItemExpectSuccess(alice, fungibleCollectionId, {Value: 1n}); + await transferExpectFailure(fungibleCollectionId, 0, alice, bob, 1); + + expect(await getBalance(api, fungibleCollectionId, alice.address, 0)).to.equal(1n); + expect(await getBalance(api, fungibleCollectionId, bob.address, 0)).to.equal(U128_MAX); + }); + }); + + it('fails when overflows on transferFrom', async () => { + await usingApi(async api => { + const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); + await createFungibleItemExpectSuccess(alice, fungibleCollectionId, {Value: U128_MAX}); + await approveExpectSuccess(fungibleCollectionId, 0, alice, bob.address, U128_MAX); + await transferFromExpectSuccess(fungibleCollectionId, 0, bob, alice, charlie, U128_MAX, 'Fungible'); + + expect(await getBalance(api, fungibleCollectionId, charlie.address, 0)).to.equal(U128_MAX); + expect(await getAllowance(api, fungibleCollectionId, alice.address, bob.address, 0)).to.equal(0n); + + await createFungibleItemExpectSuccess(alice, fungibleCollectionId, {Value: U128_MAX}); + await approveExpectSuccess(fungibleCollectionId, 0, alice, bob.address, 1n); + await transferFromExpectFail(fungibleCollectionId, 0, bob, alice, charlie, 1); + + expect(await getBalance(api, fungibleCollectionId, charlie.address, 0)).to.equal(U128_MAX); + expect(await getAllowance(api, fungibleCollectionId, alice.address, bob.address, 0)).to.equal(1n); + }); + }); +}); --- /dev/null +++ b/tests/src/.outdated/removeFromContractAllowList.test.ts @@ -0,0 +1,92 @@ +// 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 usingApi from '../substrate/substrate-api'; +import {deployFlipper, toggleFlipValueExpectFailure, toggleFlipValueExpectSuccess} from '../deprecated-helpers/contracthelpers'; +import {addToContractAllowListExpectSuccess, isAllowlistedInContract, removeFromContractAllowListExpectFailure, removeFromContractAllowListExpectSuccess, toggleContractAllowlistExpectSuccess} from '../deprecated-helpers/helpers'; +import {IKeyringPair} from '@polkadot/types/types'; +import {expect} from 'chai'; + +// todo:playgrounds skipped again +describe.skip('Integration Test removeFromContractAllowList', () => { + let bob: IKeyringPair; + + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + bob = privateKeyWrapper('//Bob'); + }); + }); + + it('user is no longer allowlisted after removal', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); + + await addToContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); + await removeFromContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); + + expect(await isAllowlistedInContract(flipper.address, bob.address)).to.be.false; + }); + }); + + it('user can\'t execute contract after removal', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); + await toggleContractAllowlistExpectSuccess(deployer, flipper.address.toString(), true); + + await addToContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); + await toggleFlipValueExpectSuccess(bob, flipper); + + await removeFromContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); + await toggleFlipValueExpectFailure(bob, flipper); + }); + }); + + it('can be called twice', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); + + await addToContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); + await removeFromContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); + await removeFromContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); + }); + }); +}); + +describe.skip('Negative Integration Test removeFromContractAllowList', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + bob = privateKeyWrapper('//Bob'); + }); + }); + + it('fails when called with non-contract address', async () => { + await usingApi(async () => { + await removeFromContractAllowListExpectFailure(alice, alice.address, bob.address); + }); + }); + + it('fails when executed by non owner', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [flipper] = await deployFlipper(api, privateKeyWrapper); + + await removeFromContractAllowListExpectFailure(alice, flipper.address.toString(), bob.address); + }); + }); +}); --- /dev/null +++ b/tests/src/.outdated/scheduler.test.ts @@ -0,0 +1,210 @@ +// 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 chai, {expect} from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { + default as usingApi, + submitTransactionAsync, +} from '../substrate/substrate-api'; +import { + createItemExpectSuccess, + createCollectionExpectSuccess, + scheduleTransferExpectSuccess, + scheduleTransferAndWaitExpectSuccess, + setCollectionSponsorExpectSuccess, + confirmSponsorshipExpectSuccess, + findUnusedAddress, + UNIQUE, + enablePublicMintingExpectSuccess, + addToAllowListExpectSuccess, + waitNewBlocks, + normalizeAccountId, + getTokenOwner, + getGenericResult, + scheduleTransferFundsPeriodicExpectSuccess, + getFreeBalance, + confirmSponsorshipByKeyExpectSuccess, + scheduleExpectFailure, +} from '../deprecated-helpers/helpers'; +import {IKeyringPair} from '@polkadot/types/types'; + +chai.use(chaiAsPromised); + +// todo:playgrounds skipped ~ postponed +describe.skip('Scheduling token and balance transfers', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let scheduledIdBase: string; + let scheduledIdSlider: number; + + before(async() => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + bob = privateKeyWrapper('//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 () => { + 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, makeScheduledId()); + }); + }); + + it('Can transfer funds periodically', async () => { + await usingApi(async () => { + const waitForBlocks = 4; + const period = 2; + 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 getFreeBalance(bob); + expect(bobsBalanceAfterFirst > bobsBalanceBefore).to.be.true; + + await waitNewBlocks(period); + const bobsBalanceAfterSecond = await getFreeBalance(bob); + expect(bobsBalanceAfterSecond > bobsBalanceAfterFirst).to.be.true; + }); + }); + + it('Can sponsor scheduling a transaction', async () => { + const collectionId = await createCollectionExpectSuccess(); + await setCollectionSponsorExpectSuccess(collectionId, bob.address); + await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); + + await usingApi(async () => { + const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); + + 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, 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('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => { + await usingApi(async (api, privateKeyWrapper) => { + // Find an empty, unused account + const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); + + const collectionId = await createCollectionExpectSuccess(); + + // Add zeroBalance address to allow list + await enablePublicMintingExpectSuccess(alice, collectionId); + await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address); + + // Grace zeroBalance with money, enough to cover future transactions + const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE); + await submitTransactionAsync(alice, balanceTx); + + // Mint a fresh NFT + const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT'); + + // Schedule transfer of the NFT a few blocks ahead + const waitForBlocks = 5; + await scheduleTransferExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, makeScheduledId()); + + // Get rid of the account's funds before the scheduled transaction takes place + 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;*/ + + // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions + await waitNewBlocks(waitForBlocks - 3); + + expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address)); + }); + }); + + it('Sponsor going bankrupt does not impact a scheduled transaction', async () => { + const collectionId = await createCollectionExpectSuccess(); + + await usingApi(async (api, privateKeyWrapper) => { + const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); + const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE); + await submitTransactionAsync(alice, balanceTx); + + await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address); + await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance); + + const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); + + 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); + const events = await submitTransactionAsync(alice, sudoTx); + 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(waitForBlocks - 3); + + expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address)); + }); + }); + + 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, privateKeyWrapper) => { + const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); + + await enablePublicMintingExpectSuccess(alice, collectionId); + await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address); + + const bobBalanceBefore = await getFreeBalance(bob); + + const createData = {nft: {const_data: [], variable_data: []}}; + const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any); + + /*const badTransaction = async function () { + await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice); + }; + await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/ + + await scheduleExpectFailure(creationTx, zeroBalance, 3, makeScheduledId(), 1, 3); + + expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore); + }); + }); +}); --- /dev/null +++ b/tests/src/.outdated/setChainLimits.test.ts @@ -0,0 +1,64 @@ +// 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 usingApi from '../substrate/substrate-api'; +import { + createCollectionExpectSuccess, + addCollectionAdminExpectSuccess, + setChainLimitsExpectFailure, + IChainLimits, +} from '../deprecated-helpers/helpers'; + +// todo:playgrounds skipped ~ postponed +describe.skip('Negative Integration Test setChainLimits', () => { + let alice: IKeyringPair; + let bob: IKeyringPair; + let dave: IKeyringPair; + let limits: IChainLimits; + + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + bob = privateKeyWrapper('//Bob'); + dave = privateKeyWrapper('//Dave'); + limits = { + collectionNumbersLimit : 1, + accountTokenOwnershipLimit: 1, + collectionsAdminsLimit: 1, + customDataLimit: 1, + nftSponsorTransferTimeout: 1, + fungibleSponsorTransferTimeout: 1, + refungibleSponsorTransferTimeout: 1, + }; + }); + }); + + it('Collection owner cannot set chain limits', async () => { + await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + await setChainLimitsExpectFailure(alice, limits); + }); + + it('Collection admin cannot set chain limits', async () => { + const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); + await setChainLimitsExpectFailure(bob, limits); + }); + + it('Regular user cannot set chain limits', async () => { + await setChainLimitsExpectFailure(dave, limits); + }); +}); --- /dev/null +++ b/tests/src/.outdated/setContractSponsoringRateLimit.test.ts @@ -0,0 +1,80 @@ +// 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 usingApi from '../substrate/substrate-api'; +import waitNewBlocks from '../substrate/wait-new-blocks'; +import {deployFlipper, toggleFlipValueExpectFailure, toggleFlipValueExpectSuccess} from '../deprecated-helpers/contracthelpers'; +import { + enableContractSponsoringExpectSuccess, + findUnusedAddress, + setContractSponsoringRateLimitExpectFailure, + setContractSponsoringRateLimitExpectSuccess, +} from '../deprecated-helpers/helpers'; + +// todo:playgrounds skipped~postponed test +describe.skip('Integration Test setContractSponsoringRateLimit', () => { + it('ensure sponsored contract can\'t be called twice without pause for free', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const user = await findUnusedAddress(api, privateKeyWrapper); + + const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); + await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); + await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 10); + await toggleFlipValueExpectSuccess(user, flipper); + await toggleFlipValueExpectFailure(user, flipper); + }); + }); + + it('ensure sponsored contract can be called twice with pause for free', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const user = await findUnusedAddress(api, privateKeyWrapper); + + const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); + await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); + await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 1); + await toggleFlipValueExpectSuccess(user, flipper); + await waitNewBlocks(api, 1); + await toggleFlipValueExpectSuccess(user, flipper); + }); + }); +}); + +describe.skip('Negative Integration Test setContractSponsoringRateLimit', () => { + let alice: IKeyringPair; + + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + }); + }); + + it('fails when called for non-contract address', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const user = await findUnusedAddress(api, privateKeyWrapper); + + await setContractSponsoringRateLimitExpectFailure(alice, user.address, 1); + }); + }); + + it('fails when called by non-owning user', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [flipper] = await deployFlipper(api, privateKeyWrapper); + + await setContractSponsoringRateLimitExpectFailure(alice, flipper.address, 1); + }); + }); +}); --- /dev/null +++ b/tests/src/.outdated/toggleContractAllowList.test.ts @@ -0,0 +1,167 @@ +// 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 chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import usingApi, {submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api'; +import { + deployFlipper, + getFlipValue, +} from '../deprecated-helpers/contracthelpers'; +import { + getGenericResult, +} from '../deprecated-helpers/helpers'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +const value = 0; +const gasLimit = 3000n * 1000000n; + +// todo:playgrounds skipped ~ postpone +describe.skip('Integration Test toggleContractAllowList', () => { + + it('Enable allow list contract mode', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); + + const enabledBefore = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); + const enableAllowListTx = api.tx.unique.toggleContractAllowList(contract.address, true); + const enableEvents = await submitTransactionAsync(deployer, enableAllowListTx); + const enabled = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); + + expect(getGenericResult(enableEvents).success).to.be.true; + expect(enabledBefore).to.be.false; + expect(enabled).to.be.true; + }); + }); + + it('Only allowlisted account can call contract', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const bob = privateKeyWrapper('//Bob'); + + const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); + + let flipValueBefore = await getFlipValue(contract, deployer); + const flip = contract.tx.flip({value, gasLimit}); + await submitTransactionAsync(bob, flip); + const flipValueAfter = await getFlipValue(contract,deployer); + expect(flipValueAfter).to.be.eq(!flipValueBefore, 'Anyone can call new contract.'); + + const deployerCanFlip = async () => { + const flipValueBefore = await getFlipValue(contract, deployer); + const deployerFlip = contract.tx.flip({value, gasLimit}); + await submitTransactionAsync(deployer, deployerFlip); + const aliceFlip1Response = await getFlipValue(contract, deployer); + expect(aliceFlip1Response).to.be.eq(!flipValueBefore, 'Deployer always can flip.'); + }; + await deployerCanFlip(); + + flipValueBefore = await getFlipValue(contract, deployer); + const enableAllowListTx = api.tx.unique.toggleContractAllowList(contract.address, true); + await submitTransactionAsync(deployer, enableAllowListTx); + const flipWithEnabledAllowList = contract.tx.flip({value, gasLimit}); + await expect(submitTransactionExpectFailAsync(bob, flipWithEnabledAllowList)).to.be.rejected; + const flipValueAfterEnableAllowList = await getFlipValue(contract, deployer); + expect(flipValueAfterEnableAllowList).to.be.eq(flipValueBefore, 'Enabling allowlist doesn\'t make it possible to call contract for everyone.'); + + await deployerCanFlip(); + + flipValueBefore = await getFlipValue(contract, deployer); + const addBobToAllowListTx = api.tx.unique.addToContractAllowList(contract.address, bob.address); + await submitTransactionAsync(deployer, addBobToAllowListTx); + const flipWithAllowlistedBob = contract.tx.flip({value, gasLimit}); + await submitTransactionAsync(bob, flipWithAllowlistedBob); + const flipAfterAllowListed = await getFlipValue(contract,deployer); + expect(flipAfterAllowListed).to.be.eq(!flipValueBefore, 'Bob was allowlisted, now he can flip.'); + + await deployerCanFlip(); + + flipValueBefore = await getFlipValue(contract, deployer); + const removeBobFromAllowListTx = api.tx.unique.removeFromContractAllowList(contract.address, bob.address); + await submitTransactionAsync(deployer, removeBobFromAllowListTx); + const bobRemoved = contract.tx.flip({value, gasLimit}); + await expect(submitTransactionExpectFailAsync(bob, bobRemoved)).to.be.rejected; + const afterBobRemoved = await getFlipValue(contract, deployer); + expect(afterBobRemoved).to.be.eq(flipValueBefore, 'Bob can\'t call contract, now when he is removeed from allow list.'); + + await deployerCanFlip(); + + flipValueBefore = await getFlipValue(contract, deployer); + const disableAllowListTx = api.tx.unique.toggleContractAllowList(contract.address, false); + await submitTransactionAsync(deployer, disableAllowListTx); + const allowListDisabledFlip = contract.tx.flip({value, gasLimit}); + await submitTransactionAsync(bob, allowListDisabledFlip); + const afterAllowListDisabled = await getFlipValue(contract,deployer); + expect(afterAllowListDisabled).to.be.eq(!flipValueBefore, 'Anyone can call contract with disabled allowlist.'); + + }); + }); + + it('Enabling allow list repeatedly should not produce errors', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); + + const enabledBefore = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); + const enableAllowListTx = api.tx.unique.toggleContractAllowList(contract.address, true); + const enableEvents = await submitTransactionAsync(deployer, enableAllowListTx); + const enabled = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); + const enableAgainEvents = await submitTransactionAsync(deployer, enableAllowListTx); + const enabledAgain = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); + + expect(getGenericResult(enableEvents).success).to.be.true; + expect(enabledBefore).to.be.false; + expect(enabled).to.be.true; + expect(getGenericResult(enableAgainEvents).success).to.be.true; + expect(enabledAgain).to.be.true; + }); + }); + +}); + +describe.skip('Negative Integration Test toggleContractAllowList', () => { + + it('Enable allow list for a non-contract', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const alice = privateKeyWrapper('//Alice'); + const bobGuineaPig = privateKeyWrapper('//Bob'); + + const enabledBefore = (await api.query.unique.contractAllowListEnabled(bobGuineaPig.address)).toJSON(); + const enableAllowListTx = api.tx.unique.toggleContractAllowList(bobGuineaPig.address, true); + await expect(submitTransactionExpectFailAsync(alice, enableAllowListTx)).to.be.rejected; + const enabled = (await api.query.unique.contractAllowListEnabled(bobGuineaPig.address)).toJSON(); + + expect(enabledBefore).to.be.false; + expect(enabled).to.be.false; + }); + }); + + it('Enable allow list using a non-owner address', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const bob = privateKeyWrapper('//Bob'); + const [contract] = await deployFlipper(api, privateKeyWrapper); + + const enabledBefore = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); + const enableAllowListTx = api.tx.unique.toggleContractAllowList(contract.address, true); + await expect(submitTransactionExpectFailAsync(bob, enableAllowListTx)).to.be.rejected; + const enabled = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); + + expect(enabledBefore).to.be.false; + expect(enabled).to.be.false; + }); + }); + +}); --- /dev/null +++ b/tests/src/.outdated/transfer_contract/metadata.json @@ -0,0 +1,469 @@ +{ + "metadataVersion": "0.1.0", + "source": { + "hash": "0xc6c3f47adeafe86d1674ed72c7179605787842f2f05a2d7da0dbabf3c4fa1aa8", + "language": "ink! 3.0.0-rc3", + "compiler": "rustc 1.52.0-nightly" + }, + "contract": { + "name": "nft_transfer", + "version": "0.1.0", + "authors": [ + "[Greg Zaitsev] <[your_email]>" + ] + }, + "spec": { + "constructors": [ + { + "args": [], + "docs": [ + "Default Constructor", + "", + "Constructors can delegate to other constructors." + ], + "name": [ + "default" + ], + "selector": "0xed4b9d1b" + } + ], + "docs": [], + "events": [], + "messages": [ + { + "args": [ + { + "name": "recipient", + "type": { + "displayName": [ + "AccountId" + ], + "type": 1 + } + }, + { + "name": "collection_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "token_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "amount", + "type": { + "displayName": [ + "u128" + ], + "type": 5 + } + } + ], + "docs": [ + " Transfer one NFT token" + ], + "mutates": true, + "name": [ + "transfer" + ], + "payable": false, + "returnType": null, + "selector": "0x84a15da1" + }, + { + "args": [ + { + "name": "recipient", + "type": { + "displayName": [ + "AccountId" + ], + "type": 1 + } + }, + { + "name": "collection_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "data", + "type": { + "displayName": [ + "CreateItemData" + ], + "type": 6 + } + } + ], + "docs": [], + "mutates": true, + "name": [ + "create_item" + ], + "payable": false, + "returnType": null, + "selector": "0xd7c3f083" + }, + { + "args": [ + { + "name": "owner", + "type": { + "displayName": [ + "AccountId" + ], + "type": 1 + } + }, + { + "name": "collection_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "data", + "type": { + "displayName": [ + "Vec" + ], + "type": 8 + } + } + ], + "docs": [], + "mutates": true, + "name": [ + "create_multiple_items" + ], + "payable": false, + "returnType": null, + "selector": "0x15f9a1eb" + }, + { + "args": [ + { + "name": "spender", + "type": { + "displayName": [ + "AccountId" + ], + "type": 1 + } + }, + { + "name": "collection_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "item_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "amount", + "type": { + "displayName": [ + "u128" + ], + "type": 5 + } + } + ], + "docs": [], + "mutates": true, + "name": [ + "approve" + ], + "payable": false, + "returnType": null, + "selector": "0x681266a0" + }, + { + "args": [ + { + "name": "owner", + "type": { + "displayName": [ + "AccountId" + ], + "type": 1 + } + }, + { + "name": "recipient", + "type": { + "displayName": [ + "AccountId" + ], + "type": 1 + } + }, + { + "name": "collection_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "item_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "amount", + "type": { + "displayName": [ + "u128" + ], + "type": 5 + } + } + ], + "docs": [], + "mutates": true, + "name": [ + "transfer_from" + ], + "payable": false, + "returnType": null, + "selector": "0x0b396f18" + }, + { + "args": [ + { + "name": "collection_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "item_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "data", + "type": { + "displayName": [ + "Vec" + ], + "type": 7 + } + } + ], + "docs": [], + "mutates": true, + "name": [ + "set_variable_meta_data" + ], + "payable": false, + "returnType": null, + "selector": "0xb0b26da2" + }, + { + "args": [ + { + "name": "collection_id", + "type": { + "displayName": [ + "u32" + ], + "type": 4 + } + }, + { + "name": "address", + "type": { + "displayName": [ + "AccountId" + ], + "type": 1 + } + }, + { + "name": "allowlisted", + "type": { + "displayName": [ + "bool" + ], + "type": 9 + } + } + ], + "docs": [], + "mutates": true, + "name": [ + "toggle_allow_list" + ], + "payable": false, + "returnType": null, + "selector": "0x98574dac" + } + ] + }, + "storage": { + "struct": { + "fields": [] + } + }, + "types": [ + { + "def": { + "composite": { + "fields": [ + { + "type": 2, + "typeName": "[u8; 32]" + } + ] + } + }, + "path": [ + "ink_env", + "types", + "AccountId" + ] + }, + { + "def": { + "array": { + "len": 32, + "type": 3 + } + } + }, + { + "def": { + "primitive": "u8" + } + }, + { + "def": { + "primitive": "u32" + } + }, + { + "def": { + "primitive": "u128" + } + }, + { + "def": { + "variant": { + "variants": [ + { + "fields": [ + { + "name": "const_data", + "type": 7, + "typeName": "Vec" + }, + { + "name": "variable_data", + "type": 7, + "typeName": "Vec" + } + ], + "name": "Nft" + }, + { + "fields": [ + { + "name": "value", + "type": 5, + "typeName": "u128" + } + ], + "name": "Fungible" + }, + { + "fields": [ + { + "name": "const_data", + "type": 7, + "typeName": "Vec" + }, + { + "name": "variable_data", + "type": 7, + "typeName": "Vec" + }, + { + "name": "pieces", + "type": 5, + "typeName": "u128" + } + ], + "name": "ReFungible" + } + ] + } + }, + "path": [ + "nft_transfer", + "CreateItemData" + ] + }, + { + "def": { + "sequence": { + "type": 3 + } + } + }, + { + "def": { + "sequence": { + "type": 6 + } + } + }, + { + "def": { + "primitive": "bool" + } + } + ] +} \ No newline at end of file --- /dev/null +++ b/tests/src/.outdated/xcmTransfer.test.ts @@ -0,0 +1,187 @@ +// 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 chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; + +import {WsProvider} from '@polkadot/api'; +import {ApiOptions} from '@polkadot/api/types'; +import {IKeyringPair} from '@polkadot/types/types'; +import usingApi, {submitTransactionAsync} from '../substrate/substrate-api'; +import {getGenericResult} from '../deprecated-helpers/helpers'; +import waitNewBlocks from '../substrate/wait-new-blocks'; +import getBalance from '../substrate/get-balance'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +const UNIQUE_CHAIN = 1000; +const KARURA_CHAIN = 2000; +const KARURA_PORT = '9946'; +const TRANSFER_AMOUNT = 2000000000000000000000000n; + +// todo:playgrounds refit when XCM drops +describe.skip('Integration test: Exchanging QTZ with Karura', () => { + let alice: IKeyringPair; + + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + }); + + const karuraApiOptions: ApiOptions = { + provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT), + }; + + await usingApi(async (api) => { + const destination = { + V0: { + X2: [ + 'Parent', + { + Parachain: UNIQUE_CHAIN, + }, + ], + }, + }; + + const metadata = + { + name: 'QTZ', + symbol: 'QTZ', + decimals: 18, + minimalBalance: 1, + }; + + const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata); + const sudoTx = api.tx.sudo.sudo(tx as any); + const events = await submitTransactionAsync(alice, sudoTx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + }, karuraApiOptions); + }); + + it('Should connect and send QTZ to Karura', async () => { + let balanceOnKaruraBefore: bigint; + + await usingApi(async (api) => { + const {free} = (await api.query.tokens.accounts(alice.addressRaw, {ForeignAssetId: 0})).toJSON() as any; + balanceOnKaruraBefore = free; + }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)}); + + await usingApi(async (api) => { + const destination = { + V0: { + X2: [ + 'Parent', + { + Parachain: KARURA_CHAIN, + }, + ], + }, + }; + + const beneficiary = { + V0: { + X1: { + AccountId32: { + network: 'Any', + id: alice.addressRaw, + }, + }, + }, + }; + + const assets = { + V1: [ + { + id: { + Concrete: { + parents: 0, + interior: 'Here', + }, + }, + fun: { + Fungible: TRANSFER_AMOUNT, + }, + }, + ], + }; + + const feeAssetItem = 0; + + const weightLimit = { + Limited: 5000000000, + }; + + const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit); + const events = await submitTransactionAsync(alice, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + }); + + await usingApi(async (api) => { + // todo do something about instant sealing, where there might not be any new blocks + await waitNewBlocks(api, 3); + const {free} = (await api.query.tokens.accounts(alice.addressRaw, {ForeignAssetId: 0})).toJSON() as any; + expect(free > balanceOnKaruraBefore).to.be.true; + }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)}); + }); + + it('Should connect to Karura and send QTZ back', async () => { + let balanceBefore: bigint; + + await usingApi(async (api) => { + [balanceBefore] = await getBalance(api, [alice.address]); + }); + + await usingApi(async (api) => { + const destination = { + V1: { + parents: 1, + interior: { + X2: [ + {Parachain: UNIQUE_CHAIN}, + {AccountId32: { + network: 'Any', + id: alice.addressRaw, + }}, + ], + }, + }, + }; + + const id = { + ForeignAssetId: 0, + }; + + const amount = TRANSFER_AMOUNT; + const destWeight = 50000000; + + const tx = api.tx.xTokens.transfer(id, amount, destination, destWeight); + const events = await submitTransactionAsync(alice, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)}); + + await usingApi(async (api) => { + // todo do something about instant sealing, where there might not be any new blocks + await waitNewBlocks(api, 3); + const [balanceAfter] = await getBalance(api, [alice.address]); + expect(balanceAfter > balanceBefore).to.be.true; + }); + }); +}); --- a/tests/src/addCollectionAdmin.test.ts +++ b/tests/src/addCollectionAdmin.test.ts @@ -110,7 +110,7 @@ const [alice, ...accounts] = await helper.arrange.createAccounts([10n, 0n, 0n, 0n, 0n, 0n, 0n, 0n], donor); const collection = await helper.nft.mintCollection(alice, {name: 'Collection Name', description: 'Collection Description', tokenPrefix: 'COL'}); - const chainAdminLimit = (helper.api!.consts.common.collectionAdminsLimit as any).toNumber(); + const chainAdminLimit = (helper.getApi().consts.common.collectionAdminsLimit as any).toNumber(); expect(chainAdminLimit).to.be.equal(5); for (let i = 0; i < chainAdminLimit; i++) { --- a/tests/src/addToContractAllowList.test.ts +++ /dev/null @@ -1,103 +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 chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import usingApi, {submitTransactionAsync, submitTransactionExpectFailAsync} from './substrate/substrate-api'; -import { - deployFlipper, -} from './util/contracthelpers'; -import { - getGenericResult, -} from './util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -// todo:playgrounds skipped ~ postponed -describe.skip('Integration Test addToContractAllowList', () => { - - it('Add an address to a contract allow list', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const bob = privateKeyWrapper('//Bob'); - const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); - - const allowListedBefore = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); - const addTx = api.tx.unique.addToContractAllowList(contract.address, bob.address); - const addEvents = await submitTransactionAsync(deployer, addTx); - const allowListedAfter = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); - - expect(getGenericResult(addEvents).success).to.be.true; - expect(allowListedBefore).to.be.false; - expect(allowListedAfter).to.be.true; - }); - }); - - it('Adding same address to allow list repeatedly should not produce errors', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const bob = privateKeyWrapper('//Bob'); - const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); - - const allowListedBefore = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); - const addTx = api.tx.unique.addToContractAllowList(contract.address, bob.address); - const addEvents = await submitTransactionAsync(deployer, addTx); - const allowListedAfter = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); - const addAgainEvents = await submitTransactionAsync(deployer, addTx); - const allowListedAgainAfter = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); - - expect(getGenericResult(addEvents).success).to.be.true; - expect(allowListedBefore).to.be.false; - expect(allowListedAfter).to.be.true; - expect(getGenericResult(addAgainEvents).success).to.be.true; - expect(allowListedAgainAfter).to.be.true; - }); - }); -}); - -describe.skip('Negative Integration Test addToContractAllowList', () => { - - it('Add an address to a allow list of a non-contract', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Bob'); - const bob = privateKeyWrapper('//Bob'); - const charlieGuineaPig = privateKeyWrapper('//Charlie'); - - const allowListedBefore = (await api.query.unique.contractAllowList(charlieGuineaPig.address, bob.address)).toJSON(); - const addTx = api.tx.unique.addToContractAllowList(charlieGuineaPig.address, bob.address); - await expect(submitTransactionExpectFailAsync(alice, addTx)).to.be.rejected; - const allowListedAfter = (await api.query.unique.contractAllowList(charlieGuineaPig.address, bob.address)).toJSON(); - - expect(allowListedBefore).to.be.false; - expect(allowListedAfter).to.be.false; - }); - }); - - it('Add to a contract allow list using a non-owner address', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const bob = privateKeyWrapper('//Bob'); - const [contract] = await deployFlipper(api, privateKeyWrapper); - - const allowListedBefore = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); - const addTx = api.tx.unique.addToContractAllowList(contract.address, bob.address); - await expect(submitTransactionExpectFailAsync(bob, addTx)).to.be.rejected; - const allowListedAfter = (await api.query.unique.contractAllowList(contract.address, bob.address)).toJSON(); - - expect(allowListedBefore).to.be.false; - expect(allowListedAfter).to.be.false; - }); - }); - -}); --- a/tests/src/app-promotion.test.ts +++ b/tests/src/app-promotion.test.ts @@ -15,17 +15,11 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; -import { - normalizeAccountId, - getModuleNames, - Pallets, -} from './util/helpers'; -import {itSub, usingPlaygrounds} from './util/playgrounds'; +import {itSub, usingPlaygrounds, Pallets, requirePalletsOrSkip} from './util/playgrounds'; import {encodeAddress} from '@polkadot/util-crypto'; import {stringToU8a} from '@polkadot/util'; -import {SponsoringMode} from './eth/util/helpers'; import {DevUniqueHelper} from './util/playgrounds/unique.dev'; -import {itEth, expect} from './eth/util/playgrounds'; +import {itEth, expect, SponsoringMode} from './eth/util/playgrounds'; let alice: IKeyringPair; let palletAdmin: IKeyringPair; @@ -42,10 +36,11 @@ describe('App promotion', () => { before(async function () { await usingPlaygrounds(async (helper, privateKey) => { - if (!getModuleNames(helper.api!).includes(Pallets.AppPromotion)) this.skip(); + requirePalletsOrSkip(this, helper, [Pallets.AppPromotion]); alice = await privateKey({filename: __filename}); [palletAdmin] = await helper.arrange.createAccounts([100n], alice); - await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}))); + const api = helper.getApi(); + await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address}))); nominal = helper.balance.getOneTokenNominal(); await helper.balance.transferToSubstrate(alice, palletAdmin.address, 1000n * nominal); await helper.balance.transferToSubstrate(alice, palletAddress, 1000n * nominal); @@ -231,54 +226,59 @@ describe('admin adress', () => { itSub('can be set by sudo only', async ({helper}) => { + const api = helper.getApi(); const nonAdmin = accounts.pop()!; // nonAdmin can not set admin not from himself nor as a sudo - await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address}))).to.be.rejected; - await expect(helper.signTransaction(nonAdmin, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address})))).to.be.rejected; + await expect(helper.signTransaction(nonAdmin, api.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address}))).to.be.rejected; + await expect(helper.signTransaction(nonAdmin, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: nonAdmin.address})))).to.be.rejected; // Alice can - await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled; + await expect(helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled; }); itSub('can be any valid CrossAccountId', async ({helper}) => { // We are not going to set an eth address as a sponsor, // but we do want to check, it doesn't break anything; + const api = helper.getApi(); const account = accounts.pop()!; const ethAccount = helper.address.substrateToEth(account.address); // Alice sets Ethereum address as a sudo. Then Substrate address back... - await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Ethereum: ethAccount})))).to.be.fulfilled; - await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled; + await expect(helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Ethereum: ethAccount})))).to.be.fulfilled; + await expect(helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})))).to.be.fulfilled; // ...It doesn't break anything; const collection = await helper.nft.mintCollection(account, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); - await expect(helper.signTransaction(account, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; + await expect(helper.signTransaction(account, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; }); itSub('can be reassigned', async ({helper}) => { + const api = helper.getApi(); const [oldAdmin, newAdmin, collectionOwner] = [accounts.pop()!, accounts.pop()!, accounts.pop()!]; const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); - await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(oldAdmin))))).to.be.fulfilled; - await expect(helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress(normalizeAccountId(newAdmin))))).to.be.fulfilled; - await expect(helper.signTransaction(oldAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; + await expect(helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: oldAdmin.address})))).to.be.fulfilled; + await expect(helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: newAdmin.address})))).to.be.fulfilled; + await expect(helper.signTransaction(oldAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; - await expect(helper.signTransaction(newAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled; + await expect(helper.signTransaction(newAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled; }); }); describe('collection sponsoring', () => { before(async function () { await usingPlaygrounds(async (helper) => { - const tx = helper.api!.tx.sudo.sudo(helper.api!.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})); + const api = helper.getApi(); + const tx = api.tx.sudo.sudo(api.tx.appPromotion.setAdminAddress({Substrate: palletAdmin.address})); await helper.signTransaction(alice, tx); }); }); itSub('should actually sponsor transactions', async ({helper}) => { + const api = helper.getApi(); const [collectionOwner, tokenSender, receiver] = [accounts.pop()!, accounts.pop()!, accounts.pop()!]; const collection = await helper.nft.mintCollection(collectionOwner, {name: 'Name', description: 'Description', tokenPrefix: 'Prefix', limits: {sponsorTransferTimeout: 0}}); const token = await collection.mintToken(collectionOwner, {Substrate: tokenSender.address}); - await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId)); + await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId)); const palletBalanceBefore = await helper.balance.getSubstrate(palletAddress); await token.transfer(tokenSender, {Substrate: receiver.address}); @@ -291,41 +291,44 @@ }); itSub('can not be set by non admin', async ({helper}) => { + const api = helper.getApi(); const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!]; const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); - await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; + await expect(helper.signTransaction(nonAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; expect((await collection.getData())?.raw.sponsorship).to.equal('Disabled'); }); itSub('should set pallet address as confirmed admin', async ({helper}) => { + const api = helper.getApi(); const [collectionOwner, oldSponsor] = [accounts.pop()!, accounts.pop()!]; // Can set sponsoring for collection without sponsor const collectionWithoutSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'No-sponsor', description: 'New Collection', tokenPrefix: 'Promotion'}); - await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithoutSponsor.collectionId))).to.be.fulfilled; + await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collectionWithoutSponsor.collectionId))).to.be.fulfilled; expect((await collectionWithoutSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress}); // Can set sponsoring for collection with unconfirmed sponsor const collectionWithUnconfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Unconfirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address}); expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Unconfirmed: oldSponsor.address}); - await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithUnconfirmedSponsor.collectionId))).to.be.fulfilled; + await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collectionWithUnconfirmedSponsor.collectionId))).to.be.fulfilled; expect((await collectionWithUnconfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress}); // Can set sponsoring for collection with confirmed sponsor const collectionWithConfirmedSponsor = await helper.nft.mintCollection(collectionOwner, {name: 'Confirmed', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: oldSponsor.address}); await collectionWithConfirmedSponsor.confirmSponsorship(oldSponsor); - await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithConfirmedSponsor.collectionId))).to.be.fulfilled; + await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collectionWithConfirmedSponsor.collectionId))).to.be.fulfilled; expect((await collectionWithConfirmedSponsor.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress}); }); itSub('can be overwritten by collection owner', async ({helper}) => { + const api = helper.getApi(); const [collectionOwner, newSponsor] = [accounts.pop()!, accounts.pop()!]; const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); const collectionId = collection.collectionId; - await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionId))).to.be.fulfilled; + await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collectionId))).to.be.fulfilled; // Collection limits still can be changed by the owner expect(await collection.setLimits(collectionOwner, {sponsorTransferTimeout: 0})).to.be.true; @@ -338,44 +341,48 @@ }); itSub('should not overwrite collection limits set by the owner earlier', async ({helper}) => { + const api = helper.getApi(); const limits = {ownerCanDestroy: true, ownerCanTransfer: true, sponsorTransferTimeout: 0}; const collectionWithLimits = await helper.nft.mintCollection(alice, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits}); - await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collectionWithLimits.collectionId))).to.be.fulfilled; + await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collectionWithLimits.collectionId))).to.be.fulfilled; expect((await collectionWithLimits.getData())?.raw.limits).to.be.deep.contain(limits); }); itSub('should reject transaction if collection doesn\'t exist', async ({helper}) => { + const api = helper.getApi(); const collectionOwner = accounts.pop()!; // collection has never existed - await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(999999999))).to.be.rejected; + await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(999999999))).to.be.rejected; // collection has been burned const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); await collection.burn(collectionOwner); - await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; + await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.rejected; }); }); describe('stopSponsoringCollection', () => { - itSub('can not be called by non-admin', async ({helper}) => { + itSub('can not be called by non-admin', async ({helper}) => { + const api = helper.getApi(); const [collectionOwner, nonAdmin] = [accounts.pop()!, accounts.pop()!]; const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion'}); - await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled; + await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId))).to.be.fulfilled; - await expect(helper.signTransaction(nonAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected; + await expect(helper.signTransaction(nonAdmin, api.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected; expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: palletAddress}); }); itSub('should set sponsoring as disabled', async ({helper}) => { + const api = helper.getApi(); const [collectionOwner, recepient] = [accounts.pop()!, accounts.pop()!]; const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', limits: {sponsorTransferTimeout: 0}}); const token = await collection.mintToken(collectionOwner, {Substrate: collectionOwner.address}); - await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorCollection(collection.collectionId)); - await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId)); + await helper.signTransaction(palletAdmin, api.tx.appPromotion.sponsorCollection(collection.collectionId)); + await helper.signTransaction(palletAdmin, api.tx.appPromotion.stopSponsoringCollection(collection.collectionId)); expect((await collection.getData())?.raw.sponsorship).to.be.equal('Disabled'); @@ -387,11 +394,12 @@ }); itSub('should not affect collection which is not sponsored by pallete', async ({helper}) => { + const api = helper.getApi(); const collectionOwner = accounts.pop()!; const collection = await helper.nft.mintCollection(collectionOwner, {name: 'New', description: 'New Collection', tokenPrefix: 'Promotion', pendingSponsor: collectionOwner.address}); await collection.confirmSponsorship(collectionOwner); - await expect(helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected; + await expect(helper.signTransaction(palletAdmin, api.tx.appPromotion.stopSponsoringCollection(collection.collectionId))).to.be.rejected; expect((await collection.getData())?.raw.sponsorship).to.be.deep.equal({Confirmed: collectionOwner.address}); }); @@ -415,8 +423,8 @@ await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address]); expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true; - expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner); - expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({ + expect((await helper.callRpc('api.query.evmContractHelpers.owner', [flipper.options.address])).toJSON()).to.be.equal(contractOwner); + expect((await helper.callRpc('api.query.evmContractHelpers.sponsoring', [flipper.options.address])).toJSON()).to.deep.equal({ confirmed: { substrate: palletAddress, }, @@ -431,7 +439,7 @@ await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.fulfilled; // Contract is self sponsored - expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.be.deep.equal({ + expect((await helper.callRpc('api.query.evmContractHelpers.sponsoring', [flipper.options.address])).toJSON()).to.be.deep.equal({ confirmed: { ethereum: flipper.options.address.toLowerCase(), }, @@ -462,8 +470,8 @@ await expect(contractHelper.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected; expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.true; - expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner); - expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({ + expect((await helper.callRpc('api.query.evmContractHelpers.owner', [flipper.options.address])).toJSON()).to.be.equal(contractOwner); + expect((await helper.callRpc('api.query.evmContractHelpers.sponsoring', [flipper.options.address])).toJSON()).to.deep.equal({ confirmed: { ethereum: flipper.options.address.toLowerCase(), }, @@ -482,7 +490,7 @@ await expect(helper.executeExtrinsic(nonAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true)).to.be.rejectedWith('appPromotion.NoPermission'); // contract still self-sponsored - expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({ + expect((await helper.callRpc('api.query.evmContractHelpers.sponsoring', [flipper.options.address])).toJSON()).to.deep.equal({ confirmed: { ethereum: flipper.options.address.toLowerCase(), }, @@ -505,7 +513,7 @@ await helper.eth.transferBalanceFromSubstrate(alice, flipper.options.address, 1000n); // transferBalanceToEth(api, alice, flipper.options.address, 1000n); // Set promotion to the Flipper - await helper.signTransaction(palletAdmin, helper.api!.tx.appPromotion.sponsorContract(flipper.options.address)); + await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.sponsorContract', [flipper.options.address], true); // Caller calls Flipper await flipper.methods.flip().send({from: caller}); @@ -536,8 +544,8 @@ await helper.executeExtrinsic(palletAdmin, 'api.tx.appPromotion.stopSponsoringContract', [flipper.options.address], true); expect(await contractHelper.methods.hasSponsor(flipper.options.address).call()).to.be.false; - expect((await helper.api!.query.evmContractHelpers.owner(flipper.options.address)).toJSON()).to.be.equal(contractOwner); - expect((await helper.api!.query.evmContractHelpers.sponsoring(flipper.options.address)).toJSON()).to.deep.equal({ + expect((await helper.callRpc('api.query.evmContractHelpers.owner', [flipper.options.address])).toJSON()).to.be.equal(contractOwner); + expect((await helper.callRpc('api.query.evmContractHelpers.sponsoring', [flipper.options.address])).toJSON()).to.deep.equal({ disabled: null, }); @@ -717,7 +725,7 @@ // Wait while promotion period less than specified block, to avoid boundary cases // 0 if this should be the beginning of the period. async function waitPromotionPeriodDoesntEnd(helper: DevUniqueHelper, waitBlockLessThan = LOCKING_PERIOD / 3n) { - const relayBlockNumber = (await helper.api!.query.parachainSystem.validationData()).value.relayParentNumber.toNumber(); // await helper.chain.getLatestBlockNumber(); + const relayBlockNumber = (await helper.callRpc('api.query.parachainSystem.validationData', [])).value.relayParentNumber.toNumber(); // await helper.chain.getLatestBlockNumber(); const currentPeriodBlock = BigInt(relayBlockNumber) % LOCKING_PERIOD; if (currentPeriodBlock > waitBlockLessThan) { --- a/tests/src/approve.test.ts +++ b/tests/src/approve.test.ts @@ -60,7 +60,7 @@ const {tokenId} = await helper.nft.mintToken(alice, {collectionId: collectionId, owner: alice.address}); await helper.nft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}); expect(await helper.nft.isTokenApproved(collectionId, tokenId, {Substrate: bob.address})).to.be.true; - await helper.signTransaction(alice, helper.api?.tx.unique.approve({Substrate: bob.address}, collectionId, tokenId, 0)); + await helper.signTransaction(alice, helper.constructApiCall('api.tx.unique.approve', [{Substrate: bob.address}, collectionId, tokenId, 0])); expect(await helper.nft.isTokenApproved(collectionId, tokenId, {Substrate: bob.address})).to.be.false; }); @@ -275,7 +275,7 @@ const {tokenId} = await helper.nft.mintToken(alice, {collectionId: collectionId, owner: alice.address}); await helper.nft.approveToken(alice, collectionId, tokenId, {Substrate: bob.address}); expect(await helper.nft.isTokenApproved(collectionId, tokenId, {Substrate: bob.address})).to.be.true; - await helper.signTransaction(alice, helper.api?.tx.unique.approve({Substrate: bob.address}, collectionId, tokenId, 0)); + await helper.signTransaction(alice, helper.constructApiCall('api.tx.unique.approve', [{Substrate: bob.address}, collectionId, tokenId, 0])); expect(await helper.nft.isTokenApproved(collectionId, tokenId, {Substrate: bob.address})).to.be.false; const transferTokenFromTx = async () => helper.nft.transferTokenFrom(bob, collectionId, tokenId, {Substrate: bob.address}, {Substrate: bob.address}); await expect(transferTokenFromTx()).to.be.rejected; @@ -328,7 +328,7 @@ itSub('1 for NFT', async ({helper}) => { const {collectionId} = await helper.nft.mintCollection(alice, {name: 'col', description: 'descr', tokenPrefix: 'COL'}); const {tokenId} = await helper.nft.mintToken(alice, {collectionId: collectionId, owner: bob.address}); - const approveTx = async () => helper.signTransaction(bob, helper.api?.tx.unique.approve({Substrate: charlie.address}, collectionId, tokenId, 2)); + const approveTx = async () => helper.signTransaction(bob, helper.constructApiCall('api.tx.unique.approve', [{Substrate: charlie.address}, collectionId, tokenId, 2])); await expect(approveTx()).to.be.rejected; expect(await helper.nft.isTokenApproved(collectionId, tokenId, {Substrate: charlie.address})).to.be.false; }); --- a/tests/src/balance-transfer-contract/metadata.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "registry": { - "strings": [ - "Storage", - "calls", - "__ink_private", - "__ink_storage", - "new", - "balance_transfer", - "dest", - "AccountId", - "value", - "Balance" - ], - "types": [ - { - "id": { - "custom.name": 1, - "custom.namespace": [ - 2, - 2, - 3, - 4 - ], - "custom.params": [] - }, - "def": { - "struct.fields": [] - } - }, - { - "id": { - "array.len": 32, - "array.type": 3 - }, - "def": "builtin" - }, - { - "id": "u8", - "def": "builtin" - }, - { - "id": "u128", - "def": "builtin" - } - ] - }, - "storage": { - "struct.type": 1, - "struct.fields": [] - }, - "contract": { - "name": 2, - "constructors": [ - { - "name": 5, - "selector": "[\"0x5E\",\"0xBD\",\"0x88\",\"0xD6\"]", - "args": [], - "docs": [] - } - ], - "messages": [ - { - "name": 6, - "selector": "[\"0xEC\",\"0x9F\",\"0xA4\",\"0xF0\"]", - "mutates": false, - "args": [ - { - "name": 7, - "type": { - "ty": 2, - "display_name": [ - 8 - ] - } - }, - { - "name": 9, - "type": { - "ty": 4, - "display_name": [ - 10 - ] - } - } - ], - "return_type": null, - "docs": [ - "Dispatches a `transfer` call to the Balances srml module" - ] - } - ], - "events": [], - "docs": [] - } -} \ No newline at end of file --- a/tests/src/block-production.test.ts +++ b/tests/src/block-production.test.ts @@ -37,7 +37,7 @@ describe('Block Production smoke test', () => { itSub('Node produces new blocks', async ({helper}) => { - const blocks: number[] | undefined = await getBlocks(helper.api!); + const blocks: number[] | undefined = await getBlocks(helper.getApi()); expect(blocks[0]).to.be.lessThan(blocks[1]); }); }); --- a/tests/src/calibrate.ts +++ b/tests/src/calibrate.ts @@ -1,11 +1,8 @@ -import {ApiPromise} from '@polkadot/api'; import {IKeyringPair} from '@polkadot/types/types'; -import Web3 from 'web3'; -import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, recordEthFee, usingWeb3} from './eth/util/helpers'; -import usingApi, {executeTransaction} from './substrate/substrate-api'; -import {createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, UNIQUE, waitNewBlocks} from './util/helpers'; -import nonFungibleAbi from './eth/nonFungibleAbi.json'; +import {usingEthPlaygrounds, EthUniqueHelper} from './eth/util/playgrounds'; + + function linearRegression(points: { x: bigint, y: bigint }[]) { let sumxy = 0n; let sumx = 0n; @@ -59,32 +56,33 @@ }).reduce((a, b) => a + b, 0n) / BigInt(points.length)); } -async function calibrateWeightToFee(api: ApiPromise, privateKey: (account: string) => IKeyringPair) { +async function calibrateWeightToFee(helper: EthUniqueHelper, privateKey: (account: string) => IKeyringPair) { const alice = privateKey('//Alice'); const bob = privateKey('//Bob'); const dataPoints = []; { - const collectionId = await createCollectionExpectSuccess(); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT'); - const aliceBalanceBefore = (await api.query.system.account(alice.address)).data.free.toBigInt(); - await transferExpectSuccess(collectionId, tokenId, alice, bob, 1, 'NFT'); - const aliceBalanceAfter = (await api.query.system.account(alice.address)).data.free.toBigInt(); + const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}); + const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address); + await token.transfer(alice, {Substrate: bob.address}); + const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address); - console.log(`Original price: ${Number(aliceBalanceBefore - aliceBalanceAfter) / Number(UNIQUE)} UNQ`); + console.log(`Original price: ${Number(aliceBalanceBefore - aliceBalanceAfter) / Number(helper.balance.getOneTokenNominal())} UNQ`); } + const api = helper.getApi(); const defaultCoeff = (api.consts.configuration.defaultWeightToFeeCoefficient as any).toBigInt(); for (let i = -5; i < 5; i++) { - await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(defaultCoeff + defaultCoeff / 1000n * BigInt(i)))); + await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(defaultCoeff + defaultCoeff / 1000n * BigInt(i)))); const coefficient = (await api.query.configuration.weightToFeeCoefficientOverride() as any).toBigInt(); - const collectionId = await createCollectionExpectSuccess(); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT'); + const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}); - const aliceBalanceBefore = (await api.query.system.account(alice.address)).data.free.toBigInt(); - await transferExpectSuccess(collectionId, tokenId, alice, bob, 1, 'NFT'); - const aliceBalanceAfter = (await api.query.system.account(alice.address)).data.free.toBigInt(); + const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address); + await token.transfer(alice, {Substrate: bob.address}); + const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address); const transferPrice = aliceBalanceBefore - aliceBalanceAfter; @@ -94,52 +92,53 @@ // console.log(`Error: ${error(dataPoints, x => a*x+b)}`); - const perfectValue = a * UNIQUE / 10n + b; - await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(perfectValue.toString()))); + const perfectValue = a * helper.balance.getOneTokenNominal() / 10n + b; + await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setWeightToFeeCoefficientOverride(perfectValue.toString()))); { - const collectionId = await createCollectionExpectSuccess(); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT'); - const aliceBalanceBefore = (await api.query.system.account(alice.address)).data.free.toBigInt(); - await transferExpectSuccess(collectionId, tokenId, alice, bob, 1, 'NFT'); - const aliceBalanceAfter = (await api.query.system.account(alice.address)).data.free.toBigInt(); + const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'}); + const token = await collection.mintToken(alice, {Substrate: alice.address}); + const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address); + await token.transfer(alice, {Substrate: bob.address}); + const aliceBalanceAfter = await helper.balance.getSubstrate(alice.address); - console.log(`Calibrated price: ${Number(aliceBalanceBefore - aliceBalanceAfter) / Number(UNIQUE)} UNQ`); + console.log(`Calibrated price: ${Number(aliceBalanceBefore - aliceBalanceAfter) / Number(helper.balance.getOneTokenNominal())} UNQ`); } } -async function calibrateMinGasPrice(api: ApiPromise, web3: Web3, privateKey: (account: string) => IKeyringPair) { +async function calibrateMinGasPrice(helper: EthUniqueHelper, privateKey: (account: string) => IKeyringPair) { const alice = privateKey('//Alice'); - const caller = await createEthAccountWithBalance(api, web3, privateKey); - const receiver = createEthAccount(web3); + const caller = await helper.eth.createAccountWithBalance(alice); + const receiver = helper.eth.createAccount(); const dataPoints = []; { - const collectionId = await createCollectionExpectSuccess(); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Ethereum: caller}); + const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'}); + const token = await collection.mintToken(alice, {Ethereum: caller}); - const address = collectionIdToAddress(collectionId); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}); + const address = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(address, 'nft', caller); - const cost = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send(caller)); + const cost = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gas: helper.eth.DEFAULT_GAS})); - console.log(`Original price: ${Number(cost) / Number(UNIQUE)} UNQ`); + console.log(`Original price: ${Number(cost) / Number(helper.balance.getOneTokenNominal())} UNQ`); } + const api = helper.getApi(); const defaultCoeff = (api.consts.configuration.defaultMinGasPrice as any).toBigInt(); for (let i = -8; i < 8; i++) { const gasPrice = defaultCoeff + defaultCoeff / 100000n * BigInt(i); const gasPriceStr = '0x' + gasPrice.toString(16); - await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(gasPrice))); + await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(gasPrice))); const coefficient = (await api.query.configuration.minGasPriceOverride() as any).toBigInt(); - const collectionId = await createCollectionExpectSuccess(); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Ethereum: caller}); + const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'}); + const token = await collection.mintToken(alice, {Ethereum: caller}); - const address = collectionIdToAddress(collectionId); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, gasPrice: gasPriceStr, ...GAS_ARGS}); + const address = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(address, 'nft', caller); - const transferPrice = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send(caller)); + const transferPrice = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gasPrice: gasPriceStr, gas: helper.eth.DEFAULT_GAS})); dataPoints.push({x: transferPrice, y: coefficient}); } @@ -149,34 +148,30 @@ // console.log(`Error: ${error(dataPoints, x => a*x+b)}`); // * 0.15 = * 10000 / 66666 - const perfectValue = a * UNIQUE * 1000000n / 6666666n + b; - await executeTransaction(api, alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(perfectValue.toString()))); + const perfectValue = a * helper.balance.getOneTokenNominal() * 1000000n / 6666666n + b; + await helper.signTransaction(alice, api.tx.sudo.sudo(api.tx.configuration.setMinGasPriceOverride(perfectValue.toString()))); { - const collectionId = await createCollectionExpectSuccess(); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', {Ethereum: caller}); + const collection = await helper.nft.mintCollection(alice, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'}); + const token = await collection.mintToken(alice, {Ethereum: caller}); - const address = collectionIdToAddress(collectionId); - const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}); + const address = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(address, 'nft', caller); - const cost = await recordEthFee(api, caller, () => contract.methods.transfer(receiver, tokenId).send(caller)); + const cost = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, token.tokenId).send({from: caller, gas: helper.eth.DEFAULT_GAS})); - console.log(`Calibrated price: ${Number(cost) / Number(UNIQUE)} UNQ`); + console.log(`Calibrated price: ${Number(cost) / Number(helper.balance.getOneTokenNominal())} UNQ`); } } (async () => { - await usingApi(async (api, privateKey) => { + await usingEthPlaygrounds(async (helper: EthUniqueHelper, privateKey) => { // Second run slightly reduces error sometimes, as price line is not actually straight, this is a curve - await calibrateWeightToFee(api, privateKey); - await calibrateWeightToFee(api, privateKey); + await calibrateWeightToFee(helper, privateKey); + await calibrateWeightToFee(helper, privateKey); - await usingWeb3(async web3 => { - await calibrateMinGasPrice(api, web3, privateKey); - await calibrateMinGasPrice(api, web3, privateKey); - }); - - await api.disconnect(); + await calibrateMinGasPrice(helper, privateKey); + await calibrateMinGasPrice(helper, privateKey); }); })(); --- a/tests/src/collision-tests/admVsOwnerChanges.test.ts +++ /dev/null @@ -1,73 +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 . - -/* broken by design -// substrate transactions are sequential, not parallel -// the order of execution is indeterminate - -import { IKeyringPair } from '@polkadot/types/types'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import privateKey from '../substrate/privateKey'; -import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; -import { - createCollectionExpectSuccess, - createItemExpectSuccess, - normalizeAccountId, - waitNewBlocks, -} from '../util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -let Alice: IKeyringPair; -let Bob: IKeyringPair; -let Ferdie: IKeyringPair; - -before(async () => { - await usingApi(async () => { - Alice = privateKey('//Alice'); - Bob = privateKey('//Bob'); - Ferdie = privateKey('//Ferdie'); - }); -}); - -describe('Admin vs Owner changes token: ', () => { - // tslint:disable-next-line: max-line-length - it('The collection admin changes the owner of the token and in the same block the current owner transfers the token to another address ', async () => { - - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const changeAdminTxBob = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); - await submitTransactionAsync(Alice, changeAdminTxBob); - const changeAdminTxFerdie = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Ferdie.address)); - await submitTransactionAsync(Bob, changeAdminTxFerdie); - const itemId = await createItemExpectSuccess(Ferdie, collectionId, 'NFT'); - - const changeOwner = api.tx.unique.transferFrom(normalizeAccountId(Ferdie.address), normalizeAccountId(Bob.address), collectionId, itemId, 1); - const approve = api.tx.unique.approve(normalizeAccountId(Bob.address), collectionId, itemId, 1); - const sendItem = api.tx.unique.transfer(normalizeAccountId(Alice.address), collectionId, itemId, 1); - await Promise.all([ - changeOwner.signAndSend(Alice), - approve.signAndSend(Bob), - sendItem.signAndSend(Ferdie), - ]); - const itemBefore: any = await api.query.unique.nftItemList(collectionId, itemId); - expect(itemBefore.owner).not.to.be.eq(Bob.address); - await waitNewBlocks(2); - }); - }); -}); -*/ \ No newline at end of file --- a/tests/src/collision-tests/admVsOwnerData.test.ts +++ /dev/null @@ -1,69 +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 . - -/* broken by design -// substrate transactions are sequential, not parallel -// the order of execution is indeterminate - -import { IKeyringPair } from '@polkadot/types/types'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import privateKey from '../substrate/privateKey'; -import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; -import { - createCollectionExpectSuccess, - createItemExpectSuccess, - normalizeAccountId, - waitNewBlocks, -} from '../util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -let Alice: IKeyringPair; -let Bob: IKeyringPair; - -before(async () => { - await usingApi(async () => { - Alice = privateKey('//Alice'); - Bob = privateKey('//Bob'); - }); -}); - -describe('Admin vs Owner changes the data in the token: ', () => { - it('The collection admin changes the data in the token and in the same block the token owner also changes the data in it ', async () => { - await usingApi(async (api) => { - const AliceData = 1; - const BobData = 2; - const collectionId = await createCollectionExpectSuccess(); - const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); - await submitTransactionAsync(Alice, changeAdminTx); - const itemId = await createItemExpectSuccess(Bob, collectionId, 'NFT'); - // - // tslint:disable-next-line: max-line-length - const AliceTx = api.tx.unique.setVariableMetaData(collectionId, itemId, AliceData.toString()); - // tslint:disable-next-line: max-line-length - const BobTx = api.tx.unique.setVariableMetaData(collectionId, itemId, BobData.toString()); - await Promise.all([ - AliceTx.signAndSend(Alice), - BobTx.signAndSend(Bob), - ]); - const item: any = await api.query.unique.nftItemList(collectionId, itemId); - expect(item.variableData).not.to.be.eq(null); // Pseudo-random selection of one of two values - await waitNewBlocks(2); - }); - }); -}); -*/ \ No newline at end of file --- a/tests/src/collision-tests/admVsOwnerTake.test.ts +++ /dev/null @@ -1,71 +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 . - -/* broken by design -// substrate transactions are sequential, not parallel -// the order of execution is indeterminate - -import { IKeyringPair } from '@polkadot/types/types'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import privateKey from '../substrate/privateKey'; -import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; -import { - createCollectionExpectSuccess, - createItemExpectSuccess, - normalizeAccountId, - waitNewBlocks, -} from '../util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -let Alice: IKeyringPair; -let Bob: IKeyringPair; -let Ferdie: IKeyringPair; - -before(async () => { - await usingApi(async () => { - Alice = privateKey('//Alice'); - Bob = privateKey('//Bob'); - Ferdie = privateKey('//Ferdie'); - }); -}); - -describe('Admin vs Owner take token: ', () => { - // tslint:disable-next-line: max-line-length - it('The collection admin burns the token and in the same block the token owner performs a transaction on it ', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); - await submitTransactionAsync(Alice, changeAdminTx); - const itemId = await createItemExpectSuccess(Bob, collectionId, 'NFT'); - // - const sendItem = api.tx.unique.transfer(normalizeAccountId(Ferdie.address), collectionId, itemId, 1); - const burnItem = api.tx.unique.burnItem(collectionId, itemId, 1); - await Promise.all([ - sendItem.signAndSend(Bob), - burnItem.signAndSend(Alice), - ]); - await waitNewBlocks(2); - let itemBurn = false; - itemBurn = (await (api.query.unique.nftItemList(collectionId, itemId))).toJSON() as boolean; - // tslint:disable-next-line: no-unused-expression - expect(itemBurn).to.be.null; - await waitNewBlocks(2); - }); - }); -}); -*/ \ No newline at end of file --- a/tests/src/collision-tests/adminDestroyCollection.test.ts +++ /dev/null @@ -1,70 +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 . - -/* broken by design -// substrate transactions are sequential, not parallel -// the order of execution is indeterminate - -import { IKeyringPair } from '@polkadot/types/types'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import privateKey from '../substrate/privateKey'; -import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; -import { - createCollectionExpectSuccess, - normalizeAccountId, - waitNewBlocks, -} from '../util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -let Alice: IKeyringPair; -let Bob: IKeyringPair; -let Ferdie: IKeyringPair; - -before(async () => { - await usingApi(async () => { - Alice = privateKey('//Alice'); - Bob = privateKey('//Bob'); - Ferdie = privateKey('//Ferdie'); - }); -}); - -describe('Deleting a collection while add address to allowlist: ', () => { - // tslint:disable-next-line: max-line-length - it('Adding an address to the collection allowlist in a block by the admin, and deleting the collection by the owner ', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); - await submitTransactionAsync(Alice, changeAdminTx); - await waitNewBlocks(1); - // - const addAllowlistAdm = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(Ferdie.address)); - const destroyCollection = api.tx.unique.destroyCollection(collectionId); - await Promise.all([ - addAllowlistAdm.signAndSend(Bob), - destroyCollection.signAndSend(Alice), - ]); - await waitNewBlocks(1); - let allowList = false; - allowList = (await api.query.unique.allowList(collectionId, Ferdie.address)).toJSON() as boolean; - // tslint:disable-next-line: no-unused-expression - expect(allowList).to.be.false; - await waitNewBlocks(2); - }); - }); -}); -*/ \ No newline at end of file --- a/tests/src/collision-tests/adminLimitsOff.test.ts +++ /dev/null @@ -1,86 +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 . - -/* broken by design -// substrate transactions are sequential, not parallel -// the order of execution is indeterminate - -import { IKeyringPair } from '@polkadot/types/types'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import privateKey from '../substrate/privateKey'; -import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from '../substrate/substrate-api'; -import { - createCollectionExpectSuccess, - normalizeAccountId, - waitNewBlocks, -} from '../util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -let Alice: IKeyringPair; -let Bob: IKeyringPair; -let Ferdie: IKeyringPair; -let Charlie: IKeyringPair; -let Eve: IKeyringPair; -let Dave: IKeyringPair; - -before(async () => { - await usingApi(async () => { - Alice = privateKey('//Alice'); - Bob = privateKey('//Bob'); - Ferdie = privateKey('//Ferdie'); - Charlie = privateKey('//Charlie'); - Eve = privateKey('//Eve'); - Dave = privateKey('//Dave'); - }); -}); - -describe('Admin limit exceeded collection: ', () => { - // tslint:disable-next-line: max-line-length - it('In one block, the owner and admin add new admins to the collection more than the limit ', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - - const chainAdminLimit = (api.consts.unique.collectionAdminsLimit as any).toNumber(); - expect(chainAdminLimit).to.be.equal(5); - - const changeAdminTx1 = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Eve.address)); - await submitTransactionAsync(Alice, changeAdminTx1); - const changeAdminTx2 = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Dave.address)); - await submitTransactionAsync(Alice, changeAdminTx2); - const changeAdminTx3 = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); - await submitTransactionAsync(Alice, changeAdminTx3); - - const addAdmOne = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Ferdie.address)); - const addAdmTwo = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Charlie.address)); - await Promise.all([ - addAdmOne.signAndSend(Bob), - addAdmTwo.signAndSend(Alice), - ]); - await waitNewBlocks(2); - const changeAdminTx4 = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Alice.address)); - await expect(submitTransactionExpectFailAsync(Alice, changeAdminTx4)).to.be.rejected; - - const adminListAfterAddAdmin: any = (await api.query.unique.adminList(collectionId)); - expect(adminListAfterAddAdmin).to.be.contains(normalizeAccountId(Eve.address)); - expect(adminListAfterAddAdmin).to.be.contains(normalizeAccountId(Ferdie.address)); - expect(adminListAfterAddAdmin).not.to.be.contains(normalizeAccountId(Alice.address)); - await waitNewBlocks(2); - }); - }); -}); -*/ \ No newline at end of file --- a/tests/src/collision-tests/adminRightsOff.test.ts +++ /dev/null @@ -1,69 +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 . - -/* broken by design -// substrate transactions are sequential, not parallel -// the order of execution is indeterminate - -import { IKeyringPair } from '@polkadot/types/types'; -import BN from 'bn.js'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import privateKey from '../substrate/privateKey'; -import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; -import { - createCollectionExpectSuccess, - normalizeAccountId, - waitNewBlocks, -} from '../util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -let Alice: IKeyringPair; -let Bob: IKeyringPair; - -before(async () => { - await usingApi(async () => { - Alice = privateKey('//Alice'); - Bob = privateKey('//Bob'); - }); -}); - -describe('Deprivation of admin rights: ', () => { - // tslint:disable-next-line: max-line-length - it('In the block, the collection admin adds a token or changes data, and the collection owner deprives the admin of rights ', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); - await submitTransactionAsync(Alice, changeAdminTx); - await waitNewBlocks(1); - const args = [{ nft: ['0x31', '0x31'] }, { nft: ['0x32', '0x32'] }, { nft: ['0x33', '0x33'] }]; - const addItemAdm = api.tx.unique.createMultipleItems(collectionId, normalizeAccountId(Bob.address), args); - const removeAdm = api.tx.unique.removeCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); - await Promise.all([ - addItemAdm.signAndSend(Bob), - removeAdm.signAndSend(Alice), - ]); - await waitNewBlocks(2); - const itemsListIndex = await api.query.unique.itemListIndex(collectionId) as unknown as BN; - expect(itemsListIndex.toNumber()).to.be.equal(0); - const adminList: any = (await api.query.unique.adminList(collectionId)); - expect(adminList).not.to.be.contains(normalizeAccountId(Bob.address)); - await waitNewBlocks(2); - }); - }); -}); -*/ \ No newline at end of file --- a/tests/src/collision-tests/setSponsorNewOwner.test.ts +++ /dev/null @@ -1,67 +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 . - -/* broken by design -// substrate transactions are sequential, not parallel -// the order of execution is indeterminate - -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 { - createCollectionExpectSuccess, - setCollectionSponsorExpectSuccess, - waitNewBlocks, -} from '../util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -let Alice: IKeyringPair; -let Bob: IKeyringPair; -let Ferdie: IKeyringPair; - -before(async () => { - await usingApi(async () => { - Alice = privateKey('//Alice'); - Bob = privateKey('//Bob'); - Ferdie = privateKey('//Ferdie'); - }); -}); - -describe('Sponsored with new owner ', () => { - // tslint:disable-next-line: max-line-length - it('Confirmation of sponsorship of a collection in a block with a change in the owner of the collection: ', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - await setCollectionSponsorExpectSuccess(collectionId, Bob.address); - await waitNewBlocks(2); - const confirmSponsorship = api.tx.unique.confirmSponsorship(collectionId); - const changeCollectionOwner = api.tx.unique.changeCollectionOwner(collectionId, Ferdie.address); - await Promise.all([ - confirmSponsorship.signAndSend(Bob), - changeCollectionOwner.signAndSend(Alice), - ]); - await waitNewBlocks(2); - const collection: any = (await api.query.unique.collectionById(collectionId)).toJSON(); - expect(collection.sponsorship.confirmed).to.be.eq(Bob.address); - expect(collection.owner).to.be.eq(Ferdie.address); - await waitNewBlocks(2); - }); - }); -}); -*/ \ No newline at end of file --- a/tests/src/collision-tests/sponsorPayments.test.ts +++ /dev/null @@ -1,76 +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 . - -/* broken by design -// substrate transactions are sequential, not parallel -// the order of execution is indeterminate - -import { IKeyringPair } from '@polkadot/types/types'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import { alicesPublicKey, bobsPublicKey } from '../accounts'; -import getBalance from '../substrate/get-balance'; -import privateKey from '../substrate/privateKey'; -import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; -import { - confirmSponsorshipExpectSuccess, - createCollectionExpectSuccess, - createItemExpectSuccess, - setCollectionSponsorExpectSuccess, - normalizeAccountId, - waitNewBlocks, -} from '../util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -let Alice: IKeyringPair; -let Bob: IKeyringPair; - -before(async () => { - await usingApi(async () => { - Alice = privateKey('//Alice'); - Bob = privateKey('//Bob'); - }); -}); - -describe('Payment of commission if one block: ', () => { - // tslint:disable-next-line: max-line-length - it('Payment of commission if one block contains transactions for payment from the sponsor\'s balance and his (sponsor\'s) exclusion from the collection ', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - const changeAdminTxBob = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(Bob.address)); - await submitTransactionAsync(Alice, changeAdminTxBob); - const itemId = await createItemExpectSuccess(Bob, collectionId, 'NFT'); - await setCollectionSponsorExpectSuccess(collectionId, Bob.address); - await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); - - const [alicesBalanceBefore, bobsBalanceBefore] = await getBalance(api, [alicesPublicKey, bobsPublicKey]); - const sendItem = api.tx.unique.transfer(normalizeAccountId(Alice.address), collectionId, itemId, 1); - const revokeSponsor = api.tx.unique.removeCollectionSponsor(collectionId); - await Promise.all([ - sendItem.signAndSend(Bob), - revokeSponsor.signAndSend(Alice), - ]); - const [alicesBalanceAfter, bobsBalanceAfter] = await getBalance(api, [alicesPublicKey, bobsPublicKey]); - // tslint:disable-next-line:no-unused-expression - expect(alicesBalanceAfter === alicesBalanceBefore).to.be.true; - // tslint:disable-next-line:no-unused-expression - expect(bobsBalanceAfter === bobsBalanceBefore).to.be.true; - await waitNewBlocks(2); - }); - }); -}); -*/ \ No newline at end of file --- a/tests/src/collision-tests/tokenLimitsOff.test.ts +++ /dev/null @@ -1,100 +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 . - -/* broken by design -// substrate transactions are sequential, not parallel -// the order of execution is indeterminate - -import { IKeyringPair } from '@polkadot/types/types'; -import BN from 'bn.js'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import privateKey from '../substrate/privateKey'; -import usingApi, { submitTransactionAsync } from '../substrate/substrate-api'; -import { - addToAllowListExpectSuccess, - createCollectionExpectSuccess, - getCreateItemResult, - setMintPermissionExpectSuccess, - normalizeAccountId, - waitNewBlocks, -} from '../util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -let Alice: IKeyringPair; -let Bob: IKeyringPair; -let Ferdie: IKeyringPair; - -const accountTokenOwnershipLimit = 4; -const sponsoredMintSize = 4294967295; -const tokenLimit = 4; -const sponsorTimeout = 14400; -const ownerCanTransfer = false; -const ownerCanDestroy = false; - -before(async () => { - await usingApi(async () => { - Alice = privateKey('//Alice'); - Bob = privateKey('//Bob'); - Ferdie = privateKey('//Ferdie'); - }); -}); - -describe('Token limit exceeded collection: ', () => { - // tslint:disable-next-line: max-line-length - it('The number of tokens created in the collection from different addresses exceeds the allowed collection limit ', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - await setMintPermissionExpectSuccess(Alice, collectionId, true); - await addToAllowListExpectSuccess(Alice, collectionId, Ferdie.address); - await addToAllowListExpectSuccess(Alice, collectionId, Bob.address); - const setCollectionLim = api.tx.unique.setCollectionLimits( - collectionId, - { - accountTokenOwnershipLimit, - sponsoredMintSize, - tokenLimit, - // tslint:disable-next-line: object-literal-sort-keys - sponsorTimeout, - ownerCanTransfer, - ownerCanDestroy, - }, - ); - const subTx = await submitTransactionAsync(Alice, setCollectionLim); - const subTxTesult = getCreateItemResult(subTx); - // tslint:disable-next-line:no-unused-expression - expect(subTxTesult.success).to.be.true; - await waitNewBlocks(2); - - const args = [{ nft: ['0x31', '0x31'] }, { nft: ['0x32', '0x32'] }, { nft: ['0x33', '0x33'] }]; - const mintItemOne = api.tx.unique - .createMultipleItems(collectionId, normalizeAccountId(Ferdie.address), args); - const mintItemTwo = api.tx.unique - .createMultipleItems(collectionId, normalizeAccountId(Bob.address), args); - await Promise.all([ - mintItemOne.signAndSend(Ferdie), - mintItemTwo.signAndSend(Bob), - ]); - await waitNewBlocks(2); - const itemsListIndexAfter = await api.query.unique.itemListIndex(collectionId) as unknown as BN; - expect(itemsListIndexAfter.toNumber()).to.be.equal(3); - // TokenLimit = 4. The first transaction is successful. The second should fail. - await waitNewBlocks(2); - }); - }); -}); -*/ --- a/tests/src/collision-tests/turnsOffMinting.test.ts +++ /dev/null @@ -1,68 +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 . - -/* broken by design -// substrate transactions are sequential, not parallel -// the order of execution is indeterminate - -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 { - addToAllowListExpectSuccess, - createCollectionExpectSuccess, - setMintPermissionExpectSuccess, - normalizeAccountId, - waitNewBlocks, -} from '../util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -let Alice: IKeyringPair; -let Ferdie: IKeyringPair; - -before(async () => { - await usingApi(async () => { - Alice = privateKey('//Alice'); - Ferdie = privateKey('//Ferdie'); - }); -}); - -describe('Turns off minting mode: ', () => { - // tslint:disable-next-line: max-line-length - it('The collection owner turns off minting mode and there are minting transactions in the same block ', async () => { - await usingApi(async (api) => { - const collectionId = await createCollectionExpectSuccess(); - await setMintPermissionExpectSuccess(Alice, collectionId, true); - await addToAllowListExpectSuccess(Alice, collectionId, Ferdie.address); - - const mintItem = api.tx.unique.createItem(collectionId, normalizeAccountId(Ferdie.address), 'NFT'); - const offMinting = api.tx.unique.setMintPermission(collectionId, false); - await Promise.all([ - mintItem.signAndSend(Ferdie), - offMinting.signAndSend(Alice), - ]); - let itemList = false; - itemList = (await (api.query.unique.nftItemList(collectionId, mintItem))).toJSON() as boolean; - // tslint:disable-next-line: no-unused-expression - expect(itemList).to.be.null; - await waitNewBlocks(2); - }); - }); -}); -*/ \ No newline at end of file --- a/tests/src/contracts.test.ts +++ /dev/null @@ -1,240 +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 chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import usingApi, {submitTransactionAsync} from './substrate/substrate-api'; -import fs from 'fs'; -import {Abi, ContractPromise as Contract} from '@polkadot/api-contract'; -import { - deployFlipper, - getFlipValue, - deployTransferContract, -} from './util/contracthelpers'; - -import { - addToAllowListExpectSuccess, - approveExpectSuccess, - createCollectionExpectSuccess, - createItemExpectSuccess, - enablePublicMintingExpectSuccess, - enableAllowListExpectSuccess, - getGenericResult, - normalizeAccountId, - isAllowlisted, - transferFromExpectSuccess, - getTokenOwner, -} from './util/helpers'; - - -chai.use(chaiAsPromised); -const expect = chai.expect; - -const value = 0; -const gasLimit = 9000n * 1000000n; -const marketContractAddress = '5CYN9j3YvRkqxewoxeSvRbhAym4465C57uMmX5j4yz99L5H6'; - -// todo:playgrounds skipped ~ postponed -describe.skip('Contracts', () => { - it('Can deploy smart contract Flipper, instantiate it and call it\'s get and flip messages.', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); - const initialGetResponse = await getFlipValue(contract, deployer); - - const bob = privateKeyWrapper('//Bob'); - const flip = contract.tx.flip({value, gasLimit}); - await submitTransactionAsync(bob, flip); - - const afterFlipGetResponse = await getFlipValue(contract, deployer); - expect(afterFlipGetResponse).not.to.be.eq(initialGetResponse, 'Flipping should change value.'); - }); - }); - - it('Can initialize contract instance', async () => { - await usingApi(async (api) => { - const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8')); - const abi = new Abi(metadata); - const newContractInstance = new Contract(api, abi, marketContractAddress); - expect(newContractInstance).to.have.property('address'); - expect(newContractInstance.address.toString()).to.equal(marketContractAddress); - }); - }); -}); - -describe.skip('Chain extensions', () => { - it('Transfer CE', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - - // Prep work - const collectionId = await createCollectionExpectSuccess(); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT'); - const [contract] = await deployTransferContract(api, privateKeyWrapper); - const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, contract.address); - await submitTransactionAsync(alice, changeAdminTx); - - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address)); - - // Transfer - const transferTx = contract.tx.transfer({value, gasLimit}, bob.address, collectionId, tokenId, 1); - const events = await submitTransactionAsync(alice, transferTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(bob.address)); - }); - }); - - it('Mint CE', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - - const collectionId = await createCollectionExpectSuccess(); - const [contract] = await deployTransferContract(api, privateKeyWrapper); - await enablePublicMintingExpectSuccess(alice, collectionId); - await enableAllowListExpectSuccess(alice, collectionId); - await addToAllowListExpectSuccess(alice, collectionId, contract.address); - await addToAllowListExpectSuccess(alice, collectionId, bob.address); - - const transferTx = contract.tx.createItem({value, gasLimit}, bob.address, collectionId, {Nft: {const_data: '0x010203'}}); - const events = await submitTransactionAsync(alice, transferTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - const tokensAfter = (await api.query.unique.nftItemList.entries(collectionId)).map((kv: any) => kv[1].toJSON()); - expect(tokensAfter).to.be.deep.equal([ - { - owner: bob.address, - constData: '0x010203', - }, - ]); - }); - }); - - it('Bulk mint CE', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - - const collectionId = await createCollectionExpectSuccess(); - const [contract] = await deployTransferContract(api, privateKeyWrapper); - await enablePublicMintingExpectSuccess(alice, collectionId); - await enableAllowListExpectSuccess(alice, collectionId); - await addToAllowListExpectSuccess(alice, collectionId, contract.address); - await addToAllowListExpectSuccess(alice, collectionId, bob.address); - - const transferTx = contract.tx.createMultipleItems({value, gasLimit}, bob.address, collectionId, [ - {NFT: {/*const_data: '0x010203'*/}}, - {NFT: {/*const_data: '0x010204'*/}}, - {NFT: {/*const_data: '0x010205'*/}}, - ]); - const events = await submitTransactionAsync(alice, transferTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - const tokensAfter: any = (await api.query.unique.nftItemList.entries(collectionId) as any) - .map((kv: any) => kv[1].toJSON()) - .sort((a: any, b: any) => a.constData.localeCompare(b.constData)); - expect(tokensAfter).to.be.deep.equal([ - { - Owner: bob.address, - //ConstData: '0x010203', - }, - { - Owner: bob.address, - //ConstData: '0x010204', - }, - { - Owner: bob.address, - //ConstData: '0x010205', - }, - ]); - }); - }); - - it('Approve CE', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - const charlie = privateKeyWrapper('//Charlie'); - - const collectionId = await createCollectionExpectSuccess(); - const [contract] = await deployTransferContract(api, privateKeyWrapper); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', contract.address.toString()); - - const transferTx = contract.tx.approve({value, gasLimit}, bob.address, collectionId, tokenId, 1); - const events = await submitTransactionAsync(alice, transferTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - await transferFromExpectSuccess(collectionId, tokenId, bob, normalizeAccountId(contract.address.toString()), charlie, 1, 'NFT'); - }); - }); - - it('TransferFrom CE', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - const charlie = privateKeyWrapper('//Charlie'); - - const collectionId = await createCollectionExpectSuccess(); - const [contract] = await deployTransferContract(api, privateKeyWrapper); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', bob.address); - await approveExpectSuccess(collectionId, tokenId, bob, contract.address.toString(), 1); - - const transferTx = contract.tx.transferFrom({value, gasLimit}, bob.address, charlie.address, collectionId, tokenId, 1); - const events = await submitTransactionAsync(alice, transferTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - const token: any = (await api.query.unique.nftItemList(collectionId, tokenId) as any).unwrap(); - expect(token.owner.toString()).to.be.equal(charlie.address); - }); - }); - - it('ToggleAllowList CE', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Alice'); - const bob = privateKeyWrapper('//Bob'); - - const collectionId = await createCollectionExpectSuccess(); - const [contract] = await deployTransferContract(api, privateKeyWrapper); - const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, contract.address); - await submitTransactionAsync(alice, changeAdminTx); - - expect(await isAllowlisted(api, collectionId, bob.address)).to.be.false; - - { - const transferTx = contract.tx.toggleAllowList({value, gasLimit}, collectionId, bob.address, true); - const events = await submitTransactionAsync(alice, transferTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - expect(await isAllowlisted(api, collectionId, bob.address)).to.be.true; - } - { - const transferTx = contract.tx.toggleAllowList({value, gasLimit}, collectionId, bob.address, false); - const events = await submitTransactionAsync(alice, transferTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - expect(await isAllowlisted(api, collectionId, bob.address)).to.be.false; - } - }); - }); -}); --- a/tests/src/createMultipleItemsEx.test.ts +++ b/tests/src/createMultipleItemsEx.test.ts @@ -178,13 +178,12 @@ tokenPrefix: 'COL', }, 0); - const api = helper.api; - await helper.signTransaction(alice, api?.tx.unique.createMultipleItemsEx(collection.collectionId, { + await helper.executeExtrinsic(alice, 'api.tx.unique.createMultipleItemsEx',[collection.collectionId, { Fungible: new Map([ [JSON.stringify({Substrate: alice.address}), 50], [JSON.stringify({Substrate: bob.address}), 100], ]), - })); + }], true); expect(await collection.getBalance({Substrate: alice.address})).to.be.equal(50n); expect(await collection.getBalance({Substrate: bob.address})).to.be.equal(100n); @@ -200,8 +199,7 @@ ], }); - const api = helper.api; - await helper.signTransaction(alice, api?.tx.unique.createMultipleItemsEx(collection.collectionId, { + await helper.executeExtrinsic(alice, 'api.tx.unique.createMultipleItemsEx', [collection.collectionId, { RefungibleMultipleOwners: { users: new Map([ [JSON.stringify({Substrate: alice.address}), 1], @@ -211,7 +209,7 @@ {key: 'k', value: 'v'}, ], }, - })); + }], true); const tokenId = await collection.getLastTokenId(); expect(tokenId).to.be.equal(1); expect(await collection.getTokenBalance(1, {Substrate: alice.address})).to.be.equal(1n); @@ -228,9 +226,7 @@ ], }); - const api = helper.api; - - await helper.signTransaction(alice, api?.tx.unique.createMultipleItemsEx(collection.collectionId, { + await helper.executeExtrinsic(alice, 'api.tx.unique.createMultipleItemsEx', [collection.collectionId, { RefungibleMultipleItems: [ { user: {Substrate: alice.address}, pieces: 1, @@ -245,7 +241,7 @@ ], }, ], - })); + }], true); expect(await collection.getLastTokenId()).to.be.equal(2); expect(await collection.getTokenBalance(1, {Substrate: alice.address})).to.be.equal(1n); --- a/tests/src/creditFeesToTreasury.test.ts +++ b/tests/src/creditFeesToTreasury.test.ts @@ -29,7 +29,7 @@ /*eslint no-async-promise-executor: "off"*/ function skipInflationBlock(api: ApiPromise): Promise { const promise = new Promise(async (resolve) => { - const blockInterval = (await api.consts.inflation.inflationBlockInterval).toNumber(); + const blockInterval = api.consts.inflation.inflationBlockInterval.toNumber(); const unsubscribe = await api.rpc.chain.subscribeNewHeads(head => { const currentBlock = head.number.toNumber(); if (currentBlock % blockInterval < blockInterval - 10) { @@ -56,21 +56,21 @@ }); itSub('Total issuance does not change', async ({helper}) => { - const api = helper.api!; + const api = helper.getApi(); await skipInflationBlock(api); await helper.wait.newBlocks(1); - const totalBefore = (await api.query.balances.totalIssuance()).toBigInt(); + const totalBefore = (await helper.callRpc('api.query.balances.totalIssuance', [])).toBigInt(); await helper.balance.transferToSubstrate(alice, bob.address, 1n); - const totalAfter = (await api.query.balances.totalIssuance()).toBigInt(); + const totalAfter = (await helper.callRpc('api.query.balances.totalIssuance', [])).toBigInt(); expect(totalAfter).to.be.equal(totalBefore); }); itSub('Sender balance decreased by fee+sent amount, Treasury balance increased by fee', async ({helper}) => { - await skipInflationBlock(helper.api!); + await skipInflationBlock(helper.getApi()); await helper.wait.newBlocks(1); const treasuryBalanceBefore = await helper.balance.getSubstrate(TREASURY); @@ -89,7 +89,7 @@ }); itSub('Treasury balance increased by failed tx fee', async ({helper}) => { - const api = helper.api!; + const api = helper.getApi(); await helper.wait.newBlocks(1); const treasuryBalanceBefore = await helper.balance.getSubstrate(TREASURY); @@ -107,7 +107,7 @@ }); itSub('NFT Transactions also send fees to Treasury', async ({helper}) => { - await skipInflationBlock(helper.api!); + await skipInflationBlock(helper.getApi()); await helper.wait.newBlocks(1); const treasuryBalanceBefore = await helper.balance.getSubstrate(TREASURY); @@ -125,7 +125,7 @@ itSub('Fees are sane', async ({helper}) => { const unique = helper.balance.getOneTokenNominal(); - await skipInflationBlock(helper.api!); + await skipInflationBlock(helper.getApi()); await helper.wait.newBlocks(1); const aliceBalanceBefore = await helper.balance.getSubstrate(alice.address); @@ -140,7 +140,7 @@ }); itSub('NFT Transfer fee is close to 0.1 Unique', async ({helper}) => { - await skipInflationBlock(helper.api!); + await skipInflationBlock(helper.getApi()); await helper.wait.newBlocks(1); const collection = await helper.nft.mintCollection(alice, { --- /dev/null +++ b/tests/src/deprecated-helpers/contracthelpers.ts @@ -0,0 +1,114 @@ +// 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 chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import {submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api'; +import fs from 'fs'; +import {Abi, CodePromise, ContractPromise as Contract} from '@polkadot/api-contract'; +import {IKeyringPair} from '@polkadot/types/types'; +import {ApiPromise} from '@polkadot/api'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +import {findUnusedAddress, getGenericResult} from './helpers'; + +const value = 0; +const gasLimit = '200000000000'; +const endowment = '100000000000000000'; + +/* eslint no-async-promise-executor: "off" */ +function deployContract(alice: IKeyringPair, code: CodePromise, constructor = 'default', ...args: any[]): Promise { + return new Promise(async (resolve) => { + const unsub = await (code as any) + .tx[constructor]({value: endowment, gasLimit}, ...args) + .signAndSend(alice, (result: any) => { + if (result.status.isInBlock || result.status.isFinalized) { + // here we have an additional field in the result, containing the blueprint + resolve((result as any).contract); + unsub(); + } + }); + }); +} + +async function prepareDeployer(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair) { + // Find unused address + const deployer = await findUnusedAddress(api, privateKeyWrapper); + + // Transfer balance to it + const alice = privateKeyWrapper('//Alice'); + const amount = BigInt(endowment) + 10n**15n; + const tx = api.tx.balances.transfer(deployer.address, amount); + await submitTransactionAsync(alice, tx); + + return deployer; +} + +export async function deployFlipper(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair): Promise<[Contract, IKeyringPair]> { + const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8')); + const abi = new Abi(metadata); + + const deployer = await prepareDeployer(api, privateKeyWrapper); + + const wasm = fs.readFileSync('./src/flipper/flipper.wasm'); + + const code = new CodePromise(api, abi, wasm); + + const contract = (await deployContract(deployer, code, 'new', true)) as Contract; + + const initialGetResponse = await getFlipValue(contract, deployer); + expect(initialGetResponse).to.be.true; + + return [contract, deployer]; +} + +export async function getFlipValue(contract: Contract, deployer: IKeyringPair) { + const result = await contract.query.get(deployer.address, {value, gasLimit}); + + if(!result.result.isOk) { + throw 'Failed to get flipper value'; + } + return (result.result.asOk.data[0] == 0x00) ? false : true; +} + +export async function toggleFlipValueExpectSuccess(sender: IKeyringPair, contract: Contract) { + const tx = contract.tx.flip({value, gasLimit}); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + expect(result.success).to.be.true; +} + +export async function toggleFlipValueExpectFailure(sender: IKeyringPair, contract: Contract) { + const tx = contract.tx.flip({value, gasLimit}); + await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; +} + +export async function deployTransferContract(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair): Promise<[Contract, IKeyringPair]> { + const metadata = JSON.parse(fs.readFileSync('./src/transfer_contract/metadata.json').toString('utf-8')); + const abi = new Abi(metadata); + + const deployer = await prepareDeployer(api, privateKeyWrapper); + + const wasm = fs.readFileSync('./src/transfer_contract/nft_transfer.wasm'); + + const code = new CodePromise(api, abi, wasm); + + const contract = await deployContract(deployer, code); + + return [contract, deployer]; +} --- /dev/null +++ b/tests/src/deprecated-helpers/eth/helpers.d.ts @@ -0,0 +1,17 @@ +// 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 . + +declare module 'solc'; \ No newline at end of file --- /dev/null +++ b/tests/src/deprecated-helpers/eth/helpers.ts @@ -0,0 +1,451 @@ +// 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 . + +// eslint-disable-next-line @typescript-eslint/triple-slash-reference +/// + +import {ApiPromise} from '@polkadot/api'; +import {IKeyringPair} from '@polkadot/types/types'; +import {addressToEvm, evmToAddress} from '@polkadot/util-crypto'; +import {expect} from 'chai'; +import * as solc from 'solc'; +import Web3 from 'web3'; +import config from '../../config'; +import getBalance from '../../substrate/get-balance'; +import usingApi, {submitTransactionAsync} from '../../substrate/substrate-api'; +import waitNewBlocks from '../../substrate/wait-new-blocks'; +import {CollectionMode, CrossAccountId, getDetailedCollectionInfo, getGenericResult, UNIQUE} from '../helpers'; +import collectionHelpersAbi from '../../eth/collectionHelpersAbi.json'; +import fungibleAbi from '../../eth/fungibleAbi.json'; +import nonFungibleAbi from '../../eth/nonFungibleAbi.json'; +import refungibleAbi from '../../eth/reFungibleAbi.json'; +import refungibleTokenAbi from '../../eth/reFungibleTokenAbi.json'; +import contractHelpersAbi from '../../eth/util/contractHelpersAbi.json'; + +export const GAS_ARGS = {gas: 2500000}; + +export enum SponsoringMode { + Disabled = 0, + Allowlisted = 1, + Generous = 2, +} + +let web3Connected = false; +export async function usingWeb3(cb: (web3: Web3) => Promise | T): Promise { + if (web3Connected) throw new Error('do not nest usingWeb3 calls'); + web3Connected = true; + + const provider = new Web3.providers.WebsocketProvider(config.substrateUrl); + const web3 = new Web3(provider); + + try { + return await cb(web3); + } finally { + // provider.disconnect(3000, 'normal disconnect'); + provider.connection.close(); + web3Connected = false; + } +} + +function encodeIntBE(v: number): number[] { + if (v >= 0xffffffff || v < 0) throw new Error('id overflow'); + return [ + v >> 24, + (v >> 16) & 0xff, + (v >> 8) & 0xff, + v & 0xff, + ]; +} + +export async function getCollectionAddressFromResult(api: ApiPromise, result: any) { + const collectionIdAddress = normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); + const collectionId = collectionIdFromAddress(collectionIdAddress); + const collection = (await getDetailedCollectionInfo(api, collectionId))!; + return {collectionIdAddress, collectionId, collection}; +} + +export function collectionIdToAddress(collection: number): string { + const buf = Buffer.from([0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e, + ...encodeIntBE(collection), + ]); + return Web3.utils.toChecksumAddress('0x' + buf.toString('hex')); +} +export function collectionIdFromAddress(address: string): number { + if (!address.startsWith('0x')) + throw 'address not starts with "0x"'; + if (address.length > 42) + throw 'address length is more than 20 bytes'; + return Number('0x' + address.substring(address.length - 8)); +} + +export function normalizeAddress(address: string): string { + return '0x' + address.substring(address.length - 40); +} + +export function tokenIdToAddress(collection: number, token: number): string { + const buf = Buffer.from([0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0, + ...encodeIntBE(collection), + ...encodeIntBE(token), + ]); + return Web3.utils.toChecksumAddress('0x' + buf.toString('hex')); +} + +export function tokenIdFromAddress(address: string) { + if (!address.startsWith('0x')) + throw 'address not starts with "0x"'; + if (address.length > 42) + throw 'address length is more than 20 bytes'; + return { + collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)), + tokenId: Number('0x' + address.substring(address.length - 8)), + }; +} + +export function tokenIdToCross(collection: number, token: number): CrossAccountId { + return { + Ethereum: tokenIdToAddress(collection, token), + }; +} + +export function createEthAccount(web3: Web3) { + const account = web3.eth.accounts.create(); + web3.eth.accounts.wallet.add(account.privateKey); + return account.address; +} + +export async function createEthAccountWithBalance(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair) { + const alice = privateKeyWrapper('//Alice'); + const account = createEthAccount(web3); + await transferBalanceToEth(api, alice, account); + + return account; +} + +export async function transferBalanceToEth(api: ApiPromise, source: IKeyringPair, target: string, amount = 1000n * UNIQUE) { + const tx = api.tx.balances.transfer(evmToAddress(target), amount); + const events = await submitTransactionAsync(source, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; +} + +export async function createRFTCollection(api: ApiPromise, web3: Web3, owner: string) { + const collectionHelper = evmCollectionHelpers(web3, owner); + const result = await collectionHelper.methods + .createRFTCollection('A', 'B', 'C') + .send({value: Number(2n * UNIQUE)}); + return await getCollectionAddressFromResult(api, result); +} + + +export async function createNonfungibleCollection(api: ApiPromise, web3: Web3, owner: string) { + const collectionHelper = evmCollectionHelpers(web3, owner); + const result = await collectionHelper.methods + .createNonfungibleCollection('A', 'B', 'C') + .send({value: Number(2n * UNIQUE)}); + return await getCollectionAddressFromResult(api, result); +} + +export function uniqueNFT(web3: Web3, address: string, owner: string) { + return new web3.eth.Contract(nonFungibleAbi as any, address, { + from: owner, + ...GAS_ARGS, + }); +} + +export function uniqueRefungible(web3: Web3, collectionAddress: string, owner: string) { + return new web3.eth.Contract(refungibleAbi as any, collectionAddress, { + from: owner, + ...GAS_ARGS, + }); +} + +export function uniqueRefungibleToken(web3: Web3, tokenAddress: string, owner: string | undefined = undefined) { + return new web3.eth.Contract(refungibleTokenAbi as any, tokenAddress, { + from: owner, + ...GAS_ARGS, + }); +} + +export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) { + let i: any = it; + if (opts.only) i = i.only; + else if (opts.skip) i = i.skip; + i(name, async () => { + await usingApi(async (api, privateKeyWrapper) => { + await usingWeb3(async web3 => { + await cb({api, web3, privateKeyWrapper}); + }); + }); + }); +} +itWeb3.only = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {only: true}); +itWeb3.skip = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {skip: true}); + +export async function generateSubstrateEthPair(web3: Web3) { + const account = web3.eth.accounts.create(); + evmToAddress(account.address); +} + +type NormalizedEvent = { + address: string, + event: string, + args: { [key: string]: string } +}; + +export function normalizeEvents(events: any): NormalizedEvent[] { + const output = []; + for (const key of Object.keys(events)) { + if (key.match(/^[0-9]+$/)) { + output.push(events[key]); + } else if (Array.isArray(events[key])) { + output.push(...events[key]); + } else { + output.push(events[key]); + } + } + output.sort((a, b) => a.logIndex - b.logIndex); + return output.map(({address, event, returnValues}) => { + const args: { [key: string]: string } = {}; + for (const key of Object.keys(returnValues)) { + if (!key.match(/^[0-9]+$/)) { + args[key] = returnValues[key]; + } + } + return { + address, + event, + args, + }; + }); +} + +export async function recordEvents(contract: any, action: () => Promise): Promise { + const out: any = []; + contract.events.allEvents((_: any, event: any) => { + out.push(event); + }); + await action(); + return normalizeEvents(out); +} + +export function subToEthLowercase(eth: string): string { + const bytes = addressToEvm(eth); + return '0x' + Buffer.from(bytes).toString('hex'); +} + +export function subToEth(eth: string): string { + return Web3.utils.toChecksumAddress(subToEthLowercase(eth)); +} + +export interface CompiledContract { + abi: any, + object: string, +} + +export function compileContract(name: string, src: string) : CompiledContract { + const out = JSON.parse(solc.compile(JSON.stringify({ + language: 'Solidity', + sources: { + [`${name}.sol`]: { + content: ` + // SPDX-License-Identifier: UNLICENSED + pragma solidity ^0.8.6; + + ${src} + `, + }, + }, + settings: { + outputSelection: { + '*': { + '*': ['*'], + }, + }, + }, + }))).contracts[`${name}.sol`][name]; + + return { + abi: out.abi, + object: '0x' + out.evm.bytecode.object, + }; +} + +export async function deployFlipper(web3: Web3, deployer: string) { + const compiled = compileContract('Flipper', ` + contract Flipper { + bool value = false; + function flip() public { + value = !value; + } + function getValue() public view returns (bool) { + return value; + } + } + `); + const flipperContract = new web3.eth.Contract(compiled.abi, undefined, { + data: compiled.object, + from: deployer, + ...GAS_ARGS, + }); + const flipper = await flipperContract.deploy({data: compiled.object}).send({from: deployer}); + + return flipper; +} + +export async function deployCollector(web3: Web3, deployer: string) { + const compiled = compileContract('Collector', ` + contract Collector { + uint256 collected; + fallback() external payable { + giveMoney(); + } + function giveMoney() public payable { + collected += msg.value; + } + function getCollected() public view returns (uint256) { + return collected; + } + function getUnaccounted() public view returns (uint256) { + return address(this).balance - collected; + } + + function withdraw(address payable target) public { + target.transfer(collected); + collected = 0; + } + } + `); + const collectorContract = new web3.eth.Contract(compiled.abi, undefined, { + data: compiled.object, + from: deployer, + ...GAS_ARGS, + }); + const collector = await collectorContract.deploy({data: compiled.object}).send({from: deployer}); + + return collector; +} + +/** + * pallet evm_contract_helpers + * @param web3 + * @param caller - eth address + * @returns + */ +export function contractHelpers(web3: Web3, caller: string) { + return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS}); +} + +/** + * evm collection helper + * @param web3 + * @param caller - eth address + * @returns + */ +export function evmCollectionHelpers(web3: Web3, caller: string) { + return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, ...GAS_ARGS}); +} + +/** + * evm collection + * @param web3 + * @param caller - eth address + * @returns + */ +export function evmCollection(web3: Web3, caller: string, collection: string, mode: CollectionMode = {type: 'NFT'}) { + let abi; + switch (mode.type) { + case 'Fungible': + abi = fungibleAbi; + break; + + case 'NFT': + abi = nonFungibleAbi; + break; + + case 'ReFungible': + abi = refungibleAbi; + break; + + default: + throw 'Bad collection mode'; + } + const contract = new web3.eth.Contract(abi as any, collection, {from: caller, ...GAS_ARGS}); + return contract; +} + +/** + * Execute ethereum method call using substrate account + * @param to target contract + * @param mkTx - closure, receiving `contract.methods`, and returning method call, + * to be used as following (assuming `to` = erc20 contract): + * `m => m.transfer(to, amount)` + * + * # Example + * ```ts + * executeEthTxOnSub(api, alice, erc20Contract, m => m.transfer(target, amount)); + * ``` + */ +export async function executeEthTxOnSub(web3: Web3, api: ApiPromise, from: IKeyringPair, to: any, mkTx: (methods: any) => any, {value = 0}: {value?: bigint | number} = { }) { + const tx = api.tx.evm.call( + subToEth(from.address), + to.options.address, + mkTx(to.methods).encodeABI(), + value, + GAS_ARGS.gas, + await web3.eth.getGasPrice(), + null, + null, + [], + ); + const events = await submitTransactionAsync(from, tx); + expect(events.some(({event: {section, method}}) => section == 'evm' && method == 'Executed')).to.be.true; +} + +export async function ethBalanceViaSub(api: ApiPromise, address: string): Promise { + return (await getBalance(api, [evmToAddress(address)]))[0]; +} + +/** + * Measure how much gas given closure consumes + * + * @param user which user balance will be checked + */ +export async function recordEthFee(api: ApiPromise, user: string, call: () => Promise): Promise { + const before = await ethBalanceViaSub(api, user); + + await call(); + + // In dev mode, the transaction might not finish processing in time + await waitNewBlocks(api, 1); + const after = await ethBalanceViaSub(api, user); + + // Can't use .to.be.less, because chai doesn't supports bigint + expect(after < before).to.be.true; + + return before - after; +} + +type ElementOf = A extends readonly (infer T)[] ? T : never; +// I want a fancier api, not a memory efficiency +export function* cartesian>, R extends Array>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf}]> { + if(args.length === 0) { + yield internalRest as any; + return; + } + for(const value of args[0]) { + yield* cartesian([...internalRest, value], ...args.slice(1)) as any; + } +} \ No newline at end of file --- /dev/null +++ b/tests/src/deprecated-helpers/helpers.ts @@ -0,0 +1,1879 @@ +// 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 '../interfaces/augment-api-rpc'; +import '../interfaces/augment-api-query'; +import {ApiPromise, Keyring} from '@polkadot/api'; +import type {AccountId, EventRecord, Event, BlockNumber} from '@polkadot/types/interfaces'; +import type {GenericEventData} from '@polkadot/types'; +import {AnyTuple, IEvent, IKeyringPair} from '@polkadot/types/types'; +import {evmToAddress} from '@polkadot/util-crypto'; +import {AnyNumber} from '@polkadot/types-codec/types'; +import BN from 'bn.js'; +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import {default as usingApi, executeTransaction, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api'; +import {hexToStr, strToUTF16, utf16ToStr} from './util'; +import {UpDataStructsRpcCollection, UpDataStructsCreateItemData, UpDataStructsProperty} from '@polkadot/types/lookup'; +import {UpDataStructsTokenChild} from '../interfaces'; +import {Context} from 'mocha'; + +chai.use(chaiAsPromised); +const expect = chai.expect; + +export type CrossAccountId = { + Substrate: string, +} | { + Ethereum: string, +}; + + +export enum Pallets { + Inflation = 'inflation', + RmrkCore = 'rmrkcore', + RmrkEquip = 'rmrkequip', + ReFungible = 'refungible', + Fungible = 'fungible', + NFT = 'nonfungible', + Scheduler = 'scheduler', + AppPromotion = 'apppromotion', +} + +export async function isUnique(): Promise { + return usingApi(async api => { + const chain = await api.rpc.system.chain(); + + return chain.eq('UNIQUE'); + }); +} + +export async function isQuartz(): Promise { + return usingApi(async api => { + const chain = await api.rpc.system.chain(); + + return chain.eq('QUARTZ'); + }); +} + +let modulesNames: any; +export function getModuleNames(api: ApiPromise): string[] { + if (typeof modulesNames === 'undefined') + modulesNames = api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()); + return modulesNames; +} + +export async function missingRequiredPallets(requiredPallets: string[]): Promise { + return await usingApi(async api => { + const pallets = getModuleNames(api); + + return requiredPallets.filter(p => !pallets.includes(p)); + }); +} + +export async function checkPalletsPresence(requiredPallets: string[]): Promise { + return (await missingRequiredPallets(requiredPallets)).length == 0; +} + +export async function requirePallets(mocha: Context, requiredPallets: string[]) { + const missingPallets = await missingRequiredPallets(requiredPallets); + + if (missingPallets.length > 0) { + const skippingTestMsg = `\tSkipping test "${mocha.test?.title}".`; + const missingPalletsMsg = `\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`; + const skipMsg = `${skippingTestMsg}\n${missingPalletsMsg}`; + + console.error('\x1b[38:5:208m%s\x1b[0m', skipMsg); + + mocha.skip(); + } +} + +export function bigIntToSub(api: ApiPromise, number: bigint) { + return api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON(); +} + +export function bigIntToDecimals(number: bigint, decimals = 18): string { + const numberStr = number.toString(); + const dotPos = numberStr.length - decimals; + + if (dotPos <= 0) { + return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr; + } else { + const intPart = numberStr.substring(0, dotPos); + const fractPart = numberStr.substring(dotPos); + return intPart + '.' + fractPart; + } +} + +export function normalizeAccountId(input: string | AccountId | CrossAccountId | IKeyringPair): CrossAccountId { + if (typeof input === 'string') { + if (input.length >= 47) { + return {Substrate: input}; + } else if (input.length === 42 && input.startsWith('0x')) { + return {Ethereum: input.toLowerCase()}; + } else if (input.length === 40 && !input.startsWith('0x')) { + return {Ethereum: '0x' + input.toLowerCase()}; + } else { + throw new Error(`Unknown address format: "${input}"`); + } + } + if ('address' in input) { + return {Substrate: input.address}; + } + if ('Ethereum' in input) { + return { + Ethereum: input.Ethereum.toLowerCase(), + }; + } else if ('ethereum' in input) { + return { + Ethereum: (input as any).ethereum.toLowerCase(), + }; + } else if ('Substrate' in input) { + return input; + } else if ('substrate' in input) { + return { + Substrate: (input as any).substrate, + }; + } + + // AccountId + return {Substrate: input.toString()}; +} +export function toSubstrateAddress(input: string | CrossAccountId | IKeyringPair): string { + input = normalizeAccountId(input); + if ('Substrate' in input) { + return input.Substrate; + } else { + return evmToAddress(input.Ethereum); + } +} + +export const U128_MAX = (1n << 128n) - 1n; + +const MICROUNIQUE = 1_000_000_000_000n; +const MILLIUNIQUE = 1_000n * MICROUNIQUE; +const CENTIUNIQUE = 10n * MILLIUNIQUE; +export const UNIQUE = 100n * CENTIUNIQUE; + +interface GenericResult { + success: boolean; + data: T | null; +} + +interface CreateCollectionResult { + success: boolean; + collectionId: number; +} + +interface CreateItemResult { + success: boolean; + collectionId: number; + itemId: number; + recipient?: CrossAccountId; + amount?: number; +} + +interface DestroyItemResult { + success: boolean; + collectionId: number; + itemId: number; + owner: CrossAccountId; + amount: number; +} + +interface TransferResult { + collectionId: number; + itemId: number; + sender?: CrossAccountId; + recipient?: CrossAccountId; + value: bigint; +} + +interface IReFungibleOwner { + fraction: BN; + owner: number[]; +} + +interface IGetMessage { + checkMsgUnqMethod: string; + checkMsgTrsMethod: string; + checkMsgSysMethod: string; +} + +export interface IFungibleTokenDataType { + value: number; +} + +export interface IChainLimits { + collectionNumbersLimit: number; + accountTokenOwnershipLimit: number; + collectionsAdminsLimit: number; + customDataLimit: number; + nftSponsorTransferTimeout: number; + fungibleSponsorTransferTimeout: number; + refungibleSponsorTransferTimeout: number; + //offchainSchemaLimit: number; + //constOnChainSchemaLimit: number; +} + +export interface IReFungibleTokenDataType { + owner: IReFungibleOwner[]; +} + +export function uniqueEventMessage(events: EventRecord[]): IGetMessage { + let checkMsgUnqMethod = ''; + let checkMsgTrsMethod = ''; + let checkMsgSysMethod = ''; + events.forEach(({event: {method, section}}) => { + if (section === 'common') { + checkMsgUnqMethod = method; + } else if (section === 'treasury') { + checkMsgTrsMethod = method; + } else if (section === 'system') { + checkMsgSysMethod = method; + } else { return null; } + }); + const result: IGetMessage = { + checkMsgUnqMethod, + checkMsgTrsMethod, + checkMsgSysMethod, + }; + return result; +} + +export function getEvent(events: EventRecord[], check: (event: IEvent) => event is T): T | undefined { + const event = events.find(r => check(r.event)); + if (!event) return; + return event.event as T; +} + +export function getGenericResult(events: EventRecord[]): GenericResult; +export function getGenericResult( + events: EventRecord[], + expectSection: string, + expectMethod: string, + extractAction: (data: GenericEventData) => T +): GenericResult; + +export function getGenericResult( + events: EventRecord[], + expectSection?: string, + expectMethod?: string, + extractAction?: (data: GenericEventData) => T, +): GenericResult { + let success = false; + let successData = null; + + events.forEach(({event: {data, method, section}}) => { + // console.log(` ${phase}: ${section}.${method}:: ${data}`); + if (method === 'ExtrinsicSuccess') { + success = true; + } else if ((expectSection == section) && (expectMethod == method)) { + successData = extractAction!(data as any); + } + }); + + const result: GenericResult = { + success, + data: successData, + }; + return result; +} + +export function getCreateCollectionResult(events: EventRecord[]): CreateCollectionResult { + const genericResult = getGenericResult(events, 'common', 'CollectionCreated', (data) => parseInt(data[0].toString(), 10)); + const result: CreateCollectionResult = { + success: genericResult.success, + collectionId: genericResult.data ?? 0, + }; + return result; +} + +export function getCreateItemsResult(events: EventRecord[]): CreateItemResult[] { + const results: CreateItemResult[] = []; + + const genericResult = getGenericResult(events, 'common', 'ItemCreated', (data) => { + const collectionId = parseInt(data[0].toString(), 10); + const itemId = parseInt(data[1].toString(), 10); + const recipient = normalizeAccountId(data[2].toJSON() as any); + const amount = parseInt(data[3].toString(), 10); + + const itemRes: CreateItemResult = { + success: true, + collectionId, + itemId, + recipient, + amount, + }; + + results.push(itemRes); + return results; + }); + + if (!genericResult.success) return []; + return results; +} + +export function getCreateItemResult(events: EventRecord[]): CreateItemResult { + const genericResult = getGenericResult(events, 'common', 'ItemCreated', (data) => data.map(function(value) { return value.toJSON(); })); + + if (genericResult.data == null) + return { + success: genericResult.success, + collectionId: 0, + itemId: 0, + amount: 0, + }; + else + return { + success: genericResult.success, + collectionId: genericResult.data[0] as number, + itemId: genericResult.data[1] as number, + recipient: normalizeAccountId(genericResult.data![2] as any), + amount: genericResult.data[3] as number, + }; +} + +export function getDestroyItemsResult(events: EventRecord[]): DestroyItemResult[] { + const results: DestroyItemResult[] = []; + + const genericResult = getGenericResult(events, 'common', 'ItemDestroyed', (data) => { + const collectionId = parseInt(data[0].toString(), 10); + const itemId = parseInt(data[1].toString(), 10); + const owner = normalizeAccountId(data[2].toJSON() as any); + const amount = parseInt(data[3].toString(), 10); + + const itemRes: DestroyItemResult = { + success: true, + collectionId, + itemId, + owner, + amount, + }; + + results.push(itemRes); + return results; + }); + + if (!genericResult.success) return []; + return results; +} + +export function getTransferResult(api: ApiPromise, events: EventRecord[]): TransferResult { + for (const {event} of events) { + if (api.events.common.Transfer.is(event)) { + const [collection, token, sender, recipient, value] = event.data; + return { + collectionId: collection.toNumber(), + itemId: token.toNumber(), + sender: normalizeAccountId(sender.toJSON() as any), + recipient: normalizeAccountId(recipient.toJSON() as any), + value: value.toBigInt(), + }; + } + } + throw new Error('no transfer event'); +} + +interface Nft { + type: 'NFT'; +} + +interface Fungible { + type: 'Fungible'; + decimalPoints: number; +} + +interface ReFungible { + type: 'ReFungible'; +} + +export type CollectionMode = Nft | Fungible | ReFungible; + +export type Property = { + key: any, + value: any, +}; + +type Permission = { + mutable: boolean; + collectionAdmin: boolean; + tokenOwner: boolean; +} + +type PropertyPermission = { + key: any; + permission: Permission; +} + +export type CreateCollectionParams = { + mode: CollectionMode, + name: string, + description: string, + tokenPrefix: string, + properties?: Array, + propPerm?: Array +}; + +const defaultCreateCollectionParams: CreateCollectionParams = { + description: 'description', + mode: {type: 'NFT'}, + name: 'name', + tokenPrefix: 'prefix', +}; + +export async function +createCollection( + api: ApiPromise, + sender: IKeyringPair, + params: Partial = {}, +): Promise { + const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; + + let modeprm = {}; + if (mode.type === 'NFT') { + modeprm = {nft: null}; + } else if (mode.type === 'Fungible') { + modeprm = {fungible: mode.decimalPoints}; + } else if (mode.type === 'ReFungible') { + modeprm = {refungible: null}; + } + + const tx = api.tx.unique.createCollectionEx({ + name: strToUTF16(name), + description: strToUTF16(description), + tokenPrefix: strToUTF16(tokenPrefix), + mode: modeprm as any, + }); + const events = await executeTransaction(api, sender, tx); + return getCreateCollectionResult(events); +} + +export async function createCollectionExpectSuccess(params: Partial = {}): Promise { + const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; + + let collectionId = 0; + await usingApi(async (api, privateKeyWrapper) => { + // Get number of collections before the transaction + const collectionCountBefore = await getCreatedCollectionCount(api); + + // Run the CreateCollection transaction + const alicePrivateKey = privateKeyWrapper('//Alice'); + + const result = await createCollection(api, alicePrivateKey, params); + + // Get number of collections after the transaction + const collectionCountAfter = await getCreatedCollectionCount(api); + + // Get the collection + const collection = await queryCollectionExpectSuccess(api, result.collectionId); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.true; + expect(result.collectionId).to.be.equal(collectionCountAfter); + // tslint:disable-next-line:no-unused-expression + expect(collection).to.be.not.null; + expect(collectionCountAfter).to.be.equal(collectionCountBefore + 1, 'Error: NFT collection NOT created.'); + expect(collection.owner.toString()).to.be.equal(toSubstrateAddress(alicePrivateKey)); + expect(utf16ToStr(collection.name.toJSON() as any)).to.be.equal(name); + expect(utf16ToStr(collection.description.toJSON() as any)).to.be.equal(description); + expect(hexToStr(collection.tokenPrefix.toJSON())).to.be.equal(tokenPrefix); + + collectionId = result.collectionId; + }); + + return collectionId; +} + +export async function createCollectionWithPropsExpectSuccess(params: Partial = {}): Promise { + const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; + + let collectionId = 0; + await usingApi(async (api, privateKeyWrapper) => { + // Get number of collections before the transaction + const collectionCountBefore = await getCreatedCollectionCount(api); + + // Run the CreateCollection transaction + const alicePrivateKey = privateKeyWrapper('//Alice'); + + let modeprm = {}; + if (mode.type === 'NFT') { + modeprm = {nft: null}; + } else if (mode.type === 'Fungible') { + modeprm = {fungible: mode.decimalPoints}; + } else if (mode.type === 'ReFungible') { + modeprm = {refungible: null}; + } + + const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, properties: params.properties, tokenPropertyPermissions: params.propPerm}); + const events = await submitTransactionAsync(alicePrivateKey, tx); + const result = getCreateCollectionResult(events); + + // Get number of collections after the transaction + const collectionCountAfter = await getCreatedCollectionCount(api); + + // Get the collection + const collection = await queryCollectionExpectSuccess(api, result.collectionId); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.true; + expect(result.collectionId).to.be.equal(collectionCountAfter); + // tslint:disable-next-line:no-unused-expression + expect(collection).to.be.not.null; + expect(collectionCountAfter).to.be.equal(collectionCountBefore + 1, 'Error: NFT collection NOT created.'); + expect(collection.owner.toString()).to.be.equal(toSubstrateAddress(alicePrivateKey)); + expect(utf16ToStr(collection.name.toJSON() as any)).to.be.equal(name); + expect(utf16ToStr(collection.description.toJSON() as any)).to.be.equal(description); + expect(hexToStr(collection.tokenPrefix.toJSON())).to.be.equal(tokenPrefix); + + + collectionId = result.collectionId; + }); + + return collectionId; +} + +export async function createCollectionWithPropsExpectFailure(params: Partial = {}) { + const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; + + await usingApi(async (api, privateKeyWrapper) => { + // Get number of collections before the transaction + const collectionCountBefore = await getCreatedCollectionCount(api); + + // Run the CreateCollection transaction + const alicePrivateKey = privateKeyWrapper('//Alice'); + + let modeprm = {}; + if (mode.type === 'NFT') { + modeprm = {nft: null}; + } else if (mode.type === 'Fungible') { + modeprm = {fungible: mode.decimalPoints}; + } else if (mode.type === 'ReFungible') { + modeprm = {refungible: null}; + } + + const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, properties: params.properties, tokenPropertyPermissions: params.propPerm}); + await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; + + + // Get number of collections after the transaction + const collectionCountAfter = await getCreatedCollectionCount(api); + + expect(collectionCountAfter).to.be.equal(collectionCountBefore, 'Error: Collection with incorrect data created.'); + }); +} + +export async function createCollectionExpectFailure(params: Partial = {}) { + const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; + + let modeprm = {}; + if (mode.type === 'NFT') { + modeprm = {nft: null}; + } else if (mode.type === 'Fungible') { + modeprm = {fungible: mode.decimalPoints}; + } else if (mode.type === 'ReFungible') { + modeprm = {refungible: null}; + } + + await usingApi(async (api, privateKeyWrapper) => { + // Get number of collections before the transaction + const collectionCountBefore = await getCreatedCollectionCount(api); + + // Run the CreateCollection transaction + const alicePrivateKey = privateKeyWrapper('//Alice'); + const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any}); + await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; + + // Get number of collections after the transaction + const collectionCountAfter = await getCreatedCollectionCount(api); + + // What to expect + expect(collectionCountAfter).to.be.equal(collectionCountBefore, 'Error: Collection with incorrect data created.'); + }); +} + +export async function findUnusedAddress(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair, seedAddition = ''): Promise { + let bal = 0n; + let unused; + do { + const randomSeed = 'seed' + Math.floor(Math.random() * Math.floor(10000)) + seedAddition; + unused = privateKeyWrapper(`//${randomSeed}`); + bal = (await api.query.system.account(unused.address)).data.free.toBigInt(); + } while (bal !== 0n); + return unused; +} + +export async function getAllowance(api: ApiPromise, collectionId: number, owner: CrossAccountId | string | IKeyringPair, approved: CrossAccountId | string | IKeyringPair, tokenId: number) { + return (await api.rpc.unique.allowance(collectionId, normalizeAccountId(owner), normalizeAccountId(approved), tokenId)).toBigInt(); +} + +export function findUnusedAddresses(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair, amount: number): Promise { + return Promise.all(new Array(amount).fill(null).map(() => findUnusedAddress(api, privateKeyWrapper, '_' + Date.now()))); +} + +export async function findNotExistingCollection(api: ApiPromise): Promise { + const totalNumber = await getCreatedCollectionCount(api); + const newCollection: number = totalNumber + 1; + return newCollection; +} + +function getDestroyResult(events: EventRecord[]): boolean { + let success = false; + events.forEach(({event: {method}}) => { + if (method == 'ExtrinsicSuccess') { + success = true; + } + }); + return success; +} + +export async function destroyCollectionExpectFailure(collectionId: number, senderSeed = '//Alice') { + await usingApi(async (api, privateKeyWrapper) => { + // Run the DestroyCollection transaction + const alicePrivateKey = privateKeyWrapper(senderSeed); + const tx = api.tx.unique.destroyCollection(collectionId); + await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; + }); +} + +export async function destroyCollectionExpectSuccess(collectionId: number, senderSeed = '//Alice') { + await usingApi(async (api, privateKeyWrapper) => { + // Run the DestroyCollection transaction + const alicePrivateKey = privateKeyWrapper(senderSeed); + const tx = api.tx.unique.destroyCollection(collectionId); + const events = await submitTransactionAsync(alicePrivateKey, tx); + const result = getDestroyResult(events); + expect(result).to.be.true; + + // What to expect + expect(await getDetailedCollectionInfo(api, collectionId)).to.be.null; + }); +} + +export async function setCollectionLimitsExpectSuccess(sender: IKeyringPair, collectionId: number, limits: any) { + await usingApi(async (api) => { + const tx = api.tx.unique.setCollectionLimits(collectionId, limits); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + expect(result.success).to.be.true; + }); +} + +export const setCollectionPermissionsExpectSuccess = async (sender: IKeyringPair, collectionId: number, permissions: any) => { + await usingApi(async(api) => { + const tx = api.tx.unique.setCollectionPermissions(collectionId, permissions); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + expect(result.success).to.be.true; + }); +}; + +export async function setCollectionLimitsExpectFailure(sender: IKeyringPair, collectionId: number, limits: any) { + await usingApi(async (api) => { + const tx = api.tx.unique.setCollectionLimits(collectionId, limits); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getGenericResult(events); + + expect(result.success).to.be.false; + }); +} + +export async function setCollectionSponsorExpectSuccess(collectionId: number, sponsor: string, sender = '//Alice') { + await usingApi(async (api, privateKeyWrapper) => { + + // Run the transaction + const senderPrivateKey = privateKeyWrapper(sender); + const tx = api.tx.unique.setCollectionSponsor(collectionId, sponsor); + const events = await submitTransactionAsync(senderPrivateKey, tx); + const result = getGenericResult(events); + + // Get the collection + const collection = await queryCollectionExpectSuccess(api, collectionId); + + // What to expect + expect(result.success).to.be.true; + expect(collection.sponsorship.toJSON()).to.deep.equal({ + unconfirmed: sponsor, + }); + }); +} + +export async function removeCollectionSponsorExpectSuccess(collectionId: number, sender = '//Alice') { + await usingApi(async (api, privateKeyWrapper) => { + + // Run the transaction + const alicePrivateKey = privateKeyWrapper(sender); + const tx = api.tx.unique.removeCollectionSponsor(collectionId); + const events = await submitTransactionAsync(alicePrivateKey, tx); + const result = getGenericResult(events); + + // Get the collection + const collection = await queryCollectionExpectSuccess(api, collectionId); + + // What to expect + expect(result.success).to.be.true; + expect(collection.sponsorship.toJSON()).to.be.deep.equal({disabled: null}); + }); +} + +export async function removeCollectionSponsorExpectFailure(collectionId: number, senderSeed = '//Alice') { + await usingApi(async (api, privateKeyWrapper) => { + + // Run the transaction + const alicePrivateKey = privateKeyWrapper(senderSeed); + const tx = api.tx.unique.removeCollectionSponsor(collectionId); + await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; + }); +} + +export async function setCollectionSponsorExpectFailure(collectionId: number, sponsor: string, senderSeed = '//Alice') { + await usingApi(async (api, privateKeyWrapper) => { + + // Run the transaction + const alicePrivateKey = privateKeyWrapper(senderSeed); + const tx = api.tx.unique.setCollectionSponsor(collectionId, sponsor); + await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; + }); +} + +export async function confirmSponsorshipExpectSuccess(collectionId: number, senderSeed = '//Alice') { + await usingApi(async (api, privateKeyWrapper) => { + + // Run the transaction + const sender = privateKeyWrapper(senderSeed); + await confirmSponsorshipByKeyExpectSuccess(collectionId, sender); + }); +} + +export async function confirmSponsorshipByKeyExpectSuccess(collectionId: number, sender: IKeyringPair) { + await usingApi(async (api, privateKeyWrapper) => { + + // Run the transaction + const tx = api.tx.unique.confirmSponsorship(collectionId); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + // Get the collection + const collection = await queryCollectionExpectSuccess(api, collectionId); + + // What to expect + expect(result.success).to.be.true; + expect(collection.sponsorship.toJSON()).to.be.deep.equal({ + confirmed: sender.address, + }); + }); +} + + +export async function confirmSponsorshipExpectFailure(collectionId: number, senderSeed = '//Alice') { + await usingApi(async (api, privateKeyWrapper) => { + + // Run the transaction + const sender = privateKeyWrapper(senderSeed); + const tx = api.tx.unique.confirmSponsorship(collectionId); + await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + }); +} + +export async function enableContractSponsoringExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) { + await usingApi(async (api) => { + const tx = api.tx.unique.enableContractSponsoring(contractAddress, enable); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + expect(result.success).to.be.true; + }); +} + +export async function enableContractSponsoringExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) { + await usingApi(async (api) => { + const tx = api.tx.unique.enableContractSponsoring(contractAddress, enable); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getGenericResult(events); + + expect(result.success).to.be.false; + }); +} + +export async function setTransferFlagExpectSuccess(sender: IKeyringPair, collectionId: number, enabled: boolean) { + + await usingApi(async (api) => { + + const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + expect(result.success).to.be.true; + }); +} + +export async function setTransferFlagExpectFailure(sender: IKeyringPair, collectionId: number, enabled: boolean) { + + await usingApi(async (api) => { + + const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getGenericResult(events); + + expect(result.success).to.be.false; + }); +} + +export async function setContractSponsoringRateLimitExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) { + await usingApi(async (api) => { + const tx = api.tx.unique.setContractSponsoringRateLimit(contractAddress, rateLimit); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + expect(result.success).to.be.true; + }); +} + +export async function setContractSponsoringRateLimitExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) { + await usingApi(async (api) => { + const tx = api.tx.unique.setContractSponsoringRateLimit(contractAddress, rateLimit); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getGenericResult(events); + + expect(result.success).to.be.false; + }); +} + +export async function getNextSponsored( + api: ApiPromise, + collectionId: number, + account: string | CrossAccountId, + tokenId: number, +): Promise { + return Number((await api.rpc.unique.nextSponsored(collectionId, account, tokenId)).unwrapOr(-1)); +} + +export async function toggleContractAllowlistExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, value = true) { + await usingApi(async (api) => { + const tx = api.tx.unique.toggleContractAllowList(contractAddress, value); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + expect(result.success).to.be.true; + }); +} + +export async function isAllowlistedInContract(contractAddress: AccountId | string, user: string) { + let allowlisted = false; + await usingApi(async (api) => { + allowlisted = (await api.query.unique.contractAllowList(contractAddress, user)).toJSON() as boolean; + }); + return allowlisted; +} + +export async function addToContractAllowListExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) { + await usingApi(async (api) => { + const tx = api.tx.unique.addToContractAllowList(contractAddress.toString(), user.toString()); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + expect(result.success).to.be.true; + }); +} + +export async function removeFromContractAllowListExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) { + await usingApi(async (api) => { + const tx = api.tx.unique.removeFromContractAllowList(contractAddress.toString(), user.toString()); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + expect(result.success).to.be.true; + }); +} + +export async function removeFromContractAllowListExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) { + await usingApi(async (api) => { + const tx = api.tx.unique.removeFromContractAllowList(contractAddress.toString(), user.toString()); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getGenericResult(events); + + expect(result.success).to.be.false; + }); +} + +export interface CreateFungibleData { + readonly Value: bigint; +} + +export interface CreateReFungibleData { } +export interface CreateNftData { } + +export type CreateItemData = { + NFT: CreateNftData; +} | { + Fungible: CreateFungibleData; +} | { + ReFungible: CreateReFungibleData; +}; + +export async function burnItem(api: ApiPromise, sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint) : Promise { + const tx = api.tx.unique.burnItem(collectionId, tokenId, value); + const events = await submitTransactionAsync(sender, tx); + return getGenericResult(events).success; +} + +export async function burnItemExpectSuccess(sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint = 1) { + await usingApi(async (api) => { + const balanceBefore = await getBalance(api, collectionId, normalizeAccountId(sender), tokenId); + // if burning token by admin - use adminButnItemExpectSuccess + expect(balanceBefore >= BigInt(value)).to.be.true; + + expect(await burnItem(api, sender, collectionId, tokenId, value)).to.be.true; + + const balanceAfter = await getBalance(api, collectionId, normalizeAccountId(sender), tokenId); + expect(balanceAfter + BigInt(value)).to.be.equal(balanceBefore); + }); +} + +export async function burnItemExpectFailure(sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint = 1) { + await usingApi(async (api) => { + const tx = api.tx.unique.burnItem(collectionId, tokenId, value); + + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getCreateCollectionResult(events); + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + }); +} + +export async function burnFromExpectSuccess(sender: IKeyringPair, from: IKeyringPair | CrossAccountId, collectionId: number, tokenId: number, value: number | bigint = 1) { + await usingApi(async (api) => { + const tx = api.tx.unique.burnFrom(collectionId, normalizeAccountId(from), tokenId, value); + const events = await submitTransactionAsync(sender, tx); + return getGenericResult(events).success; + }); +} + +export async function +approve( + api: ApiPromise, + collectionId: number, + tokenId: number, owner: IKeyringPair, approved: CrossAccountId | string | IKeyringPair, amount: number | bigint, +) { + const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); + const events = await submitTransactionAsync(owner, approveUniqueTx); + return getGenericResult(events).success; +} + +export async function +approveExpectSuccess( + collectionId: number, + tokenId: number, owner: IKeyringPair, approved: CrossAccountId | string, amount: number | bigint = 1, +) { + await usingApi(async (api: ApiPromise) => { + const result = await approve(api, collectionId, tokenId, owner, approved, amount); + expect(result).to.be.true; + + expect(await getAllowance(api, collectionId, owner, approved, tokenId)).to.be.equal(BigInt(amount)); + }); +} + +export async function adminApproveFromExpectSuccess( + collectionId: number, + tokenId: number, admin: IKeyringPair, owner: CrossAccountId | string, approved: CrossAccountId | string, amount: number | bigint = 1, +) { + await usingApi(async (api: ApiPromise) => { + const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); + const events = await submitTransactionAsync(admin, approveUniqueTx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + expect(await getAllowance(api, collectionId, owner, approved, tokenId)).to.be.equal(BigInt(amount)); + }); +} + +export async function +transferFrom( + api: ApiPromise, + collectionId: number, + tokenId: number, + accountApproved: IKeyringPair, + accountFrom: IKeyringPair | CrossAccountId, + accountTo: IKeyringPair | CrossAccountId, + value: number | bigint, +) { + const from = normalizeAccountId(accountFrom); + const to = normalizeAccountId(accountTo); + const transferFromTx = api.tx.unique.transferFrom(from, to, collectionId, tokenId, value); + const events = await submitTransactionAsync(accountApproved, transferFromTx); + return getGenericResult(events).success; +} + +export async function +transferFromExpectSuccess( + collectionId: number, + tokenId: number, + accountApproved: IKeyringPair, + accountFrom: IKeyringPair | CrossAccountId, + accountTo: IKeyringPair | CrossAccountId, + value: number | bigint = 1, + type = 'NFT', +) { + await usingApi(async (api: ApiPromise) => { + const from = normalizeAccountId(accountFrom); + const to = normalizeAccountId(accountTo); + let balanceBefore = 0n; + if (type === 'Fungible' || type === 'ReFungible') { + balanceBefore = await getBalance(api, collectionId, to, tokenId); + } + expect(await transferFrom(api, collectionId, tokenId, accountApproved, accountFrom, accountTo, value)).to.be.true; + if (type === 'NFT') { + expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to); + } + if (type === 'Fungible') { + const balanceAfter = await getBalance(api, collectionId, to, tokenId); + if (JSON.stringify(to) !== JSON.stringify(from)) { + expect(balanceAfter - balanceBefore).to.be.equal(BigInt(value)); + } else { + expect(balanceAfter).to.be.equal(balanceBefore); + } + } + if (type === 'ReFungible') { + expect(await getBalance(api, collectionId, to, tokenId)).to.be.equal(balanceBefore + BigInt(value)); + } + }); +} + +export async function +transferFromExpectFail( + collectionId: number, + tokenId: number, + accountApproved: IKeyringPair, + accountFrom: IKeyringPair, + accountTo: IKeyringPair, + value: number | bigint = 1, +) { + await usingApi(async (api: ApiPromise) => { + const transferFromTx = api.tx.unique.transferFrom(normalizeAccountId(accountFrom.address), normalizeAccountId(accountTo.address), collectionId, tokenId, value); + const events = await expect(submitTransactionExpectFailAsync(accountApproved, transferFromTx)).to.be.rejected; + const result = getCreateCollectionResult(events); + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + }); +} + +/* eslint no-async-promise-executor: "off" */ +export async function getBlockNumber(api: ApiPromise): Promise { + return new Promise(async (resolve) => { + const unsubscribe = await api.rpc.chain.subscribeNewHeads((head) => { + unsubscribe(); + resolve(head.number.toNumber()); + }); + }); +} + +export async function addCollectionAdminExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | CrossAccountId) { + await usingApi(async (api) => { + const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(address)); + const events = await submitTransactionAsync(sender, changeAdminTx); + const result = getCreateCollectionResult(events); + expect(result.success).to.be.true; + }); +} + +export async function adminApproveFromExpectFail( + collectionId: number, + tokenId: number, admin: IKeyringPair, owner: CrossAccountId | string, approved: CrossAccountId | string, amount: number | bigint = 1, +) { + await usingApi(async (api: ApiPromise) => { + const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); + const events = await expect(submitTransactionAsync(admin, approveUniqueTx)).to.be.rejected; + const result = getGenericResult(events); + expect(result.success).to.be.false; + }); +} + +export async function +getFreeBalance(account: IKeyringPair): Promise { + let balance = 0n; + await usingApi(async (api) => { + balance = BigInt((await api.query.system.account(account.address)).data.free.toString()); + }); + + return balance; +} + +export async function paraSiblingSovereignAccount(paraid: number): Promise { + return usingApi(async api => { + // We are getting a *sibling* parachain sovereign account, + // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c + const siblingPrefix = '0x7369626c'; + + const encodedParaId = api.createType('u32', paraid).toHex(true).substring(2); + const suffix = '000000000000000000000000000000000000000000000000'; + + return siblingPrefix + encodedParaId + suffix; + }); +} + +export async function transferBalanceTo(api: ApiPromise, source: IKeyringPair, target: string, amount = 1000n * UNIQUE) { + const tx = api.tx.balances.transfer(target, amount); + const events = await submitTransactionAsync(source, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; +} + +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, + sender: IKeyringPair, + recipient: IKeyringPair, + value: number | bigint = 1, + blockSchedule: number, + scheduledId: string, +) { + await usingApi(async (api: ApiPromise) => { + await scheduleTransferExpectSuccess(collectionId, tokenId, sender, recipient, value, blockSchedule, scheduledId); + + const recipientBalanceBefore = (await api.query.system.account(recipient.address)).data.free.toBigInt(); + + // sleep for n + 1 blocks + await waitNewBlocks(blockSchedule + 1); + + const recipientBalanceAfter = (await api.query.system.account(recipient.address)).data.free.toBigInt(); + + expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(recipient.address)); + expect(recipientBalanceAfter).to.be.equal(recipientBalanceBefore); + }); +} + +export async function +scheduleTransferExpectSuccess( + collectionId: number, + tokenId: number, + sender: IKeyringPair, + recipient: IKeyringPair, + value: number | bigint = 1, + blockSchedule: number, + scheduledId: string, +) { + await usingApi(async (api: ApiPromise) => { + const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value); + + await scheduleExpectSuccess(transferTx, sender, blockSchedule, scheduledId); + + expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(sender.address)); + }); +} + +export async function +scheduleTransferFundsPeriodicExpectSuccess( + amount: bigint, + sender: IKeyringPair, + recipient: IKeyringPair, + blockSchedule: number, + scheduledId: string, + period: number, + repetitions: number, +) { + await usingApi(async (api: ApiPromise) => { + const transferTx = api.tx.balances.transfer(recipient.address, amount); + + const balanceBefore = await getFreeBalance(recipient); + + await scheduleExpectSuccess(transferTx, sender, blockSchedule, scheduledId, period, repetitions); + + expect(await getFreeBalance(recipient)).to.be.equal(balanceBefore); + }); +} + +export async function +transfer( + api: ApiPromise, + collectionId: number, + tokenId: number, + sender: IKeyringPair, + recipient: IKeyringPair | CrossAccountId, + value: number | bigint, +) : Promise { + const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); + const events = await executeTransaction(api, sender, transferTx); + return getGenericResult(events).success; +} + +export async function +transferExpectSuccess( + collectionId: number, + tokenId: number, + sender: IKeyringPair, + recipient: IKeyringPair | CrossAccountId, + value: number | bigint = 1, + type = 'NFT', +) { + await usingApi(async (api: ApiPromise) => { + const from = normalizeAccountId(sender); + const to = normalizeAccountId(recipient); + + let balanceBefore = 0n; + if (type === 'Fungible' || type === 'ReFungible') { + balanceBefore = await getBalance(api, collectionId, to, tokenId); + } + + const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); + const events = await executeTransaction(api, sender, transferTx); + const result = getTransferResult(api, events); + + expect(result.collectionId).to.be.equal(collectionId); + expect(result.itemId).to.be.equal(tokenId); + expect(result.sender).to.be.deep.equal(normalizeAccountId(sender.address)); + expect(result.recipient).to.be.deep.equal(to); + expect(result.value).to.be.equal(BigInt(value)); + + if (type === 'NFT') { + expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to); + } + if (type === 'Fungible' || type === 'ReFungible') { + const balanceAfter = await getBalance(api, collectionId, to, tokenId); + if (JSON.stringify(to) !== JSON.stringify(from)) { + expect(balanceAfter - balanceBefore).to.be.equal(BigInt(value)); + } else { + expect(balanceAfter).to.be.equal(balanceBefore); + } + } + }); +} + +export async function +transferExpectFailure( + collectionId: number, + tokenId: number, + sender: IKeyringPair, + recipient: IKeyringPair | CrossAccountId, + value: number | bigint = 1, +) { + await usingApi(async (api: ApiPromise) => { + const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); + const events = await expect(submitTransactionExpectFailAsync(sender, transferTx)).to.be.rejected; + const result = getGenericResult(events); + // if (events && Array.isArray(events)) { + // const result = getCreateCollectionResult(events); + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + //} + }); +} + +export async function +approveExpectFail( + collectionId: number, + tokenId: number, owner: IKeyringPair, approved: IKeyringPair, amount: number | bigint = 1, +) { + await usingApi(async (api: ApiPromise) => { + const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved.address), collectionId, tokenId, amount); + const events = await expect(submitTransactionExpectFailAsync(owner, approveUniqueTx)).to.be.rejected; + const result = getCreateCollectionResult(events); + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + }); +} + +export async function getBalance( + api: ApiPromise, + collectionId: number, + owner: string | CrossAccountId | IKeyringPair, + token: number, +): Promise { + return (await api.rpc.unique.balance(collectionId, normalizeAccountId(owner), token)).toBigInt(); +} +export async function getTokenOwner( + api: ApiPromise, + collectionId: number, + token: number, +): Promise { + const owner = (await api.rpc.unique.tokenOwner(collectionId, token)).toJSON() as any; + if (owner == null) throw new Error('owner == null'); + return normalizeAccountId(owner); +} +export async function getTopmostTokenOwner( + api: ApiPromise, + collectionId: number, + token: number, +): Promise { + const owner = (await api.rpc.unique.topmostTokenOwner(collectionId, token)).toJSON() as any; + if (owner == null) throw new Error('owner == null'); + return normalizeAccountId(owner); +} +export async function getTokenChildren( + api: ApiPromise, + collectionId: number, + tokenId: number, +): Promise { + return (await api.rpc.unique.tokenChildren(collectionId, tokenId)).toJSON() as any; +} +export async function isTokenExists( + api: ApiPromise, + collectionId: number, + token: number, +): Promise { + return (await api.rpc.unique.tokenExists(collectionId, token)).toJSON(); +} +export async function getLastTokenId( + api: ApiPromise, + collectionId: number, +): Promise { + return (await api.rpc.unique.lastTokenId(collectionId)).toJSON(); +} +export async function getAdminList( + api: ApiPromise, + collectionId: number, +): Promise { + return (await api.rpc.unique.adminlist(collectionId)).toHuman() as any; +} +export async function getTokenProperties( + api: ApiPromise, + collectionId: number, + tokenId: number, + propertyKeys: string[], +): Promise { + return (await api.rpc.unique.tokenProperties(collectionId, tokenId, propertyKeys)).toHuman() as any; +} + +export async function createFungibleItemExpectSuccess( + sender: IKeyringPair, + collectionId: number, + data: CreateFungibleData, + owner: CrossAccountId | string = sender.address, +) { + return await usingApi(async (api) => { + const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), {Fungible: data}); + + const events = await submitTransactionAsync(sender, tx); + const result = getCreateItemResult(events); + + expect(result.success).to.be.true; + return result.itemId; + }); +} + +export async function createMultipleItemsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any, owner: CrossAccountId | string = sender.address) { + await usingApi(async (api) => { + const to = normalizeAccountId(owner); + const tx = api.tx.unique.createMultipleItems(collectionId, to, itemsData); + + const events = await submitTransactionAsync(sender, tx); + expect(getGenericResult(events).success).to.be.true; + }); +} + +export async function createMultipleItemsWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any, owner: CrossAccountId | string = sender.address) { + await usingApi(async (api) => { + const to = normalizeAccountId(owner); + const tx = api.tx.unique.createMultipleItems(collectionId, to, itemsData); + + const events = await submitTransactionAsync(sender, tx); + const result = getCreateItemsResult(events); + + for (const res of result) { + expect(await api.rpc.unique.tokenProperties(collectionId, res.itemId)).not.to.be.empty; + } + }); +} + +export async function createMultipleItemsExWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any) { + await usingApi(async (api) => { + const tx = api.tx.unique.createMultipleItemsEx(collectionId, itemsData); + + const events = await submitTransactionAsync(sender, tx); + const result = getCreateItemsResult(events); + + for (const res of result) { + expect(await api.rpc.unique.tokenProperties(collectionId, res.itemId)).not.to.be.empty; + } + }); +} + +export async function createItemWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, props: Array, owner: CrossAccountId | string = sender.address) { + let newItemId = 0; + await usingApi(async (api) => { + const to = normalizeAccountId(owner); + const itemCountBefore = await getLastTokenId(api, collectionId); + const itemBalanceBefore = await getBalance(api, collectionId, to, newItemId); + + let tx; + if (createMode === 'Fungible') { + const createData = {fungible: {value: 10}}; + tx = api.tx.unique.createItem(collectionId, to, createData as any); + } else if (createMode === 'ReFungible') { + const createData = {refungible: {pieces: 100}}; + tx = api.tx.unique.createItem(collectionId, to, createData as any); + } else { + const data = api.createType('UpDataStructsCreateItemData', {NFT: {properties: props}}); + tx = api.tx.unique.createItem(collectionId, to, data as UpDataStructsCreateItemData); + } + + const events = await submitTransactionAsync(sender, tx); + const result = getCreateItemResult(events); + + const itemCountAfter = await getLastTokenId(api, collectionId); + const itemBalanceAfter = await getBalance(api, collectionId, to, newItemId); + + if (createMode === 'NFT') { + expect(await api.rpc.unique.tokenProperties(collectionId, result.itemId)).not.to.be.empty; + } + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.true; + if (createMode === 'Fungible') { + expect(itemBalanceAfter - itemBalanceBefore).to.be.equal(10n); + } else { + expect(itemCountAfter).to.be.equal(itemCountBefore + 1); + } + expect(collectionId).to.be.equal(result.collectionId); + expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString()); + expect(to).to.be.deep.equal(result.recipient); + newItemId = result.itemId; + }); + return newItemId; +} + +export async function createItemWithPropsExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, props: Array, owner: CrossAccountId | string = sender.address) { + await usingApi(async (api) => { + + let tx; + if (createMode === 'NFT') { + const data = api.createType('UpDataStructsCreateItemData', {NFT: {properties: props}}) as UpDataStructsCreateItemData; + tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), data); + } else { + tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createMode); + } + + + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + if(events.message && events.message.toString().indexOf('1002: Verification Error') > -1) return; + const result = getCreateItemResult(events); + + expect(result.success).to.be.false; + }); +} + +export async function createItemExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, owner: CrossAccountId | string = sender.address) { + let newItemId = 0; + await usingApi(async (api) => { + const to = normalizeAccountId(owner); + const itemCountBefore = await getLastTokenId(api, collectionId); + const itemBalanceBefore = await getBalance(api, collectionId, to, newItemId); + + let tx; + if (createMode === 'Fungible') { + const createData = {fungible: {value: 10}}; + tx = api.tx.unique.createItem(collectionId, to, createData as any); + } else if (createMode === 'ReFungible') { + const createData = {refungible: {pieces: 100}}; + tx = api.tx.unique.createItem(collectionId, to, createData as any); + } else { + const createData = {nft: {}}; + tx = api.tx.unique.createItem(collectionId, to, createData as any); + } + + const events = await executeTransaction(api, sender, tx); + const result = getCreateItemResult(events); + + const itemCountAfter = await getLastTokenId(api, collectionId); + const itemBalanceAfter = await getBalance(api, collectionId, to, newItemId); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.true; + if (createMode === 'Fungible') { + expect(itemBalanceAfter - itemBalanceBefore).to.be.equal(10n); + } else { + expect(itemCountAfter).to.be.equal(itemCountBefore + 1); + } + expect(collectionId).to.be.equal(result.collectionId); + expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString()); + expect(to).to.be.deep.equal(result.recipient); + newItemId = result.itemId; + }); + return newItemId; +} + +export async function createRefungibleToken(api: ApiPromise, sender: IKeyringPair, collectionId: number, amount: bigint, owner: CrossAccountId | IKeyringPair | string = sender.address) : Promise { + const createData = {refungible: {pieces: amount}}; + const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createData as any); + + const events = await submitTransactionAsync(sender, tx); + return getCreateItemResult(events); +} + +export async function createItemExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, owner: CrossAccountId | string = sender.address) { + await usingApi(async (api) => { + const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createMode); + + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getCreateItemResult(events); + + expect(result.success).to.be.false; + }); +} + +export async function setPublicAccessModeExpectSuccess( + sender: IKeyringPair, collectionId: number, + accessMode: 'Normal' | 'AllowList', +) { + await usingApi(async (api) => { + + // Run the transaction + const tx = api.tx.unique.setCollectionPermissions(collectionId, {access: accessMode}); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + // Get the collection + const collection = await queryCollectionExpectSuccess(api, collectionId); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.true; + expect(collection.permissions.access.toHuman()).to.be.equal(accessMode); + }); +} + +export async function setPublicAccessModeExpectFail( + sender: IKeyringPair, collectionId: number, + accessMode: 'Normal' | 'AllowList', +) { + await usingApi(async (api) => { + + // Run the transaction + const tx = api.tx.unique.setCollectionPermissions(collectionId, {access: accessMode}); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getGenericResult(events); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + }); +} + +export async function enableAllowListExpectSuccess(sender: IKeyringPair, collectionId: number) { + await setPublicAccessModeExpectSuccess(sender, collectionId, 'AllowList'); +} + +export async function enableAllowListExpectFail(sender: IKeyringPair, collectionId: number) { + await setPublicAccessModeExpectFail(sender, collectionId, 'AllowList'); +} + +export async function disableAllowListExpectSuccess(sender: IKeyringPair, collectionId: number) { + await setPublicAccessModeExpectSuccess(sender, collectionId, 'Normal'); +} + +export async function setMintPermissionExpectSuccess(sender: IKeyringPair, collectionId: number, enabled: boolean) { + await usingApi(async (api) => { + + // Run the transaction + const tx = api.tx.unique.setCollectionPermissions(collectionId, {mintMode: enabled}); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + // Get the collection + const collection = await queryCollectionExpectSuccess(api, collectionId); + + expect(collection.permissions.mintMode.toHuman()).to.be.equal(enabled); + }); +} + +export async function enablePublicMintingExpectSuccess(sender: IKeyringPair, collectionId: number) { + await setMintPermissionExpectSuccess(sender, collectionId, true); +} + +export async function setMintPermissionExpectFailure(sender: IKeyringPair, collectionId: number, enabled: boolean) { + await usingApi(async (api) => { + // Run the transaction + const tx = api.tx.unique.setCollectionPermissions(collectionId, {mintMode: enabled}); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getCreateCollectionResult(events); + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + }); +} + +export async function setChainLimitsExpectFailure(sender: IKeyringPair, limits: IChainLimits) { + await usingApi(async (api) => { + // Run the transaction + const tx = api.tx.unique.setChainLimits(limits); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getCreateCollectionResult(events); + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + }); +} + +export async function isAllowlisted(api: ApiPromise, collectionId: number, address: string | CrossAccountId | IKeyringPair) { + return (await api.rpc.unique.allowed(collectionId, normalizeAccountId(address))).toJSON(); +} + +export async function addToAllowListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId | CrossAccountId) { + await usingApi(async (api) => { + expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.false; + + // Run the transaction + const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address)); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true; + }); +} + +export async function addToAllowListAgainExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId) { + await usingApi(async (api) => { + + expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true; + + // Run the transaction + const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address)); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + expect(result.success).to.be.true; + + expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true; + }); +} + +export async function addToAllowListExpectFail(sender: IKeyringPair, collectionId: number, address: string | AccountId) { + await usingApi(async (api) => { + + // Run the transaction + const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address)); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getGenericResult(events); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + }); +} + +export async function removeFromAllowListExpectSuccess(sender: IKeyringPair, collectionId: number, address: CrossAccountId) { + await usingApi(async (api) => { + // Run the transaction + const tx = api.tx.unique.removeFromAllowList(collectionId, normalizeAccountId(address)); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.true; + }); +} + +export async function removeFromAllowListExpectFailure(sender: IKeyringPair, collectionId: number, address: CrossAccountId) { + await usingApi(async (api) => { + // Run the transaction + const tx = api.tx.unique.removeFromAllowList(collectionId, normalizeAccountId(address)); + const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; + const result = getGenericResult(events); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(result.success).to.be.false; + }); +} + +export const getDetailedCollectionInfo = async (api: ApiPromise, collectionId: number) + : Promise => { + return (await api.rpc.unique.collectionById(collectionId)).unwrapOr(null); +}; + +export const getCreatedCollectionCount = async (api: ApiPromise): Promise => { + // set global object - collectionsCount + return (await api.rpc.unique.collectionStats()).created.toNumber(); +}; + +export async function queryCollectionExpectSuccess(api: ApiPromise, collectionId: number): Promise { + return (await api.rpc.unique.collectionById(collectionId)).unwrap(); +} + +export const describe_xcm = ( + process.env.RUN_XCM_TESTS + ? describe + : describe.skip +); + +export async function waitNewBlocks(blocksCount = 1): Promise { + await usingApi(async (api) => { + const promise = new Promise(async (resolve) => { + const unsubscribe = await api.rpc.chain.subscribeNewHeads(() => { + if (blocksCount > 0) { + blocksCount--; + } else { + unsubscribe(); + resolve(); + } + }); + }); + return promise; + }); +} + +export async function waitEvent( + api: ApiPromise, + maxBlocksToWait: number, + eventSection: string, + eventMethod: string, +): Promise { + + const promise = new Promise(async (resolve) => { + const unsubscribe = await api.rpc.chain.subscribeNewHeads(async header => { + const blockNumber = header.number.toHuman(); + const blockHash = header.hash; + const eventIdStr = `${eventSection}.${eventMethod}`; + const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`; + + console.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`); + + const apiAt = await api.at(blockHash); + const eventRecords = await apiAt.query.system.events(); + + const neededEvent = eventRecords.find(r => { + return r.event.section == eventSection && r.event.method == eventMethod; + }); + + if (neededEvent) { + unsubscribe(); + resolve(neededEvent); + } else if (maxBlocksToWait > 0) { + maxBlocksToWait--; + } else { + console.log(`Event \`${eventIdStr}\` is NOT found`); + + unsubscribe(); + resolve(null); + } + }); + }); + return promise; +} + +export async function repartitionRFT( + api: ApiPromise, + collectionId: number, + sender: IKeyringPair, + tokenId: number, + amount: bigint, +): Promise { + const tx = api.tx.unique.repartition(collectionId, tokenId, amount); + const events = await submitTransactionAsync(sender, tx); + const result = getGenericResult(events); + + return result.success; +} + +export async function itApi(name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) { + let i: any = it; + if (opts.only) i = i.only; + else if (opts.skip) i = i.skip; + i(name, async () => { + await usingApi(async (api, privateKeyWrapper) => { + await cb({api, privateKeyWrapper}); + }); + }); +} + +itApi.only = (name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itApi(name, cb, {only: true}); +itApi.skip = (name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itApi(name, cb, {skip: true}); + +let accountSeed = 10000; +export function generateKeyringPair(keyring: Keyring) { + const privateKey = `0xDEADBEEF${(Date.now() + (accountSeed++)).toString(16).padStart(64 - 8, '0')}`; + return keyring.addFromUri(privateKey); +} + +export async function expectSubstrateEventsAtBlock(api: ApiPromise, blockNumber: AnyNumber | BlockNumber, section: string, methods: string[], dryRun = false) { + const blockHash = await api.rpc.chain.getBlockHash(blockNumber); + const subEvents = (await api.query.system.events.at(blockHash)) + .filter(x => x.event.section === section) + .map((x) => x.toHuman()); + const events = methods.map((m) => { + return { + event: { + method: m, + section, + }, + }; + }); + if (!dryRun) { + expect(subEvents).to.be.like(events); + } + return subEvents; +} --- /dev/null +++ b/tests/src/deprecated-helpers/util.ts @@ -0,0 +1,46 @@ +// 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 . + +export function strToUTF16(str: string): any { + const buf: number[] = []; + for (let i=0, strLen=str.length; i < strLen; i++) { + buf.push(str.charCodeAt(i)); + } + return buf; +} + +export function utf16ToStr(buf: number[]): string { + let str = ''; + for (let i=0, strLen=buf.length; i < strLen; i++) { + if (buf[i] != 0) str += String.fromCharCode(buf[i]); + else break; + } + return str; +} + +export function hexToStr(buf: string): string { + let str = ''; + let hexStart = buf.indexOf('0x'); + if (hexStart < 0) hexStart = 0; + else hexStart = 2; + for (let i=hexStart, strLen=buf.length; i < strLen; i+=2) { + const ch = buf[i] + buf[i+1]; + const num = parseInt(ch, 16); + if (num != 0) str += String.fromCharCode(num); + else break; + } + return str; +} --- a/tests/src/enableContractSponsoring.test.ts +++ /dev/null @@ -1,102 +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 chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import usingApi from './substrate/substrate-api'; -import {deployFlipper, getFlipValue, toggleFlipValueExpectSuccess} from './util/contracthelpers'; -import { - enableContractSponsoringExpectFailure, - enableContractSponsoringExpectSuccess, - findUnusedAddress, - setContractSponsoringRateLimitExpectSuccess, -} from './util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -// todo:playgrounds skipped ~ postponed -describe.skip('Integration Test enableContractSponsoring', () => { - it('ensure tx fee is paid from endowment', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const user = await findUnusedAddress(api, privateKeyWrapper); - - const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); - await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); - await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 1); - await toggleFlipValueExpectSuccess(user, flipper); - - expect(await getFlipValue(flipper, deployer)).to.be.false; - }); - }); - - it('ensure it can be enabled twice', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); - - await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); - await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); - }); - }); - - it('ensure it can be disabled twice', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); - - await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); - await enableContractSponsoringExpectSuccess(deployer, flipper.address, false); - await enableContractSponsoringExpectSuccess(deployer, flipper.address, false); - }); - }); - - it('ensure it can be re-enabled', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); - - await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); - await enableContractSponsoringExpectSuccess(deployer, flipper.address, false); - await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); - }); - }); - -}); - -describe.skip('Negative Integration Test enableContractSponsoring', () => { - let alice: IKeyringPair; - - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - }); - }); - - it('fails when called for non-contract address', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const user = await findUnusedAddress(api, privateKeyWrapper); - - await enableContractSponsoringExpectFailure(alice, user.address, true); - }); - }); - - it('fails when called by non-owning user', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [flipper] = await deployFlipper(api, privateKeyWrapper); - - await enableContractSponsoringExpectFailure(alice, flipper.address, true); - }); - }); -}); --- a/tests/src/eth/base.test.ts +++ b/tests/src/eth/base.test.ts @@ -14,16 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import { - ethBalanceViaSub, - GAS_ARGS, - recordEthFee, -} from './util/helpers'; import {Contract} from 'web3-eth-contract'; import {IKeyringPair} from '@polkadot/types/types'; import {EthUniqueHelper, itEth, usingEthPlaygrounds, expect} from './util/playgrounds'; + describe('Contract calls', () => { let donor: IKeyringPair; @@ -37,15 +33,15 @@ const deployer = await helper.eth.createAccountWithBalance(donor); const flipper = await helper.eth.deployFlipper(deployer); - const cost = await recordEthFee(helper.api!, deployer, () => flipper.methods.flip().send({from: deployer})); + const cost = await helper.eth.calculateFee({Ethereum: deployer}, () => flipper.methods.flip().send({from: deployer})); expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))).to.be.true; }); itEth('Balance transfer fee is less than 0.2 UNQ', async ({helper}) => { const userA = await helper.eth.createAccountWithBalance(donor); const userB = helper.eth.createAccount(); - const cost = await recordEthFee(helper.api!, userA, () => helper.web3!.eth.sendTransaction({from: userA, to: userB, value: '1000000', ...GAS_ARGS})); - const balanceB = await ethBalanceViaSub(helper.api!, userB); + const cost = await helper.eth.calculateFee({Ethereum: userA}, () => helper.getWeb3().eth.sendTransaction({from: userA, to: userB, value: '1000000', gas: helper.eth.DEFAULT_GAS})); + const balanceB = await helper.balance.getEthereum(userB); expect(cost - balanceB < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))).to.be.true; }); @@ -60,7 +56,7 @@ const address = helper.ethAddress.fromCollectionId(collection.collectionId); const contract = helper.ethNativeContract.collection(address, 'nft', caller); - const cost = await recordEthFee(helper.api!, caller, () => contract.methods.transfer(receiver, tokenId).send(caller)); + const cost = await helper.eth.calculateFee({Ethereum: caller}, () => contract.methods.transfer(receiver, tokenId).send(caller)); const fee = Number(cost) / Number(helper.balance.getOneTokenNominal()); const expectedFee = 0.15; --- a/tests/src/eth/collectionSponsoring.test.ts +++ b/tests/src/eth/collectionSponsoring.test.ts @@ -1,33 +1,45 @@ -import {addToAllowListExpectSuccess, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, getDetailedCollectionInfo, setCollectionSponsorExpectSuccess, UNIQUE} from '../util/helpers'; -import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents, createEthAccountWithBalance, evmCollectionHelpers, getCollectionAddressFromResult, evmCollection, ethBalanceViaSub} from './util/helpers'; -import nonFungibleAbi from './nonFungibleAbi.json'; -import {expect} from 'chai'; -import {evmToAddress} from '@polkadot/util-crypto'; +import {IKeyringPair} from '@polkadot/types/types'; +import {usingPlaygrounds} from './../util/playgrounds/index'; +import {itEth, expect} from '../eth/util/playgrounds'; describe('evm collection sponsoring', () => { - itWeb3('sponsors mint transactions', async ({web3, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); + let donor: IKeyringPair; + let alice: IKeyringPair; + let nominal: bigint; + + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + nominal = helper.balance.getOneTokenNominal(); + }); + }); + + beforeEach(async () => { + await usingPlaygrounds(async (helper) => { + [alice] = await helper.arrange.createAccounts([1000n], donor); + }); + }); - const collection = await createCollectionExpectSuccess(); - await setCollectionSponsorExpectSuccess(collection, alice.address); - await confirmSponsorshipExpectSuccess(collection); + itEth('sponsors mint transactions', async ({helper}) => { + const collection = await helper.nft.mintCollection(alice, {tokenPrefix: 'spnr', permissions: {mintMode: true}}); + await collection.setSponsor(alice, alice.address); + await collection.confirmSponsorship(alice); - const minter = createEthAccount(web3); - expect(await web3.eth.getBalance(minter)).to.equal('0'); + const minter = helper.eth.createAccount(); + expect(await helper.balance.getEthereum(minter)).to.equal(0n); - const address = collectionIdToAddress(collection); - const contract = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collection), {from: minter, ...GAS_ARGS}); + const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', minter); - await enablePublicMintingExpectSuccess(alice, collection); - await addToAllowListExpectSuccess(alice, collection, {Ethereum: minter}); + await collection.addToAllowList(alice, {Ethereum: minter}); const nextTokenId = await contract.methods.nextTokenId().call(); expect(nextTokenId).to.equal('1'); const result = await contract.methods.mint(minter, nextTokenId).send(); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { - address, + address: collectionAddress, event: 'Transfer', args: { from: '0x0000000000000000000000000000000000000000', @@ -59,13 +71,14 @@ // expect(bigIntToSub(api, BigInt(sponsorTuple[1]))).to.be.eq(sponsor.address); // }); - itWeb3('Remove sponsor', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const collectionHelpers = evmCollectionHelpers(web3, owner); - let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * UNIQUE)}); - const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + itEth('Remove sponsor', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner); + + let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)}); + const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const collectionEvm = helper.ethNativeContract.collection(collectionIdAddress, 'nft', owner); expect(await collectionEvm.methods.hasCollectionPendingSponsor().call({from: owner})).to.be.false; result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner}); @@ -80,30 +93,31 @@ expect(sponsorTuple.field_0).to.be.eq('0x0000000000000000000000000000000000000000'); }); - itWeb3('Sponsoring collection from evm address via access list', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const collectionHelpers = evmCollectionHelpers(web3, owner); - let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * UNIQUE)}); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + itEth('Sponsoring collection from evm address via access list', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner); + + let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)}); + const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); + const collectionId = helper.ethAddress.extractCollectionId(collectionIdAddress); + const collection = helper.nft.getCollectionObject(collectionId); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const collectionEvm = helper.ethNativeContract.collection(collectionIdAddress, 'nft', owner); + result = await collectionEvm.methods.setCollectionSponsor(sponsor).send({from: owner}); - let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!; - const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format; - expect(collectionSub.sponsorship.isUnconfirmed).to.be.true; - expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format))); + let collectionData = (await collection.getData())!; + expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor'); await collectionEvm.methods.confirmCollectionSponsorship().send({from: sponsor}); - collectionSub = (await getDetailedCollectionInfo(api, collectionId))!; - expect(collectionSub.sponsorship.isConfirmed).to.be.true; - expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format))); + collectionData = (await collection.getData())!; + expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); - const user = createEthAccount(web3); + const user = helper.eth.createAccount(); const nextTokenId = await collectionEvm.methods.nextTokenId().call(); expect(nextTokenId).to.be.equal('1'); - const oldPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman(); + const oldPermissions = (await collection.getData())!.raw.permissions; // (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman(); expect(oldPermissions.mintMode).to.be.false; expect(oldPermissions.access).to.be.equal('Normal'); @@ -111,12 +125,12 @@ await collectionEvm.methods.addToCollectionAllowList(user).send({from: owner}); await collectionEvm.methods.setCollectionMintMode(true).send({from: owner}); - const newPermissions = (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman(); + const newPermissions = (await collection.getData())!.raw.permissions; // (await getDetailedCollectionInfo(api, collectionId))!.permissions.toHuman(); expect(newPermissions.mintMode).to.be.true; expect(newPermissions.access).to.be.equal('AllowList'); - const ownerBalanceBefore = await ethBalanceViaSub(api, owner); - const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor); + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); { const nextTokenId = await collectionEvm.methods.nextTokenId().call(); @@ -126,7 +140,7 @@ nextTokenId, 'Test URI', ).send({from: user}); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { @@ -140,8 +154,8 @@ }, ]); - const ownerBalanceAfter = await ethBalanceViaSub(api, owner); - const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor); + const ownerBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(owner)); + const sponsorBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); expect(await collectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); expect(ownerBalanceBefore).to.be.eq(ownerBalanceAfter); @@ -205,33 +219,34 @@ // } // }); - itWeb3('Check that transaction via EVM spend money from sponsor address', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const collectionHelpers = evmCollectionHelpers(web3, owner); - let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * UNIQUE)}); - const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const collectionEvm = evmCollection(web3, owner, collectionIdAddress); + itEth('Check that transaction via EVM spend money from sponsor address', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const collectionHelpers = helper.ethNativeContract.collectionHelpers(owner); + + let result = await collectionHelpers.methods.createNonfungibleCollection('Sponsor collection', '1', '1').send({value: Number(2n * nominal)}); + const collectionIdAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); + const collectionId = helper.ethAddress.extractCollectionId(collectionIdAddress); + const collection = helper.nft.getCollectionObject(collectionId); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const collectionEvm = helper.ethNativeContract.collection(collectionIdAddress, 'nft', owner); + result = await collectionEvm.methods.setCollectionSponsor(sponsor).send(); - let collectionSub = (await getDetailedCollectionInfo(api, collectionId))!; - const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format; - expect(collectionSub.sponsorship.isUnconfirmed).to.be.true; - expect(collectionSub.sponsorship.asUnconfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format))); + let collectionData = (await collection.getData())!; + expect(collectionData.raw.sponsorship.Unconfirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); await expect(collectionEvm.methods.confirmCollectionSponsorship().call()).to.be.rejectedWith('caller is not set as sponsor'); - const sponsorCollection = evmCollection(web3, sponsor, collectionIdAddress); + + const sponsorCollection = helper.ethNativeContract.collection(collectionIdAddress, 'nft', sponsor); await sponsorCollection.methods.confirmCollectionSponsorship().send(); - collectionSub = (await getDetailedCollectionInfo(api, collectionId))!; - expect(collectionSub.sponsorship.isConfirmed).to.be.true; - expect(collectionSub.sponsorship.asConfirmed.toHuman()).to.be.eq(evmToAddress(sponsor, Number(ss58Format))); + collectionData = (await collection.getData())!; + expect(collectionData.raw.sponsorship.Confirmed).to.be.eq(helper.address.ethToSubstrate(sponsor, true)); - const user = createEthAccount(web3); + const user = helper.eth.createAccount(); await collectionEvm.methods.addCollectionAdmin(user).send(); - const ownerBalanceBefore = await ethBalanceViaSub(api, owner); - const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor); - - - const userCollectionEvm = evmCollection(web3, user, collectionIdAddress); + const ownerBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); + const sponsorBalanceBefore = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); + + const userCollectionEvm = helper.ethNativeContract.collection(collectionIdAddress, 'nft', user); const nextTokenId = await userCollectionEvm.methods.nextTokenId().call(); expect(nextTokenId).to.be.equal('1'); result = await userCollectionEvm.methods.mintWithTokenURI( @@ -240,8 +255,8 @@ 'Test URI', ).send(); - const events = normalizeEvents(result.events); - const address = collectionIdToAddress(collectionId); + const events = helper.eth.normalizeEvents(result.events); + const address = helper.ethAddress.fromCollectionId(collectionId); expect(events).to.be.deep.equal([ { @@ -256,9 +271,9 @@ ]); expect(await userCollectionEvm.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI'); - const ownerBalanceAfter = await ethBalanceViaSub(api, owner); + const ownerBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(owner)); expect(ownerBalanceAfter).to.be.eq(ownerBalanceBefore); - const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor); + const sponsorBalanceAfter = await helper.balance.getSubstrate(helper.address.ethToSubstrate(sponsor)); expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; }); }); --- a/tests/src/eth/contractSponsoring.test.ts +++ b/tests/src/eth/contractSponsoring.test.ts @@ -14,45 +14,39 @@ // 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 * as solc from 'solc'; -import {expect} from 'chai'; -import {expectSubstrateEventsAtBlock} from '../util/helpers'; -import Web3 from 'web3'; +import {EthUniqueHelper} from './util/playgrounds/unique.dev'; +import {itEth, expect, SponsoringMode, usingEthPlaygrounds} from '../eth/util/playgrounds'; +import {usingPlaygrounds} from '../util/playgrounds'; +import {CompiledContract} from './util/playgrounds/types'; -import { - contractHelpers, - createEthAccountWithBalance, - transferBalanceToEth, - deployFlipper, - itWeb3, - SponsoringMode, - createEthAccount, - ethBalanceViaSub, - normalizeEvents, - CompiledContract, - GAS_ARGS, - subToEth, -} from './util/helpers'; -import {submitTransactionAsync} from '../substrate/substrate-api'; +describe('Sponsoring EVM contracts', () => { + let donor: IKeyringPair; + + before(async () => { + await usingPlaygrounds(async (_helper, privateKey) => { + donor = privateKey('//Alice'); + }); + }); + + itEth('Self sponsored can be set by the address that deployed the contract', async ({helper, privateKey}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const flipper = await helper.eth.deployFlipper(owner); + const helpers = helper.ethNativeContract.contractHelpers(owner); -describe('Sponsoring EVM contracts', () => { - itWeb3('Self sponsored can be set by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; await expect(helpers.methods.selfSponsoredEnable(flipper.options.address).send()).to.be.not.rejected; expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true; }); - itWeb3('Set self sponsored events', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Set self sponsored events', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const flipper = await helper.eth.deployFlipper(owner); + const helpers = helper.ethNativeContract.contractHelpers(owner); const result = await helpers.methods.selfSponsoredEnable(flipper.options.address).send(); - // console.log(result); - const ethEvents = normalizeEvents(result.events); + const ethEvents = helper.eth.helper.eth.normalizeEvents(result.events); expect(ethEvents).to.be.deep.equal([ { address: flipper.options.address, @@ -71,62 +65,59 @@ }, }, ]); - - await expectSubstrateEventsAtBlock( - api, - result.blockNumber, - 'evmContractHelpers', - ['ContractSponsorSet','ContractSponsorshipConfirmed'], - ); }); - itWeb3('Self sponsored can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Self sponsored can not be set by the address that did not deployed the contract', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const notOwner = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; await expect(helpers.methods.selfSponsoredEnable(flipper.options.address).call({from: notOwner})).to.be.rejectedWith('NoPermission'); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; }); - itWeb3('Sponsoring can be set by the address that has deployed the contract', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Sponsoring can be set by the address that has deployed the contract', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false; await expect(helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner})).to.be.not.rejected; expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true; }); - itWeb3('Sponsoring cannot be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Sponsoring cannot be set by the address that did not deployed the contract', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const notOwner = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false; await expect(helpers.methods.setSponsoringMode(notOwner, SponsoringMode.Allowlisted).call({from: notOwner})).to.be.rejectedWith('NoPermission'); expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.false; }); - itWeb3('Sponsor can be set by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Sponsor can be set by the address that deployed the contract', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false; await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected; expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.true; }); - itWeb3('Set sponsor event', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Set sponsor event', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); const result = await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { address: flipper.options.address, @@ -137,45 +128,41 @@ }, }, ]); - - await expectSubstrateEventsAtBlock( - api, - result.blockNumber, - 'evmContractHelpers', - ['ContractSponsorSet'], - ); }); - itWeb3('Sponsor can not be set by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Sponsor can not be set by the address that did not deployed the contract', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const notOwner = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false; await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).call({from: notOwner})).to.be.rejectedWith('NoPermission'); expect(await helpers.methods.hasPendingSponsor(flipper.options.address).call()).to.be.false; }); - itWeb3('Sponsorship can be confirmed by the address that pending as sponsor', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Sponsorship can be confirmed by the address that pending as sponsor', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected; await expect(helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor})).to.be.not.rejected; expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true; }); - itWeb3('Confirm sponsorship event', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Confirm sponsorship event', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected; const result = await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { address: flipper.options.address, @@ -186,41 +173,37 @@ }, }, ]); + }); - await expectSubstrateEventsAtBlock( - api, - result.blockNumber, - 'evmContractHelpers', - ['ContractSponsorshipConfirmed'], - ); - }); + itEth('Sponsorship can not be confirmed by the address that not pending as sponsor', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const notSponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); - itWeb3('Sponsorship can not be confirmed by the address that not pending as sponsor', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const notSponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; await expect(helpers.methods.setSponsor(flipper.options.address, sponsor).send()).to.be.not.rejected; await expect(helpers.methods.confirmSponsorship(flipper.options.address).call({from: notSponsor})).to.be.rejectedWith('NoPermission'); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; }); - itWeb3('Sponsorship can not be confirmed by the address that not set as sponsor', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const notSponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Sponsorship can not be confirmed by the address that not set as sponsor', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const notSponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; await expect(helpers.methods.confirmSponsorship(flipper.options.address).call({from: notSponsor})).to.be.rejectedWith('NoPendingSponsor'); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; }); - itWeb3('Get self sponsored sponsor', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Get self sponsored sponsor', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + await helpers.methods.selfSponsoredEnable(flipper.options.address).send(); const result = await helpers.methods.sponsor(flipper.options.address).call(); @@ -229,11 +212,12 @@ expect(result[1]).to.be.eq('0'); }); - itWeb3('Get confirmed sponsor', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Get confirmed sponsor', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); @@ -243,11 +227,11 @@ expect(result[1]).to.be.eq('0'); }); - itWeb3('Sponsor can be removed by the address that deployed the contract', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Sponsor can be removed by the address that deployed the contract', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); @@ -258,17 +242,17 @@ expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; }); - itWeb3('Remove sponsor event', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Remove sponsor event', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); const result = await helpers.methods.removeSponsor(flipper.options.address).send(); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { address: flipper.options.address, @@ -278,21 +262,14 @@ }, }, ]); - - await expectSubstrateEventsAtBlock( - api, - result.blockNumber, - 'evmContractHelpers', - ['ContractSponsorRemoved'], - ); }); - itWeb3('Sponsor can not be removed by the address that did not deployed the contract', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const notOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Sponsor can not be removed by the address that did not deployed the contract', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const notOwner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.false; await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); @@ -303,14 +280,12 @@ expect(await helpers.methods.hasSponsor(flipper.options.address).call()).to.be.true; }); - itWeb3('In generous mode, non-allowlisted user transaction will be sponsored', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const flipper = await deployFlipper(web3, owner); - - const helpers = contractHelpers(web3, owner); + itEth('In generous mode, non-allowlisted user transaction will be sponsored', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const caller = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); @@ -318,57 +293,52 @@ await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner}); await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner}); - const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor); - const callerBalanceBefore = await ethBalanceViaSub(api, caller); + const sponsorBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); + const callerBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(caller)); await flipper.methods.flip().send({from: caller}); expect(await flipper.methods.getValue().call()).to.be.true; // Balance should be taken from sponsor instead of caller - const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor); - const callerBalanceAfter = await ethBalanceViaSub(api, caller); + const sponsorBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); + const callerBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(caller)); expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; expect(callerBalanceAfter).to.be.eq(callerBalanceBefore); }); - - itWeb3('In generous mode, non-allowlisted user transaction will be self sponsored', async ({api, web3, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('In generous mode, non-allowlisted user transaction will be self sponsored', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const caller = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); await helpers.methods.selfSponsoredEnable(flipper.options.address).send(); await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Generous).send({from: owner}); await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner}); - await transferBalanceToEth(api, alice, flipper.options.address); + await helper.eth.transferBalanceFromSubstrate(donor, flipper.options.address); - const contractBalanceBefore = await ethBalanceViaSub(api, flipper.options.address); - const callerBalanceBefore = await ethBalanceViaSub(api, caller); + const contractBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(flipper.options.address)); + const callerBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(caller)); await flipper.methods.flip().send({from: caller}); expect(await flipper.methods.getValue().call()).to.be.true; // Balance should be taken from sponsor instead of caller - const contractBalanceAfter = await ethBalanceViaSub(api, flipper.options.address); - const callerBalanceAfter = await ethBalanceViaSub(api, caller); + const contractBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(flipper.options.address)); + const callerBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(caller)); expect(contractBalanceAfter < contractBalanceBefore).to.be.true; expect(callerBalanceAfter).to.be.eq(callerBalanceBefore); }); - itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const caller = createEthAccount(web3); - - const flipper = await deployFlipper(web3, owner); + itEth('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should decrease (allowlisted)', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const caller = helper.eth.createAccount(); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); - const helpers = contractHelpers(web3, owner); await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner}); await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner}); @@ -378,51 +348,47 @@ await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); - const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor); + const sponsorBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); expect(sponsorBalanceBefore).to.be.not.equal('0'); await flipper.methods.flip().send({from: caller}); expect(await flipper.methods.getValue().call()).to.be.true; // Balance should be taken from flipper instead of caller - const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor); + const sponsorBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; }); - itWeb3('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({api, web3, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const caller = createEthAccount(web3); - - const flipper = await deployFlipper(web3, owner); + itEth('Sponsoring is set, an address that has no UNQ can send a transaction and it works. Sponsor balance should not decrease (non-allowlisted)', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const caller = await helper.eth.createAccount(); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); - const helpers = contractHelpers(web3, owner); - await helpers.methods.setSponsoringMode(flipper.options.address, SponsoringMode.Allowlisted).send({from: owner}); await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner}); - await transferBalanceToEth(api, alice, flipper.options.address); + await helper.eth.transferBalanceFromSubstrate(donor, flipper.options.address); - const originalFlipperBalance = await web3.eth.getBalance(flipper.options.address); + const originalFlipperBalance = await helper.balance.getEthereum(flipper.options.address); expect(originalFlipperBalance).to.be.not.equal('0'); await expect(flipper.methods.flip().send({from: caller})).to.be.rejectedWith(/InvalidTransaction::Payment/); expect(await flipper.methods.getValue().call()).to.be.false; // Balance should be taken from flipper instead of caller - const balanceAfter = await web3.eth.getBalance(flipper.options.address); - expect(+balanceAfter).to.be.equals(+originalFlipperBalance); + // FIXME the comment is wrong! What check should be here? + const balanceAfter = await helper.balance.getEthereum(flipper.options.address); + expect(balanceAfter).to.be.equals(originalFlipperBalance); }); - itWeb3('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const flipper = await deployFlipper(web3, owner); + itEth('Sponsoring is set, an address that has UNQ can send a transaction and it works. User balance should not change', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const caller = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); - const helpers = contractHelpers(web3, owner); await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner}); await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner}); @@ -432,27 +398,26 @@ await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); - const sponsorBalanceBefore = await ethBalanceViaSub(api, sponsor); - const callerBalanceBefore = await ethBalanceViaSub(api, caller); + const sponsorBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); + const callerBalanceBefore = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(caller)); await flipper.methods.flip().send({from: caller}); expect(await flipper.methods.getValue().call()).to.be.true; - const sponsorBalanceAfter = await ethBalanceViaSub(api, sponsor); - const callerBalanceAfter = await ethBalanceViaSub(api, caller); + const sponsorBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(sponsor)); + const callerBalanceAfter = await helper.balance.getSubstrate(await helper.address.ethToSubstrate(caller)); expect(sponsorBalanceAfter < sponsorBalanceBefore).to.be.true; expect(callerBalanceAfter).to.be.equals(callerBalanceBefore); }); - itWeb3('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const originalCallerBalance = await web3.eth.getBalance(caller); - - const flipper = await deployFlipper(web3, owner); - - const helpers = contractHelpers(web3, owner); + itEth('Sponsoring is limited, with setContractRateLimit. The limitation is working if transactions are sent more often, the sender pays the commission.', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const caller = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + + const originalCallerBalance = await helper.balance.getEthereum(caller); await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner}); await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner}); @@ -462,34 +427,36 @@ await helpers.methods.setSponsor(flipper.options.address, sponsor).send(); await helpers.methods.confirmSponsorship(flipper.options.address).send({from: sponsor}); - const originalFlipperBalance = await web3.eth.getBalance(sponsor); + const originalFlipperBalance = await helper.balance.getEthereum(sponsor); expect(originalFlipperBalance).to.be.not.equal('0'); await flipper.methods.flip().send({from: caller}); expect(await flipper.methods.getValue().call()).to.be.true; - expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance); + expect(await helper.balance.getEthereum(caller)).to.be.equals(originalCallerBalance); - const newFlipperBalance = await web3.eth.getBalance(sponsor); + const newFlipperBalance = await helper.balance.getEthereum(sponsor); expect(newFlipperBalance).to.be.not.equals(originalFlipperBalance); await flipper.methods.flip().send({from: caller}); - expect(await web3.eth.getBalance(sponsor)).to.be.equal(newFlipperBalance); - expect(await web3.eth.getBalance(caller)).to.be.not.equals(originalCallerBalance); + expect(await helper.balance.getEthereum(sponsor)).to.be.equal(newFlipperBalance); + expect(await helper.balance.getEthereum(caller)).to.be.not.equals(originalCallerBalance); }); // TODO: Find a way to calculate default rate limit - itWeb3('Default rate limit equals 7200', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Default rate limit equals 7200', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + expect(await helpers.methods.sponsoringRateLimit(flipper.options.address).call()).to.be.equals('7200'); }); }); describe('Sponsoring Fee Limit', () => { + let donor: IKeyringPair; + let alice: IKeyringPair; + let DEFAULT_GAS: number; - let testContract: CompiledContract; - function compileTestContract() { if (!testContract) { const input = { @@ -537,46 +504,67 @@ return testContract; } - async function deployTestContract(web3: Web3, owner: string) { + async function deployTestContract(helper: EthUniqueHelper, owner: string) { + const web3 = helper.getWeb3(); const compiled = compileTestContract(); const testContract = new web3.eth.Contract(compiled.abi, undefined, { data: compiled.object, from: owner, - ...GAS_ARGS, + gas: DEFAULT_GAS, }); return await testContract.deploy({data: compiled.object}).send({from: owner}); } - itWeb3('Default fee limit', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + before(async () => { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + DEFAULT_GAS = helper.eth.DEFAULT_GAS; + }); + }); + + beforeEach(async () => { + await usingPlaygrounds(async (helper) => { + [alice] = await helper.arrange.createAccounts([1000n], donor); + }); + }); + + let testContract: CompiledContract; + + + + itEth('Default fee limit', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + expect(await helpers.methods.sponsoringFeeLimit(flipper.options.address).call()).to.be.equals('115792089237316195423570985008687907853269984665640564039457584007913129639935'); }); - itWeb3('Set fee limit', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Set fee limit', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + await helpers.methods.setSponsoringFeeLimit(flipper.options.address, 100).send(); expect(await helpers.methods.sponsoringFeeLimit(flipper.options.address).call()).to.be.equals('100'); }); - itWeb3('Negative test - set fee limit by non-owner', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const stranger = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, owner); - const helpers = contractHelpers(web3, owner); + itEth('Negative test - set fee limit by non-owner', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const stranger = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); + const flipper = await helper.eth.deployFlipper(owner); + await expect(helpers.methods.setSponsoringFeeLimit(flipper.options.address, 100).send({from: stranger})).to.be.rejected; }); - itWeb3('Negative test - check that eth transactions exceeding fee limit are not executed', async ({api, web3, privateKeyWrapper}) => { - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const user = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + itEth('Negative test - check that eth transactions exceeding fee limit are not executed', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const user = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); - const testContract = await deployTestContract(web3, owner); - const helpers = contractHelpers(web3, owner); + const testContract = await deployTestContract(helper, owner); await helpers.methods.setSponsoringMode(testContract.options.address, SponsoringMode.Generous).send({from: owner}); await helpers.methods.setSponsoringRateLimit(testContract.options.address, 0).send({from: owner}); @@ -584,24 +572,24 @@ await helpers.methods.setSponsor(testContract.options.address, sponsor).send(); await helpers.methods.confirmSponsorship(testContract.options.address).send({from: sponsor}); - const gasPrice = BigInt(await web3.eth.getGasPrice()); + const gasPrice = BigInt(await helper.getWeb3().eth.getGasPrice()); await helpers.methods.setSponsoringFeeLimit(testContract.options.address, 2_000_000n * gasPrice).send(); - const originalUserBalance = await web3.eth.getBalance(user); + const originalUserBalance = await helper.balance.getEthereum(user); await testContract.methods.test(100).send({from: user, gas: 2_000_000}); - expect(await web3.eth.getBalance(user)).to.be.equal(originalUserBalance); + expect(await helper.balance.getEthereum(user)).to.be.equal(originalUserBalance); await testContract.methods.test(100).send({from: user, gas: 2_100_000}); - expect(await web3.eth.getBalance(user)).to.not.be.equal(originalUserBalance); + expect(await helper.balance.getEthereum(user)).to.not.be.equal(originalUserBalance); }); - itWeb3('Negative test - check that evm.call transactions exceeding fee limit are not executed', async ({api, web3, privateKeyWrapper}) => { - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + itEth('Negative test - check that evm.call transactions exceeding fee limit are not executed', async ({helper, privateKey}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(owner); - const testContract = await deployTestContract(web3, owner); - const helpers = contractHelpers(web3, owner); + const testContract = await deployTestContract(helper, owner); await helpers.methods.setSponsoringMode(testContract.options.address, SponsoringMode.Generous).send({from: owner}); await helpers.methods.setSponsoringRateLimit(testContract.options.address, 0).send({from: owner}); @@ -609,43 +597,29 @@ await helpers.methods.setSponsor(testContract.options.address, sponsor).send(); await helpers.methods.confirmSponsorship(testContract.options.address).send({from: sponsor}); - const gasPrice = BigInt(await web3.eth.getGasPrice()); + const gasPrice = BigInt(await helper.getWeb3().eth.getGasPrice()); await helpers.methods.setSponsoringFeeLimit(testContract.options.address, 2_000_000n * gasPrice).send(); - const alice = privateKeyWrapper('//Alice'); - const originalAliceBalance = (await api.query.system.account(alice.address)).data.free.toBigInt(); - - await submitTransactionAsync( + const originalAliceBalance = await helper.balance.getSubstrate(alice.address); + + await helper.eth.sendEVM( alice, - api.tx.evm.call( - subToEth(alice.address), - testContract.options.address, - testContract.methods.test(100).encodeABI(), - Uint8Array.from([]), - 2_000_000n, - gasPrice, - null, - null, - [], - ), + testContract.options.address, + testContract.methods.test(100).encodeABI(), + '0', + 2_000_000, ); - expect((await api.query.system.account(alice.address)).data.free.toBigInt()).to.be.equal(originalAliceBalance); + // expect((await api.query.system.account(alice.address)).data.free.toBigInt()).to.be.equal(originalAliceBalance); + expect(await helper.balance.getSubstrate(alice.address)).to.be.equal(originalAliceBalance); - await submitTransactionAsync( + await helper.eth.sendEVM( alice, - api.tx.evm.call( - subToEth(alice.address), - testContract.options.address, - testContract.methods.test(100).encodeABI(), - Uint8Array.from([]), - 2_100_000n, - gasPrice, - null, - null, - [], - ), + testContract.options.address, + testContract.methods.test(100).encodeABI(), + '0', + 2_100_000, ); - expect((await api.query.system.account(alice.address)).data.free.toBigInt()).to.not.be.equal(originalAliceBalance); + expect(await helper.balance.getSubstrate(alice.address)).to.not.be.equal(originalAliceBalance); }); }); --- a/tests/src/eth/createNFTCollection.test.ts +++ b/tests/src/eth/createNFTCollection.test.ts @@ -17,8 +17,8 @@ import {evmToAddress} from '@polkadot/util-crypto'; import {IKeyringPair} from '@polkadot/types/types'; import {expect, itEth, usingEthPlaygrounds} from './util/playgrounds'; -import {UNIQUE} from '../util/helpers'; + describe('Create NFT collection from EVM', () => { let donor: IKeyringPair; @@ -65,7 +65,7 @@ await collectionHelpers.methods .createNonfungibleCollection('A', 'A', 'A') - .send({value: Number(2n * UNIQUE)}); + .send({value: Number(2n * helper.balance.getOneTokenNominal())}); expect(await collectionHelpers.methods .isCollectionExist(expectedCollectionAddress) @@ -147,10 +147,12 @@ describe('(!negative tests!) Create NFT collection from EVM', () => { let donor: IKeyringPair; + let nominal: bigint; before(async function() { - await usingEthPlaygrounds(async (_helper, privateKey) => { + await usingEthPlaygrounds(async (helper, privateKey) => { donor = await privateKey({filename: __filename}); + nominal = helper.balance.getOneTokenNominal(); }); }); @@ -165,7 +167,7 @@ await expect(collectionHelper.methods .createNonfungibleCollection(collectionName, description, tokenPrefix) - .call({value: Number(2n * UNIQUE)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH); + .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH); } { @@ -175,7 +177,7 @@ const tokenPrefix = 'A'; await expect(collectionHelper.methods .createNonfungibleCollection(collectionName, description, tokenPrefix) - .call({value: Number(2n * UNIQUE)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH); + .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH); } { const MAX_TOKEN_PREFIX_LENGTH = 16; @@ -184,7 +186,7 @@ const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1); await expect(collectionHelper.methods .createNonfungibleCollection(collectionName, description, tokenPrefix) - .call({value: Number(2n * UNIQUE)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH); + .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH); } }); @@ -193,7 +195,7 @@ const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); await expect(collectionHelper.methods .createNonfungibleCollection('Peasantry', 'absolutely anything', 'CVE') - .call({value: Number(1n * UNIQUE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)'); + .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)'); }); itEth('(!negative test!) Check owner', async ({helper}) => { --- a/tests/src/eth/createRFTCollection.test.ts +++ b/tests/src/eth/createRFTCollection.test.ts @@ -18,8 +18,8 @@ import {IKeyringPair} from '@polkadot/types/types'; import {Pallets, requirePalletsOrSkip} from '../util/playgrounds'; import {expect, itEth, usingEthPlaygrounds} from './util/playgrounds'; -import {UNIQUE} from '../util/helpers'; + describe('Create RFT collection from EVM', () => { let donor: IKeyringPair; @@ -66,7 +66,7 @@ await collectionHelpers.methods .createRFTCollection('A', 'A', 'A') - .send({value: Number(2n * UNIQUE)}); + .send({value: Number(2n * helper.balance.getOneTokenNominal())}); expect(await collectionHelpers.methods .isCollectionExist(expectedCollectionAddress) @@ -148,11 +148,13 @@ describe('(!negative tests!) Create RFT collection from EVM', () => { let donor: IKeyringPair; + let nominal: bigint; before(async function() { await usingEthPlaygrounds(async (helper, privateKey) => { requirePalletsOrSkip(this, helper, [Pallets.ReFungible]); donor = await privateKey({filename: __filename}); + nominal = helper.balance.getOneTokenNominal(); }); }); @@ -167,7 +169,7 @@ await expect(collectionHelper.methods .createRFTCollection(collectionName, description, tokenPrefix) - .call({value: Number(2n * UNIQUE)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH); + .call({value: Number(2n * nominal)})).to.be.rejectedWith('name is too long. Max length is ' + MAX_NAME_LENGTH); } { const MAX_DESCRIPTION_LENGTH = 256; @@ -176,7 +178,7 @@ const tokenPrefix = 'A'; await expect(collectionHelper.methods .createRFTCollection(collectionName, description, tokenPrefix) - .call({value: Number(2n * UNIQUE)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH); + .call({value: Number(2n * nominal)})).to.be.rejectedWith('description is too long. Max length is ' + MAX_DESCRIPTION_LENGTH); } { const MAX_TOKEN_PREFIX_LENGTH = 16; @@ -185,7 +187,7 @@ const tokenPrefix = 'A'.repeat(MAX_TOKEN_PREFIX_LENGTH + 1); await expect(collectionHelper.methods .createRFTCollection(collectionName, description, tokenPrefix) - .call({value: Number(2n * UNIQUE)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH); + .call({value: Number(2n * nominal)})).to.be.rejectedWith('token_prefix is too long. Max length is ' + MAX_TOKEN_PREFIX_LENGTH); } }); @@ -194,7 +196,7 @@ const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); await expect(collectionHelper.methods .createRFTCollection('Peasantry', 'absolutely anything', 'TWIW') - .call({value: Number(1n * UNIQUE)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)'); + .call({value: Number(1n * nominal)})).to.be.rejectedWith('Sent amount not equals to collection creation price (2000000000000000000)'); }); itEth('(!negative test!) Check owner', async ({helper}) => { --- a/tests/src/eth/marketplace/marketplace.test.ts +++ b/tests/src/eth/marketplace/marketplace.test.ts @@ -14,215 +14,208 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . +import {usingPlaygrounds} from './../../util/playgrounds/index'; +import {IKeyringPair} from '@polkadot/types/types'; import {readFile} from 'fs/promises'; -import {getBalanceSingle} from '../../substrate/get-balance'; -import { - addToAllowListExpectSuccess, - confirmSponsorshipExpectSuccess, - createCollectionExpectSuccess, - createItemExpectSuccess, - getTokenOwner, - setCollectionLimitsExpectSuccess, - setCollectionSponsorExpectSuccess, - transferExpectSuccess, - transferFromExpectSuccess, - transferBalanceTo, -} from '../../util/helpers'; -import {collectionIdToAddress, contractHelpers, createEthAccountWithBalance, executeEthTxOnSub, GAS_ARGS, itWeb3, SponsoringMode, subToEth, subToEthLowercase, transferBalanceToEth} from '../util/helpers'; -import {evmToAddress} from '@polkadot/util-crypto'; -import nonFungibleAbi from '../nonFungibleAbi.json'; +import {itEth, expect, SponsoringMode} from '../util/playgrounds'; -import {expect} from 'chai'; +describe('Matcher contract usage', () => { + const PRICE = 2000n; + let donor: IKeyringPair; + let alice: IKeyringPair; + let aliceMirror: string; + let aliceDoubleMirror: string; + let seller: IKeyringPair; + let sellerMirror: string; + + before(async () => { + await usingPlaygrounds(async (_helper, privateKey) => { + donor = privateKey('//Alice'); + }); + }); + + beforeEach(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + [alice] = await helper.arrange.createAccounts([10000n], donor); + aliceMirror = helper.address.substrateToEth(alice.address).toLowerCase(); + aliceDoubleMirror = helper.address.ethToSubstrate(aliceMirror); + seller = privateKey(`//Seller/${Date.now()}`); + sellerMirror = helper.address.substrateToEth(seller.address).toLowerCase(); -const PRICE = 2000n; + await helper.balance.transferToSubstrate(donor, aliceDoubleMirror, 10_000_000_000_000_000_000n); + }); + }); -describe('Matcher contract usage', () => { - itWeb3('With UNQ', async ({api, web3, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + itEth('With UNQ', async ({helper}) => { + const web3 = helper.getWeb3(); + const matcherOwner = await helper.eth.createAccountWithBalance(donor); const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, { from: matcherOwner, - ...GAS_ARGS, + gas: helper.eth.DEFAULT_GAS, }); const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlace.bin`)).toString(), arguments:[matcherOwner]}).send({from: matcherOwner}); - const helpers = contractHelpers(web3, matcherOwner); + + const sponsor = await helper.eth.createAccountWithBalance(donor); + const helpers = helper.ethNativeContract.contractHelpers(matcherOwner); await helpers.methods.setSponsoringMode(matcher.options.address, SponsoringMode.Allowlisted).send({from: matcherOwner}); await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).send({from: matcherOwner}); await helpers.methods.setSponsor(matcher.options.address, sponsor).send({from: matcherOwner}); await helpers.methods.confirmSponsorship(matcher.options.address).send({from: sponsor}); - - const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionLimitsExpectSuccess(alice, collectionId, {sponsorApproveTimeout: 1}); - const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId), {from: matcherOwner}); - await setCollectionSponsorExpectSuccess(collectionId, alice.address); - await transferBalanceToEth(api, alice, subToEth(alice.address)); - await confirmSponsorshipExpectSuccess(collectionId); - await helpers.methods.toggleAllowed(matcher.options.address, subToEth(alice.address), true).send({from: matcherOwner}); - await addToAllowListExpectSuccess(alice, collectionId, evmToAddress(subToEth(alice.address))); - - const seller = privateKeyWrapper(`//Seller/${Date.now()}`); - await helpers.methods.toggleAllowed(matcher.options.address, subToEth(seller.address), true).send({from: matcherOwner}); + const collection = await helper.nft.mintCollection(alice, {limits: {sponsorApproveTimeout: 1}, pendingSponsor: alice.address}); + await collection.confirmSponsorship(alice); + await collection.addToAllowList(alice, {Substrate: aliceDoubleMirror}); + const evmCollection = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft'); + await helper.eth.transferBalanceFromSubstrate(donor, aliceMirror); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', seller.address); + await helpers.methods.toggleAllowed(matcher.options.address, aliceMirror, true).send({from: matcherOwner}); + await helpers.methods.toggleAllowed(matcher.options.address, sellerMirror, true).send({from: matcherOwner}); - // To transfer item to matcher it first needs to be transfered to EVM account of bob - await transferExpectSuccess(collectionId, tokenId, seller, {Ethereum: subToEth(seller.address)}); + const token = await collection.mintToken(alice, {Ethereum: sellerMirror}); // Token is owned by seller initially - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(seller.address)}); + expect(await token.getOwner()).to.be.deep.equal({Ethereum: sellerMirror}); // Ask { - await executeEthTxOnSub(web3, api, seller, evmCollection, m => m.approve(matcher.options.address, tokenId)); - await executeEthTxOnSub(web3, api, seller, matcher, m => m.addAsk(PRICE, '0x0000000000000000000000000000000000000001', evmCollection.options.address, tokenId)); + await helper.eth.sendEVM(seller, evmCollection.options.address, evmCollection.methods.approve(matcher.options.address, token.tokenId).encodeABI(), '0'); + await helper.eth.sendEVM(seller, matcher.options.address, matcher.methods.addAsk(PRICE, '0x0000000000000000000000000000000000000001', evmCollection.options.address, token.tokenId).encodeABI(), '0'); } // Token is transferred to matcher - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: matcher.options.address.toLowerCase()}); + expect(await token.getOwner()).to.be.deep.equal({Ethereum: matcher.options.address.toLowerCase()}); // Buy { - const sellerBalanceBeforePurchase = await getBalanceSingle(api, seller.address); - await executeEthTxOnSub(web3, api, alice, matcher, m => m.buy(evmCollection.options.address, tokenId), {value: PRICE}); - expect(await getBalanceSingle(api, seller.address) - sellerBalanceBeforePurchase === PRICE); + const sellerBalanceBeforePurchase = await helper.balance.getSubstrate(seller.address); + await helper.eth.sendEVM(alice, matcher.options.address, matcher.methods.buy(evmCollection.options.address, token.tokenId).encodeABI(), PRICE.toString()); + expect(await helper.balance.getSubstrate(seller.address) - sellerBalanceBeforePurchase === PRICE); } // Token is transferred to evm account of alice - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(alice.address)}); + expect(await token.getOwner()).to.be.deep.equal({Ethereum: aliceMirror}); // Transfer token to substrate side of alice - await transferFromExpectSuccess(collectionId, tokenId, alice, {Ethereum: subToEth(alice.address)}, {Substrate: alice.address}); + await token.transferFrom(alice, {Ethereum: aliceMirror}, {Substrate: alice.address}); // Token is transferred to substrate account of alice, seller received funds - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Substrate: alice.address}); + expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); }); - - itWeb3('With escrow', async ({api, web3, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const escrow = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + itEth('With escrow', async ({helper}) => { + const web3 = helper.getWeb3(); + const matcherOwner = await helper.eth.createAccountWithBalance(donor); const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, { from: matcherOwner, - ...GAS_ARGS, + gas: helper.eth.DEFAULT_GAS, }); const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlace.bin`)).toString(), arguments: [matcherOwner]}).send({from: matcherOwner, gas: 10000000}); + + const sponsor = await helper.eth.createAccountWithBalance(donor); + const escrow = await helper.eth.createAccountWithBalance(donor); await matcher.methods.setEscrow(escrow).send({from: matcherOwner}); - const helpers = contractHelpers(web3, matcherOwner); + const helpers = helper.ethNativeContract.contractHelpers(matcherOwner); await helpers.methods.setSponsoringMode(matcher.options.address, SponsoringMode.Allowlisted).send({from: matcherOwner}); await helpers.methods.setSponsoringRateLimit(matcher.options.address, 1).send({from: matcherOwner}); await helpers.methods.setSponsor(matcher.options.address, sponsor).send({from: matcherOwner}); await helpers.methods.confirmSponsorship(matcher.options.address).send({from: sponsor}); - const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionLimitsExpectSuccess(alice, collectionId, {sponsorApproveTimeout: 1}); - const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId), {from: matcherOwner}); - await setCollectionSponsorExpectSuccess(collectionId, alice.address); - await transferBalanceToEth(api, alice, subToEth(alice.address)); - await confirmSponsorshipExpectSuccess(collectionId); + const collection = await helper.nft.mintCollection(alice, {limits: {sponsorApproveTimeout: 1}, pendingSponsor: alice.address}); + await collection.confirmSponsorship(alice); + await collection.addToAllowList(alice, {Substrate: aliceDoubleMirror}); + const evmCollection = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft'); + await helper.eth.transferBalanceFromSubstrate(donor, aliceMirror); - await helpers.methods.toggleAllowed(matcher.options.address, subToEth(alice.address), true).send({from: matcherOwner}); - await addToAllowListExpectSuccess(alice, collectionId, evmToAddress(subToEth(alice.address))); - const seller = privateKeyWrapper(`//Seller/${Date.now()}`); - await helpers.methods.toggleAllowed(matcher.options.address, subToEth(seller.address), true).send({from: matcherOwner}); + await helpers.methods.toggleAllowed(matcher.options.address, aliceMirror, true).send({from: matcherOwner}); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', seller.address); + await helpers.methods.toggleAllowed(matcher.options.address, sellerMirror, true).send({from: matcherOwner}); - // To transfer item to matcher it first needs to be transfered to EVM account of bob - await transferExpectSuccess(collectionId, tokenId, seller, {Ethereum: subToEth(seller.address)}); + const token = await collection.mintToken(alice, {Ethereum: sellerMirror}); // Token is owned by seller initially - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(seller.address)}); + expect(await token.getOwner()).to.be.deep.equal({Ethereum: sellerMirror}); // Ask { - await executeEthTxOnSub(web3, api, seller, evmCollection, m => m.approve(matcher.options.address, tokenId)); - await executeEthTxOnSub(web3, api, seller, matcher, m => m.addAsk(PRICE, '0x0000000000000000000000000000000000000001', evmCollection.options.address, tokenId)); + await helper.eth.sendEVM(seller, evmCollection.options.address, evmCollection.methods.approve(matcher.options.address, token.tokenId).encodeABI(), '0'); + await helper.eth.sendEVM(seller, matcher.options.address, matcher.methods.addAsk(PRICE, '0x0000000000000000000000000000000000000001', evmCollection.options.address, token.tokenId).encodeABI(), '0'); } // Token is transferred to matcher - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: matcher.options.address.toLowerCase()}); + expect(await token.getOwner()).to.be.deep.equal({Ethereum: matcher.options.address.toLowerCase()}); // Give buyer KSM - await matcher.methods.depositKSM(PRICE, subToEth(alice.address)).send({from: escrow}); + await matcher.methods.depositKSM(PRICE, aliceMirror).send({from: escrow}); // Buy { - expect(await matcher.methods.balanceKSM(subToEth(seller.address)).call()).to.be.equal('0'); - expect(await matcher.methods.balanceKSM(subToEth(alice.address)).call()).to.be.equal(PRICE.toString()); + expect(await matcher.methods.balanceKSM(sellerMirror).call()).to.be.equal('0'); + expect(await matcher.methods.balanceKSM(aliceMirror).call()).to.be.equal(PRICE.toString()); - await executeEthTxOnSub(web3, api, alice, matcher, m => m.buyKSM(evmCollection.options.address, tokenId, subToEth(alice.address), subToEth(alice.address))); + await helper.eth.sendEVM(alice, matcher.options.address, matcher.methods.buyKSM(evmCollection.options.address, token.tokenId, aliceMirror, aliceMirror).encodeABI(), '0'); // Price is removed from buyer balance, and added to seller - expect(await matcher.methods.balanceKSM(subToEth(alice.address)).call()).to.be.equal('0'); - expect(await matcher.methods.balanceKSM(subToEth(seller.address)).call()).to.be.equal(PRICE.toString()); + expect(await matcher.methods.balanceKSM(aliceMirror).call()).to.be.equal('0'); + expect(await matcher.methods.balanceKSM(sellerMirror).call()).to.be.equal(PRICE.toString()); } // Token is transferred to evm account of alice - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(alice.address)}); + expect(await token.getOwner()).to.be.deep.equal({Ethereum: aliceMirror}); // Transfer token to substrate side of alice - await transferFromExpectSuccess(collectionId, tokenId, alice, {Ethereum: subToEth(alice.address)}, {Substrate: alice.address}); + await token.transferFrom(alice, {Ethereum: aliceMirror}, {Substrate: alice.address}); // Token is transferred to substrate account of alice, seller received funds - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Substrate: alice.address}); + expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); }); - - itWeb3('Sell tokens from substrate user via EVM contract', async ({api, web3, privateKeyWrapper}) => { - const alice = privateKeyWrapper('//Alice'); - const matcherOwner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); + itEth('Sell tokens from substrate user via EVM contract', async ({helper}) => { + const web3 = helper.getWeb3(); + const matcherOwner = await helper.eth.createAccountWithBalance(donor); const matcherContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/MarketPlace.abi`)).toString()), undefined, { from: matcherOwner, - ...GAS_ARGS, + gas: helper.eth.DEFAULT_GAS, }); const matcher = await matcherContract.deploy({data: (await readFile(`${__dirname}/MarketPlace.bin`)).toString(), arguments:[matcherOwner]}).send({from: matcherOwner}); - await transferBalanceToEth(api, alice, matcher.options.address); - const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setCollectionLimitsExpectSuccess(alice, collectionId, {sponsorApproveTimeout: 1}); - const evmCollection = new web3.eth.Contract(nonFungibleAbi as any, collectionIdToAddress(collectionId), {from: matcherOwner}); + await helper.eth.transferBalanceFromSubstrate(donor, matcher.options.address); + + const collection = await helper.nft.mintCollection(alice, {limits: {sponsorApproveTimeout: 1}}); + const evmCollection = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft'); - const seller = privateKeyWrapper(`//Seller/${Date.now()}`); - await transferBalanceTo(api, alice, seller.address); + await helper.balance.transferToSubstrate(donor, seller.address, 100_000_000_000_000_000_000n); - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', seller.address); - - // To transfer item to matcher it first needs to be transfered to EVM account of bob - await transferExpectSuccess(collectionId, tokenId, seller, {Ethereum: subToEth(seller.address)}); + const token = await collection.mintToken(alice, {Ethereum: sellerMirror}); // Token is owned by seller initially - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(seller.address)}); + expect(await token.getOwner()).to.be.deep.equal({Ethereum: sellerMirror}); // Ask { - await executeEthTxOnSub(web3, api, seller, evmCollection, m => m.approve(matcher.options.address, tokenId)); - await executeEthTxOnSub(web3, api, seller, matcher, m => m.addAsk(PRICE, '0x0000000000000000000000000000000000000001', evmCollection.options.address, tokenId)); + await helper.eth.sendEVM(seller, evmCollection.options.address, evmCollection.methods.approve(matcher.options.address, token.tokenId).encodeABI(), '0'); + await helper.eth.sendEVM(seller, matcher.options.address, matcher.methods.addAsk(PRICE, '0x0000000000000000000000000000000000000001', evmCollection.options.address, token.tokenId).encodeABI(), '0'); } // Token is transferred to matcher - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: matcher.options.address.toLowerCase()}); + expect(await token.getOwner()).to.be.deep.equal({Ethereum: matcher.options.address.toLowerCase()}); // Buy { - const sellerBalanceBeforePurchase = await getBalanceSingle(api, seller.address); - await executeEthTxOnSub(web3, api, alice, matcher, m => m.buy(evmCollection.options.address, tokenId), {value: PRICE}); - expect(await getBalanceSingle(api, seller.address) - sellerBalanceBeforePurchase === PRICE); + const sellerBalanceBeforePurchase = await helper.balance.getSubstrate(seller.address); + await helper.eth.sendEVM(alice, matcher.options.address, matcher.methods.buy(evmCollection.options.address, token.tokenId).encodeABI(), PRICE.toString()); + expect(await helper.balance.getSubstrate(seller.address) - sellerBalanceBeforePurchase === PRICE); } // Token is transferred to evm account of alice - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Ethereum: subToEthLowercase(alice.address)}); + expect(await token.getOwner()).to.be.deep.equal({Ethereum: aliceMirror}); // Transfer token to substrate side of alice - await transferFromExpectSuccess(collectionId, tokenId, alice, {Ethereum: subToEth(alice.address)}, {Substrate: alice.address}); + await token.transferFrom(alice, {Ethereum: aliceMirror}, {Substrate: alice.address}); // Token is transferred to substrate account of alice, seller received funds - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal({Substrate: alice.address}); + expect(await token.getOwner()).to.be.deep.equal({Substrate: alice.address}); }); }); --- a/tests/src/eth/nonFungible.test.ts +++ b/tests/src/eth/nonFungible.test.ts @@ -17,8 +17,8 @@ import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util/playgrounds'; import {IKeyringPair} from '@polkadot/types/types'; import {Contract} from 'web3-eth-contract'; -import {UNIQUE} from '../util/helpers'; + describe('NFT: Information getting', () => { let donor: IKeyringPair; let alice: IKeyringPair; @@ -84,7 +84,7 @@ const receiver = helper.eth.createAccount(); const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); - let result = await collectionHelper.methods.createERC721MetadataCompatibleCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * UNIQUE)}); + let result = await collectionHelper.methods.createERC721MetadataCompatibleCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * helper.balance.getOneTokenNominal())}); const collectionAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); --- a/tests/src/eth/proxy/fungibleProxy.test.ts +++ b/tests/src/eth/proxy/fungibleProxy.test.ts @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {GAS_ARGS, normalizeEvents} from '../util/helpers'; import {expect} from 'chai'; import {readFile} from 'fs/promises'; import {IKeyringPair} from '@polkadot/types/types'; @@ -26,7 +25,7 @@ const web3 = helper.getWeb3(); const proxyContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/UniqueFungibleProxy.abi`)).toString()), undefined, { from: owner, - ...GAS_ARGS, + gas: helper.eth.DEFAULT_GAS, }); const proxy = await proxyContract.deploy({data: (await readFile(`${__dirname}/UniqueFungibleProxy.bin`)).toString(), arguments: [wrapped.options.address]}).send({from: owner}); return proxy; @@ -94,7 +93,7 @@ { const result = await contract.methods.approve(spender, 100).send({from: caller}); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { @@ -131,7 +130,7 @@ { const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: caller}); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { address, @@ -177,7 +176,7 @@ { const result = await contract.methods.transfer(receiver, 50).send({from: caller}); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { address, --- a/tests/src/eth/proxy/nonFungibleProxy.test.ts +++ b/tests/src/eth/proxy/nonFungibleProxy.test.ts @@ -14,11 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {GAS_ARGS, itWeb3, normalizeEvents} from '../util/helpers'; -import {expect} from 'chai'; import {readFile} from 'fs/promises'; import {IKeyringPair} from '@polkadot/types/types'; -import {EthUniqueHelper, itEth, usingEthPlaygrounds} from '../util/playgrounds'; +import {EthUniqueHelper, itEth, usingEthPlaygrounds, expect} from '../util/playgrounds'; + async function proxyWrap(helper: EthUniqueHelper, wrapped: any, donor: IKeyringPair) { // Proxy owner has no special privilegies, we don't need to reuse them @@ -26,7 +25,7 @@ const web3 = helper.getWeb3(); const proxyContract = new web3.eth.Contract(JSON.parse((await readFile(`${__dirname}/UniqueNFTProxy.abi`)).toString()), undefined, { from: owner, - ...GAS_ARGS, + gas: helper.eth.DEFAULT_GAS, }); const proxy = await proxyContract.deploy({data: (await readFile(`${__dirname}/UniqueNFTProxy.bin`)).toString(), arguments: [wrapped.options.address]}).send({from: owner}); return proxy; @@ -119,7 +118,7 @@ nextTokenId, 'Test URI', ).send({from: caller}); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); events[0].address = events[0].address.toLocaleLowerCase(); expect(events).to.be.deep.equal([ @@ -139,20 +138,16 @@ }); //TODO: CORE-302 add eth methods - itWeb3.skip('Can perform mintBulk()', async () => { - /* - const collection = await createCollectionExpectSuccess({ - mode: {type: 'NFT'}, - }); - const alice = privateKeyWrapper('//Alice'); + itEth.skip('Can perform mintBulk()', async ({helper}) => { + const collection = await helper.nft.mintCollection(donor, {name: 'New', description: 'New collection', tokenPrefix: 'NEW'}); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const receiver = createEthAccount(web3); + const caller = await helper.eth.createAccountWithBalance(donor, 30n); + const receiver = helper.eth.createAccount(); - const address = collectionIdToAddress(collection); - const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS}), privateKeyWrapper); - const changeAdminTx = api.tx.unique.addCollectionAdmin(collection, {Ethereum: contract.options.address}); - await submitTransactionAsync(alice, changeAdminTx); + const address = helper.ethAddress.fromCollectionId(collection.collectionId); + const evmCollection = helper.ethNativeContract.collection(address, 'nft', caller); + const contract = await proxyWrap(helper, evmCollection, donor); + await collection.addAdmin(donor, {Ethereum: contract.options.address}); { const nextTokenId = await contract.methods.nextTokenId().call(); @@ -165,7 +160,7 @@ [+nextTokenId + 2, 'Test URI 2'], ], ).send({from: caller}); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { @@ -201,7 +196,6 @@ expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1'); expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2'); } - */ }); itEth('Can perform burn()', async ({helper}) => { @@ -216,7 +210,7 @@ { const result = await contract.methods.burn(tokenId).send({from: caller}); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { @@ -243,8 +237,8 @@ const {tokenId} = await collection.mintToken(alice, {Ethereum: contract.options.address}); { - const result = await contract.methods.approve(spender, tokenId).send({from: caller, ...GAS_ARGS}); - const events = normalizeEvents(result.events); + const result = await contract.methods.approve(spender, tokenId).send({from: caller, gas: helper.eth.DEFAULT_GAS}); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { @@ -276,7 +270,7 @@ { const result = await contract.methods.transferFrom(owner, receiver, tokenId).send({from: caller}); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { address, @@ -313,7 +307,7 @@ { const result = await contract.methods.transfer(receiver, tokenId).send({from: caller}); - const events = normalizeEvents(result.events); + const events = helper.eth.normalizeEvents(result.events); expect(events).to.be.deep.equal([ { address, --- a/tests/src/eth/reFungibleToken.test.ts +++ b/tests/src/eth/reFungibleToken.test.ts @@ -18,8 +18,8 @@ import {EthUniqueHelper, expect, itEth, usingEthPlaygrounds} from './util/playgrounds'; import {IKeyringPair} from '@polkadot/types/types'; import {Contract} from 'web3-eth-contract'; -import {UNIQUE} from '../util/helpers'; + describe('Refungible token: Information getting', () => { let donor: IKeyringPair; let alice: IKeyringPair; @@ -81,7 +81,7 @@ const receiver = helper.eth.createAccount(); const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); - let result = await collectionHelper.methods.createERC721MetadataCompatibleCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * UNIQUE)}); + let result = await collectionHelper.methods.createERC721MetadataCompatibleCollection('Mint collection', 'a', 'b', tokenPrefix).send({value: Number(2n * helper.balance.getOneTokenNominal())}); const collectionAddress = helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner); --- a/tests/src/eth/scheduling.test.ts +++ /dev/null @@ -1,58 +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 {expect} from 'chai'; -import {createEthAccountWithBalance, deployFlipper, GAS_ARGS, itWeb3, subToEth, transferBalanceToEth} from './util/helpers'; -import {scheduleExpectSuccess, waitNewBlocks, requirePallets, Pallets} from '../util/helpers'; - -describe('Scheduing EVM smart contracts', () => { - before(async function() { - await requirePallets(this, [Pallets.Scheduler]); - }); - - itWeb3('Successfully schedules and periodically executes an EVM contract', async ({api, web3, privateKeyWrapper}) => { - const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const flipper = await deployFlipper(web3, deployer); - const initialValue = await flipper.methods.getValue().call(); - const alice = privateKeyWrapper('//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); - } - }); -}); --- a/tests/src/eth/sponsoring.test.ts +++ b/tests/src/eth/sponsoring.test.ts @@ -14,20 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {expect} from 'chai'; -import {contractHelpers, createEthAccount, createEthAccountWithBalance, deployCollector, deployFlipper, itWeb3, SponsoringMode} from './util/helpers'; +import {IKeyringPair} from '@polkadot/types/types'; +import {itEth, expect, SponsoringMode} from '../eth/util/playgrounds'; +import {usingPlaygrounds} from './../util/playgrounds/index'; describe('EVM sponsoring', () => { - itWeb3('Fee is deducted from contract if sponsoring is enabled', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const caller = createEthAccount(web3); - const originalCallerBalance = await web3.eth.getBalance(caller); - expect(originalCallerBalance).to.be.equal('0'); + let donor: IKeyringPair; - const flipper = await deployFlipper(web3, owner); + before(async () => { + await usingPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + }); + }); + + itEth('Fee is deducted from contract if sponsoring is enabled', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const caller = helper.eth.createAccount(); + const originalCallerBalance = await helper.balance.getEthereum(caller); + + expect(originalCallerBalance).to.be.equal(0n); + + const flipper = await helper.eth.deployFlipper(owner); + + const helpers = helper.ethNativeContract.contractHelpers(owner); - const helpers = contractHelpers(web3, owner); await helpers.methods.toggleAllowlist(flipper.options.address, true).send({from: owner}); await helpers.methods.toggleAllowed(flipper.options.address, caller, true).send({from: owner}); @@ -39,27 +50,29 @@ await helpers.methods.setSponsoringRateLimit(flipper.options.address, 0).send({from: owner}); expect(await helpers.methods.sponsoringEnabled(flipper.options.address).call()).to.be.true; - const originalSponsorBalance = await web3.eth.getBalance(sponsor); - expect(originalSponsorBalance).to.be.not.equal('0'); + const originalSponsorBalance = await helper.balance.getEthereum(sponsor); + expect(originalSponsorBalance).to.be.not.equal(0n); await flipper.methods.flip().send({from: caller}); expect(await flipper.methods.getValue().call()).to.be.true; // Balance should be taken from flipper instead of caller - expect(await web3.eth.getBalance(caller)).to.be.equals(originalCallerBalance); - expect(await web3.eth.getBalance(sponsor)).to.be.not.equals(originalSponsorBalance); + expect(await helper.balance.getEthereum(caller)).to.be.equal(originalCallerBalance); + expect(await helper.balance.getEthereum(sponsor)).to.be.not.equal(originalSponsorBalance); }); - itWeb3('...but this doesn\'t applies to payable value', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const sponsor = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const caller = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const originalCallerBalance = await web3.eth.getBalance(caller); - expect(originalCallerBalance).to.be.not.equal('0'); + itEth('...but this doesn\'t applies to payable value', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const sponsor = await helper.eth.createAccountWithBalance(donor); + const caller = await helper.eth.createAccountWithBalance(donor); + const originalCallerBalance = await helper.balance.getEthereum(caller); + + expect(originalCallerBalance).to.be.not.equal(0n); + + const collector = await helper.eth.deployCollectorContract(owner); - const collector = await deployCollector(web3, owner); + const helpers = helper.ethNativeContract.contractHelpers(owner); - const helpers = contractHelpers(web3, owner); await helpers.methods.toggleAllowlist(collector.options.address, true).send({from: owner}); await helpers.methods.toggleAllowed(collector.options.address, caller, true).send({from: owner}); @@ -71,14 +84,14 @@ await helpers.methods.setSponsor(collector.options.address, sponsor).send({from: owner}); await helpers.methods.confirmSponsorship(collector.options.address).send({from: sponsor}); - const originalSponsorBalance = await web3.eth.getBalance(sponsor); - expect(originalSponsorBalance).to.be.not.equal('0'); + const originalSponsorBalance = await helper.balance.getEthereum(sponsor); + expect(originalSponsorBalance).to.be.not.equal(0n); await collector.methods.giveMoney().send({from: caller, value: '10000'}); // Balance will be taken from both caller (value) and from collector (fee) - expect(await web3.eth.getBalance(caller)).to.be.equals((BigInt(originalCallerBalance) - 10000n).toString()); - expect(await web3.eth.getBalance(sponsor)).to.be.not.equals(originalSponsorBalance); + expect(await helper.balance.getEthereum(caller)).to.be.equals((originalCallerBalance - 10000n)); + expect(await helper.balance.getEthereum(sponsor)).to.be.not.equals(originalSponsorBalance); expect(await collector.methods.getCollected().call()).to.be.equal('10000'); }); }); --- a/tests/src/eth/tokenProperties.test.ts +++ b/tests/src/eth/tokenProperties.test.ts @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {itEth, usingEthPlaygrounds, expect, cartesian} from './util/playgrounds'; +import {itEth, usingEthPlaygrounds, expect} from './util/playgrounds'; import {IKeyringPair} from '@polkadot/types/types'; describe('EVM token properties', () => { @@ -48,16 +48,16 @@ itEth('Can be set', async({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); - const collection = await helper.nft.mintCollection(alice); + const collection = await helper.nft.mintCollection(alice, { + tokenPropertyPermissions: [{ + key: 'testKey', + permission: { + collectionAdmin: true, + }, + }], + }); const token = await collection.mintToken(alice); - await collection.setTokenPropertyPermissions(alice, [{ - key: 'testKey', - permission: { - collectionAdmin: true, - }, - }]); - await collection.addAdmin(alice, {Ethereum: caller}); const address = helper.ethAddress.fromCollectionId(collection.collectionId); @@ -71,16 +71,17 @@ itEth('Can be deleted', async({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); - const collection = await helper.nft.mintCollection(alice); + const collection = await helper.nft.mintCollection(alice, { + tokenPropertyPermissions: [{ + key: 'testKey', + permission: { + mutable: true, + collectionAdmin: true, + }, + }], + }); + const token = await collection.mintToken(alice); - - await collection.setTokenPropertyPermissions(alice, [{ - key: 'testKey', - permission: { - mutable: true, - collectionAdmin: true, - }, - }]); await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]); await collection.addAdmin(alice, {Ethereum: caller}); @@ -96,15 +97,16 @@ itEth('Can be read', async({helper}) => { const caller = helper.eth.createAccount(); - const collection = await helper.nft.mintCollection(alice); + const collection = await helper.nft.mintCollection(alice, { + tokenPropertyPermissions: [{ + key: 'testKey', + permission: { + collectionAdmin: true, + }, + }], + }); + const token = await collection.mintToken(alice); - - await collection.setTokenPropertyPermissions(alice, [{ - key: 'testKey', - permission: { - collectionAdmin: true, - }, - }]); await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]); const address = helper.ethAddress.fromCollectionId(collection.collectionId); @@ -114,3 +116,15 @@ expect(value).to.equal(helper.getWeb3().utils.toHex('testValue')); }); }); + + +type ElementOf = A extends readonly (infer T)[] ? T : never; +function* cartesian>, R extends Array>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf}]> { + if(args.length === 0) { + yield internalRest as any; + return; + } + for(const value of args[0]) { + yield* cartesian([...internalRest, value], ...args.slice(1)) as any; + } +} --- a/tests/src/eth/util/helpers.d.ts +++ /dev/null @@ -1,17 +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 . - -declare module 'solc'; \ No newline at end of file --- a/tests/src/eth/util/helpers.ts +++ /dev/null @@ -1,451 +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 . - -// eslint-disable-next-line @typescript-eslint/triple-slash-reference -/// - -import {ApiPromise} from '@polkadot/api'; -import {IKeyringPair} from '@polkadot/types/types'; -import {addressToEvm, evmToAddress} from '@polkadot/util-crypto'; -import {expect} from 'chai'; -import * as solc from 'solc'; -import Web3 from 'web3'; -import config from '../../config'; -import getBalance from '../../substrate/get-balance'; -import usingApi, {submitTransactionAsync} from '../../substrate/substrate-api'; -import waitNewBlocks from '../../substrate/wait-new-blocks'; -import {CollectionMode, CrossAccountId, getDetailedCollectionInfo, getGenericResult, UNIQUE} from '../../util/helpers'; -import collectionHelpersAbi from '../collectionHelpersAbi.json'; -import fungibleAbi from '../fungibleAbi.json'; -import nonFungibleAbi from '../nonFungibleAbi.json'; -import refungibleAbi from '../reFungibleAbi.json'; -import refungibleTokenAbi from '../reFungibleTokenAbi.json'; -import contractHelpersAbi from './contractHelpersAbi.json'; - -export const GAS_ARGS = {gas: 2500000}; - -export enum SponsoringMode { - Disabled = 0, - Allowlisted = 1, - Generous = 2, -} - -let web3Connected = false; -export async function usingWeb3(cb: (web3: Web3) => Promise | T): Promise { - if (web3Connected) throw new Error('do not nest usingWeb3 calls'); - web3Connected = true; - - const provider = new Web3.providers.WebsocketProvider(config.substrateUrl); - const web3 = new Web3(provider); - - try { - return await cb(web3); - } finally { - // provider.disconnect(3000, 'normal disconnect'); - provider.connection.close(); - web3Connected = false; - } -} - -function encodeIntBE(v: number): number[] { - if (v >= 0xffffffff || v < 0) throw new Error('id overflow'); - return [ - v >> 24, - (v >> 16) & 0xff, - (v >> 8) & 0xff, - v & 0xff, - ]; -} - -export async function getCollectionAddressFromResult(api: ApiPromise, result: any) { - const collectionIdAddress = normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); - const collectionId = collectionIdFromAddress(collectionIdAddress); - const collection = (await getDetailedCollectionInfo(api, collectionId))!; - return {collectionIdAddress, collectionId, collection}; -} - -export function collectionIdToAddress(collection: number): string { - const buf = Buffer.from([0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e, - ...encodeIntBE(collection), - ]); - return Web3.utils.toChecksumAddress('0x' + buf.toString('hex')); -} -export function collectionIdFromAddress(address: string): number { - if (!address.startsWith('0x')) - throw 'address not starts with "0x"'; - if (address.length > 42) - throw 'address length is more than 20 bytes'; - return Number('0x' + address.substring(address.length - 8)); -} - -export function normalizeAddress(address: string): string { - return '0x' + address.substring(address.length - 40); -} - -export function tokenIdToAddress(collection: number, token: number): string { - const buf = Buffer.from([0xf8, 0x23, 0x8c, 0xcf, 0xff, 0x8e, 0xd8, 0x87, 0x46, 0x3f, 0xd5, 0xe0, - ...encodeIntBE(collection), - ...encodeIntBE(token), - ]); - return Web3.utils.toChecksumAddress('0x' + buf.toString('hex')); -} - -export function tokenIdFromAddress(address: string) { - if (!address.startsWith('0x')) - throw 'address not starts with "0x"'; - if (address.length > 42) - throw 'address length is more than 20 bytes'; - return { - collectionId: Number('0x' + address.substring(address.length - 16, address.length - 8)), - tokenId: Number('0x' + address.substring(address.length - 8)), - }; -} - -export function tokenIdToCross(collection: number, token: number): CrossAccountId { - return { - Ethereum: tokenIdToAddress(collection, token), - }; -} - -export function createEthAccount(web3: Web3) { - const account = web3.eth.accounts.create(); - web3.eth.accounts.wallet.add(account.privateKey); - return account.address; -} - -export async function createEthAccountWithBalance(api: ApiPromise, web3: Web3, privateKeyWrapper: (account: string) => IKeyringPair) { - const alice = privateKeyWrapper('//Alice'); - const account = createEthAccount(web3); - await transferBalanceToEth(api, alice, account); - - return account; -} - -export async function transferBalanceToEth(api: ApiPromise, source: IKeyringPair, target: string, amount = 1000n * UNIQUE) { - const tx = api.tx.balances.transfer(evmToAddress(target), amount); - const events = await submitTransactionAsync(source, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; -} - -export async function createRFTCollection(api: ApiPromise, web3: Web3, owner: string) { - const collectionHelper = evmCollectionHelpers(web3, owner); - const result = await collectionHelper.methods - .createRFTCollection('A', 'B', 'C') - .send({value: Number(2n * UNIQUE)}); - return await getCollectionAddressFromResult(api, result); -} - - -export async function createNonfungibleCollection(api: ApiPromise, web3: Web3, owner: string) { - const collectionHelper = evmCollectionHelpers(web3, owner); - const result = await collectionHelper.methods - .createNonfungibleCollection('A', 'B', 'C') - .send({value: Number(2n * UNIQUE)}); - return await getCollectionAddressFromResult(api, result); -} - -export function uniqueNFT(web3: Web3, address: string, owner: string) { - return new web3.eth.Contract(nonFungibleAbi as any, address, { - from: owner, - ...GAS_ARGS, - }); -} - -export function uniqueRefungible(web3: Web3, collectionAddress: string, owner: string) { - return new web3.eth.Contract(refungibleAbi as any, collectionAddress, { - from: owner, - ...GAS_ARGS, - }); -} - -export function uniqueRefungibleToken(web3: Web3, tokenAddress: string, owner: string | undefined = undefined) { - return new web3.eth.Contract(refungibleTokenAbi as any, tokenAddress, { - from: owner, - ...GAS_ARGS, - }); -} - -export async function itWeb3(name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) { - let i: any = it; - if (opts.only) i = i.only; - else if (opts.skip) i = i.skip; - i(name, async () => { - await usingApi(async (api, privateKeyWrapper) => { - await usingWeb3(async web3 => { - await cb({api, web3, privateKeyWrapper}); - }); - }); - }); -} -itWeb3.only = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {only: true}); -itWeb3.skip = (name: string, cb: (apis: { web3: Web3, api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itWeb3(name, cb, {skip: true}); - -export async function generateSubstrateEthPair(web3: Web3) { - const account = web3.eth.accounts.create(); - evmToAddress(account.address); -} - -type NormalizedEvent = { - address: string, - event: string, - args: { [key: string]: string } -}; - -export function normalizeEvents(events: any): NormalizedEvent[] { - const output = []; - for (const key of Object.keys(events)) { - if (key.match(/^[0-9]+$/)) { - output.push(events[key]); - } else if (Array.isArray(events[key])) { - output.push(...events[key]); - } else { - output.push(events[key]); - } - } - output.sort((a, b) => a.logIndex - b.logIndex); - return output.map(({address, event, returnValues}) => { - const args: { [key: string]: string } = {}; - for (const key of Object.keys(returnValues)) { - if (!key.match(/^[0-9]+$/)) { - args[key] = returnValues[key]; - } - } - return { - address, - event, - args, - }; - }); -} - -export async function recordEvents(contract: any, action: () => Promise): Promise { - const out: any = []; - contract.events.allEvents((_: any, event: any) => { - out.push(event); - }); - await action(); - return normalizeEvents(out); -} - -export function subToEthLowercase(eth: string): string { - const bytes = addressToEvm(eth); - return '0x' + Buffer.from(bytes).toString('hex'); -} - -export function subToEth(eth: string): string { - return Web3.utils.toChecksumAddress(subToEthLowercase(eth)); -} - -export interface CompiledContract { - abi: any, - object: string, -} - -export function compileContract(name: string, src: string) : CompiledContract { - const out = JSON.parse(solc.compile(JSON.stringify({ - language: 'Solidity', - sources: { - [`${name}.sol`]: { - content: ` - // SPDX-License-Identifier: UNLICENSED - pragma solidity ^0.8.6; - - ${src} - `, - }, - }, - settings: { - outputSelection: { - '*': { - '*': ['*'], - }, - }, - }, - }))).contracts[`${name}.sol`][name]; - - return { - abi: out.abi, - object: '0x' + out.evm.bytecode.object, - }; -} - -export async function deployFlipper(web3: Web3, deployer: string) { - const compiled = compileContract('Flipper', ` - contract Flipper { - bool value = false; - function flip() public { - value = !value; - } - function getValue() public view returns (bool) { - return value; - } - } - `); - const flipperContract = new web3.eth.Contract(compiled.abi, undefined, { - data: compiled.object, - from: deployer, - ...GAS_ARGS, - }); - const flipper = await flipperContract.deploy({data: compiled.object}).send({from: deployer}); - - return flipper; -} - -export async function deployCollector(web3: Web3, deployer: string) { - const compiled = compileContract('Collector', ` - contract Collector { - uint256 collected; - fallback() external payable { - giveMoney(); - } - function giveMoney() public payable { - collected += msg.value; - } - function getCollected() public view returns (uint256) { - return collected; - } - function getUnaccounted() public view returns (uint256) { - return address(this).balance - collected; - } - - function withdraw(address payable target) public { - target.transfer(collected); - collected = 0; - } - } - `); - const collectorContract = new web3.eth.Contract(compiled.abi, undefined, { - data: compiled.object, - from: deployer, - ...GAS_ARGS, - }); - const collector = await collectorContract.deploy({data: compiled.object}).send({from: deployer}); - - return collector; -} - -/** - * pallet evm_contract_helpers - * @param web3 - * @param caller - eth address - * @returns - */ -export function contractHelpers(web3: Web3, caller: string) { - return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, ...GAS_ARGS}); -} - -/** - * evm collection helper - * @param web3 - * @param caller - eth address - * @returns - */ -export function evmCollectionHelpers(web3: Web3, caller: string) { - return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, ...GAS_ARGS}); -} - -/** - * evm collection - * @param web3 - * @param caller - eth address - * @returns - */ -export function evmCollection(web3: Web3, caller: string, collection: string, mode: CollectionMode = {type: 'NFT'}) { - let abi; - switch (mode.type) { - case 'Fungible': - abi = fungibleAbi; - break; - - case 'NFT': - abi = nonFungibleAbi; - break; - - case 'ReFungible': - abi = refungibleAbi; - break; - - default: - throw 'Bad collection mode'; - } - const contract = new web3.eth.Contract(abi as any, collection, {from: caller, ...GAS_ARGS}); - return contract; -} - -/** - * Execute ethereum method call using substrate account - * @param to target contract - * @param mkTx - closure, receiving `contract.methods`, and returning method call, - * to be used as following (assuming `to` = erc20 contract): - * `m => m.transfer(to, amount)` - * - * # Example - * ```ts - * executeEthTxOnSub(api, alice, erc20Contract, m => m.transfer(target, amount)); - * ``` - */ -export async function executeEthTxOnSub(web3: Web3, api: ApiPromise, from: IKeyringPair, to: any, mkTx: (methods: any) => any, {value = 0}: {value?: bigint | number} = { }) { - const tx = api.tx.evm.call( - subToEth(from.address), - to.options.address, - mkTx(to.methods).encodeABI(), - value, - GAS_ARGS.gas, - await web3.eth.getGasPrice(), - null, - null, - [], - ); - const events = await submitTransactionAsync(from, tx); - expect(events.some(({event: {section, method}}) => section == 'evm' && method == 'Executed')).to.be.true; -} - -export async function ethBalanceViaSub(api: ApiPromise, address: string): Promise { - return (await getBalance(api, [evmToAddress(address)]))[0]; -} - -/** - * Measure how much gas given closure consumes - * - * @param user which user balance will be checked - */ -export async function recordEthFee(api: ApiPromise, user: string, call: () => Promise): Promise { - const before = await ethBalanceViaSub(api, user); - - await call(); - - // In dev mode, the transaction might not finish processing in time - await waitNewBlocks(api, 1); - const after = await ethBalanceViaSub(api, user); - - // Can't use .to.be.less, because chai doesn't supports bigint - expect(after < before).to.be.true; - - return before - after; -} - -type ElementOf = A extends readonly (infer T)[] ? T : never; -// I want a fancier api, not a memory efficiency -export function* cartesian>, R extends Array>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf}]> { - if(args.length === 0) { - yield internalRest as any; - return; - } - for(const value of args[0]) { - yield* cartesian([...internalRest, value], ...args.slice(1)) as any; - } -} \ No newline at end of file --- a/tests/src/eth/util/playgrounds/index.ts +++ b/tests/src/eth/util/playgrounds/index.ts @@ -20,6 +20,12 @@ chai.use(chaiLike); export const expect = chai.expect; +export enum SponsoringMode { + Disabled = 0, + Allowlisted = 1, + Generous = 2, +} + export const usingEthPlaygrounds = async (code: (helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise) => Promise) => { const silentConsole = new SilentConsole(); silentConsole.enable(); @@ -48,7 +54,6 @@ } finally { await helper.disconnect(); - await helper.disconnectWeb3(); silentConsole.disable(); } }; @@ -76,15 +81,3 @@ itEthIfWithPallet.only = (name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise }) => any) => itEthIfWithPallet(name, required, cb, {only: true}); itEthIfWithPallet.skip = (name: string, required: string[], cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string | {filename: string}) => Promise }) => any) => itEthIfWithPallet(name, required, cb, {skip: true}); itEth.ifWithPallets = itEthIfWithPallet; - -type ElementOf = A extends readonly (infer T)[] ? T : never; -// I want a fancier api, not a memory efficiency -export function* cartesian>, R extends Array>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf}]> { - if(args.length === 0) { - yield internalRest as any; - return; - } - for(const value of args[0]) { - yield* cartesian([...internalRest, value], ...args.slice(1)) as any; - } -} --- a/tests/src/eth/util/playgrounds/types.ts +++ b/tests/src/eth/util/playgrounds/types.ts @@ -6,4 +6,10 @@ export interface CompiledContract { abi: any; object: string; -} \ No newline at end of file +} + +export type NormalizedEvent = { + address: string, + event: string, + args: { [key: string]: string } +}; --- a/tests/src/eth/util/playgrounds/unique.dev.ts +++ b/tests/src/eth/util/playgrounds/unique.dev.ts @@ -18,7 +18,7 @@ import {DevUniqueHelper} from '../../../util/playgrounds/unique.dev'; -import {ContractImports, CompiledContract} from './types'; +import {ContractImports, CompiledContract, NormalizedEvent} from './types'; // Native contracts ABI import collectionHelpersAbi from '../../collectionHelpersAbi.json'; @@ -27,7 +27,7 @@ import refungibleAbi from '../../reFungibleAbi.json'; import refungibleTokenAbi from '../../reFungibleTokenAbi.json'; import contractHelpersAbi from './../contractHelpersAbi.json'; -import {TEthereumAccount} from '../../../util/playgrounds/types'; +import {ICrossAccountId, TEthereumAccount} from '../../../util/playgrounds/types'; class EthGroupBase { helper: EthUniqueHelper; @@ -252,6 +252,42 @@ return before - after; } + + normalizeEvents(events: any): NormalizedEvent[] { + const output = []; + for (const key of Object.keys(events)) { + if (key.match(/^[0-9]+$/)) { + output.push(events[key]); + } else if (Array.isArray(events[key])) { + output.push(...events[key]); + } else { + output.push(events[key]); + } + } + output.sort((a, b) => a.logIndex - b.logIndex); + return output.map(({address, event, returnValues}) => { + const args: { [key: string]: string } = {}; + for (const key of Object.keys(returnValues)) { + if (!key.match(/^[0-9]+$/)) { + args[key] = returnValues[key]; + } + } + return { + address, + event, + args, + }; + }); + } + + async calculateFee(address: ICrossAccountId, code: () => Promise): Promise { + const wrappedCode = async () => { + await code(); + // In dev mode, the transaction might not finish processing in time + await this.helper.wait.newBlocks(1); + }; + return await this.helper.arrange.calculcateFee(address, wrappedCode); + } } class EthAddressGroup extends EthGroupBase { @@ -285,6 +321,7 @@ } } +export type EthUniqueHelperConstructor = new (...args: any[]) => EthUniqueHelper; export class EthUniqueHelper extends DevUniqueHelper { web3: Web3 | null = null; @@ -295,8 +332,10 @@ ethNativeContract: NativeContractGroup; ethContract: ContractGroup; - constructor(logger: { log: (msg: any, level: any) => void, level: any }) { - super(logger); + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? EthUniqueHelper; + + super(logger, options); this.eth = new EthGroup(this); this.ethAddress = new EthAddressGroup(this); this.ethNativeContract = new NativeContractGroup(this); @@ -314,10 +353,23 @@ this.web3 = new Web3(this.web3Provider); } - async disconnectWeb3() { + async disconnect() { if(this.web3 === null) return; this.web3Provider?.connection.close(); + + await super.disconnect(); + } + + clearApi() { this.web3 = null; } + + clone(helperCls: EthUniqueHelperConstructor, options?: { [key: string]: any; }): EthUniqueHelper { + const newHelper = super.clone(helperCls, options) as EthUniqueHelper; + newHelper.web3 = this.web3; + newHelper.web3Provider = this.web3Provider; + + return newHelper; + } } \ No newline at end of file --- a/tests/src/flipper/metadata.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "metadataVersion": "0.1.0", - "source": { - "hash": "0x5b02ceadaacee8408d3c6496394847092c099bcb897221dbe8d22c16d372fa17", - "language": "ink! 3.0.0-rc2", - "compiler": "rustc 1.51.0-nightly" - }, - "contract": { - "name": "flipper", - "version": "0.1.0", - "authors": [ - "[your_name] <[your_email]>" - ] - }, - "spec": { - "constructors": [ - { - "args": [ - { - "name": "init_value", - "type": { - "displayName": [ - "bool" - ], - "type": 1 - } - } - ], - "docs": [ - " Constructor that initializes the `bool` value to the given `init_value`." - ], - "name": [ - "new" - ], - "selector": "0xd183512b" - }, - { - "args": [], - "docs": [ - " Constructor that initializes the `bool` value to `false`.", - "", - " Constructors can delegate to other constructors." - ], - "name": [ - "default" - ], - "selector": "0x6a3712e2" - } - ], - "docs": [], - "events": [], - "messages": [ - { - "args": [], - "docs": [ - " A message that can be called on instantiated contracts.", - " This one flips the value of the stored `bool` from `true`", - " to `false` and vice versa." - ], - "mutates": true, - "name": [ - "flip" - ], - "payable": false, - "returnType": null, - "selector": "0xc096a5f3" - }, - { - "args": [], - "docs": [ - " Simply returns the current value of our `bool`." - ], - "mutates": false, - "name": [ - "get" - ], - "payable": false, - "returnType": { - "displayName": [ - "bool" - ], - "type": 1 - }, - "selector": "0x1e5ca456" - } - ] - }, - "storage": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000000", - "ty": 1 - } - }, - "name": "value" - } - ] - } - }, - "types": [ - { - "def": { - "primitive": "bool" - } - } - ] -} \ No newline at end of file --- a/tests/src/inflation.test.ts +++ b/tests/src/inflation.test.ts @@ -40,9 +40,9 @@ const tx = helper.constructApiCall('api.tx.inflation.startInflation', [1]); await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [tx])).to.not.be.rejected; - const blockInterval = (helper.api!.consts.inflation.inflationBlockInterval as any).toBigInt(); - const totalIssuanceStart = ((await helper.api!.query.inflation.startingYearTotalIssuance()) as any).toBigInt(); - const blockInflation = (await helper.api!.query.inflation.blockInflation() as any).toBigInt(); + const blockInterval = (helper.getApi().consts.inflation.inflationBlockInterval as any).toBigInt(); + const totalIssuanceStart = ((await helper.callRpc('api.query.inflation.startingYearTotalIssuance', [])) as any).toBigInt(); + const blockInflation = (await helper.callRpc('api.query.inflation.blockInflation', []) as any).toBigInt(); const YEAR = 5259600n; // 6-second block. Blocks in one year // const YEAR = 2629800n; // 12-second block. Blocks in one year --- a/tests/src/load_test_sc/metadata.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "metadataVersion": "0.1.0", - "source": { - "hash": "0x168cc3cba9657ad3950fb506e568751f99b90fb097685107f6101675662a8303", - "language": "ink! 3.0.0-rc2", - "compiler": "rustc 1.49.0-nightly" - }, - "contract": { - "name": "loadtester", - "version": "0.1.0", - "authors": [ - "[your_name] <[your_email]>" - ] - }, - "spec": { - "constructors": [ - { - "args": [], - "docs": [], - "name": [ - "new" - ], - "selector": "0xd183512b" - } - ], - "docs": [], - "events": [], - "messages": [ - { - "args": [ - { - "name": "count", - "type": { - "displayName": [ - "u64" - ], - "type": 2 - } - } - ], - "docs": [], - "mutates": true, - "name": [ - "bloat" - ], - "payable": false, - "returnType": null, - "selector": "0x49891c2a" - }, - { - "args": [], - "docs": [], - "mutates": false, - "name": [ - "get" - ], - "payable": false, - "returnType": { - "displayName": [ - "u128" - ], - "type": 3 - }, - "selector": "0x1e5ca456" - } - ] - }, - "storage": { - "struct": { - "fields": [ - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000000", - "ty": 1 - } - }, - "name": "len" - }, - { - "layout": { - "array": { - "cellsPerElem": 1, - "layout": { - "cell": { - "key": "0x0000000001000000000000000000000000000000000000000000000000000000", - "ty": 2 - } - }, - "len": 4294967295, - "offset": "0x0100000000000000000000000000000000000000000000000000000000000000" - } - }, - "name": "elems" - } - ] - } - }, - "name": "vector" - } - ] - } - }, - "types": [ - { - "def": { - "primitive": "u32" - } - }, - { - "def": { - "primitive": "u64" - } - }, - { - "def": { - "primitive": "u128" - } - } - ] -} \ No newline at end of file --- a/tests/src/nesting/migration-check.test.ts +++ /dev/null @@ -1,173 +0,0 @@ -import {expect} from 'chai'; -import usingApi, {executeTransaction, submitTransactionAsync} from '../substrate/substrate-api'; -import {getCreateCollectionResult, getCreateItemResult, normalizeAccountId} from '../util/helpers'; -import {IKeyringPair} from '@polkadot/types/types'; -import {strToUTF16} from '../util/util'; -import waitNewBlocks from '../substrate/wait-new-blocks'; -// Used for polkadot-launch signalling -import find from 'find-process'; - -// todo un-skip for migrations -// todo:playgrounds skipped, this one is outdated. Probably to be deleted/replaced. -describe.skip('Migration testing: Properties', () => { - let alice: IKeyringPair; - - before(async() => { - await usingApi(async (_, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - }); - }); - - it('Preserves collection settings after migration', async () => { - let oldVersion: number; - let collectionId: number; - let collectionOld: any; - let nftId: number; - let nftOld: any; - - await usingApi(async api => { - // ----------- Collection pre-upgrade ------------ - const txCollection = api.tx.unique.createCollectionEx({ - mode: 'NFT', - access: 'AllowList', - name: strToUTF16('Mojave Pictures'), - description: strToUTF16('$2.2 billion power plant!'), - tokenPrefix: '0x0002030', - offchainSchema: '0x111111', - schemaVersion: 'Unique', - limits: { - accountTokenOwnershipLimit: 3, - }, - constOnChainSchema: '0x333333', - variableOnChainSchema: '0x22222', - }); - const events0 = await submitTransactionAsync(alice, txCollection); - const result0 = getCreateCollectionResult(events0); - collectionId = result0.collectionId; - - // Get the pre-upgrade collection info - collectionOld = (await api.query.common.collectionById(collectionId)).toJSON(); - - // ---------- NFT pre-upgrade ------------ - const txNft = api.tx.unique.createItem( - collectionId, - normalizeAccountId(alice), - { - NFT: { - owner: {substrate: alice.address}, - constData: '0x0000', - variableData: '0x1111', - }, - }, - ); - const events1 = await executeTransaction(api, alice, txNft); - const result1 = getCreateItemResult(events1); - nftId = result1.itemId; - - // Get the pre-upgrade NFT data - nftOld = (await api.query.nonfungible.tokenData(collectionId, nftId)).toJSON(); - - // Get the pre-upgrade spec version - oldVersion = (api.consts.system.version.toJSON() as any).specVersion; - }); - - console.log(`Now waiting for the parachain upgrade from ${oldVersion!}...`); - - let newVersion = oldVersion!; - let connectionFailCounter = 0; - - // Cooperate with polkadot-launch if it's running (assuming custom name change to 'polkadot-launch'), and send a custom signal - find('name', 'polkadot-launch', true).then((list) => { - for (const proc of list) { - process.kill(proc.pid, 'SIGUSR1'); - } - }); - - // And wait for the parachain upgrade - { - // Catch warnings like 'RPC methods not decorated' and keep the 'waiting' message in front - const stdlog = console.warn.bind(console); - let warnCount = 0; - console.warn = function(...args){ - if (arguments.length <= 2 || !args[2].includes('RPC methods not decorated')) { - warnCount++; - stdlog.apply(console, args as any); - } - }; - - let oldWarnCount = 0; - while (newVersion == oldVersion! && connectionFailCounter < 5) { - await new Promise(resolve => setTimeout(resolve, 12000)); - try { - await usingApi(async api => { - await waitNewBlocks(api); - newVersion = (api.consts.system.version.toJSON() as any).specVersion; - if (warnCount > oldWarnCount) { - console.log(`Still waiting for the parachain upgrade from ${oldVersion!}...`); - oldWarnCount = warnCount; - } - }); - } catch (_) { - connectionFailCounter++; - } - } - } - - await usingApi(async api => { - // ---------- Collection comparison ----------- - const collectionNew = (await api.query.common.collectionById(collectionId)).toJSON() as any; - - // Make sure the extra fields are what they should be - expect(( - await api.rpc.unique.collectionProperties(collectionId, ['_old_constOnChainSchema']) - )[0].value.toHex()).to.be.equal(collectionOld.constOnChainSchema); - - expect(( - await api.rpc.unique.collectionProperties(collectionId, ['_old_variableOnChainSchema']) - )[0].value.toHex()).to.be.equal(collectionOld.variableOnChainSchema); - - expect(( - await api.rpc.unique.collectionProperties(collectionId, ['_old_offchainSchema']) - )[0].value.toHex()).to.be.equal(collectionOld.offchainSchema); - - expect(( - await api.rpc.unique.collectionProperties(collectionId, ['_old_schemaVersion']) - )[0].value.toHuman()).to.be.equal(collectionOld.schemaVersion); - - expect(collectionNew.permissions).to.be.deep.equal({ - access: collectionOld.access, - mintMode: collectionOld.mintMode, - nesting: null, - }); - - expect(collectionNew.externalCollection).to.be.equal(false); - - // Get rid of extra fields to perform comparison on the rest of the collection - delete collectionNew.permissions; - delete collectionNew.externalCollection; - delete collectionOld.schemaVersion; - delete collectionOld.constOnChainSchema; - delete collectionOld.variableOnChainSchema; - delete collectionOld.offchainSchema; - delete collectionOld.mintMode; - delete collectionOld.access; - delete collectionOld.metaUpdatePermission; // todo look into, doesn't migrate - - expect(collectionNew).to.be.deep.equal(collectionOld); - - // ---------- NFT comparison ----------- - const nftNew = (await api.query.nonfungible.tokenData(collectionId, nftId)).toJSON() as any; - - // Make sure the extra fields are what they should be - expect((await api.rpc.unique.tokenProperties(collectionId, nftId, ['_old_constData']))[0].value.toHex()).to.be.equal(nftOld.constData); - - expect((await api.rpc.unique.tokenProperties(collectionId, nftId, ['_old_variableData']))[0].value.toHex()).to.be.equal(nftOld.variableData); - - // Get rid of extra fields to perform comparison on the rest of the NFT - delete nftOld.constData; - delete nftOld.variableData; - - expect(nftNew).to.be.deep.equal(nftOld); - }); - }); -}); --- a/tests/src/nesting/properties.test.ts +++ b/tests/src/nesting/properties.test.ts @@ -276,7 +276,7 @@ itSub('Reads access rights to properties of a collection', async ({helper}) => { const collection = await helper.nft.mintCollection(alice); - const propertyRights = (await helper.api!.query.common.collectionPropertyPermissions(collection.collectionId)).toJSON(); + const propertyRights = (await helper.callRpc('api.query.common.collectionPropertyPermissions', [collection.collectionId])).toJSON(); expect(propertyRights).to.be.empty; }); @@ -817,7 +817,7 @@ ).to.be.fulfilled; } - const originalSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + const originalSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); return originalSpace; } @@ -840,7 +840,7 @@ ).to.be.rejectedWith(/common\.NoPermission/); } - const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); expect(consumedSpace).to.be.equal(originalSpace); } @@ -875,7 +875,7 @@ ).to.be.rejectedWith(/common\.NoPermission/); } - const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); expect(consumedSpace).to.be.equal(originalSpace); } @@ -911,7 +911,7 @@ expect(await token.getProperties(['non-existent', 'now-existent'])).to.be.empty; - const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); expect(consumedSpace).to.be.equal(originalSpace); } @@ -951,7 +951,7 @@ ])).to.be.rejectedWith(/common\.NoSpaceForProperty/); expect(await token.getProperties(['a_holy_book', 'young_years'])).to.be.empty; - const consumedSpace = await getConsumedSpace(token.collection.helper.api, token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); + const consumedSpace = await getConsumedSpace(token.collection.helper.getApi(), token.collectionId, token.tokenId, pieces == 1n ? 'NFT' : 'RFT'); expect(consumedSpace).to.be.equal(originalSpace); } --- a/tests/src/overflow.test.ts +++ /dev/null @@ -1,73 +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 chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import usingApi from './substrate/substrate-api'; -import {approveExpectSuccess, createCollectionExpectSuccess, createFungibleItemExpectSuccess, getAllowance, getBalance, transferExpectFailure, transferExpectSuccess, transferFromExpectFail, transferFromExpectSuccess, U128_MAX} from './util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -// todo:playgrounds skipped ~ postponed -describe.skip('Integration Test fungible overflows', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - let charlie: IKeyringPair; - - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); - charlie = privateKeyWrapper('//Charlie'); - }); - }); - - it('fails when overflows on transfer', async () => { - await usingApi(async api => { - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - - await createFungibleItemExpectSuccess(alice, fungibleCollectionId, {Value: U128_MAX}); - await transferExpectSuccess(fungibleCollectionId, 0, alice, bob, U128_MAX, 'Fungible'); - - await createFungibleItemExpectSuccess(alice, fungibleCollectionId, {Value: 1n}); - await transferExpectFailure(fungibleCollectionId, 0, alice, bob, 1); - - expect(await getBalance(api, fungibleCollectionId, alice.address, 0)).to.equal(1n); - expect(await getBalance(api, fungibleCollectionId, bob.address, 0)).to.equal(U128_MAX); - }); - }); - - it('fails when overflows on transferFrom', async () => { - await usingApi(async api => { - const fungibleCollectionId = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); - await createFungibleItemExpectSuccess(alice, fungibleCollectionId, {Value: U128_MAX}); - await approveExpectSuccess(fungibleCollectionId, 0, alice, bob.address, U128_MAX); - await transferFromExpectSuccess(fungibleCollectionId, 0, bob, alice, charlie, U128_MAX, 'Fungible'); - - expect(await getBalance(api, fungibleCollectionId, charlie.address, 0)).to.equal(U128_MAX); - expect(await getAllowance(api, fungibleCollectionId, alice.address, bob.address, 0)).to.equal(0n); - - await createFungibleItemExpectSuccess(alice, fungibleCollectionId, {Value: U128_MAX}); - await approveExpectSuccess(fungibleCollectionId, 0, alice, bob.address, 1n); - await transferFromExpectFail(fungibleCollectionId, 0, bob, alice, charlie, 1); - - expect(await getBalance(api, fungibleCollectionId, charlie.address, 0)).to.equal(U128_MAX); - expect(await getAllowance(api, fungibleCollectionId, alice.address, bob.address, 0)).to.equal(1n); - }); - }); -}); --- a/tests/src/pallet-presence.test.ts +++ b/tests/src/pallet-presence.test.ts @@ -59,7 +59,7 @@ describe('Pallet presence', () => { before(async () => { await usingPlaygrounds(async helper => { - const chain = await helper.api!.rpc.system.chain(); + const chain = await helper.callRpc('api.rpc.system.chain', []); const refungible = 'refungible'; const scheduler = 'scheduler'; --- a/tests/src/removeFromContractAllowList.test.ts +++ /dev/null @@ -1,92 +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 usingApi from './substrate/substrate-api'; -import {deployFlipper, toggleFlipValueExpectFailure, toggleFlipValueExpectSuccess} from './util/contracthelpers'; -import {addToContractAllowListExpectSuccess, isAllowlistedInContract, removeFromContractAllowListExpectFailure, removeFromContractAllowListExpectSuccess, toggleContractAllowlistExpectSuccess} from './util/helpers'; -import {IKeyringPair} from '@polkadot/types/types'; -import {expect} from 'chai'; - -// todo:playgrounds skipped again -describe.skip('Integration Test removeFromContractAllowList', () => { - let bob: IKeyringPair; - - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - bob = privateKeyWrapper('//Bob'); - }); - }); - - it('user is no longer allowlisted after removal', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); - - await addToContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); - await removeFromContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); - - expect(await isAllowlistedInContract(flipper.address, bob.address)).to.be.false; - }); - }); - - it('user can\'t execute contract after removal', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); - await toggleContractAllowlistExpectSuccess(deployer, flipper.address.toString(), true); - - await addToContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); - await toggleFlipValueExpectSuccess(bob, flipper); - - await removeFromContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); - await toggleFlipValueExpectFailure(bob, flipper); - }); - }); - - it('can be called twice', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); - - await addToContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); - await removeFromContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); - await removeFromContractAllowListExpectSuccess(deployer, flipper.address.toString(), bob.address); - }); - }); -}); - -describe.skip('Negative Integration Test removeFromContractAllowList', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); - }); - }); - - it('fails when called with non-contract address', async () => { - await usingApi(async () => { - await removeFromContractAllowListExpectFailure(alice, alice.address, bob.address); - }); - }); - - it('fails when executed by non owner', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [flipper] = await deployFlipper(api, privateKeyWrapper); - - await removeFromContractAllowListExpectFailure(alice, flipper.address.toString(), bob.address); - }); - }); -}); --- a/tests/src/rmrk/acceptNft.test.ts +++ b/tests/src/rmrk/acceptNft.test.ts @@ -8,7 +8,7 @@ } from './util/tx'; import {NftIdTuple} from './util/fetch'; import {isNftChildOfAnother, expectTxFailure} from './util/helpers'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; describe('integration test: accept NFT', () => { let api: any; --- a/tests/src/rmrk/addResource.test.ts +++ b/tests/src/rmrk/addResource.test.ts @@ -12,7 +12,7 @@ addNftComposableResource, } from './util/tx'; import {RmrkTraitsResourceResourceInfo as ResourceInfo} from '@polkadot/types/lookup'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; describe('integration test: add NFT resource', () => { const Alice = '//Alice'; --- a/tests/src/rmrk/addTheme.test.ts +++ b/tests/src/rmrk/addTheme.test.ts @@ -3,7 +3,7 @@ import {createBase, addTheme} from './util/tx'; import {expectTxFailure} from './util/helpers'; import {getThemeNames} from './util/fetch'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; describe('integration test: add Theme to Base', () => { let api: any; --- a/tests/src/rmrk/burnNft.test.ts +++ b/tests/src/rmrk/burnNft.test.ts @@ -5,7 +5,7 @@ import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; chai.use(chaiAsPromised); const expect = chai.expect; --- a/tests/src/rmrk/changeCollectionIssuer.test.ts +++ b/tests/src/rmrk/changeCollectionIssuer.test.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {expectTxFailure} from './util/helpers'; import { changeIssuer, --- a/tests/src/rmrk/createBase.test.ts +++ b/tests/src/rmrk/createBase.test.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {createCollection, createBase} from './util/tx'; describe('integration test: create new Base', () => { --- a/tests/src/rmrk/createCollection.test.ts +++ b/tests/src/rmrk/createCollection.test.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {createCollection} from './util/tx'; describe('Integration test: create new collection', () => { --- a/tests/src/rmrk/deleteCollection.test.ts +++ b/tests/src/rmrk/deleteCollection.test.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {expectTxFailure} from './util/helpers'; import {createCollection, deleteCollection} from './util/tx'; --- a/tests/src/rmrk/equipNft.test.ts +++ b/tests/src/rmrk/equipNft.test.ts @@ -1,7 +1,7 @@ import {ApiPromise} from '@polkadot/api'; import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {getNft, getParts, NftIdTuple} from './util/fetch'; import {expectTxFailure} from './util/helpers'; import { --- a/tests/src/rmrk/getOwnedNfts.test.ts +++ b/tests/src/rmrk/getOwnedNfts.test.ts @@ -1,6 +1,6 @@ import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {getOwnedNfts} from './util/fetch'; import {mintNft, createCollection} from './util/tx'; --- a/tests/src/rmrk/lockCollection.test.ts +++ b/tests/src/rmrk/lockCollection.test.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {expectTxFailure} from './util/helpers'; import {createCollection, lockCollection, mintNft} from './util/tx'; --- a/tests/src/rmrk/mintNft.test.ts +++ b/tests/src/rmrk/mintNft.test.ts @@ -1,6 +1,6 @@ import {expect} from 'chai'; import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {getNft} from './util/fetch'; import {expectTxFailure} from './util/helpers'; import {createCollection, mintNft} from './util/tx'; --- a/tests/src/rmrk/rejectNft.test.ts +++ b/tests/src/rmrk/rejectNft.test.ts @@ -8,7 +8,7 @@ } from './util/tx'; import {getChildren, NftIdTuple} from './util/fetch'; import {isNftChildOfAnother, expectTxFailure} from './util/helpers'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; describe('integration test: reject NFT', () => { let api: any; --- a/tests/src/rmrk/removeResource.test.ts +++ b/tests/src/rmrk/removeResource.test.ts @@ -1,7 +1,7 @@ import {expect} from 'chai'; import privateKey from '../substrate/privateKey'; import {executeTransaction, getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {getNft, NftIdTuple} from './util/fetch'; import {expectTxFailure} from './util/helpers'; import { --- a/tests/src/rmrk/rmrkIsolation.test.ts +++ b/tests/src/rmrk/rmrkIsolation.test.ts @@ -9,7 +9,7 @@ requirePallets, normalizeAccountId, Pallets, -} from '../util/helpers'; +} from '../deprecated-helpers/helpers'; import {IKeyringPair} from '@polkadot/types/types'; import {ApiPromise} from '@polkadot/api'; import {it} from 'mocha'; --- a/tests/src/rmrk/sendNft.test.ts +++ b/tests/src/rmrk/sendNft.test.ts @@ -3,7 +3,7 @@ import {createCollection, mintNft, sendNft} from './util/tx'; import {NftIdTuple} from './util/fetch'; import {isNftChildOfAnother, expectTxFailure} from './util/helpers'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; describe('integration test: send NFT', () => { let api: any; --- a/tests/src/rmrk/setCollectionProperty.test.ts +++ b/tests/src/rmrk/setCollectionProperty.test.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {expectTxFailure} from './util/helpers'; import {createCollection, setPropertyCollection} from './util/tx'; --- a/tests/src/rmrk/setEquippableList.test.ts +++ b/tests/src/rmrk/setEquippableList.test.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {expectTxFailure} from './util/helpers'; import {createCollection, createBase, setEquippableList} from './util/tx'; --- a/tests/src/rmrk/setNftProperty.test.ts +++ b/tests/src/rmrk/setNftProperty.test.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {NftIdTuple} from './util/fetch'; import {expectTxFailure} from './util/helpers'; import {createCollection, mintNft, sendNft, setNftProperty} from './util/tx'; --- a/tests/src/rmrk/setResourcePriorities.test.ts +++ b/tests/src/rmrk/setResourcePriorities.test.ts @@ -1,5 +1,5 @@ import {getApiConnection} from '../substrate/substrate-api'; -import {requirePallets, Pallets} from '../util/helpers'; +import {requirePallets, Pallets} from '../deprecated-helpers/helpers'; import {expectTxFailure} from './util/helpers'; import {mintNft, createCollection, setResourcePriorities} from './util/tx'; --- a/tests/src/rpc.load.ts +++ /dev/null @@ -1,155 +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 usingApi, {submitTransactionAsync} from './substrate/substrate-api'; -import {IKeyringPair} from '@polkadot/types/types'; -import {Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract} from '@polkadot/api-contract'; -import {ApiPromise, Keyring} from '@polkadot/api'; -import {findUnusedAddress} from './util/helpers'; -import fs from 'fs'; - -const value = 0; -const gasLimit = 500000n * 1000000n; -const endowment = '1000000000000000'; - -/*eslint no-async-promise-executor: "off"*/ -function deployBlueprint(alice: IKeyringPair, code: CodePromise): Promise { - return new Promise(async (resolve) => { - const unsub = await code - .createBlueprint() - .signAndSend(alice, (result) => { - if (result.status.isInBlock || result.status.isFinalized) { - // here we have an additional field in the result, containing the blueprint - resolve(result.blueprint); - unsub(); - } - }); - }); -} - -/*eslint no-async-promise-executor: "off"*/ -function deployContract(alice: IKeyringPair, blueprint: Blueprint) : Promise { - return new Promise(async (resolve) => { - const unsub = await blueprint.tx - .new(endowment, gasLimit) - .signAndSend(alice, (result) => { - if (result.status.isInBlock || result.status.isFinalized) { - unsub(); - resolve(result); - } - }); - }); -} - -async function prepareDeployer(api: ApiPromise, privateKeyWrapper: ((account: string) => IKeyringPair)) { - // Find unused address - const deployer = await findUnusedAddress(api, privateKeyWrapper); - - // Transfer balance to it - const alice = privateKeyWrapper('//Alice'); - const amount = BigInt(endowment) + 10n**15n; - const tx = api.tx.balances.transfer(deployer.address, amount); - await submitTransactionAsync(alice, tx); - - return deployer; -} - -async function deployLoadTester(api: ApiPromise, privateKeyWrapper: ((account: string) => IKeyringPair)): Promise<[Contract, IKeyringPair]> { - const metadata = JSON.parse(fs.readFileSync('./src/load_test_sc/metadata.json').toString('utf-8')); - const abi = new Abi(metadata); - - const deployer = await prepareDeployer(api, privateKeyWrapper); - - const wasm = fs.readFileSync('./src/load_test_sc/loadtester.wasm'); - - const code = new CodePromise(api, abi, wasm); - - const blueprint = await deployBlueprint(deployer, code); - const contract = (await deployContract(deployer, blueprint))['contract'] as Contract; - - return [contract, deployer]; -} - -async function getScData(contract: Contract, deployer: IKeyringPair) { - const result = await contract.query.get(deployer.address, value, gasLimit); - - if(!result.result.isSuccess) { - throw 'Failed to get value'; - } - return result.result.asSuccess.data; -} - - -describe('RPC Tests', () => { - it('Simple RPC Load Test', async () => { - await usingApi(async api => { - let count = 0; - let hrTime = process.hrtime(); - let microsec1 = hrTime[0] * 1000000 + hrTime[1] / 1000; - let rate = 0; - const checkPoint = 1000; - - /* eslint no-constant-condition: "off" */ - while (true) { - await api.rpc.system.chain(); - count++; - process.stdout.write(`RPC reads: ${count} times at rate ${rate} r/s \r`); - - if (count % checkPoint == 0) { - hrTime = process.hrtime(); - const microsec2 = hrTime[0] * 1000000 + hrTime[1] / 1000; - rate = 1000000*checkPoint/(microsec2 - microsec1); - microsec1 = microsec2; - } - } - }); - }); - - it('Smart Contract RPC Load Test', async () => { - await usingApi(async (api, privateKeyWrapper) => { - - // Deploy smart contract - const [contract, deployer] = await deployLoadTester(api, privateKeyWrapper); - - // Fill smart contract up with data - const bob = privateKeyWrapper('//Bob'); - const tx = contract.tx.bloat(value, gasLimit, 200); - await submitTransactionAsync(bob, tx); - - // Run load test - let count = 0; - let hrTime = process.hrtime(); - let microsec1 = hrTime[0] * 1000000 + hrTime[1] / 1000; - let rate = 0; - const checkPoint = 10; - - /* eslint no-constant-condition: "off" */ - while (true) { - await getScData(contract, deployer); - count++; - process.stdout.write(`SC reads: ${count} times at rate ${rate} r/s \r`); - - if (count % checkPoint == 0) { - hrTime = process.hrtime(); - const microsec2 = hrTime[0] * 1000000 + hrTime[1] / 1000; - rate = 1000000*checkPoint/(microsec2 - microsec1); - microsec1 = microsec2; - } - } - }); - }); - -}); --- a/tests/src/scheduler.test.ts +++ /dev/null @@ -1,210 +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 chai, {expect} from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import { - default as usingApi, - submitTransactionAsync, -} from './substrate/substrate-api'; -import { - createItemExpectSuccess, - createCollectionExpectSuccess, - scheduleTransferExpectSuccess, - scheduleTransferAndWaitExpectSuccess, - setCollectionSponsorExpectSuccess, - confirmSponsorshipExpectSuccess, - findUnusedAddress, - UNIQUE, - enablePublicMintingExpectSuccess, - addToAllowListExpectSuccess, - waitNewBlocks, - normalizeAccountId, - getTokenOwner, - getGenericResult, - scheduleTransferFundsPeriodicExpectSuccess, - getFreeBalance, - confirmSponsorshipByKeyExpectSuccess, - scheduleExpectFailure, -} from './util/helpers'; -import {IKeyringPair} from '@polkadot/types/types'; - -chai.use(chaiAsPromised); - -// todo:playgrounds skipped ~ postponed -describe.skip('Scheduling token and balance transfers', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - let scheduledIdBase: string; - let scheduledIdSlider: number; - - before(async() => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//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 () => { - 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, makeScheduledId()); - }); - }); - - it('Can transfer funds periodically', async () => { - await usingApi(async () => { - const waitForBlocks = 4; - const period = 2; - 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 getFreeBalance(bob); - expect(bobsBalanceAfterFirst > bobsBalanceBefore).to.be.true; - - await waitNewBlocks(period); - const bobsBalanceAfterSecond = await getFreeBalance(bob); - expect(bobsBalanceAfterSecond > bobsBalanceAfterFirst).to.be.true; - }); - }); - - it('Can sponsor scheduling a transaction', async () => { - const collectionId = await createCollectionExpectSuccess(); - await setCollectionSponsorExpectSuccess(collectionId, bob.address); - await confirmSponsorshipExpectSuccess(collectionId, '//Bob'); - - await usingApi(async () => { - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); - - 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, 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('Schedules and dispatches a transaction even if the caller has no funds at the time of the dispatch', async () => { - await usingApi(async (api, privateKeyWrapper) => { - // Find an empty, unused account - const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); - - const collectionId = await createCollectionExpectSuccess(); - - // Add zeroBalance address to allow list - await enablePublicMintingExpectSuccess(alice, collectionId); - await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address); - - // Grace zeroBalance with money, enough to cover future transactions - const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE); - await submitTransactionAsync(alice, balanceTx); - - // Mint a fresh NFT - const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT'); - - // Schedule transfer of the NFT a few blocks ahead - const waitForBlocks = 5; - await scheduleTransferExpectSuccess(collectionId, tokenId, zeroBalance, alice, 1, waitForBlocks, makeScheduledId()); - - // Get rid of the account's funds before the scheduled transaction takes place - 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;*/ - - // Wait for a certain number of blocks, discarding the ones that already happened while accepting the late transactions - await waitNewBlocks(waitForBlocks - 3); - - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(alice.address)); - }); - }); - - it('Sponsor going bankrupt does not impact a scheduled transaction', async () => { - const collectionId = await createCollectionExpectSuccess(); - - await usingApi(async (api, privateKeyWrapper) => { - const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); - const balanceTx = api.tx.balances.transfer(zeroBalance.address, 1n * UNIQUE); - await submitTransactionAsync(alice, balanceTx); - - await setCollectionSponsorExpectSuccess(collectionId, zeroBalance.address); - await confirmSponsorshipByKeyExpectSuccess(collectionId, zeroBalance); - - const tokenId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address); - - 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); - const events = await submitTransactionAsync(alice, sudoTx); - 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(waitForBlocks - 3); - - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(zeroBalance.address)); - }); - }); - - 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, privateKeyWrapper) => { - const zeroBalance = await findUnusedAddress(api, privateKeyWrapper); - - await enablePublicMintingExpectSuccess(alice, collectionId); - await addToAllowListExpectSuccess(alice, collectionId, zeroBalance.address); - - const bobBalanceBefore = await getFreeBalance(bob); - - const createData = {nft: {const_data: [], variable_data: []}}; - const creationTx = api.tx.unique.createItem(collectionId, normalizeAccountId(zeroBalance), createData as any); - - /*const badTransaction = async function () { - await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice); - }; - await expect(badTransaction()).to.be.rejectedWith('Inability to pay some fees');*/ - - await scheduleExpectFailure(creationTx, zeroBalance, 3, makeScheduledId(), 1, 3); - - expect(await getFreeBalance(bob)).to.be.equal(bobBalanceBefore); - }); - }); -}); --- a/tests/src/setChainLimits.test.ts +++ /dev/null @@ -1,64 +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 usingApi from './substrate/substrate-api'; -import { - createCollectionExpectSuccess, - addCollectionAdminExpectSuccess, - setChainLimitsExpectFailure, - IChainLimits, -} from './util/helpers'; - -// todo:playgrounds skipped ~ postponed -describe.skip('Negative Integration Test setChainLimits', () => { - let alice: IKeyringPair; - let bob: IKeyringPair; - let dave: IKeyringPair; - let limits: IChainLimits; - - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - bob = privateKeyWrapper('//Bob'); - dave = privateKeyWrapper('//Dave'); - limits = { - collectionNumbersLimit : 1, - accountTokenOwnershipLimit: 1, - collectionsAdminsLimit: 1, - customDataLimit: 1, - nftSponsorTransferTimeout: 1, - fungibleSponsorTransferTimeout: 1, - refungibleSponsorTransferTimeout: 1, - }; - }); - }); - - it('Collection owner cannot set chain limits', async () => { - await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await setChainLimitsExpectFailure(alice, limits); - }); - - it('Collection admin cannot set chain limits', async () => { - const collectionId = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); - await addCollectionAdminExpectSuccess(alice, collectionId, bob.address); - await setChainLimitsExpectFailure(bob, limits); - }); - - it('Regular user cannot set chain limits', async () => { - await setChainLimitsExpectFailure(dave, limits); - }); -}); --- a/tests/src/setContractSponsoringRateLimit.test.ts +++ /dev/null @@ -1,80 +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 usingApi from './substrate/substrate-api'; -import waitNewBlocks from './substrate/wait-new-blocks'; -import {deployFlipper, toggleFlipValueExpectFailure, toggleFlipValueExpectSuccess} from './util/contracthelpers'; -import { - enableContractSponsoringExpectSuccess, - findUnusedAddress, - setContractSponsoringRateLimitExpectFailure, - setContractSponsoringRateLimitExpectSuccess, -} from './util/helpers'; - -// todo:playgrounds skipped~postponed test -describe.skip('Integration Test setContractSponsoringRateLimit', () => { - it('ensure sponsored contract can\'t be called twice without pause for free', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const user = await findUnusedAddress(api, privateKeyWrapper); - - const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); - await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); - await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 10); - await toggleFlipValueExpectSuccess(user, flipper); - await toggleFlipValueExpectFailure(user, flipper); - }); - }); - - it('ensure sponsored contract can be called twice with pause for free', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const user = await findUnusedAddress(api, privateKeyWrapper); - - const [flipper, deployer] = await deployFlipper(api, privateKeyWrapper); - await enableContractSponsoringExpectSuccess(deployer, flipper.address, true); - await setContractSponsoringRateLimitExpectSuccess(deployer, flipper.address, 1); - await toggleFlipValueExpectSuccess(user, flipper); - await waitNewBlocks(api, 1); - await toggleFlipValueExpectSuccess(user, flipper); - }); - }); -}); - -describe.skip('Negative Integration Test setContractSponsoringRateLimit', () => { - let alice: IKeyringPair; - - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - }); - }); - - it('fails when called for non-contract address', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const user = await findUnusedAddress(api, privateKeyWrapper); - - await setContractSponsoringRateLimitExpectFailure(alice, user.address, 1); - }); - }); - - it('fails when called by non-owning user', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [flipper] = await deployFlipper(api, privateKeyWrapper); - - await setContractSponsoringRateLimitExpectFailure(alice, flipper.address, 1); - }); - }); -}); --- a/tests/src/substrate/get-balance.ts +++ b/tests/src/substrate/get-balance.ts @@ -19,7 +19,7 @@ import promisifySubstrate from './promisify-substrate'; import {IKeyringPair} from '@polkadot/types/types'; import {submitTransactionAsync} from './substrate-api'; -import {getGenericResult} from '../util/helpers'; +import {getGenericResult} from '../deprecated-helpers/helpers'; import {expect} from 'chai'; export default async function getBalance(api: ApiPromise, accounts: string[]): Promise> { --- a/tests/src/toggleContractAllowList.test.ts +++ /dev/null @@ -1,167 +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 chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import usingApi, {submitTransactionAsync, submitTransactionExpectFailAsync} from './substrate/substrate-api'; -import { - deployFlipper, - getFlipValue, -} from './util/contracthelpers'; -import { - getGenericResult, -} from './util/helpers'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -const value = 0; -const gasLimit = 3000n * 1000000n; - -// todo:playgrounds skipped ~ postpone -describe.skip('Integration Test toggleContractAllowList', () => { - - it('Enable allow list contract mode', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); - - const enabledBefore = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); - const enableAllowListTx = api.tx.unique.toggleContractAllowList(contract.address, true); - const enableEvents = await submitTransactionAsync(deployer, enableAllowListTx); - const enabled = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); - - expect(getGenericResult(enableEvents).success).to.be.true; - expect(enabledBefore).to.be.false; - expect(enabled).to.be.true; - }); - }); - - it('Only allowlisted account can call contract', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const bob = privateKeyWrapper('//Bob'); - - const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); - - let flipValueBefore = await getFlipValue(contract, deployer); - const flip = contract.tx.flip({value, gasLimit}); - await submitTransactionAsync(bob, flip); - const flipValueAfter = await getFlipValue(contract,deployer); - expect(flipValueAfter).to.be.eq(!flipValueBefore, 'Anyone can call new contract.'); - - const deployerCanFlip = async () => { - const flipValueBefore = await getFlipValue(contract, deployer); - const deployerFlip = contract.tx.flip({value, gasLimit}); - await submitTransactionAsync(deployer, deployerFlip); - const aliceFlip1Response = await getFlipValue(contract, deployer); - expect(aliceFlip1Response).to.be.eq(!flipValueBefore, 'Deployer always can flip.'); - }; - await deployerCanFlip(); - - flipValueBefore = await getFlipValue(contract, deployer); - const enableAllowListTx = api.tx.unique.toggleContractAllowList(contract.address, true); - await submitTransactionAsync(deployer, enableAllowListTx); - const flipWithEnabledAllowList = contract.tx.flip({value, gasLimit}); - await expect(submitTransactionExpectFailAsync(bob, flipWithEnabledAllowList)).to.be.rejected; - const flipValueAfterEnableAllowList = await getFlipValue(contract, deployer); - expect(flipValueAfterEnableAllowList).to.be.eq(flipValueBefore, 'Enabling allowlist doesn\'t make it possible to call contract for everyone.'); - - await deployerCanFlip(); - - flipValueBefore = await getFlipValue(contract, deployer); - const addBobToAllowListTx = api.tx.unique.addToContractAllowList(contract.address, bob.address); - await submitTransactionAsync(deployer, addBobToAllowListTx); - const flipWithAllowlistedBob = contract.tx.flip({value, gasLimit}); - await submitTransactionAsync(bob, flipWithAllowlistedBob); - const flipAfterAllowListed = await getFlipValue(contract,deployer); - expect(flipAfterAllowListed).to.be.eq(!flipValueBefore, 'Bob was allowlisted, now he can flip.'); - - await deployerCanFlip(); - - flipValueBefore = await getFlipValue(contract, deployer); - const removeBobFromAllowListTx = api.tx.unique.removeFromContractAllowList(contract.address, bob.address); - await submitTransactionAsync(deployer, removeBobFromAllowListTx); - const bobRemoved = contract.tx.flip({value, gasLimit}); - await expect(submitTransactionExpectFailAsync(bob, bobRemoved)).to.be.rejected; - const afterBobRemoved = await getFlipValue(contract, deployer); - expect(afterBobRemoved).to.be.eq(flipValueBefore, 'Bob can\'t call contract, now when he is removeed from allow list.'); - - await deployerCanFlip(); - - flipValueBefore = await getFlipValue(contract, deployer); - const disableAllowListTx = api.tx.unique.toggleContractAllowList(contract.address, false); - await submitTransactionAsync(deployer, disableAllowListTx); - const allowListDisabledFlip = contract.tx.flip({value, gasLimit}); - await submitTransactionAsync(bob, allowListDisabledFlip); - const afterAllowListDisabled = await getFlipValue(contract,deployer); - expect(afterAllowListDisabled).to.be.eq(!flipValueBefore, 'Anyone can call contract with disabled allowlist.'); - - }); - }); - - it('Enabling allow list repeatedly should not produce errors', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const [contract, deployer] = await deployFlipper(api, privateKeyWrapper); - - const enabledBefore = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); - const enableAllowListTx = api.tx.unique.toggleContractAllowList(contract.address, true); - const enableEvents = await submitTransactionAsync(deployer, enableAllowListTx); - const enabled = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); - const enableAgainEvents = await submitTransactionAsync(deployer, enableAllowListTx); - const enabledAgain = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); - - expect(getGenericResult(enableEvents).success).to.be.true; - expect(enabledBefore).to.be.false; - expect(enabled).to.be.true; - expect(getGenericResult(enableAgainEvents).success).to.be.true; - expect(enabledAgain).to.be.true; - }); - }); - -}); - -describe.skip('Negative Integration Test toggleContractAllowList', () => { - - it('Enable allow list for a non-contract', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const alice = privateKeyWrapper('//Alice'); - const bobGuineaPig = privateKeyWrapper('//Bob'); - - const enabledBefore = (await api.query.unique.contractAllowListEnabled(bobGuineaPig.address)).toJSON(); - const enableAllowListTx = api.tx.unique.toggleContractAllowList(bobGuineaPig.address, true); - await expect(submitTransactionExpectFailAsync(alice, enableAllowListTx)).to.be.rejected; - const enabled = (await api.query.unique.contractAllowListEnabled(bobGuineaPig.address)).toJSON(); - - expect(enabledBefore).to.be.false; - expect(enabled).to.be.false; - }); - }); - - it('Enable allow list using a non-owner address', async () => { - await usingApi(async (api, privateKeyWrapper) => { - const bob = privateKeyWrapper('//Bob'); - const [contract] = await deployFlipper(api, privateKeyWrapper); - - const enabledBefore = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); - const enableAllowListTx = api.tx.unique.toggleContractAllowList(contract.address, true); - await expect(submitTransactionExpectFailAsync(bob, enableAllowListTx)).to.be.rejected; - const enabled = (await api.query.unique.contractAllowListEnabled(contract.address)).toJSON(); - - expect(enabledBefore).to.be.false; - expect(enabled).to.be.false; - }); - }); - -}); --- a/tests/src/transfer.nload.ts +++ b/tests/src/transfer.nload.ts @@ -18,10 +18,24 @@ import {IKeyringPair} from '@polkadot/types/types'; import usingApi, {submitTransactionAsync} from './substrate/substrate-api'; import waitNewBlocks from './substrate/wait-new-blocks'; -import {findUnusedAddresses} from './util/helpers'; import * as cluster from 'cluster'; import os from 'os'; +async function findUnusedAddress(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair, seedAddition = ''): Promise { + let bal = 0n; + let unused; + do { + const randomSeed = 'seed' + Math.floor(Math.random() * Math.floor(10000)) + seedAddition; + unused = privateKeyWrapper(`//${randomSeed}`); + bal = (await api.query.system.account(unused.address)).data.free.toBigInt(); + } while (bal !== 0n); + return unused; +} + +function findUnusedAddresses(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair, amount: number): Promise { + return Promise.all(new Array(amount).fill(null).map(() => findUnusedAddress(api, privateKeyWrapper, '_' + Date.now()))); +} + // Innacurate transfer fee const FEE = 10n ** 8n; --- a/tests/src/transfer_contract/metadata.json +++ /dev/null @@ -1,469 +0,0 @@ -{ - "metadataVersion": "0.1.0", - "source": { - "hash": "0xc6c3f47adeafe86d1674ed72c7179605787842f2f05a2d7da0dbabf3c4fa1aa8", - "language": "ink! 3.0.0-rc3", - "compiler": "rustc 1.52.0-nightly" - }, - "contract": { - "name": "nft_transfer", - "version": "0.1.0", - "authors": [ - "[Greg Zaitsev] <[your_email]>" - ] - }, - "spec": { - "constructors": [ - { - "args": [], - "docs": [ - "Default Constructor", - "", - "Constructors can delegate to other constructors." - ], - "name": [ - "default" - ], - "selector": "0xed4b9d1b" - } - ], - "docs": [], - "events": [], - "messages": [ - { - "args": [ - { - "name": "recipient", - "type": { - "displayName": [ - "AccountId" - ], - "type": 1 - } - }, - { - "name": "collection_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "token_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "amount", - "type": { - "displayName": [ - "u128" - ], - "type": 5 - } - } - ], - "docs": [ - " Transfer one NFT token" - ], - "mutates": true, - "name": [ - "transfer" - ], - "payable": false, - "returnType": null, - "selector": "0x84a15da1" - }, - { - "args": [ - { - "name": "recipient", - "type": { - "displayName": [ - "AccountId" - ], - "type": 1 - } - }, - { - "name": "collection_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "data", - "type": { - "displayName": [ - "CreateItemData" - ], - "type": 6 - } - } - ], - "docs": [], - "mutates": true, - "name": [ - "create_item" - ], - "payable": false, - "returnType": null, - "selector": "0xd7c3f083" - }, - { - "args": [ - { - "name": "owner", - "type": { - "displayName": [ - "AccountId" - ], - "type": 1 - } - }, - { - "name": "collection_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "data", - "type": { - "displayName": [ - "Vec" - ], - "type": 8 - } - } - ], - "docs": [], - "mutates": true, - "name": [ - "create_multiple_items" - ], - "payable": false, - "returnType": null, - "selector": "0x15f9a1eb" - }, - { - "args": [ - { - "name": "spender", - "type": { - "displayName": [ - "AccountId" - ], - "type": 1 - } - }, - { - "name": "collection_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "item_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "amount", - "type": { - "displayName": [ - "u128" - ], - "type": 5 - } - } - ], - "docs": [], - "mutates": true, - "name": [ - "approve" - ], - "payable": false, - "returnType": null, - "selector": "0x681266a0" - }, - { - "args": [ - { - "name": "owner", - "type": { - "displayName": [ - "AccountId" - ], - "type": 1 - } - }, - { - "name": "recipient", - "type": { - "displayName": [ - "AccountId" - ], - "type": 1 - } - }, - { - "name": "collection_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "item_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "amount", - "type": { - "displayName": [ - "u128" - ], - "type": 5 - } - } - ], - "docs": [], - "mutates": true, - "name": [ - "transfer_from" - ], - "payable": false, - "returnType": null, - "selector": "0x0b396f18" - }, - { - "args": [ - { - "name": "collection_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "item_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "data", - "type": { - "displayName": [ - "Vec" - ], - "type": 7 - } - } - ], - "docs": [], - "mutates": true, - "name": [ - "set_variable_meta_data" - ], - "payable": false, - "returnType": null, - "selector": "0xb0b26da2" - }, - { - "args": [ - { - "name": "collection_id", - "type": { - "displayName": [ - "u32" - ], - "type": 4 - } - }, - { - "name": "address", - "type": { - "displayName": [ - "AccountId" - ], - "type": 1 - } - }, - { - "name": "allowlisted", - "type": { - "displayName": [ - "bool" - ], - "type": 9 - } - } - ], - "docs": [], - "mutates": true, - "name": [ - "toggle_allow_list" - ], - "payable": false, - "returnType": null, - "selector": "0x98574dac" - } - ] - }, - "storage": { - "struct": { - "fields": [] - } - }, - "types": [ - { - "def": { - "composite": { - "fields": [ - { - "type": 2, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_env", - "types", - "AccountId" - ] - }, - { - "def": { - "array": { - "len": 32, - "type": 3 - } - } - }, - { - "def": { - "primitive": "u8" - } - }, - { - "def": { - "primitive": "u32" - } - }, - { - "def": { - "primitive": "u128" - } - }, - { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "name": "const_data", - "type": 7, - "typeName": "Vec" - }, - { - "name": "variable_data", - "type": 7, - "typeName": "Vec" - } - ], - "name": "Nft" - }, - { - "fields": [ - { - "name": "value", - "type": 5, - "typeName": "u128" - } - ], - "name": "Fungible" - }, - { - "fields": [ - { - "name": "const_data", - "type": 7, - "typeName": "Vec" - }, - { - "name": "variable_data", - "type": 7, - "typeName": "Vec" - }, - { - "name": "pieces", - "type": 5, - "typeName": "u128" - } - ], - "name": "ReFungible" - } - ] - } - }, - "path": [ - "nft_transfer", - "CreateItemData" - ] - }, - { - "def": { - "sequence": { - "type": 3 - } - } - }, - { - "def": { - "sequence": { - "type": 6 - } - } - }, - { - "def": { - "primitive": "bool" - } - } - ] -} \ No newline at end of file --- a/tests/src/tx-version-presence.test.ts +++ b/tests/src/tx-version-presence.test.ts @@ -22,7 +22,7 @@ describe('TxVersion is present', () => { before(async () => { await usingPlaygrounds(async helper => { - metadata = await helper.api!.rpc.state.getMetadata(); + metadata = await helper.callRpc('api.rpc.state.getMetadata', []); }); }); --- a/tests/src/util/contracthelpers.ts +++ /dev/null @@ -1,114 +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 chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import {submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api'; -import fs from 'fs'; -import {Abi, CodePromise, ContractPromise as Contract} from '@polkadot/api-contract'; -import {IKeyringPair} from '@polkadot/types/types'; -import {ApiPromise} from '@polkadot/api'; - -chai.use(chaiAsPromised); -const expect = chai.expect; -import {findUnusedAddress, getGenericResult} from '../util/helpers'; - -const value = 0; -const gasLimit = '200000000000'; -const endowment = '100000000000000000'; - -/* eslint no-async-promise-executor: "off" */ -function deployContract(alice: IKeyringPair, code: CodePromise, constructor = 'default', ...args: any[]): Promise { - return new Promise(async (resolve) => { - const unsub = await (code as any) - .tx[constructor]({value: endowment, gasLimit}, ...args) - .signAndSend(alice, (result: any) => { - if (result.status.isInBlock || result.status.isFinalized) { - // here we have an additional field in the result, containing the blueprint - resolve((result as any).contract); - unsub(); - } - }); - }); -} - -async function prepareDeployer(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair) { - // Find unused address - const deployer = await findUnusedAddress(api, privateKeyWrapper); - - // Transfer balance to it - const alice = privateKeyWrapper('//Alice'); - const amount = BigInt(endowment) + 10n**15n; - const tx = api.tx.balances.transfer(deployer.address, amount); - await submitTransactionAsync(alice, tx); - - return deployer; -} - -export async function deployFlipper(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair): Promise<[Contract, IKeyringPair]> { - const metadata = JSON.parse(fs.readFileSync('./src/flipper/metadata.json').toString('utf-8')); - const abi = new Abi(metadata); - - const deployer = await prepareDeployer(api, privateKeyWrapper); - - const wasm = fs.readFileSync('./src/flipper/flipper.wasm'); - - const code = new CodePromise(api, abi, wasm); - - const contract = (await deployContract(deployer, code, 'new', true)) as Contract; - - const initialGetResponse = await getFlipValue(contract, deployer); - expect(initialGetResponse).to.be.true; - - return [contract, deployer]; -} - -export async function getFlipValue(contract: Contract, deployer: IKeyringPair) { - const result = await contract.query.get(deployer.address, {value, gasLimit}); - - if(!result.result.isOk) { - throw 'Failed to get flipper value'; - } - return (result.result.asOk.data[0] == 0x00) ? false : true; -} - -export async function toggleFlipValueExpectSuccess(sender: IKeyringPair, contract: Contract) { - const tx = contract.tx.flip({value, gasLimit}); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; -} - -export async function toggleFlipValueExpectFailure(sender: IKeyringPair, contract: Contract) { - const tx = contract.tx.flip({value, gasLimit}); - await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; -} - -export async function deployTransferContract(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair): Promise<[Contract, IKeyringPair]> { - const metadata = JSON.parse(fs.readFileSync('./src/transfer_contract/metadata.json').toString('utf-8')); - const abi = new Abi(metadata); - - const deployer = await prepareDeployer(api, privateKeyWrapper); - - const wasm = fs.readFileSync('./src/transfer_contract/nft_transfer.wasm'); - - const code = new CodePromise(api, abi, wasm); - - const contract = await deployContract(deployer, code); - - return [contract, deployer]; -} --- a/tests/src/util/helpers.ts +++ /dev/null @@ -1,1879 +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 '../interfaces/augment-api-rpc'; -import '../interfaces/augment-api-query'; -import {ApiPromise, Keyring} from '@polkadot/api'; -import type {AccountId, EventRecord, Event, BlockNumber} from '@polkadot/types/interfaces'; -import type {GenericEventData} from '@polkadot/types'; -import {AnyTuple, IEvent, IKeyringPair} from '@polkadot/types/types'; -import {evmToAddress} from '@polkadot/util-crypto'; -import {AnyNumber} from '@polkadot/types-codec/types'; -import BN from 'bn.js'; -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import {default as usingApi, executeTransaction, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api'; -import {hexToStr, strToUTF16, utf16ToStr} from './util'; -import {UpDataStructsRpcCollection, UpDataStructsCreateItemData, UpDataStructsProperty} from '@polkadot/types/lookup'; -import {UpDataStructsTokenChild} from '../interfaces'; -import {Context} from 'mocha'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -export type CrossAccountId = { - Substrate: string, -} | { - Ethereum: string, -}; - - -export enum Pallets { - Inflation = 'inflation', - RmrkCore = 'rmrkcore', - RmrkEquip = 'rmrkequip', - ReFungible = 'refungible', - Fungible = 'fungible', - NFT = 'nonfungible', - Scheduler = 'scheduler', - AppPromotion = 'apppromotion', -} - -export async function isUnique(): Promise { - return usingApi(async api => { - const chain = await api.rpc.system.chain(); - - return chain.eq('UNIQUE'); - }); -} - -export async function isQuartz(): Promise { - return usingApi(async api => { - const chain = await api.rpc.system.chain(); - - return chain.eq('QUARTZ'); - }); -} - -let modulesNames: any; -export function getModuleNames(api: ApiPromise): string[] { - if (typeof modulesNames === 'undefined') - modulesNames = api.runtimeMetadata.asLatest.pallets.map(m => m.name.toString().toLowerCase()); - return modulesNames; -} - -export async function missingRequiredPallets(requiredPallets: string[]): Promise { - return await usingApi(async api => { - const pallets = getModuleNames(api); - - return requiredPallets.filter(p => !pallets.includes(p)); - }); -} - -export async function checkPalletsPresence(requiredPallets: string[]): Promise { - return (await missingRequiredPallets(requiredPallets)).length == 0; -} - -export async function requirePallets(mocha: Context, requiredPallets: string[]) { - const missingPallets = await missingRequiredPallets(requiredPallets); - - if (missingPallets.length > 0) { - const skippingTestMsg = `\tSkipping test "${mocha.test?.title}".`; - const missingPalletsMsg = `\tThe following pallets are missing:\n\t- ${missingPallets.join('\n\t- ')}`; - const skipMsg = `${skippingTestMsg}\n${missingPalletsMsg}`; - - console.error('\x1b[38:5:208m%s\x1b[0m', skipMsg); - - mocha.skip(); - } -} - -export function bigIntToSub(api: ApiPromise, number: bigint) { - return api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON(); -} - -export function bigIntToDecimals(number: bigint, decimals = 18): string { - const numberStr = number.toString(); - const dotPos = numberStr.length - decimals; - - if (dotPos <= 0) { - return '0.' + '0'.repeat(Math.abs(dotPos)) + numberStr; - } else { - const intPart = numberStr.substring(0, dotPos); - const fractPart = numberStr.substring(dotPos); - return intPart + '.' + fractPart; - } -} - -export function normalizeAccountId(input: string | AccountId | CrossAccountId | IKeyringPair): CrossAccountId { - if (typeof input === 'string') { - if (input.length >= 47) { - return {Substrate: input}; - } else if (input.length === 42 && input.startsWith('0x')) { - return {Ethereum: input.toLowerCase()}; - } else if (input.length === 40 && !input.startsWith('0x')) { - return {Ethereum: '0x' + input.toLowerCase()}; - } else { - throw new Error(`Unknown address format: "${input}"`); - } - } - if ('address' in input) { - return {Substrate: input.address}; - } - if ('Ethereum' in input) { - return { - Ethereum: input.Ethereum.toLowerCase(), - }; - } else if ('ethereum' in input) { - return { - Ethereum: (input as any).ethereum.toLowerCase(), - }; - } else if ('Substrate' in input) { - return input; - } else if ('substrate' in input) { - return { - Substrate: (input as any).substrate, - }; - } - - // AccountId - return {Substrate: input.toString()}; -} -export function toSubstrateAddress(input: string | CrossAccountId | IKeyringPair): string { - input = normalizeAccountId(input); - if ('Substrate' in input) { - return input.Substrate; - } else { - return evmToAddress(input.Ethereum); - } -} - -export const U128_MAX = (1n << 128n) - 1n; - -const MICROUNIQUE = 1_000_000_000_000n; -const MILLIUNIQUE = 1_000n * MICROUNIQUE; -const CENTIUNIQUE = 10n * MILLIUNIQUE; -export const UNIQUE = 100n * CENTIUNIQUE; - -interface GenericResult { - success: boolean; - data: T | null; -} - -interface CreateCollectionResult { - success: boolean; - collectionId: number; -} - -interface CreateItemResult { - success: boolean; - collectionId: number; - itemId: number; - recipient?: CrossAccountId; - amount?: number; -} - -interface DestroyItemResult { - success: boolean; - collectionId: number; - itemId: number; - owner: CrossAccountId; - amount: number; -} - -interface TransferResult { - collectionId: number; - itemId: number; - sender?: CrossAccountId; - recipient?: CrossAccountId; - value: bigint; -} - -interface IReFungibleOwner { - fraction: BN; - owner: number[]; -} - -interface IGetMessage { - checkMsgUnqMethod: string; - checkMsgTrsMethod: string; - checkMsgSysMethod: string; -} - -export interface IFungibleTokenDataType { - value: number; -} - -export interface IChainLimits { - collectionNumbersLimit: number; - accountTokenOwnershipLimit: number; - collectionsAdminsLimit: number; - customDataLimit: number; - nftSponsorTransferTimeout: number; - fungibleSponsorTransferTimeout: number; - refungibleSponsorTransferTimeout: number; - //offchainSchemaLimit: number; - //constOnChainSchemaLimit: number; -} - -export interface IReFungibleTokenDataType { - owner: IReFungibleOwner[]; -} - -export function uniqueEventMessage(events: EventRecord[]): IGetMessage { - let checkMsgUnqMethod = ''; - let checkMsgTrsMethod = ''; - let checkMsgSysMethod = ''; - events.forEach(({event: {method, section}}) => { - if (section === 'common') { - checkMsgUnqMethod = method; - } else if (section === 'treasury') { - checkMsgTrsMethod = method; - } else if (section === 'system') { - checkMsgSysMethod = method; - } else { return null; } - }); - const result: IGetMessage = { - checkMsgUnqMethod, - checkMsgTrsMethod, - checkMsgSysMethod, - }; - return result; -} - -export function getEvent(events: EventRecord[], check: (event: IEvent) => event is T): T | undefined { - const event = events.find(r => check(r.event)); - if (!event) return; - return event.event as T; -} - -export function getGenericResult(events: EventRecord[]): GenericResult; -export function getGenericResult( - events: EventRecord[], - expectSection: string, - expectMethod: string, - extractAction: (data: GenericEventData) => T -): GenericResult; - -export function getGenericResult( - events: EventRecord[], - expectSection?: string, - expectMethod?: string, - extractAction?: (data: GenericEventData) => T, -): GenericResult { - let success = false; - let successData = null; - - events.forEach(({event: {data, method, section}}) => { - // console.log(` ${phase}: ${section}.${method}:: ${data}`); - if (method === 'ExtrinsicSuccess') { - success = true; - } else if ((expectSection == section) && (expectMethod == method)) { - successData = extractAction!(data as any); - } - }); - - const result: GenericResult = { - success, - data: successData, - }; - return result; -} - -export function getCreateCollectionResult(events: EventRecord[]): CreateCollectionResult { - const genericResult = getGenericResult(events, 'common', 'CollectionCreated', (data) => parseInt(data[0].toString(), 10)); - const result: CreateCollectionResult = { - success: genericResult.success, - collectionId: genericResult.data ?? 0, - }; - return result; -} - -export function getCreateItemsResult(events: EventRecord[]): CreateItemResult[] { - const results: CreateItemResult[] = []; - - const genericResult = getGenericResult(events, 'common', 'ItemCreated', (data) => { - const collectionId = parseInt(data[0].toString(), 10); - const itemId = parseInt(data[1].toString(), 10); - const recipient = normalizeAccountId(data[2].toJSON() as any); - const amount = parseInt(data[3].toString(), 10); - - const itemRes: CreateItemResult = { - success: true, - collectionId, - itemId, - recipient, - amount, - }; - - results.push(itemRes); - return results; - }); - - if (!genericResult.success) return []; - return results; -} - -export function getCreateItemResult(events: EventRecord[]): CreateItemResult { - const genericResult = getGenericResult(events, 'common', 'ItemCreated', (data) => data.map(function(value) { return value.toJSON(); })); - - if (genericResult.data == null) - return { - success: genericResult.success, - collectionId: 0, - itemId: 0, - amount: 0, - }; - else - return { - success: genericResult.success, - collectionId: genericResult.data[0] as number, - itemId: genericResult.data[1] as number, - recipient: normalizeAccountId(genericResult.data![2] as any), - amount: genericResult.data[3] as number, - }; -} - -export function getDestroyItemsResult(events: EventRecord[]): DestroyItemResult[] { - const results: DestroyItemResult[] = []; - - const genericResult = getGenericResult(events, 'common', 'ItemDestroyed', (data) => { - const collectionId = parseInt(data[0].toString(), 10); - const itemId = parseInt(data[1].toString(), 10); - const owner = normalizeAccountId(data[2].toJSON() as any); - const amount = parseInt(data[3].toString(), 10); - - const itemRes: DestroyItemResult = { - success: true, - collectionId, - itemId, - owner, - amount, - }; - - results.push(itemRes); - return results; - }); - - if (!genericResult.success) return []; - return results; -} - -export function getTransferResult(api: ApiPromise, events: EventRecord[]): TransferResult { - for (const {event} of events) { - if (api.events.common.Transfer.is(event)) { - const [collection, token, sender, recipient, value] = event.data; - return { - collectionId: collection.toNumber(), - itemId: token.toNumber(), - sender: normalizeAccountId(sender.toJSON() as any), - recipient: normalizeAccountId(recipient.toJSON() as any), - value: value.toBigInt(), - }; - } - } - throw new Error('no transfer event'); -} - -interface Nft { - type: 'NFT'; -} - -interface Fungible { - type: 'Fungible'; - decimalPoints: number; -} - -interface ReFungible { - type: 'ReFungible'; -} - -export type CollectionMode = Nft | Fungible | ReFungible; - -export type Property = { - key: any, - value: any, -}; - -type Permission = { - mutable: boolean; - collectionAdmin: boolean; - tokenOwner: boolean; -} - -type PropertyPermission = { - key: any; - permission: Permission; -} - -export type CreateCollectionParams = { - mode: CollectionMode, - name: string, - description: string, - tokenPrefix: string, - properties?: Array, - propPerm?: Array -}; - -const defaultCreateCollectionParams: CreateCollectionParams = { - description: 'description', - mode: {type: 'NFT'}, - name: 'name', - tokenPrefix: 'prefix', -}; - -export async function -createCollection( - api: ApiPromise, - sender: IKeyringPair, - params: Partial = {}, -): Promise { - const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; - - let modeprm = {}; - if (mode.type === 'NFT') { - modeprm = {nft: null}; - } else if (mode.type === 'Fungible') { - modeprm = {fungible: mode.decimalPoints}; - } else if (mode.type === 'ReFungible') { - modeprm = {refungible: null}; - } - - const tx = api.tx.unique.createCollectionEx({ - name: strToUTF16(name), - description: strToUTF16(description), - tokenPrefix: strToUTF16(tokenPrefix), - mode: modeprm as any, - }); - const events = await executeTransaction(api, sender, tx); - return getCreateCollectionResult(events); -} - -export async function createCollectionExpectSuccess(params: Partial = {}): Promise { - const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; - - let collectionId = 0; - await usingApi(async (api, privateKeyWrapper) => { - // Get number of collections before the transaction - const collectionCountBefore = await getCreatedCollectionCount(api); - - // Run the CreateCollection transaction - const alicePrivateKey = privateKeyWrapper('//Alice'); - - const result = await createCollection(api, alicePrivateKey, params); - - // Get number of collections after the transaction - const collectionCountAfter = await getCreatedCollectionCount(api); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, result.collectionId); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - expect(result.collectionId).to.be.equal(collectionCountAfter); - // tslint:disable-next-line:no-unused-expression - expect(collection).to.be.not.null; - expect(collectionCountAfter).to.be.equal(collectionCountBefore + 1, 'Error: NFT collection NOT created.'); - expect(collection.owner.toString()).to.be.equal(toSubstrateAddress(alicePrivateKey)); - expect(utf16ToStr(collection.name.toJSON() as any)).to.be.equal(name); - expect(utf16ToStr(collection.description.toJSON() as any)).to.be.equal(description); - expect(hexToStr(collection.tokenPrefix.toJSON())).to.be.equal(tokenPrefix); - - collectionId = result.collectionId; - }); - - return collectionId; -} - -export async function createCollectionWithPropsExpectSuccess(params: Partial = {}): Promise { - const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; - - let collectionId = 0; - await usingApi(async (api, privateKeyWrapper) => { - // Get number of collections before the transaction - const collectionCountBefore = await getCreatedCollectionCount(api); - - // Run the CreateCollection transaction - const alicePrivateKey = privateKeyWrapper('//Alice'); - - let modeprm = {}; - if (mode.type === 'NFT') { - modeprm = {nft: null}; - } else if (mode.type === 'Fungible') { - modeprm = {fungible: mode.decimalPoints}; - } else if (mode.type === 'ReFungible') { - modeprm = {refungible: null}; - } - - const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, properties: params.properties, tokenPropertyPermissions: params.propPerm}); - const events = await submitTransactionAsync(alicePrivateKey, tx); - const result = getCreateCollectionResult(events); - - // Get number of collections after the transaction - const collectionCountAfter = await getCreatedCollectionCount(api); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, result.collectionId); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - expect(result.collectionId).to.be.equal(collectionCountAfter); - // tslint:disable-next-line:no-unused-expression - expect(collection).to.be.not.null; - expect(collectionCountAfter).to.be.equal(collectionCountBefore + 1, 'Error: NFT collection NOT created.'); - expect(collection.owner.toString()).to.be.equal(toSubstrateAddress(alicePrivateKey)); - expect(utf16ToStr(collection.name.toJSON() as any)).to.be.equal(name); - expect(utf16ToStr(collection.description.toJSON() as any)).to.be.equal(description); - expect(hexToStr(collection.tokenPrefix.toJSON())).to.be.equal(tokenPrefix); - - - collectionId = result.collectionId; - }); - - return collectionId; -} - -export async function createCollectionWithPropsExpectFailure(params: Partial = {}) { - const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; - - await usingApi(async (api, privateKeyWrapper) => { - // Get number of collections before the transaction - const collectionCountBefore = await getCreatedCollectionCount(api); - - // Run the CreateCollection transaction - const alicePrivateKey = privateKeyWrapper('//Alice'); - - let modeprm = {}; - if (mode.type === 'NFT') { - modeprm = {nft: null}; - } else if (mode.type === 'Fungible') { - modeprm = {fungible: mode.decimalPoints}; - } else if (mode.type === 'ReFungible') { - modeprm = {refungible: null}; - } - - const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any, properties: params.properties, tokenPropertyPermissions: params.propPerm}); - await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; - - - // Get number of collections after the transaction - const collectionCountAfter = await getCreatedCollectionCount(api); - - expect(collectionCountAfter).to.be.equal(collectionCountBefore, 'Error: Collection with incorrect data created.'); - }); -} - -export async function createCollectionExpectFailure(params: Partial = {}) { - const {name, description, mode, tokenPrefix} = {...defaultCreateCollectionParams, ...params}; - - let modeprm = {}; - if (mode.type === 'NFT') { - modeprm = {nft: null}; - } else if (mode.type === 'Fungible') { - modeprm = {fungible: mode.decimalPoints}; - } else if (mode.type === 'ReFungible') { - modeprm = {refungible: null}; - } - - await usingApi(async (api, privateKeyWrapper) => { - // Get number of collections before the transaction - const collectionCountBefore = await getCreatedCollectionCount(api); - - // Run the CreateCollection transaction - const alicePrivateKey = privateKeyWrapper('//Alice'); - const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any}); - await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; - - // Get number of collections after the transaction - const collectionCountAfter = await getCreatedCollectionCount(api); - - // What to expect - expect(collectionCountAfter).to.be.equal(collectionCountBefore, 'Error: Collection with incorrect data created.'); - }); -} - -export async function findUnusedAddress(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair, seedAddition = ''): Promise { - let bal = 0n; - let unused; - do { - const randomSeed = 'seed' + Math.floor(Math.random() * Math.floor(10000)) + seedAddition; - unused = privateKeyWrapper(`//${randomSeed}`); - bal = (await api.query.system.account(unused.address)).data.free.toBigInt(); - } while (bal !== 0n); - return unused; -} - -export async function getAllowance(api: ApiPromise, collectionId: number, owner: CrossAccountId | string | IKeyringPair, approved: CrossAccountId | string | IKeyringPair, tokenId: number) { - return (await api.rpc.unique.allowance(collectionId, normalizeAccountId(owner), normalizeAccountId(approved), tokenId)).toBigInt(); -} - -export function findUnusedAddresses(api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair, amount: number): Promise { - return Promise.all(new Array(amount).fill(null).map(() => findUnusedAddress(api, privateKeyWrapper, '_' + Date.now()))); -} - -export async function findNotExistingCollection(api: ApiPromise): Promise { - const totalNumber = await getCreatedCollectionCount(api); - const newCollection: number = totalNumber + 1; - return newCollection; -} - -function getDestroyResult(events: EventRecord[]): boolean { - let success = false; - events.forEach(({event: {method}}) => { - if (method == 'ExtrinsicSuccess') { - success = true; - } - }); - return success; -} - -export async function destroyCollectionExpectFailure(collectionId: number, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - // Run the DestroyCollection transaction - const alicePrivateKey = privateKeyWrapper(senderSeed); - const tx = api.tx.unique.destroyCollection(collectionId); - await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; - }); -} - -export async function destroyCollectionExpectSuccess(collectionId: number, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - // Run the DestroyCollection transaction - const alicePrivateKey = privateKeyWrapper(senderSeed); - const tx = api.tx.unique.destroyCollection(collectionId); - const events = await submitTransactionAsync(alicePrivateKey, tx); - const result = getDestroyResult(events); - expect(result).to.be.true; - - // What to expect - expect(await getDetailedCollectionInfo(api, collectionId)).to.be.null; - }); -} - -export async function setCollectionLimitsExpectSuccess(sender: IKeyringPair, collectionId: number, limits: any) { - await usingApi(async (api) => { - const tx = api.tx.unique.setCollectionLimits(collectionId, limits); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export const setCollectionPermissionsExpectSuccess = async (sender: IKeyringPair, collectionId: number, permissions: any) => { - await usingApi(async(api) => { - const tx = api.tx.unique.setCollectionPermissions(collectionId, permissions); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -}; - -export async function setCollectionLimitsExpectFailure(sender: IKeyringPair, collectionId: number, limits: any) { - await usingApi(async (api) => { - const tx = api.tx.unique.setCollectionLimits(collectionId, limits); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function setCollectionSponsorExpectSuccess(collectionId: number, sponsor: string, sender = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const senderPrivateKey = privateKeyWrapper(sender); - const tx = api.tx.unique.setCollectionSponsor(collectionId, sponsor); - const events = await submitTransactionAsync(senderPrivateKey, tx); - const result = getGenericResult(events); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, collectionId); - - // What to expect - expect(result.success).to.be.true; - expect(collection.sponsorship.toJSON()).to.deep.equal({ - unconfirmed: sponsor, - }); - }); -} - -export async function removeCollectionSponsorExpectSuccess(collectionId: number, sender = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const alicePrivateKey = privateKeyWrapper(sender); - const tx = api.tx.unique.removeCollectionSponsor(collectionId); - const events = await submitTransactionAsync(alicePrivateKey, tx); - const result = getGenericResult(events); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, collectionId); - - // What to expect - expect(result.success).to.be.true; - expect(collection.sponsorship.toJSON()).to.be.deep.equal({disabled: null}); - }); -} - -export async function removeCollectionSponsorExpectFailure(collectionId: number, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const alicePrivateKey = privateKeyWrapper(senderSeed); - const tx = api.tx.unique.removeCollectionSponsor(collectionId); - await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; - }); -} - -export async function setCollectionSponsorExpectFailure(collectionId: number, sponsor: string, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const alicePrivateKey = privateKeyWrapper(senderSeed); - const tx = api.tx.unique.setCollectionSponsor(collectionId, sponsor); - await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; - }); -} - -export async function confirmSponsorshipExpectSuccess(collectionId: number, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const sender = privateKeyWrapper(senderSeed); - await confirmSponsorshipByKeyExpectSuccess(collectionId, sender); - }); -} - -export async function confirmSponsorshipByKeyExpectSuccess(collectionId: number, sender: IKeyringPair) { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const tx = api.tx.unique.confirmSponsorship(collectionId); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, collectionId); - - // What to expect - expect(result.success).to.be.true; - expect(collection.sponsorship.toJSON()).to.be.deep.equal({ - confirmed: sender.address, - }); - }); -} - - -export async function confirmSponsorshipExpectFailure(collectionId: number, senderSeed = '//Alice') { - await usingApi(async (api, privateKeyWrapper) => { - - // Run the transaction - const sender = privateKeyWrapper(senderSeed); - const tx = api.tx.unique.confirmSponsorship(collectionId); - await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - }); -} - -export async function enableContractSponsoringExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) { - await usingApi(async (api) => { - const tx = api.tx.unique.enableContractSponsoring(contractAddress, enable); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function enableContractSponsoringExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, enable: boolean) { - await usingApi(async (api) => { - const tx = api.tx.unique.enableContractSponsoring(contractAddress, enable); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function setTransferFlagExpectSuccess(sender: IKeyringPair, collectionId: number, enabled: boolean) { - - await usingApi(async (api) => { - - const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function setTransferFlagExpectFailure(sender: IKeyringPair, collectionId: number, enabled: boolean) { - - await usingApi(async (api) => { - - const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function setContractSponsoringRateLimitExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) { - await usingApi(async (api) => { - const tx = api.tx.unique.setContractSponsoringRateLimit(contractAddress, rateLimit); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function setContractSponsoringRateLimitExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, rateLimit: number) { - await usingApi(async (api) => { - const tx = api.tx.unique.setContractSponsoringRateLimit(contractAddress, rateLimit); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function getNextSponsored( - api: ApiPromise, - collectionId: number, - account: string | CrossAccountId, - tokenId: number, -): Promise { - return Number((await api.rpc.unique.nextSponsored(collectionId, account, tokenId)).unwrapOr(-1)); -} - -export async function toggleContractAllowlistExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, value = true) { - await usingApi(async (api) => { - const tx = api.tx.unique.toggleContractAllowList(contractAddress, value); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function isAllowlistedInContract(contractAddress: AccountId | string, user: string) { - let allowlisted = false; - await usingApi(async (api) => { - allowlisted = (await api.query.unique.contractAllowList(contractAddress, user)).toJSON() as boolean; - }); - return allowlisted; -} - -export async function addToContractAllowListExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) { - await usingApi(async (api) => { - const tx = api.tx.unique.addToContractAllowList(contractAddress.toString(), user.toString()); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function removeFromContractAllowListExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) { - await usingApi(async (api) => { - const tx = api.tx.unique.removeFromContractAllowList(contractAddress.toString(), user.toString()); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - expect(result.success).to.be.true; - }); -} - -export async function removeFromContractAllowListExpectFailure(sender: IKeyringPair, contractAddress: AccountId | string, user: AccountId | string) { - await usingApi(async (api) => { - const tx = api.tx.unique.removeFromContractAllowList(contractAddress.toString(), user.toString()); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - expect(result.success).to.be.false; - }); -} - -export interface CreateFungibleData { - readonly Value: bigint; -} - -export interface CreateReFungibleData { } -export interface CreateNftData { } - -export type CreateItemData = { - NFT: CreateNftData; -} | { - Fungible: CreateFungibleData; -} | { - ReFungible: CreateReFungibleData; -}; - -export async function burnItem(api: ApiPromise, sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint) : Promise { - const tx = api.tx.unique.burnItem(collectionId, tokenId, value); - const events = await submitTransactionAsync(sender, tx); - return getGenericResult(events).success; -} - -export async function burnItemExpectSuccess(sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint = 1) { - await usingApi(async (api) => { - const balanceBefore = await getBalance(api, collectionId, normalizeAccountId(sender), tokenId); - // if burning token by admin - use adminButnItemExpectSuccess - expect(balanceBefore >= BigInt(value)).to.be.true; - - expect(await burnItem(api, sender, collectionId, tokenId, value)).to.be.true; - - const balanceAfter = await getBalance(api, collectionId, normalizeAccountId(sender), tokenId); - expect(balanceAfter + BigInt(value)).to.be.equal(balanceBefore); - }); -} - -export async function burnItemExpectFailure(sender: IKeyringPair, collectionId: number, tokenId: number, value: number | bigint = 1) { - await usingApi(async (api) => { - const tx = api.tx.unique.burnItem(collectionId, tokenId, value); - - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function burnFromExpectSuccess(sender: IKeyringPair, from: IKeyringPair | CrossAccountId, collectionId: number, tokenId: number, value: number | bigint = 1) { - await usingApi(async (api) => { - const tx = api.tx.unique.burnFrom(collectionId, normalizeAccountId(from), tokenId, value); - const events = await submitTransactionAsync(sender, tx); - return getGenericResult(events).success; - }); -} - -export async function -approve( - api: ApiPromise, - collectionId: number, - tokenId: number, owner: IKeyringPair, approved: CrossAccountId | string | IKeyringPair, amount: number | bigint, -) { - const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); - const events = await submitTransactionAsync(owner, approveUniqueTx); - return getGenericResult(events).success; -} - -export async function -approveExpectSuccess( - collectionId: number, - tokenId: number, owner: IKeyringPair, approved: CrossAccountId | string, amount: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const result = await approve(api, collectionId, tokenId, owner, approved, amount); - expect(result).to.be.true; - - expect(await getAllowance(api, collectionId, owner, approved, tokenId)).to.be.equal(BigInt(amount)); - }); -} - -export async function adminApproveFromExpectSuccess( - collectionId: number, - tokenId: number, admin: IKeyringPair, owner: CrossAccountId | string, approved: CrossAccountId | string, amount: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); - const events = await submitTransactionAsync(admin, approveUniqueTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - expect(await getAllowance(api, collectionId, owner, approved, tokenId)).to.be.equal(BigInt(amount)); - }); -} - -export async function -transferFrom( - api: ApiPromise, - collectionId: number, - tokenId: number, - accountApproved: IKeyringPair, - accountFrom: IKeyringPair | CrossAccountId, - accountTo: IKeyringPair | CrossAccountId, - value: number | bigint, -) { - const from = normalizeAccountId(accountFrom); - const to = normalizeAccountId(accountTo); - const transferFromTx = api.tx.unique.transferFrom(from, to, collectionId, tokenId, value); - const events = await submitTransactionAsync(accountApproved, transferFromTx); - return getGenericResult(events).success; -} - -export async function -transferFromExpectSuccess( - collectionId: number, - tokenId: number, - accountApproved: IKeyringPair, - accountFrom: IKeyringPair | CrossAccountId, - accountTo: IKeyringPair | CrossAccountId, - value: number | bigint = 1, - type = 'NFT', -) { - await usingApi(async (api: ApiPromise) => { - const from = normalizeAccountId(accountFrom); - const to = normalizeAccountId(accountTo); - let balanceBefore = 0n; - if (type === 'Fungible' || type === 'ReFungible') { - balanceBefore = await getBalance(api, collectionId, to, tokenId); - } - expect(await transferFrom(api, collectionId, tokenId, accountApproved, accountFrom, accountTo, value)).to.be.true; - if (type === 'NFT') { - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to); - } - if (type === 'Fungible') { - const balanceAfter = await getBalance(api, collectionId, to, tokenId); - if (JSON.stringify(to) !== JSON.stringify(from)) { - expect(balanceAfter - balanceBefore).to.be.equal(BigInt(value)); - } else { - expect(balanceAfter).to.be.equal(balanceBefore); - } - } - if (type === 'ReFungible') { - expect(await getBalance(api, collectionId, to, tokenId)).to.be.equal(balanceBefore + BigInt(value)); - } - }); -} - -export async function -transferFromExpectFail( - collectionId: number, - tokenId: number, - accountApproved: IKeyringPair, - accountFrom: IKeyringPair, - accountTo: IKeyringPair, - value: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const transferFromTx = api.tx.unique.transferFrom(normalizeAccountId(accountFrom.address), normalizeAccountId(accountTo.address), collectionId, tokenId, value); - const events = await expect(submitTransactionExpectFailAsync(accountApproved, transferFromTx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -/* eslint no-async-promise-executor: "off" */ -export async function getBlockNumber(api: ApiPromise): Promise { - return new Promise(async (resolve) => { - const unsubscribe = await api.rpc.chain.subscribeNewHeads((head) => { - unsubscribe(); - resolve(head.number.toNumber()); - }); - }); -} - -export async function addCollectionAdminExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | CrossAccountId) { - await usingApi(async (api) => { - const changeAdminTx = api.tx.unique.addCollectionAdmin(collectionId, normalizeAccountId(address)); - const events = await submitTransactionAsync(sender, changeAdminTx); - const result = getCreateCollectionResult(events); - expect(result.success).to.be.true; - }); -} - -export async function adminApproveFromExpectFail( - collectionId: number, - tokenId: number, admin: IKeyringPair, owner: CrossAccountId | string, approved: CrossAccountId | string, amount: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved), collectionId, tokenId, amount); - const events = await expect(submitTransactionAsync(admin, approveUniqueTx)).to.be.rejected; - const result = getGenericResult(events); - expect(result.success).to.be.false; - }); -} - -export async function -getFreeBalance(account: IKeyringPair): Promise { - let balance = 0n; - await usingApi(async (api) => { - balance = BigInt((await api.query.system.account(account.address)).data.free.toString()); - }); - - return balance; -} - -export async function paraSiblingSovereignAccount(paraid: number): Promise { - return usingApi(async api => { - // We are getting a *sibling* parachain sovereign account, - // so we need a sibling prefix: encoded(b"sibl") == 0x7369626c - const siblingPrefix = '0x7369626c'; - - const encodedParaId = api.createType('u32', paraid).toHex(true).substring(2); - const suffix = '000000000000000000000000000000000000000000000000'; - - return siblingPrefix + encodedParaId + suffix; - }); -} - -export async function transferBalanceTo(api: ApiPromise, source: IKeyringPair, target: string, amount = 1000n * UNIQUE) { - const tx = api.tx.balances.transfer(target, amount); - const events = await submitTransactionAsync(source, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; -} - -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, - sender: IKeyringPair, - recipient: IKeyringPair, - value: number | bigint = 1, - blockSchedule: number, - scheduledId: string, -) { - await usingApi(async (api: ApiPromise) => { - await scheduleTransferExpectSuccess(collectionId, tokenId, sender, recipient, value, blockSchedule, scheduledId); - - const recipientBalanceBefore = (await api.query.system.account(recipient.address)).data.free.toBigInt(); - - // sleep for n + 1 blocks - await waitNewBlocks(blockSchedule + 1); - - const recipientBalanceAfter = (await api.query.system.account(recipient.address)).data.free.toBigInt(); - - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(recipient.address)); - expect(recipientBalanceAfter).to.be.equal(recipientBalanceBefore); - }); -} - -export async function -scheduleTransferExpectSuccess( - collectionId: number, - tokenId: number, - sender: IKeyringPair, - recipient: IKeyringPair, - value: number | bigint = 1, - blockSchedule: number, - scheduledId: string, -) { - await usingApi(async (api: ApiPromise) => { - const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient.address), collectionId, tokenId, value); - - await scheduleExpectSuccess(transferTx, sender, blockSchedule, scheduledId); - - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(normalizeAccountId(sender.address)); - }); -} - -export async function -scheduleTransferFundsPeriodicExpectSuccess( - amount: bigint, - sender: IKeyringPair, - recipient: IKeyringPair, - blockSchedule: number, - scheduledId: string, - period: number, - repetitions: number, -) { - await usingApi(async (api: ApiPromise) => { - const transferTx = api.tx.balances.transfer(recipient.address, amount); - - const balanceBefore = await getFreeBalance(recipient); - - await scheduleExpectSuccess(transferTx, sender, blockSchedule, scheduledId, period, repetitions); - - expect(await getFreeBalance(recipient)).to.be.equal(balanceBefore); - }); -} - -export async function -transfer( - api: ApiPromise, - collectionId: number, - tokenId: number, - sender: IKeyringPair, - recipient: IKeyringPair | CrossAccountId, - value: number | bigint, -) : Promise { - const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); - const events = await executeTransaction(api, sender, transferTx); - return getGenericResult(events).success; -} - -export async function -transferExpectSuccess( - collectionId: number, - tokenId: number, - sender: IKeyringPair, - recipient: IKeyringPair | CrossAccountId, - value: number | bigint = 1, - type = 'NFT', -) { - await usingApi(async (api: ApiPromise) => { - const from = normalizeAccountId(sender); - const to = normalizeAccountId(recipient); - - let balanceBefore = 0n; - if (type === 'Fungible' || type === 'ReFungible') { - balanceBefore = await getBalance(api, collectionId, to, tokenId); - } - - const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); - const events = await executeTransaction(api, sender, transferTx); - const result = getTransferResult(api, events); - - expect(result.collectionId).to.be.equal(collectionId); - expect(result.itemId).to.be.equal(tokenId); - expect(result.sender).to.be.deep.equal(normalizeAccountId(sender.address)); - expect(result.recipient).to.be.deep.equal(to); - expect(result.value).to.be.equal(BigInt(value)); - - if (type === 'NFT') { - expect(await getTokenOwner(api, collectionId, tokenId)).to.be.deep.equal(to); - } - if (type === 'Fungible' || type === 'ReFungible') { - const balanceAfter = await getBalance(api, collectionId, to, tokenId); - if (JSON.stringify(to) !== JSON.stringify(from)) { - expect(balanceAfter - balanceBefore).to.be.equal(BigInt(value)); - } else { - expect(balanceAfter).to.be.equal(balanceBefore); - } - } - }); -} - -export async function -transferExpectFailure( - collectionId: number, - tokenId: number, - sender: IKeyringPair, - recipient: IKeyringPair | CrossAccountId, - value: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const transferTx = api.tx.unique.transfer(normalizeAccountId(recipient), collectionId, tokenId, value); - const events = await expect(submitTransactionExpectFailAsync(sender, transferTx)).to.be.rejected; - const result = getGenericResult(events); - // if (events && Array.isArray(events)) { - // const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - //} - }); -} - -export async function -approveExpectFail( - collectionId: number, - tokenId: number, owner: IKeyringPair, approved: IKeyringPair, amount: number | bigint = 1, -) { - await usingApi(async (api: ApiPromise) => { - const approveUniqueTx = api.tx.unique.approve(normalizeAccountId(approved.address), collectionId, tokenId, amount); - const events = await expect(submitTransactionExpectFailAsync(owner, approveUniqueTx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function getBalance( - api: ApiPromise, - collectionId: number, - owner: string | CrossAccountId | IKeyringPair, - token: number, -): Promise { - return (await api.rpc.unique.balance(collectionId, normalizeAccountId(owner), token)).toBigInt(); -} -export async function getTokenOwner( - api: ApiPromise, - collectionId: number, - token: number, -): Promise { - const owner = (await api.rpc.unique.tokenOwner(collectionId, token)).toJSON() as any; - if (owner == null) throw new Error('owner == null'); - return normalizeAccountId(owner); -} -export async function getTopmostTokenOwner( - api: ApiPromise, - collectionId: number, - token: number, -): Promise { - const owner = (await api.rpc.unique.topmostTokenOwner(collectionId, token)).toJSON() as any; - if (owner == null) throw new Error('owner == null'); - return normalizeAccountId(owner); -} -export async function getTokenChildren( - api: ApiPromise, - collectionId: number, - tokenId: number, -): Promise { - return (await api.rpc.unique.tokenChildren(collectionId, tokenId)).toJSON() as any; -} -export async function isTokenExists( - api: ApiPromise, - collectionId: number, - token: number, -): Promise { - return (await api.rpc.unique.tokenExists(collectionId, token)).toJSON(); -} -export async function getLastTokenId( - api: ApiPromise, - collectionId: number, -): Promise { - return (await api.rpc.unique.lastTokenId(collectionId)).toJSON(); -} -export async function getAdminList( - api: ApiPromise, - collectionId: number, -): Promise { - return (await api.rpc.unique.adminlist(collectionId)).toHuman() as any; -} -export async function getTokenProperties( - api: ApiPromise, - collectionId: number, - tokenId: number, - propertyKeys: string[], -): Promise { - return (await api.rpc.unique.tokenProperties(collectionId, tokenId, propertyKeys)).toHuman() as any; -} - -export async function createFungibleItemExpectSuccess( - sender: IKeyringPair, - collectionId: number, - data: CreateFungibleData, - owner: CrossAccountId | string = sender.address, -) { - return await usingApi(async (api) => { - const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), {Fungible: data}); - - const events = await submitTransactionAsync(sender, tx); - const result = getCreateItemResult(events); - - expect(result.success).to.be.true; - return result.itemId; - }); -} - -export async function createMultipleItemsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any, owner: CrossAccountId | string = sender.address) { - await usingApi(async (api) => { - const to = normalizeAccountId(owner); - const tx = api.tx.unique.createMultipleItems(collectionId, to, itemsData); - - const events = await submitTransactionAsync(sender, tx); - expect(getGenericResult(events).success).to.be.true; - }); -} - -export async function createMultipleItemsWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any, owner: CrossAccountId | string = sender.address) { - await usingApi(async (api) => { - const to = normalizeAccountId(owner); - const tx = api.tx.unique.createMultipleItems(collectionId, to, itemsData); - - const events = await submitTransactionAsync(sender, tx); - const result = getCreateItemsResult(events); - - for (const res of result) { - expect(await api.rpc.unique.tokenProperties(collectionId, res.itemId)).not.to.be.empty; - } - }); -} - -export async function createMultipleItemsExWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, itemsData: any) { - await usingApi(async (api) => { - const tx = api.tx.unique.createMultipleItemsEx(collectionId, itemsData); - - const events = await submitTransactionAsync(sender, tx); - const result = getCreateItemsResult(events); - - for (const res of result) { - expect(await api.rpc.unique.tokenProperties(collectionId, res.itemId)).not.to.be.empty; - } - }); -} - -export async function createItemWithPropsExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, props: Array, owner: CrossAccountId | string = sender.address) { - let newItemId = 0; - await usingApi(async (api) => { - const to = normalizeAccountId(owner); - const itemCountBefore = await getLastTokenId(api, collectionId); - const itemBalanceBefore = await getBalance(api, collectionId, to, newItemId); - - let tx; - if (createMode === 'Fungible') { - const createData = {fungible: {value: 10}}; - tx = api.tx.unique.createItem(collectionId, to, createData as any); - } else if (createMode === 'ReFungible') { - const createData = {refungible: {pieces: 100}}; - tx = api.tx.unique.createItem(collectionId, to, createData as any); - } else { - const data = api.createType('UpDataStructsCreateItemData', {NFT: {properties: props}}); - tx = api.tx.unique.createItem(collectionId, to, data as UpDataStructsCreateItemData); - } - - const events = await submitTransactionAsync(sender, tx); - const result = getCreateItemResult(events); - - const itemCountAfter = await getLastTokenId(api, collectionId); - const itemBalanceAfter = await getBalance(api, collectionId, to, newItemId); - - if (createMode === 'NFT') { - expect(await api.rpc.unique.tokenProperties(collectionId, result.itemId)).not.to.be.empty; - } - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - if (createMode === 'Fungible') { - expect(itemBalanceAfter - itemBalanceBefore).to.be.equal(10n); - } else { - expect(itemCountAfter).to.be.equal(itemCountBefore + 1); - } - expect(collectionId).to.be.equal(result.collectionId); - expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString()); - expect(to).to.be.deep.equal(result.recipient); - newItemId = result.itemId; - }); - return newItemId; -} - -export async function createItemWithPropsExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, props: Array, owner: CrossAccountId | string = sender.address) { - await usingApi(async (api) => { - - let tx; - if (createMode === 'NFT') { - const data = api.createType('UpDataStructsCreateItemData', {NFT: {properties: props}}) as UpDataStructsCreateItemData; - tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), data); - } else { - tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createMode); - } - - - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - if(events.message && events.message.toString().indexOf('1002: Verification Error') > -1) return; - const result = getCreateItemResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function createItemExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, owner: CrossAccountId | string = sender.address) { - let newItemId = 0; - await usingApi(async (api) => { - const to = normalizeAccountId(owner); - const itemCountBefore = await getLastTokenId(api, collectionId); - const itemBalanceBefore = await getBalance(api, collectionId, to, newItemId); - - let tx; - if (createMode === 'Fungible') { - const createData = {fungible: {value: 10}}; - tx = api.tx.unique.createItem(collectionId, to, createData as any); - } else if (createMode === 'ReFungible') { - const createData = {refungible: {pieces: 100}}; - tx = api.tx.unique.createItem(collectionId, to, createData as any); - } else { - const createData = {nft: {}}; - tx = api.tx.unique.createItem(collectionId, to, createData as any); - } - - const events = await executeTransaction(api, sender, tx); - const result = getCreateItemResult(events); - - const itemCountAfter = await getLastTokenId(api, collectionId); - const itemBalanceAfter = await getBalance(api, collectionId, to, newItemId); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - if (createMode === 'Fungible') { - expect(itemBalanceAfter - itemBalanceBefore).to.be.equal(10n); - } else { - expect(itemCountAfter).to.be.equal(itemCountBefore + 1); - } - expect(collectionId).to.be.equal(result.collectionId); - expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString()); - expect(to).to.be.deep.equal(result.recipient); - newItemId = result.itemId; - }); - return newItemId; -} - -export async function createRefungibleToken(api: ApiPromise, sender: IKeyringPair, collectionId: number, amount: bigint, owner: CrossAccountId | IKeyringPair | string = sender.address) : Promise { - const createData = {refungible: {pieces: amount}}; - const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createData as any); - - const events = await submitTransactionAsync(sender, tx); - return getCreateItemResult(events); -} - -export async function createItemExpectFailure(sender: IKeyringPair, collectionId: number, createMode: string, owner: CrossAccountId | string = sender.address) { - await usingApi(async (api) => { - const tx = api.tx.unique.createItem(collectionId, normalizeAccountId(owner), createMode); - - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getCreateItemResult(events); - - expect(result.success).to.be.false; - }); -} - -export async function setPublicAccessModeExpectSuccess( - sender: IKeyringPair, collectionId: number, - accessMode: 'Normal' | 'AllowList', -) { - await usingApi(async (api) => { - - // Run the transaction - const tx = api.tx.unique.setCollectionPermissions(collectionId, {access: accessMode}); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, collectionId); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - expect(collection.permissions.access.toHuman()).to.be.equal(accessMode); - }); -} - -export async function setPublicAccessModeExpectFail( - sender: IKeyringPair, collectionId: number, - accessMode: 'Normal' | 'AllowList', -) { - await usingApi(async (api) => { - - // Run the transaction - const tx = api.tx.unique.setCollectionPermissions(collectionId, {access: accessMode}); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function enableAllowListExpectSuccess(sender: IKeyringPair, collectionId: number) { - await setPublicAccessModeExpectSuccess(sender, collectionId, 'AllowList'); -} - -export async function enableAllowListExpectFail(sender: IKeyringPair, collectionId: number) { - await setPublicAccessModeExpectFail(sender, collectionId, 'AllowList'); -} - -export async function disableAllowListExpectSuccess(sender: IKeyringPair, collectionId: number) { - await setPublicAccessModeExpectSuccess(sender, collectionId, 'Normal'); -} - -export async function setMintPermissionExpectSuccess(sender: IKeyringPair, collectionId: number, enabled: boolean) { - await usingApi(async (api) => { - - // Run the transaction - const tx = api.tx.unique.setCollectionPermissions(collectionId, {mintMode: enabled}); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - // Get the collection - const collection = await queryCollectionExpectSuccess(api, collectionId); - - expect(collection.permissions.mintMode.toHuman()).to.be.equal(enabled); - }); -} - -export async function enablePublicMintingExpectSuccess(sender: IKeyringPair, collectionId: number) { - await setMintPermissionExpectSuccess(sender, collectionId, true); -} - -export async function setMintPermissionExpectFailure(sender: IKeyringPair, collectionId: number, enabled: boolean) { - await usingApi(async (api) => { - // Run the transaction - const tx = api.tx.unique.setCollectionPermissions(collectionId, {mintMode: enabled}); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function setChainLimitsExpectFailure(sender: IKeyringPair, limits: IChainLimits) { - await usingApi(async (api) => { - // Run the transaction - const tx = api.tx.unique.setChainLimits(limits); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getCreateCollectionResult(events); - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function isAllowlisted(api: ApiPromise, collectionId: number, address: string | CrossAccountId | IKeyringPair) { - return (await api.rpc.unique.allowed(collectionId, normalizeAccountId(address))).toJSON(); -} - -export async function addToAllowListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId | CrossAccountId) { - await usingApi(async (api) => { - expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.false; - - // Run the transaction - const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address)); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true; - }); -} - -export async function addToAllowListAgainExpectSuccess(sender: IKeyringPair, collectionId: number, address: string | AccountId) { - await usingApi(async (api) => { - - expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true; - - // Run the transaction - const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address)); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - - expect(await isAllowlisted(api, collectionId, normalizeAccountId(address))).to.be.true; - }); -} - -export async function addToAllowListExpectFail(sender: IKeyringPair, collectionId: number, address: string | AccountId) { - await usingApi(async (api) => { - - // Run the transaction - const tx = api.tx.unique.addToAllowList(collectionId, normalizeAccountId(address)); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export async function removeFromAllowListExpectSuccess(sender: IKeyringPair, collectionId: number, address: CrossAccountId) { - await usingApi(async (api) => { - // Run the transaction - const tx = api.tx.unique.removeFromAllowList(collectionId, normalizeAccountId(address)); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.true; - }); -} - -export async function removeFromAllowListExpectFailure(sender: IKeyringPair, collectionId: number, address: CrossAccountId) { - await usingApi(async (api) => { - // Run the transaction - const tx = api.tx.unique.removeFromAllowList(collectionId, normalizeAccountId(address)); - const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; - const result = getGenericResult(events); - - // What to expect - // tslint:disable-next-line:no-unused-expression - expect(result.success).to.be.false; - }); -} - -export const getDetailedCollectionInfo = async (api: ApiPromise, collectionId: number) - : Promise => { - return (await api.rpc.unique.collectionById(collectionId)).unwrapOr(null); -}; - -export const getCreatedCollectionCount = async (api: ApiPromise): Promise => { - // set global object - collectionsCount - return (await api.rpc.unique.collectionStats()).created.toNumber(); -}; - -export async function queryCollectionExpectSuccess(api: ApiPromise, collectionId: number): Promise { - return (await api.rpc.unique.collectionById(collectionId)).unwrap(); -} - -export const describe_xcm = ( - process.env.RUN_XCM_TESTS - ? describe - : describe.skip -); - -export async function waitNewBlocks(blocksCount = 1): Promise { - await usingApi(async (api) => { - const promise = new Promise(async (resolve) => { - const unsubscribe = await api.rpc.chain.subscribeNewHeads(() => { - if (blocksCount > 0) { - blocksCount--; - } else { - unsubscribe(); - resolve(); - } - }); - }); - return promise; - }); -} - -export async function waitEvent( - api: ApiPromise, - maxBlocksToWait: number, - eventSection: string, - eventMethod: string, -): Promise { - - const promise = new Promise(async (resolve) => { - const unsubscribe = await api.rpc.chain.subscribeNewHeads(async header => { - const blockNumber = header.number.toHuman(); - const blockHash = header.hash; - const eventIdStr = `${eventSection}.${eventMethod}`; - const waitLimitStr = `wait blocks remaining: ${maxBlocksToWait}`; - - console.log(`[Block #${blockNumber}] Waiting for event \`${eventIdStr}\` (${waitLimitStr})`); - - const apiAt = await api.at(blockHash); - const eventRecords = await apiAt.query.system.events(); - - const neededEvent = eventRecords.find(r => { - return r.event.section == eventSection && r.event.method == eventMethod; - }); - - if (neededEvent) { - unsubscribe(); - resolve(neededEvent); - } else if (maxBlocksToWait > 0) { - maxBlocksToWait--; - } else { - console.log(`Event \`${eventIdStr}\` is NOT found`); - - unsubscribe(); - resolve(null); - } - }); - }); - return promise; -} - -export async function repartitionRFT( - api: ApiPromise, - collectionId: number, - sender: IKeyringPair, - tokenId: number, - amount: bigint, -): Promise { - const tx = api.tx.unique.repartition(collectionId, tokenId, amount); - const events = await submitTransactionAsync(sender, tx); - const result = getGenericResult(events); - - return result.success; -} - -export async function itApi(name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any, opts: { only?: boolean, skip?: boolean } = {}) { - let i: any = it; - if (opts.only) i = i.only; - else if (opts.skip) i = i.skip; - i(name, async () => { - await usingApi(async (api, privateKeyWrapper) => { - await cb({api, privateKeyWrapper}); - }); - }); -} - -itApi.only = (name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itApi(name, cb, {only: true}); -itApi.skip = (name: string, cb: (apis: { api: ApiPromise, privateKeyWrapper: (account: string) => IKeyringPair }) => any) => itApi(name, cb, {skip: true}); - -let accountSeed = 10000; -export function generateKeyringPair(keyring: Keyring) { - const privateKey = `0xDEADBEEF${(Date.now() + (accountSeed++)).toString(16).padStart(64 - 8, '0')}`; - return keyring.addFromUri(privateKey); -} - -export async function expectSubstrateEventsAtBlock(api: ApiPromise, blockNumber: AnyNumber | BlockNumber, section: string, methods: string[], dryRun = false) { - const blockHash = await api.rpc.chain.getBlockHash(blockNumber); - const subEvents = (await api.query.system.events.at(blockHash)) - .filter(x => x.event.section === section) - .map((x) => x.toHuman()); - const events = methods.map((m) => { - return { - event: { - method: m, - section, - }, - }; - }); - if (!dryRun) { - expect(subEvents).to.be.like(events); - } - return subEvents; -} --- a/tests/src/util/playgrounds/index.ts +++ b/tests/src/util/playgrounds/index.ts @@ -62,6 +62,7 @@ Fungible = 'fungible', NFT = 'nonfungible', Scheduler = 'scheduler', + AppPromotion = 'apppromotion', } export function requirePalletsOrSkip(test: Context, helper: DevUniqueHelper, requiredPallets: string[]) { --- a/tests/src/util/playgrounds/types.ts +++ b/tests/src/util/playgrounds/types.ts @@ -14,6 +14,7 @@ export interface ITransactionResult { status: 'Fail' | 'Success'; result: { + dispatchError: any, events: { phase: any, // {ApplyExtrinsic: number} | 'Initialization', event: IEvent; @@ -47,6 +48,7 @@ call: string; params: any[]; moduleError?: string; + dispatchError?: any; events?: any; } @@ -162,6 +164,14 @@ amount: bigint, } +export interface ISchedulerOptions { + priority?: number, + periodic?: { + period: number, + repetitions: number, + }, +} + export type TSubstrateAccount = string; export type TEthereumAccount = string; export type TApiAllowedListeners = 'connected' | 'disconnected' | 'error' | 'ready' | 'decorated'; --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -8,7 +8,6 @@ import {IKeyringPair} from '@polkadot/types/types'; import {ICrossAccountId} from './types'; - export class SilentLogger { log(_msg: any, _level: any): void { } level = { @@ -63,8 +62,10 @@ wait: WaitGroup; admin: AdminGroup; - constructor(logger: { log: (msg: any, level: any) => void, level: any }) { - super(logger); + constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { + options.helperBase = options.helperBase ?? DevUniqueHelper; + + super(logger, options); this.arrange = new ArrangeGroup(this); this.wait = new WaitGroup(this); this.admin = new AdminGroup(this); @@ -108,9 +109,9 @@ } class ArrangeGroup { - helper: UniqueHelper; + helper: DevUniqueHelper; - constructor(helper: UniqueHelper) { + constructor(helper: DevUniqueHelper) { this.helper = helper; } @@ -215,8 +216,8 @@ }; isDevNode = async () => { - const block1 = await this.helper.api?.rpc.chain.getBlock(await this.helper.api?.rpc.chain.getBlockHash(1)); - const block2 = await this.helper.api?.rpc.chain.getBlock(await this.helper.api?.rpc.chain.getBlockHash(2)); + const block1 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [1])]); + const block2 = await this.helper.callRpc('api.rpc.chain.getBlock', [await this.helper.callRpc('api.rpc.chain.getBlockHash', [2])]); const findCreationDate = async (block: any) => { const humanBlock = block.toHuman(); let date; @@ -245,21 +246,21 @@ } class WaitGroup { - helper: UniqueHelper; + helper: DevUniqueHelper; - constructor(helper: UniqueHelper) { + constructor(helper: DevUniqueHelper) { this.helper = helper; } /** - * Wait for specified bnumber of blocks + * Wait for specified number of blocks * @param blocksCount number of blocks to wait * @returns */ async newBlocks(blocksCount = 1): Promise { // eslint-disable-next-line no-async-promise-executor const promise = new Promise(async (resolve) => { - const unsubscribe = await this.helper.api!.rpc.chain.subscribeNewHeads(() => { + const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(() => { if (blocksCount > 0) { blocksCount--; } else { @@ -274,7 +275,7 @@ async forParachainBlockNumber(blockNumber: bigint) { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve) => { - const unsubscribe = await this.helper.api!.rpc.chain.subscribeNewHeads(async (data: any) => { + const unsubscribe = await this.helper.getApi().rpc.chain.subscribeNewHeads(async (data: any) => { if (data.number.toNumber() >= blockNumber) { unsubscribe(); resolve(); @@ -286,7 +287,7 @@ async forRelayBlockNumber(blockNumber: bigint) { // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve) => { - const unsubscribe = await this.helper.api!.query.parachainSystem.validationData(async (data: any) => { + const unsubscribe = await this.helper.getApi().query.parachainSystem.validationData(async (data: any) => { if (data.value.relayParentNumber.toNumber() >= blockNumber) { // @ts-ignore unsubscribe(); --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -9,7 +9,7 @@ import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types'; import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto'; import {IKeyringPair} from '@polkadot/types/types'; -import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types'; +import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, TUniqueNetworks} from './types'; export class CrossAccountId implements ICrossAccountId { Substrate?: TSubstrateAccount; @@ -275,6 +275,7 @@ } private static extractData(data: any, type: any): any { + if(!type) return data.toHuman(); if (['u16', 'u32'].indexOf(type.type) > -1) return data.toNumber(); if (['u64', 'u128', 'u256'].indexOf(type.type) > -1) return data.toBigInt(); if(type.hasOwnProperty('sub')) return this.extractSub(data, type.sub); @@ -317,6 +318,7 @@ forcedNetwork: TUniqueNetworks | null; network: TUniqueNetworks | null; chainLog: IUniqueHelperLog[]; + children: ChainHelperBase[]; constructor(logger?: ILogger) { this.util = UniqueUtil; @@ -327,8 +329,14 @@ this.forcedNetwork = null; this.network = null; this.chainLog = []; + this.children = []; } + getApi(): ApiPromise { + if(this.api === null) throw Error('API not initialized'); + return this.api; + } + clearChainLog(): void { this.chainLog = []; } @@ -345,8 +353,16 @@ } async disconnect() { + for (const child of this.children) { + child.clearApi(); + } + if (this.api === null) return; await this.api.disconnect(); + this.clearApi(); + } + + clearApi() { this.api = null; this.network = null; } @@ -473,7 +489,7 @@ constructApiCall(apiCall: string, params: any[]) { if(!apiCall.startsWith('api.')) throw Error(`Invalid api call: ${apiCall}`); - let call = this.api as any; + let call = this.getApi() as any; for(const part of apiCall.slice(4).split('.')) { call = call[part]; } @@ -508,12 +524,18 @@ params, } as IUniqueHelperLog; - if(result.status !== this.transactionStatus.SUCCESS && result.moduleError) log.moduleError = result.moduleError; + if(result.status !== this.transactionStatus.SUCCESS) { + if (result.moduleError) log.moduleError = result.moduleError; + else if (result.result.dispatchError) log.dispatchError = result.result.dispatchError; + } if(events.length > 0) log.events = events; this.chainLog.push(log); - if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) throw Error(`${result.moduleError}`); + if(expectSuccess && result.status !== this.transactionStatus.SUCCESS) { + if (result.moduleError) throw Error(`${result.moduleError}`); + else if (result.result.dispatchError) throw Error(JSON.stringify(result.result.dispatchError)); + } return result; } @@ -1985,7 +2007,7 @@ * @returns ss58Format, token decimals, and token symbol */ getChainProperties(): IChainProperties { - const properties = (this.helper.api as any).registry.getChainProperties().toJSON(); + const properties = (this.helper.getApi() as any).registry.getChainProperties().toJSON(); return { ss58Format: properties.ss58Format.toJSON(), tokenDecimals: properties.tokenDecimals.toJSON(), @@ -2028,7 +2050,7 @@ * @returns number, account's nonce */ async getNonce(address: TSubstrateAccount): Promise { - return (await (this.helper.api as any).query.system.account(address)).nonce.toNumber(); + return (await this.helper.callRpc('api.query.system.account', [address])).nonce.toNumber(); } } @@ -2160,7 +2182,7 @@ */ async stake(signer: TSigner, amountToStake: bigint, label?: string): Promise { if(typeof label === 'undefined') label = `${signer.address} amount: ${amountToStake}`; - const stakeResult = await this.helper.executeExtrinsic( + const _stakeResult = await this.helper.executeExtrinsic( signer, 'api.tx.appPromotion.stake', [amountToStake], true, ); @@ -2177,7 +2199,7 @@ */ async unstake(signer: TSigner, label?: string): Promise { if(typeof label === 'undefined') label = `${signer.address}`; - const unstakeResult = await this.helper.executeExtrinsic( + const _unstakeResult = await this.helper.executeExtrinsic( signer, 'api.tx.appPromotion.unstake', [], true, ); @@ -2236,7 +2258,67 @@ } } +class SchedulerGroup extends HelperGroup { + constructor(helper: UniqueHelper) { + super(helper); + } + + async cancelScheduled(signer: TSigner, scheduledId: string) { + return this.helper.executeExtrinsic( + signer, + 'api.tx.scheduler.cancelNamed', + [scheduledId], + true, + ); + } + + async changePriority(signer: TSigner, scheduledId: string, priority: number) { + return this.helper.executeExtrinsic( + signer, + 'api.tx.scheduler.changeNamedPriority', + [scheduledId, priority], + true, + ); + } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + return this.schedule('scheduleNamed', scheduledId, executionBlockNumber, options); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + return this.schedule('scheduleNamedAfter', scheduledId, blocksBeforeExecution, options); + } + + schedule( + scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter', + scheduledId: string, + blocksNum: number, + options: ISchedulerOptions = {}, + ) { + // eslint-disable-next-line @typescript-eslint/naming-convention + const ScheduledHelperType = ScheduledUniqueHelper(this.helper.helperBase); + return this.helper.clone(ScheduledHelperType, { + scheduleFn, + scheduledId, + blocksNum, + options, + }) as T; + } +} + +export type UniqueHelperConstructor = new(...args: any[]) => UniqueHelper; + export class UniqueHelper extends ChainHelperBase { + helperBase: any; + chain: ChainGroup; balance: BalanceGroup; address: AddressGroup; @@ -2245,9 +2327,13 @@ rft: RFTGroup; ft: FTGroup; staking: StakingGroup; + scheduler: SchedulerGroup; - constructor(logger?: ILogger) { + constructor(logger?: ILogger, options: {[key: string]: any} = {}) { super(logger); + + this.helperBase = options.helperBase ?? UniqueHelper; + this.chain = new ChainGroup(this); this.balance = new BalanceGroup(this); this.address = new AddressGroup(this); @@ -2256,10 +2342,99 @@ this.rft = new RFTGroup(this); this.ft = new FTGroup(this); this.staking = new StakingGroup(this); + this.scheduler = new SchedulerGroup(this); + } + + clone(helperCls: UniqueHelperConstructor, options: {[key: string]: any} = {}) { + Object.setPrototypeOf(helperCls.prototype, this); + const newHelper = new helperCls(this.logger, options); + + newHelper.api = this.api; + newHelper.network = this.network; + newHelper.forceNetwork = this.forceNetwork; + + this.children.push(newHelper); + + return newHelper; + } + + getSudo() { + // eslint-disable-next-line @typescript-eslint/naming-convention + const SudoHelperType = SudoUniqueHelper(this.helperBase); + return this.clone(SudoHelperType) as T; } } +// eslint-disable-next-line @typescript-eslint/naming-convention +function ScheduledUniqueHelper(Base: T) { + return class extends Base { + scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter'; + scheduledId: string; + blocksNum: number; + options: ISchedulerOptions; + constructor(...args: any[]) { + const logger = args[0] as ILogger; + const options = args[1] as { + scheduleFn: 'scheduleNamed' | 'scheduleNamedAfter', + scheduledId: string, + blocksNum: number, + options: ISchedulerOptions + }; + + super(logger); + + this.scheduleFn = options.scheduleFn; + this.scheduledId = options.scheduledId; + this.blocksNum = options.blocksNum; + this.options = options.options; + } + + executeExtrinsic(sender: IKeyringPair, scheduledExtrinsic: string, scheduledParams: any[], expectSuccess?: boolean): Promise { + const scheduledTx = this.constructApiCall(scheduledExtrinsic, scheduledParams); + const extrinsic = 'api.tx.scheduler.' + this.scheduleFn; + + return super.executeExtrinsic( + sender, + extrinsic, + [ + this.scheduledId, + this.blocksNum, + this.options.periodic ? [this.options.periodic.period, this.options.periodic.repetitions] : null, + this.options.priority ?? null, + {Value: scheduledTx}, + ], + expectSuccess, + ); + } + }; +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +function SudoUniqueHelper(Base: T) { + return class extends Base { + constructor(...args: any[]) { + super(...args); + } + + executeExtrinsic ( + sender: IKeyringPair, + extrinsic: string, + params: any[], + expectSuccess?: boolean, + ): Promise { + const call = this.constructApiCall(extrinsic, params); + + return super.executeExtrinsic( + sender, + 'api.tx.sudo.sudo', + [call], + expectSuccess, + ); + } + }; +} + export class UniqueBaseCollection { helper: UniqueHelper; collectionId: number; @@ -2360,6 +2535,28 @@ async burn(signer: TSigner) { return await this.helper.collection.burn(signer, this.collectionId); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueBaseCollection(this.collectionId, scheduledHelper); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueBaseCollection(this.collectionId, scheduledHelper); + } + + getSudo() { + return new UniqueBaseCollection(this.collectionId, this.helper.getSudo()); + } } @@ -2447,6 +2644,28 @@ async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) { return await this.helper.nft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueNFTCollection(this.collectionId, scheduledHelper); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueNFTCollection(this.collectionId, scheduledHelper); + } + + getSudo() { + return new UniqueNFTCollection(this.collectionId, this.helper.getSudo()); + } } @@ -2530,6 +2749,28 @@ async setTokenPropertyPermissions(signer: TSigner, permissions: ITokenPropertyPermission[]) { return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueRFTCollection(this.collectionId, scheduledHelper); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueRFTCollection(this.collectionId, scheduledHelper); + } + + getSudo() { + return new UniqueRFTCollection(this.collectionId, this.helper.getSudo()); + } } @@ -2577,6 +2818,28 @@ async approveTokens(signer: TSigner, toAddressObj: ICrossAccountId, amount=1n) { return await this.helper.ft.approveTokens(signer, this.collectionId, toAddressObj, amount); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueFTCollection(this.collectionId, scheduledHelper); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledHelper = this.helper.scheduler.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueFTCollection(this.collectionId, scheduledHelper); + } + + getSudo() { + return new UniqueFTCollection(this.collectionId, this.helper.getSudo()); + } } @@ -2614,6 +2877,28 @@ nestingAccount() { return this.collection.helper.util.getTokenAccount(this); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueBaseToken(this.tokenId, scheduledCollection); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueBaseToken(this.tokenId, scheduledCollection); + } + + getSudo() { + return new UniqueBaseToken(this.tokenId, this.collection.getSudo()); + } } @@ -2672,6 +2957,28 @@ async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId) { return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueNFToken(this.tokenId, scheduledCollection); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueNFToken(this.tokenId, scheduledCollection); + } + + getSudo() { + return new UniqueNFToken(this.tokenId, this.collection.getSudo()); + } } export class UniqueRFToken extends UniqueBaseToken { @@ -2725,4 +3032,26 @@ async burnFrom(signer: TSigner, fromAddressObj: ICrossAccountId, amount=1n) { return await this.collection.burnTokenFrom(signer, this.tokenId, fromAddressObj, amount); } + + scheduleAt( + scheduledId: string, + executionBlockNumber: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAt(scheduledId, executionBlockNumber, options); + return new UniqueRFToken(this.tokenId, scheduledCollection); + } + + scheduleAfter( + scheduledId: string, + blocksBeforeExecution: number, + options: ISchedulerOptions = {}, + ) { + const scheduledCollection = this.collection.scheduleAfter(scheduledId, blocksBeforeExecution, options); + return new UniqueRFToken(this.tokenId, scheduledCollection); + } + + getSudo() { + return new UniqueRFToken(this.tokenId, this.collection.getSudo()); + } } --- a/tests/src/util/util.ts +++ /dev/null @@ -1,46 +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 . - -export function strToUTF16(str: string): any { - const buf: number[] = []; - for (let i=0, strLen=str.length; i < strLen; i++) { - buf.push(str.charCodeAt(i)); - } - return buf; -} - -export function utf16ToStr(buf: number[]): string { - let str = ''; - for (let i=0, strLen=buf.length; i < strLen; i++) { - if (buf[i] != 0) str += String.fromCharCode(buf[i]); - else break; - } - return str; -} - -export function hexToStr(buf: string): string { - let str = ''; - let hexStart = buf.indexOf('0x'); - if (hexStart < 0) hexStart = 0; - else hexStart = 2; - for (let i=hexStart, strLen=buf.length; i < strLen; i+=2) { - const ch = buf[i] + buf[i+1]; - const num = parseInt(ch, 16); - if (num != 0) str += String.fromCharCode(num); - else break; - } - return str; -} --- a/tests/src/xcm/xcmOpal.test.ts +++ b/tests/src/xcm/xcmOpal.test.ts @@ -21,9 +21,8 @@ import {ApiOptions} from '@polkadot/api/types'; import {IKeyringPair} from '@polkadot/types/types'; import usingApi, {executeTransaction} from './../substrate/substrate-api'; -import {bigIntToDecimals, describe_xcm, getGenericResult, paraSiblingSovereignAccount} from './../util/helpers'; +import {bigIntToDecimals, describe_xcm, getGenericResult, paraSiblingSovereignAccount, normalizeAccountId} from './../deprecated-helpers/helpers'; import waitNewBlocks from './../substrate/wait-new-blocks'; -import {normalizeAccountId} from './../util/helpers'; import getBalance from './../substrate/get-balance'; --- a/tests/src/xcm/xcmQuartz.test.ts +++ b/tests/src/xcm/xcmQuartz.test.ts @@ -21,7 +21,7 @@ import {ApiOptions} from '@polkadot/api/types'; import {IKeyringPair} from '@polkadot/types/types'; import usingApi, {submitTransactionAsync} from '../substrate/substrate-api'; -import {getGenericResult, generateKeyringPair, waitEvent, describe_xcm, bigIntToDecimals} from '../util/helpers'; +import {getGenericResult, generateKeyringPair, waitEvent, describe_xcm, bigIntToDecimals} from '../deprecated-helpers/helpers'; import {MultiLocation} from '@polkadot/types/interfaces'; import {blake2AsHex} from '@polkadot/util-crypto'; import waitNewBlocks from '../substrate/wait-new-blocks'; --- a/tests/src/xcm/xcmUnique.test.ts +++ b/tests/src/xcm/xcmUnique.test.ts @@ -21,7 +21,7 @@ import {ApiOptions} from '@polkadot/api/types'; import {IKeyringPair} from '@polkadot/types/types'; import usingApi, {submitTransactionAsync} from '../substrate/substrate-api'; -import {getGenericResult, generateKeyringPair, waitEvent, describe_xcm, bigIntToDecimals} from '../util/helpers'; +import {getGenericResult, generateKeyringPair, waitEvent, describe_xcm, bigIntToDecimals} from '../deprecated-helpers/helpers'; import {MultiLocation} from '@polkadot/types/interfaces'; import {blake2AsHex} from '@polkadot/util-crypto'; import waitNewBlocks from '../substrate/wait-new-blocks'; --- a/tests/src/xcmTransfer.test.ts +++ /dev/null @@ -1,187 +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 chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; - -import {WsProvider} from '@polkadot/api'; -import {ApiOptions} from '@polkadot/api/types'; -import {IKeyringPair} from '@polkadot/types/types'; -import usingApi, {submitTransactionAsync} from './substrate/substrate-api'; -import {getGenericResult} from './util/helpers'; -import waitNewBlocks from './substrate/wait-new-blocks'; -import getBalance from './substrate/get-balance'; - -chai.use(chaiAsPromised); -const expect = chai.expect; - -const UNIQUE_CHAIN = 1000; -const KARURA_CHAIN = 2000; -const KARURA_PORT = '9946'; -const TRANSFER_AMOUNT = 2000000000000000000000000n; - -// todo:playgrounds refit when XCM drops -describe.skip('Integration test: Exchanging QTZ with Karura', () => { - let alice: IKeyringPair; - - before(async () => { - await usingApi(async (api, privateKeyWrapper) => { - alice = privateKeyWrapper('//Alice'); - }); - - const karuraApiOptions: ApiOptions = { - provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT), - }; - - await usingApi(async (api) => { - const destination = { - V0: { - X2: [ - 'Parent', - { - Parachain: UNIQUE_CHAIN, - }, - ], - }, - }; - - const metadata = - { - name: 'QTZ', - symbol: 'QTZ', - decimals: 18, - minimalBalance: 1, - }; - - const tx = api.tx.assetRegistry.registerForeignAsset(destination, metadata); - const sudoTx = api.tx.sudo.sudo(tx as any); - const events = await submitTransactionAsync(alice, sudoTx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - }, karuraApiOptions); - }); - - it('Should connect and send QTZ to Karura', async () => { - let balanceOnKaruraBefore: bigint; - - await usingApi(async (api) => { - const {free} = (await api.query.tokens.accounts(alice.addressRaw, {ForeignAssetId: 0})).toJSON() as any; - balanceOnKaruraBefore = free; - }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)}); - - await usingApi(async (api) => { - const destination = { - V0: { - X2: [ - 'Parent', - { - Parachain: KARURA_CHAIN, - }, - ], - }, - }; - - const beneficiary = { - V0: { - X1: { - AccountId32: { - network: 'Any', - id: alice.addressRaw, - }, - }, - }, - }; - - const assets = { - V1: [ - { - id: { - Concrete: { - parents: 0, - interior: 'Here', - }, - }, - fun: { - Fungible: TRANSFER_AMOUNT, - }, - }, - ], - }; - - const feeAssetItem = 0; - - const weightLimit = { - Limited: 5000000000, - }; - - const tx = api.tx.polkadotXcm.limitedReserveTransferAssets(destination, beneficiary, assets, feeAssetItem, weightLimit); - const events = await submitTransactionAsync(alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - }); - - await usingApi(async (api) => { - // todo do something about instant sealing, where there might not be any new blocks - await waitNewBlocks(api, 3); - const {free} = (await api.query.tokens.accounts(alice.addressRaw, {ForeignAssetId: 0})).toJSON() as any; - expect(free > balanceOnKaruraBefore).to.be.true; - }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)}); - }); - - it('Should connect to Karura and send QTZ back', async () => { - let balanceBefore: bigint; - - await usingApi(async (api) => { - [balanceBefore] = await getBalance(api, [alice.address]); - }); - - await usingApi(async (api) => { - const destination = { - V1: { - parents: 1, - interior: { - X2: [ - {Parachain: UNIQUE_CHAIN}, - {AccountId32: { - network: 'Any', - id: alice.addressRaw, - }}, - ], - }, - }, - }; - - const id = { - ForeignAssetId: 0, - }; - - const amount = TRANSFER_AMOUNT; - const destWeight = 50000000; - - const tx = api.tx.xTokens.transfer(id, amount, destination, destWeight); - const events = await submitTransactionAsync(alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - }, {provider: new WsProvider('ws://127.0.0.1:' + KARURA_PORT)}); - - await usingApi(async (api) => { - // todo do something about instant sealing, where there might not be any new blocks - await waitNewBlocks(api, 3); - const [balanceAfter] = await getBalance(api, [alice.address]); - expect(balanceAfter > balanceBefore).to.be.true; - }); - }); -}); --- a/tests/tsconfig.json +++ b/tests/tsconfig.json @@ -21,6 +21,9 @@ "./src/**/*", "./src/interfaces/*.ts" ], + "exclude": [ + "./src/.outdated" + ], "lib": [ "es2017" ],