--- a/tests/package.json +++ b/tests/package.json @@ -30,6 +30,7 @@ "testEth": "mocha --timeout 9999999 -r ts-node/register './**/eth/**/*.test.ts'", "testEthMarketplace": "mocha --timeout 9999999 -r ts-node/register './**/eth/marketplace/**/*.test.ts'", "testEthNesting": "mocha --timeout 9999999 -r ts-node/register './**/eth/nesting/**/*.test.ts'", + "testEthPayable": "mocha --timeout 9999999 -r ts-node/register './**/eth/payable.test.ts'", "load": "mocha --timeout 9999999 -r ts-node/register './**/*.load.ts'", "loadTransfer": "ts-node src/transfer.nload.ts", "testCollision": "mocha --timeout 9999999 -r ts-node/register ./src/collision-tests/*.test.ts", @@ -79,7 +80,8 @@ "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts", "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts", "testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts", - "testEthCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createCollection.test.ts", + "testEthCreateNFTCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createNFTCollection.test.ts", + "testEthCreateRFTCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createRFTCollection.test.ts", "testRFT": "mocha --timeout 9999999 -r ts-node/register ./**/refungible.test.ts", "testFT": "mocha --timeout 9999999 -r ts-node/register ./**/fungible.test.ts", "testRPC": "mocha --timeout 9999999 -r ts-node/register ./**/rpc.test.ts", --- a/tests/src/eth/collectionAdmin.test.ts +++ b/tests/src/eth/collectionAdmin.test.ts @@ -15,7 +15,7 @@ import {expect} from 'chai'; import privateKey from '../substrate/privateKey'; -import { UNIQUE } from '../util/helpers'; +import {UNIQUE} from '../util/helpers'; import { createEthAccount, createEthAccountWithBalance, @@ -24,7 +24,6 @@ getCollectionAddressFromResult, itWeb3, recordEthFee, - subToEth, } from './util/helpers'; describe('Add collection admins', () => { @@ -37,7 +36,7 @@ .send(); const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const newAdmin = await createEthAccount(web3); + const newAdmin = createEthAccount(web3); const collectionEvm = evmCollection(web3, owner, collectionIdAddress); await collectionEvm.methods.addCollectionAdmin(newAdmin).send(); const adminList = await api.rpc.unique.adminlist(collectionId); @@ -92,7 +91,7 @@ const collectionEvm = evmCollection(web3, owner, collectionIdAddress); await collectionEvm.methods.addCollectionAdmin(admin).send(); - const user = await createEthAccount(web3); + const user = createEthAccount(web3); await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: admin})) .to.be.rejectedWith('NoPermission'); @@ -114,7 +113,7 @@ const notAdmin = await createEthAccountWithBalance(api, web3, privateKeyWrapper); const collectionEvm = evmCollection(web3, owner, collectionIdAddress); - const user = await createEthAccount(web3); + const user = createEthAccount(web3); await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: notAdmin})) .to.be.rejectedWith('NoPermission'); @@ -175,7 +174,7 @@ .send(); const {collectionIdAddress, collectionId} = await getCollectionAddressFromResult(api, result); - const newAdmin = await createEthAccount(web3); + const newAdmin = createEthAccount(web3); const collectionEvm = evmCollection(web3, owner, collectionIdAddress); await collectionEvm.methods.addCollectionAdmin(newAdmin).send(); { @@ -226,7 +225,7 @@ const admin0 = await createEthAccountWithBalance(api, web3, privateKeyWrapper); await collectionEvm.methods.addCollectionAdmin(admin0).send(); - const admin1 = await createEthAccount(web3); + const admin1 = createEthAccount(web3); await collectionEvm.methods.addCollectionAdmin(admin1).send(); await expect(collectionEvm.methods.removeCollectionAdmin(admin1).call({from: admin0})) @@ -253,7 +252,7 @@ const admin = await createEthAccountWithBalance(api, web3, privateKeyWrapper); await collectionEvm.methods.addCollectionAdmin(admin).send(); - const notAdmin = await createEthAccount(web3); + const notAdmin = createEthAccount(web3); await expect(collectionEvm.methods.removeCollectionAdmin(admin).call({from: notAdmin})) .to.be.rejectedWith('NoPermission'); --- a/tests/src/eth/collectionSponsoring.test.ts +++ b/tests/src/eth/collectionSponsoring.test.ts @@ -1,5 +1,5 @@ import {addToAllowListExpectSuccess, bigIntToSub, confirmSponsorshipExpectSuccess, createCollectionExpectSuccess, enablePublicMintingExpectSuccess, getDetailedCollectionInfo, setCollectionSponsorExpectSuccess} from '../util/helpers'; -import {itWeb3, createEthAccount, collectionIdToAddress, GAS_ARGS, normalizeEvents, createEthAccountWithBalance, evmCollectionHelpers, getCollectionAddressFromResult, evmCollection, ethBalanceViaSub, subToEth} 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'; --- a/tests/src/eth/createNFTCollection.test.ts +++ b/tests/src/eth/createNFTCollection.test.ts @@ -181,7 +181,7 @@ }); itWeb3('(!negative test!) Create collection (no funds)', async ({web3}) => { - const owner = await createEthAccount(web3); + const owner = createEthAccount(web3); const helper = evmCollectionHelpers(web3, owner); const collectionName = 'A'; const description = 'A'; @@ -194,7 +194,7 @@ itWeb3('(!negative test!) Check owner', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const notOwner = await createEthAccount(web3); + const notOwner = createEthAccount(web3); const collectionHelpers = evmCollectionHelpers(web3, owner); const result = await collectionHelpers.methods.createNonfungibleCollection('A', 'A', 'A').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); --- a/tests/src/eth/createRFTCollection.test.ts +++ b/tests/src/eth/createRFTCollection.test.ts @@ -189,7 +189,7 @@ }); itWeb3('(!negative test!) Create collection (no funds)', async ({web3}) => { - const owner = await createEthAccount(web3); + const owner = createEthAccount(web3); const helper = evmCollectionHelpers(web3, owner); const collectionName = 'A'; const description = 'A'; @@ -202,7 +202,7 @@ itWeb3('(!negative test!) Check owner', async ({api, web3, privateKeyWrapper}) => { const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const notOwner = await createEthAccount(web3); + const notOwner = createEthAccount(web3); const collectionHelpers = evmCollectionHelpers(web3, owner); const result = await collectionHelpers.methods.createRefungibleCollection('A', 'A', 'A').send(); const {collectionIdAddress} = await getCollectionAddressFromResult(api, result); --- a/tests/src/eth/nesting/nest.test.ts +++ b/tests/src/eth/nesting/nest.test.ts @@ -1,217 +1,220 @@ -import {ApiPromise} from '@polkadot/api'; +import {IKeyringPair} from '@polkadot/types/types'; import {Contract} from 'web3-eth-contract'; -import {expect} from 'chai'; -import Web3 from 'web3'; -import {createEthAccountWithBalance, evmCollectionHelpers, GAS_ARGS, getCollectionAddressFromResult, itWeb3, tokenIdToAddress} from '../../eth/util/helpers'; -import nonFungibleAbi from '../nonFungibleAbi.json'; +import {itEth, EthUniqueHelper, usingEthPlaygrounds, expect} from '../util/playgrounds'; + const createNestingCollection = async ( - api: ApiPromise, - web3: Web3, + helper: EthUniqueHelper, owner: string, ): Promise<{ collectionId: number, collectionAddress: string, contract: Contract }> => { - const collectionHelper = evmCollectionHelpers(web3, owner); - - const result = await collectionHelper.methods - .createNonfungibleCollection('A', 'B', 'C') - .send(); - const {collectionIdAddress: collectionAddress, collectionId} = await getCollectionAddressFromResult(api, result); + const {collectionAddress, collectionId} = await helper.eth.createNonfungibleCollection(owner, 'A', 'B', 'C'); - const contract = new web3.eth.Contract(nonFungibleAbi as any, collectionAddress, {from: owner, ...GAS_ARGS}); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); await contract.methods.setCollectionNesting(true).send({from: owner}); return {collectionId, collectionAddress, contract}; }; -describe('Integration Test: EVM Nesting', () => { - itWeb3('NFT: allows an Owner to nest/unnest their token', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const {collectionId, contract} = await createNestingCollection(api, web3, owner); - // Create a token to be nested - const targetNFTTokenId = await contract.methods.nextTokenId().call(); - await contract.methods.mint( - owner, - targetNFTTokenId, - ).send({from: owner}); - - const targetNftTokenAddress = tokenIdToAddress(collectionId, targetNFTTokenId); - - // Create a nested token - const firstTokenId = await contract.methods.nextTokenId().call(); - await contract.methods.mint( - targetNftTokenAddress, - firstTokenId, - ).send({from: owner}); +describe('EVM nesting tests group', () => { + let donor: IKeyringPair; - expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress); - - // Create a token to be nested and nest - const secondTokenId = await contract.methods.nextTokenId().call(); - await contract.methods.mint( - owner, - secondTokenId, - ).send({from: owner}); - - await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner}); - - expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress); - - // Unnest token back - await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner}); - expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner); + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + }); }); - itWeb3('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(api, web3, owner); - const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(api, web3, owner); - await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner}); - - // Create a token to nest into - const targetNftTokenId = await contractA.methods.nextTokenId().call(); - await contractA.methods.mint( - owner, - targetNftTokenId, - ).send({from: owner}); - const nftTokenAddressA1 = tokenIdToAddress(collectionIdA, targetNftTokenId); - - // Create a token for nesting in the same collection as the target - const nftTokenIdA = await contractA.methods.nextTokenId().call(); - await contractA.methods.mint( - owner, - nftTokenIdA, - ).send({from: owner}); - - // Create a token for nesting in a different collection - const nftTokenIdB = await contractB.methods.nextTokenId().call(); - await contractB.methods.mint( - owner, - nftTokenIdB, - ).send({from: owner}); - - // Nest - await contractA.methods.transfer(nftTokenAddressA1, nftTokenIdA).send({from: owner}); - expect(await contractA.methods.ownerOf(nftTokenIdA).call()).to.be.equal(nftTokenAddressA1); - - await contractB.methods.transfer(nftTokenAddressA1, nftTokenIdB).send({from: owner}); - expect(await contractB.methods.ownerOf(nftTokenIdB).call()).to.be.equal(nftTokenAddressA1); - }); -}); - -describe('Negative Test: EVM Nesting', async() => { - itWeb3('NFT: disallows to nest token if nesting is disabled', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const {collectionId, contract} = await createNestingCollection(api, web3, owner); - await contract.methods.setCollectionNesting(false).send({from: owner}); - - // Create a token to nest into - const targetNftTokenId = await contract.methods.nextTokenId().call(); - await contract.methods.mint( - owner, - targetNftTokenId, - ).send({from: owner}); - - const targetNftTokenAddress = tokenIdToAddress(collectionId, targetNftTokenId); - - // Create a token to nest - const nftTokenId = await contract.methods.nextTokenId().call(); - await contract.methods.mint( - owner, - nftTokenId, - ).send({from: owner}); - - // Try to nest - await expect(contract.methods - .transfer(targetNftTokenAddress, nftTokenId) - .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest'); - }); - - itWeb3('NFT: disallows a non-Owner to nest someone else\'s token', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const malignant = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const {collectionId, contract} = await createNestingCollection(api, web3, owner); - - // Mint a token - const targetTokenId = await contract.methods.nextTokenId().call(); - await contract.methods.mint( - owner, - targetTokenId, - ).send({from: owner}); - const targetTokenAddress = tokenIdToAddress(collectionId, targetTokenId); - - // Mint a token belonging to a different account - const tokenId = await contract.methods.nextTokenId().call(); - await contract.methods.mint( - malignant, - tokenId, - ).send({from: owner}); - - // Try to nest one token in another as a non-owner account - await expect(contract.methods - .transfer(targetTokenAddress, tokenId) - .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest'); - }); - - itWeb3('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const malignant = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(api, web3, owner); - const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(api, web3, owner); - - await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner}); - - // Create a token in one collection - const nftTokenIdA = await contractA.methods.nextTokenId().call(); - await contractA.methods.mint( - owner, - nftTokenIdA, - ).send({from: owner}); - const nftTokenAddressA = tokenIdToAddress(collectionIdA, nftTokenIdA); - - // Create a token in another collection belonging to someone else - const nftTokenIdB = await contractB.methods.nextTokenId().call(); - await contractB.methods.mint( - malignant, - nftTokenIdB, - ).send({from: owner}); - - // Try to drag someone else's token into the other collection and nest - await expect(contractB.methods - .transfer(nftTokenAddressA, nftTokenIdB) - .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest'); + describe('Integration Test: EVM Nesting', () => { + itEth('NFT: allows an Owner to nest/unnest their token', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const {collectionId, contract} = await createNestingCollection(helper, owner); + + // Create a token to be nested + const targetNFTTokenId = await contract.methods.nextTokenId().call(); + await contract.methods.mint( + owner, + targetNFTTokenId, + ).send({from: owner}); + + const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNFTTokenId); + + // Create a nested token + const firstTokenId = await contract.methods.nextTokenId().call(); + await contract.methods.mint( + targetNftTokenAddress, + firstTokenId, + ).send({from: owner}); + + expect(await contract.methods.ownerOf(firstTokenId).call()).to.be.equal(targetNftTokenAddress); + + // Create a token to be nested and nest + const secondTokenId = await contract.methods.nextTokenId().call(); + await contract.methods.mint( + owner, + secondTokenId, + ).send({from: owner}); + + await contract.methods.transfer(targetNftTokenAddress, secondTokenId).send({from: owner}); + + expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(targetNftTokenAddress); + + // Unnest token back + await contract.methods.transferFrom(targetNftTokenAddress, owner, secondTokenId).send({from: owner}); + expect(await contract.methods.ownerOf(secondTokenId).call()).to.be.equal(owner); + }); + + itEth('NFT: allows an Owner to nest/unnest their token (Restricted nesting)', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner); + const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(helper, owner); + await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner}); + + // Create a token to nest into + const targetNftTokenId = await contractA.methods.nextTokenId().call(); + await contractA.methods.mint( + owner, + targetNftTokenId, + ).send({from: owner}); + const nftTokenAddressA1 = helper.ethAddress.fromTokenId(collectionIdA, targetNftTokenId); + + // Create a token for nesting in the same collection as the target + const nftTokenIdA = await contractA.methods.nextTokenId().call(); + await contractA.methods.mint( + owner, + nftTokenIdA, + ).send({from: owner}); + + // Create a token for nesting in a different collection + const nftTokenIdB = await contractB.methods.nextTokenId().call(); + await contractB.methods.mint( + owner, + nftTokenIdB, + ).send({from: owner}); + + // Nest + await contractA.methods.transfer(nftTokenAddressA1, nftTokenIdA).send({from: owner}); + expect(await contractA.methods.ownerOf(nftTokenIdA).call()).to.be.equal(nftTokenAddressA1); + + await contractB.methods.transfer(nftTokenAddressA1, nftTokenIdB).send({from: owner}); + expect(await contractB.methods.ownerOf(nftTokenIdB).call()).to.be.equal(nftTokenAddressA1); + }); }); - itWeb3('NFT: disallows to nest token in an unlisted collection', async ({api, web3, privateKeyWrapper}) => { - const owner = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - - const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(api, web3, owner); - const {contract: contractB} = await createNestingCollection(api, web3, owner); - - await contractA.methods.setCollectionNesting(true, [collectionAddressA]).send({from: owner}); - - // Create a token in one collection - const nftTokenIdA = await contractA.methods.nextTokenId().call(); - await contractA.methods.mint( - owner, - nftTokenIdA, - ).send({from: owner}); - const nftTokenAddressA = tokenIdToAddress(collectionIdA, nftTokenIdA); - - // Create a token in another collection - const nftTokenIdB = await contractB.methods.nextTokenId().call(); - await contractB.methods.mint( - owner, - nftTokenIdB, - ).send({from: owner}); - - // Try to nest into a token in the other collection, disallowed in the first - await expect(contractB.methods - .transfer(nftTokenAddressA, nftTokenIdB) - .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest'); + describe('Negative Test: EVM Nesting', async() => { + itEth('NFT: disallows to nest token if nesting is disabled', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionId, contract} = await createNestingCollection(helper, owner); + await contract.methods.setCollectionNesting(false).send({from: owner}); + + // Create a token to nest into + const targetNftTokenId = await contract.methods.nextTokenId().call(); + await contract.methods.mint( + owner, + targetNftTokenId, + ).send({from: owner}); + + const targetNftTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetNftTokenId); + + // Create a token to nest + const nftTokenId = await contract.methods.nextTokenId().call(); + await contract.methods.mint( + owner, + nftTokenId, + ).send({from: owner}); + + // Try to nest + await expect(contract.methods + .transfer(targetNftTokenAddress, nftTokenId) + .call({from: owner})).to.be.rejectedWith('UserIsNotAllowedToNest'); + }); + + itEth('NFT: disallows a non-Owner to nest someone else\'s token', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const malignant = await helper.eth.createAccountWithBalance(donor); + + const {collectionId, contract} = await createNestingCollection(helper, owner); + + // Mint a token + const targetTokenId = await contract.methods.nextTokenId().call(); + await contract.methods.mint( + owner, + targetTokenId, + ).send({from: owner}); + const targetTokenAddress = helper.ethAddress.fromTokenId(collectionId, targetTokenId); + + // Mint a token belonging to a different account + const tokenId = await contract.methods.nextTokenId().call(); + await contract.methods.mint( + malignant, + tokenId, + ).send({from: owner}); + + // Try to nest one token in another as a non-owner account + await expect(contract.methods + .transfer(targetTokenAddress, tokenId) + .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest'); + }); + + itEth('NFT: disallows a non-Owner to nest someone else\'s token (Restricted nesting)', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const malignant = await helper.eth.createAccountWithBalance(donor); + + const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner); + const {collectionAddress: collectionAddressB, contract: contractB} = await createNestingCollection(helper, owner); + + await contractA.methods.setCollectionNesting(true, [collectionAddressA, collectionAddressB]).send({from: owner}); + + // Create a token in one collection + const nftTokenIdA = await contractA.methods.nextTokenId().call(); + await contractA.methods.mint( + owner, + nftTokenIdA, + ).send({from: owner}); + const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA); + + // Create a token in another collection belonging to someone else + const nftTokenIdB = await contractB.methods.nextTokenId().call(); + await contractB.methods.mint( + malignant, + nftTokenIdB, + ).send({from: owner}); + + // Try to drag someone else's token into the other collection and nest + await expect(contractB.methods + .transfer(nftTokenAddressA, nftTokenIdB) + .call({from: malignant})).to.be.rejectedWith('UserIsNotAllowedToNest'); + }); + + itEth('NFT: disallows to nest token in an unlisted collection', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionId: collectionIdA, collectionAddress: collectionAddressA, contract: contractA} = await createNestingCollection(helper, owner); + const {contract: contractB} = await createNestingCollection(helper, owner); + + await contractA.methods.setCollectionNesting(true, [collectionAddressA]).send({from: owner}); + + // Create a token in one collection + const nftTokenIdA = await contractA.methods.nextTokenId().call(); + await contractA.methods.mint( + owner, + nftTokenIdA, + ).send({from: owner}); + const nftTokenAddressA = helper.ethAddress.fromTokenId(collectionIdA, nftTokenIdA); + + // Create a token in another collection + const nftTokenIdB = await contractB.methods.nextTokenId().call(); + await contractB.methods.mint( + owner, + nftTokenIdB, + ).send({from: owner}); + + // Try to nest into a token in the other collection, disallowed in the first + await expect(contractB.methods + .transfer(nftTokenAddressA, nftTokenIdB) + .call()).to.be.rejectedWith('SourceCollectionIsNotAllowedToNest'); + }); }); }); --- a/tests/src/eth/payable.test.ts +++ b/tests/src/eth/payable.test.ts @@ -14,97 +14,91 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {expect} from 'chai'; -import {submitTransactionAsync} from '../substrate/substrate-api'; -import {createEthAccountWithBalance, deployCollector, GAS_ARGS, itWeb3, subToEth, transferBalanceToEth} from './util/helpers'; -import {evmToAddress} from '@polkadot/util-crypto'; -import {getGenericResult, UNIQUE} from '../util/helpers'; -import {getBalanceSingle, transferBalanceExpectSuccess} from '../substrate/get-balance'; +import {IKeyringPair} from '@polkadot/types/types'; + +import {itEth, expect, usingEthPlaygrounds} from './util/playgrounds'; describe('EVM payable contracts', () => { - itWeb3('Evm contract can receive wei from eth account', async ({api, web3, privateKeyWrapper}) => { - const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const contract = await deployCollector(web3, deployer); + let donor: IKeyringPair; - await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', ...GAS_ARGS}); + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = privateKey('//Alice'); + }); + }); + + itEth('Evm contract can receive wei from eth account', async ({helper}) => { + const deployer = await helper.eth.createAccountWithBalance(donor); + const contract = await helper.eth.deployCollectorContract(deployer); + + const web3 = helper.getWeb3(); + + await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: '10000', gas: helper.eth.DEFAULT_GAS}); expect(await contract.methods.getCollected().call()).to.be.equal('10000'); }); - itWeb3('Evm contract can receive wei from substrate account', async ({api, web3, privateKeyWrapper}) => { - const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const contract = await deployCollector(web3, deployer); - const alice = privateKeyWrapper('//Alice'); + itEth('Evm contract can receive wei from substrate account', async ({helper}) => { + const deployer = await helper.eth.createAccountWithBalance(donor); + const contract = await helper.eth.deployCollectorContract(deployer); + const [alice] = await helper.arrange.createAccounts([10n], donor); + + const weiCount = '10000'; // Transaction fee/value will be payed from subToEth(sender) evm balance, // which is backed by evmToAddress(subToEth(sender)) substrate balance - await transferBalanceToEth(api, alice, subToEth(alice.address)); + await helper.eth.transferBalanceFromSubstrate(alice, helper.address.substrateToEth(alice.address), 5n); + - { - const tx = api.tx.evm.call( - subToEth(alice.address), - contract.options.address, - contract.methods.giveMoney().encodeABI(), - '10000', - GAS_ARGS.gas, - await web3.eth.getGasPrice(), - null, - null, - [], - ); - const events = await submitTransactionAsync(alice, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - } + await helper.eth.callEVM(alice, contract.options.address, contract.methods.giveMoney().encodeABI(), weiCount); - expect(await contract.methods.getCollected().call()).to.be.equal('10000'); + expect(await contract.methods.getCollected().call()).to.be.equal(weiCount); }); // We can't handle sending balance to backing storage of evm balance, because evmToAddress operation is irreversible - itWeb3('Wei sent directly to backing storage of evm contract balance is unaccounted', async({api, web3, privateKeyWrapper}) => { - const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const contract = await deployCollector(web3, deployer); - const alice = privateKeyWrapper('//Alice'); + itEth('Wei sent directly to backing storage of evm contract balance is unaccounted', async({helper}) => { + const deployer = await helper.eth.createAccountWithBalance(donor); + const contract = await helper.eth.deployCollectorContract(deployer); + const [alice] = await helper.arrange.createAccounts([10n], donor); + + const weiCount = 10_000n; - await transferBalanceExpectSuccess(api, alice, evmToAddress(contract.options.address), '10000'); + await helper.eth.transferBalanceFromSubstrate(alice, contract.options.address, weiCount, false); - expect(await contract.methods.getUnaccounted().call()).to.be.equal('10000'); + expect(await contract.methods.getUnaccounted().call()).to.be.equal(weiCount.toString()); }); - itWeb3('Balance can be retrieved from evm contract', async({api, web3, privateKeyWrapper}) => { - const FEE_BALANCE = 1000n * UNIQUE; - const CONTRACT_BALANCE = 1n * UNIQUE; + itEth('Balance can be retrieved from evm contract', async({helper, privateKey}) => { + const FEE_BALANCE = 10n * helper.balance.getOneTokenNominal(); + const CONTRACT_BALANCE = 1n * helper.balance.getOneTokenNominal(); + + const deployer = await helper.eth.createAccountWithBalance(donor); + const contract = await helper.eth.deployCollectorContract(deployer); + const [alice] = await helper.arrange.createAccounts([20n], donor); - const deployer = await createEthAccountWithBalance(api, web3, privateKeyWrapper); - const contract = await deployCollector(web3, deployer); - const alice = privateKeyWrapper('//Alice'); + const web3 = helper.getWeb3(); - await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), ...GAS_ARGS}); + await web3.eth.sendTransaction({from: deployer, to: contract.options.address, value: CONTRACT_BALANCE.toString(), gas: helper.eth.DEFAULT_GAS}); - const receiver = privateKeyWrapper(`//Receiver${Date.now()}`); + const receiver = privateKey(`//Receiver${Date.now()}`); // First receive balance on eth balance of bob { - const ethReceiver = subToEth(receiver.address); + const ethReceiver = helper.address.substrateToEth(receiver.address); expect(await web3.eth.getBalance(ethReceiver)).to.be.equal('0'); await contract.methods.withdraw(ethReceiver).send({from: deployer}); expect(await web3.eth.getBalance(ethReceiver)).to.be.equal(CONTRACT_BALANCE.toString()); } // Some balance is required to pay fee for evm.withdraw call - await transferBalanceExpectSuccess(api, alice, receiver.address, FEE_BALANCE.toString()); + await helper.balance.transferToSubstrate(alice, receiver.address, FEE_BALANCE); + // await transferBalanceExpectSuccess(api, alice, receiver.address, FEE_BALANCE.toString()); // Withdraw balance from eth to substrate { - const initialReceiverBalance = await getBalanceSingle(api, receiver.address); - const tx = api.tx.evm.withdraw( - subToEth(receiver.address), - CONTRACT_BALANCE.toString(), - ); - const events = await submitTransactionAsync(receiver, tx); - const result = getGenericResult(events); - expect(result.success).to.be.true; - const finalReceiverBalance = await getBalanceSingle(api, receiver.address); + const initialReceiverBalance = await helper.balance.getSubstrate(receiver.address); + await helper.executeExtrinsic(receiver, 'api.tx.evm.withdraw', [helper.address.substrateToEth(receiver.address), CONTRACT_BALANCE.toString()], true); + const finalReceiverBalance = await helper.balance.getSubstrate(receiver.address); expect(finalReceiverBalance > initialReceiverBalance).to.be.true; } --- a/tests/src/eth/reFungibleToken.test.ts +++ b/tests/src/eth/reFungibleToken.test.ts @@ -15,7 +15,7 @@ // along with Unique Network. If not, see . import {approve, createCollection, createRefungibleToken, transfer, transferFrom, UNIQUE, requirePallets, Pallets} from '../util/helpers'; -import {collectionIdFromAddress, collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createNonfungibleCollection, createRefungibleCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueNFT, uniqueRefungible, uniqueRefungibleToken} from './util/helpers'; +import {collectionIdToAddress, createEthAccount, createEthAccountWithBalance, createRefungibleCollection, evmCollection, evmCollectionHelpers, getCollectionAddressFromResult, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, tokenIdToAddress, transferBalanceToEth, uniqueRefungible, uniqueRefungibleToken} from './util/helpers'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; --- /dev/null +++ b/tests/src/eth/util/playgrounds/index.ts @@ -0,0 +1,49 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +import {IKeyringPair} from '@polkadot/types/types'; + +import config from '../../../config'; + +import {EthUniqueHelper} from './unique.dev'; +import {SilentLogger, SilentConsole} from '../../../util/playgrounds/unique.dev'; + +export {EthUniqueHelper} from './unique.dev'; + +import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +chai.use(chaiAsPromised); +export const expect = chai.expect; + +export const usingEthPlaygrounds = async (code: (helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise) => { + const silentConsole = new SilentConsole(); + silentConsole.enable(); + + const helper = new EthUniqueHelper(new SilentLogger()); + + try { + await helper.connect(config.substrateUrl); + await helper.connectWeb3(config.substrateUrl); + const ss58Format = helper.chain.getChainProperties().ss58Format; + const privateKey = (seed: string) => helper.util.fromSeed(seed, ss58Format); + await code(helper, privateKey); + } + finally { + await helper.disconnect(); + await helper.disconnectWeb3(); + silentConsole.disable(); + } +}; + +export async function itEth(name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: 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 usingEthPlaygrounds(async (helper, privateKey) => { + await cb({helper, privateKey}); + }); + }); +} +itEth.only = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEth(name, cb, {only: true}); +itEth.skip = (name: string, cb: (apis: { helper: EthUniqueHelper, privateKey: (seed: string) => IKeyringPair }) => any) => itEth(name, cb, {skip: true}); \ No newline at end of file --- /dev/null +++ b/tests/src/eth/util/playgrounds/types.ts @@ -0,0 +1,9 @@ +export interface ContractImports { + solPath: string; + fsPath: string; +} + +export interface CompiledContract { + abi: any; + object: string; +} \ No newline at end of file --- /dev/null +++ b/tests/src/eth/util/playgrounds/unique.dev.ts @@ -0,0 +1,268 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +/* eslint-disable function-call-argument-newline */ + +import {readFile} from 'fs/promises'; + +import Web3 from 'web3'; +import {WebsocketProvider} from 'web3-core'; +import {Contract} from 'web3-eth-contract'; + +import * as solc from 'solc'; + +import {evmToAddress} from '@polkadot/util-crypto'; +import {IKeyringPair} from '@polkadot/types/types'; + +import {DevUniqueHelper} from '../../../util/playgrounds/unique.dev'; + +import {ContractImports, CompiledContract} from './types'; + +// Native contracts ABI +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'; + +class EthGroupBase { + helper: EthUniqueHelper; + + constructor(helper: EthUniqueHelper) { + this.helper = helper; + } +} + + +class ContractGroup extends EthGroupBase { + async findImports(imports?: ContractImports[]){ + if(!imports) return function(path: string) { + return {error: `File not found: ${path}`}; + }; + + const knownImports = {} as any; + for(const imp of imports) { + knownImports[imp.solPath] = (await readFile(imp.fsPath)).toString(); + } + + return function(path: string) { + if(knownImports.hasOwnPropertyDescriptor(path)) return {contents: knownImports[path]}; + return {error: `File not found: ${path}`}; + }; + } + + async compile(name: string, src: string, imports?: ContractImports[]): Promise { + const out = JSON.parse(solc.compile(JSON.stringify({ + language: 'Solidity', + sources: { + [`${name}.sol`]: { + content: src, + }, + }, + settings: { + outputSelection: { + '*': { + '*': ['*'], + }, + }, + }, + }), {import: await this.findImports(imports)})).contracts[`${name}.sol`][name]; + + return { + abi: out.abi, + object: '0x' + out.evm.bytecode.object, + }; + } + + async deployByCode(signer: string, name: string, src: string, imports?: ContractImports[]): Promise { + const compiledContract = await this.compile(name, src, imports); + return this.deployByAbi(signer, compiledContract.abi, compiledContract.object); + } + + async deployByAbi(signer: string, abi: any, object: string): Promise { + const web3 = this.helper.getWeb3(); + const contract = new web3.eth.Contract(abi, undefined, { + data: object, + from: signer, + gas: this.helper.eth.DEFAULT_GAS, + }); + return await contract.deploy({data: object}).send({from: signer}); + } + +} + +class NativeContractGroup extends EthGroupBase { + + contractHelpers(caller: string): Contract { + const web3 = this.helper.getWeb3(); + return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, gas: this.helper.eth.DEFAULT_GAS}); + } + + collectionHelpers(caller: string) { + const web3 = this.helper.getWeb3(); + return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, gas: this.helper.eth.DEFAULT_GAS}); + } + + collection(address: string, mode: 'nft' | 'rft' | 'ft', caller?: string): Contract { + const abi = { + 'nft': nonFungibleAbi, + 'rft': refungibleAbi, + 'ft': fungibleAbi, + }[mode]; + const web3 = this.helper.getWeb3(); + return new web3.eth.Contract(abi as any, address, {gas: this.helper.eth.DEFAULT_GAS, ...(caller ? {from: caller} : {})}); + } + + rftTokenByAddress(address: string, caller?: string): Contract { + const web3 = this.helper.getWeb3(); + return new web3.eth.Contract(refungibleTokenAbi as any, address, {gas: this.helper.eth.DEFAULT_GAS, ...(caller ? {from: caller} : {})}); + } + + rftToken(collectionId: number, tokenId: number, caller?: string): Contract { + return this.rftTokenByAddress(this.helper.ethAddress.fromTokenId(collectionId, tokenId), caller); + } +} + + +class EthGroup extends EthGroupBase { + DEFAULT_GAS = 2_500_000; + + createAccount() { + const web3 = this.helper.getWeb3(); + const account = web3.eth.accounts.create(); + web3.eth.accounts.wallet.add(account.privateKey); + return account.address; + } + + async createAccountWithBalance(donor: IKeyringPair, amount=1000n) { + const account = this.createAccount(); + await this.transferBalanceFromSubstrate(donor, account, amount); + + return account; + } + + async transferBalanceFromSubstrate(donor: IKeyringPair, recepient: string, amount=1000n, inTokens=true) { + return await this.helper.balance.transferToSubstrate(donor, evmToAddress(recepient), amount * (inTokens ? this.helper.balance.getOneTokenNominal() : 1n)); + } + + async callEVM(signer: IKeyringPair, contractAddress: string, abi: any, value: string, gasLimit?: number) { + if(!gasLimit) gasLimit = this.DEFAULT_GAS; + const web3 = this.helper.getWeb3(); + const gasPrice = await web3.eth.getGasPrice(); + // TODO: check execution status + await this.helper.executeExtrinsic( + signer, + 'api.tx.evm.call', [this.helper.address.substrateToEth(signer.address), contractAddress, abi, value, gasLimit, gasPrice, null, null, []], + true, + ); + } + + async createNonfungibleCollection(signer: string, name: string, description: string, tokenPrefix: string): Promise<{collectionId: number, collectionAddress: string}> { + const collectionHelper = this.helper.ethNativeContract.collectionHelpers(signer); + + const result = await collectionHelper.methods.createNonfungibleCollection(name, description, tokenPrefix).send(); + + const collectionAddress = this.helper.ethAddress.normalizeAddress(result.events.CollectionCreated.returnValues.collectionId); + const collectionId = this.helper.ethAddress.extractCollectionId(collectionAddress); + + return {collectionId, collectionAddress}; + } + + async deployCollectorContract(signer: string): Promise { + return await this.helper.ethContract.deployByCode(signer, 'Collector', ` + // SPDX-License-Identifier: UNLICENSED + pragma solidity ^0.8.6; + + 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; + } + } + `); + } +} + + +class EthAddressGroup extends EthGroupBase { + extractCollectionId(address: string): number { + if (!(address.length === 42 || address.length === 40)) throw new Error('address wrong format'); + return parseInt(address.substr(address.length - 8), 16); + } + + fromCollectionId(collectionId: number): string { + if (collectionId >= 0xffffffff || collectionId < 0) throw new Error('collectionId overflow'); + return Web3.utils.toChecksumAddress(`0x17c4e6453cc49aaaaeaca894e6d9683e${collectionId.toString(16).padStart(8, '0')}`); + } + + extractTokenId(address: string): {collectionId: number, tokenId: number} { + 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)), + }; + } + + fromTokenId(collectionId: number, tokenId: number): string { + return this.helper.util.getNestingTokenAddress(collectionId, tokenId); + } + + normalizeAddress(address: string): string { + return '0x' + address.substring(address.length - 40); + } +} + + +export class EthUniqueHelper extends DevUniqueHelper { + web3: Web3 | null = null; + web3Provider: WebsocketProvider | null = null; + + eth: EthGroup; + ethAddress: EthAddressGroup; + ethNativeContract: NativeContractGroup; + ethContract: ContractGroup; + + constructor(logger: { log: (msg: any, level: any) => void, level: any }) { + super(logger); + this.eth = new EthGroup(this); + this.ethAddress = new EthAddressGroup(this); + this.ethNativeContract = new NativeContractGroup(this); + this.ethContract = new ContractGroup(this); + } + + getWeb3(): Web3 { + if(this.web3 === null) throw Error('Web3 not connected'); + return this.web3; + } + + async connectWeb3(wsEndpoint: string) { + if(this.web3 !== null) return; + this.web3Provider = new Web3.providers.WebsocketProvider(wsEndpoint); + this.web3 = new Web3(this.web3Provider); + } + + async disconnectWeb3() { + if(this.web3 === null) return; + this.web3Provider?.connection.close(); + this.web3 = null; + } +} + \ No newline at end of file --- a/tests/src/substrate/substrate-api.ts +++ b/tests/src/substrate/substrate-api.ts @@ -25,6 +25,8 @@ import privateKey from './privateKey'; import promisifySubstrate from './promisify-substrate'; +import {SilentConsole} from '../util/playgrounds/unique.dev'; + function defaultApiOptions(): ApiOptions { @@ -74,26 +76,9 @@ settings = settings || defaultApiOptions(); const api: ApiPromise = new ApiPromise(settings); let result: T = null as unknown as T; - - // TODO: Remove, this is temporary: Filter unneeded API output - // (Jaco promised it will be removed in the next version) - const consoleErr = console.error; - const consoleLog = console.log; - const consoleWarn = console.warn; - const outFn = (printer: any) => (...args: any[]) => { - for (const arg of args) { - if (typeof arg !== 'string') - continue; - if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure') - return; - } - printer(...args); - }; - - console.error = outFn(consoleErr.bind(console)); - console.log = outFn(consoleLog.bind(console)); - console.warn = outFn(consoleWarn.bind(console)); + const silentConsole = new SilentConsole(); + silentConsole.enable(); try { await promisifySubstrate(api, async () => { @@ -106,9 +91,7 @@ })(); } finally { await api.disconnect(); - console.error = consoleErr; - console.log = consoleLog; - console.warn = consoleWarn; + silentConsole.disable(); } return result as T; } --- a/tests/src/util/playgrounds/index.ts +++ b/tests/src/util/playgrounds/index.ts @@ -4,37 +4,13 @@ import {IKeyringPair} from '@polkadot/types/types'; import config from '../../config'; import '../../interfaces/augment-api-events'; -import {DevUniqueHelper} from './unique.dev'; +import {DevUniqueHelper, SilentLogger, SilentConsole} from './unique.dev'; -class SilentLogger { - log(msg: any, level: any): void { } - level = { - ERROR: 'ERROR' as const, - WARNING: 'WARNING' as const, - INFO: 'INFO' as const, - }; -} export const usingPlaygrounds = async (code: (helper: DevUniqueHelper, privateKey: (seed: string) => IKeyringPair) => Promise) => { - // TODO: Remove, this is temporary: Filter unneeded API output - // (Jaco promised it will be removed in the next version) - const consoleErr = console.error; - const consoleLog = console.log; - const consoleWarn = console.warn; + const silentConsole = new SilentConsole(); + silentConsole.enable(); - const outFn = (printer: any) => (...args: any[]) => { - for (const arg of args) { - if (typeof arg !== 'string') - continue; - if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis:') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure') - return; - } - printer(...args); - }; - - console.error = outFn(consoleErr.bind(console)); - console.log = outFn(consoleLog.bind(console)); - console.warn = outFn(consoleWarn.bind(console)); const helper = new DevUniqueHelper(new SilentLogger()); try { @@ -45,8 +21,6 @@ } finally { await helper.disconnect(); - console.error = consoleErr; - console.log = consoleLog; - console.warn = consoleWarn; + silentConsole.disable(); } }; --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -5,10 +5,56 @@ import {UniqueHelper} from './unique'; import {ApiPromise, WsProvider} from '@polkadot/api'; import * as defs from '../../interfaces/definitions'; -import {TSigner} from './types'; import {IKeyringPair} from '@polkadot/types/types'; +export class SilentLogger { + log(_msg: any, _level: any): void { } + level = { + ERROR: 'ERROR' as const, + WARNING: 'WARNING' as const, + INFO: 'INFO' as const, + }; +} + + +export class SilentConsole { + // TODO: Remove, this is temporary: Filter unneeded API output + // (Jaco promised it will be removed in the next version) + consoleErr: any; + consoleLog: any; + consoleWarn: any; + + constructor() { + this.consoleErr = console.error; + this.consoleLog = console.log; + this.consoleWarn = console.warn; + } + + enable() { + const outFn = (printer: any) => (...args: any[]) => { + for (const arg of args) { + if (typeof arg !== 'string') + continue; + if (arg.includes('1000:: Normal connection closure') || arg.includes('Not decorating unknown runtime apis: UniqueApi/2, RmrkApi/1') || arg.includes('RPC methods not decorated:') || arg === 'Normal connection closure') + return; + } + printer(...args); + }; + + console.error = outFn(this.consoleErr.bind(console)); + console.log = outFn(this.consoleLog.bind(console)); + console.warn = outFn(this.consoleWarn.bind(console)); + } + + disable() { + console.error = this.consoleErr; + console.log = this.consoleLog; + console.warn = this.consoleWarn; + } +} + + export class DevUniqueHelper extends UniqueHelper { /** * Arrange methods for tests @@ -20,7 +66,7 @@ this.arrange = new ArrangeGroup(this); } - async connect(wsEndpoint: string, listeners?: any): Promise { + async connect(wsEndpoint: string, _listeners?: any): Promise { const wsProvider = new WsProvider(wsEndpoint); this.api = new ApiPromise({ provider: wsProvider, @@ -85,7 +131,7 @@ } } - await Promise.all(transactions).catch(e => {}); + await Promise.all(transactions).catch(_e => {}); //#region TODO remove this region, when nonce problem will be solved const checkBalances = async () => { @@ -113,13 +159,14 @@ return accounts; }; - + /** * Wait for specified bnumber of blocks * @param blocksCount number of blocks to wait * @returns */ async waitNewBlocks(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(() => { if (blocksCount > 0) { --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -317,6 +317,7 @@ if(options !== null) return transaction.signAndSend(sender, options, callback); return transaction.signAndSend(sender, callback); }; + // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { try { const unsub = await sign((result: any) => { @@ -388,6 +389,7 @@ type: this.chainLogType.EXTRINSIC, status: result.status, call: extrinsic, + signer: this.getSignerAddress(sender), params, } as IUniqueHelperLog; @@ -1087,11 +1089,11 @@ return this.getCollectionObject(this.helper.util.extractCollectionIdFromCreationResult(creationResult)); } - getCollectionObject(collectionId: number): any { + getCollectionObject(_collectionId: number): any { return null; } - getTokenObject(collectionId: number, tokenId: number): any { + getTokenObject(_collectionId: number, _tokenId: number): any { return null; } }