--- a/tests/src/eth/allowlist.test.ts +++ b/tests/src/eth/allowlist.test.ts @@ -92,21 +92,47 @@ }); itEth('Collection allowlist can be added and removed by [cross] address', async ({helper}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const user = donor; + const owner = (await helper.eth.createAccountWithBalance(donor)).toLowerCase(); + const [userSub] = await helper.arrange.createAccounts([10n], donor); + const userEth = await helper.eth.createAccountWithBalance(donor); const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - const userCross = helper.ethCrossAccount.fromKeyringPair(user); + const userCrossSub = helper.ethCrossAccount.fromKeyringPair(userSub); + const userCrossEth = helper.ethCrossAccount.fromAddress(userEth); + const ownerCrossEth = helper.ethCrossAccount.fromAddress(owner); + + // Can addToCollectionAllowListCross: + expect(await helper.collection.allowed(collectionId, {Substrate: userSub.address})).to.be.false; + await collectionEvm.methods.addToCollectionAllowListCross(userCrossSub).send({from: owner}); + await collectionEvm.methods.addToCollectionAllowListCross(userCrossEth).send({from: owner}); + await collectionEvm.methods.addToCollectionAllowListCross(ownerCrossEth).send({from: owner}); + expect(await helper.collection.allowed(collectionId, {Substrate: userSub.address})).to.be.true; + expect(await helper.collection.allowed(collectionId, {Ethereum: userEth})).to.be.true; + expect(await collectionEvm.methods.allowlistedCross(userCrossSub).call({from: owner})).to.be.true; + expect(await collectionEvm.methods.allowlistedCross(userCrossEth).call({from: owner})).to.be.true; + + await collectionEvm.methods.mint(userEth).send(); // token #1 + await collectionEvm.methods.mint(userEth).send(); // token #2 + await collectionEvm.methods.setCollectionAccess(1).send(); - expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; - await collectionEvm.methods.addToCollectionAllowListCross(userCross).send({from: owner}); - expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.true; - expect(await collectionEvm.methods.allowlistedCross(userCross).call({from: owner})).to.be.true; + // allowlisted account can transfer and transferCross: + await collectionEvm.methods.transfer(owner, 1).send({from: userEth}); + await collectionEvm.methods.transferCross(userCrossSub, 2).send({from: userEth}); + expect(await helper.nft.getTokenOwner(collectionId, 1)).to.deep.eq({Ethereum: owner}); + expect(await helper.nft.getTokenOwner(collectionId, 2)).to.deep.eq({Substrate: userSub.address}); - await collectionEvm.methods.removeFromCollectionAllowListCross(userCross).send({from: owner}); - expect(await helper.collection.allowed(collectionId, {Substrate: user.address})).to.be.false; - expect(await collectionEvm.methods.allowlistedCross(userCross).call({from: owner})).to.be.false; + // can removeFromCollectionAllowListCross: + await collectionEvm.methods.removeFromCollectionAllowListCross(userCrossSub).send({from: owner}); + await collectionEvm.methods.removeFromCollectionAllowListCross(userCrossEth).send({from: owner}); + expect(await helper.collection.allowed(collectionId, {Substrate: userSub.address})).to.be.false; + expect(await helper.collection.allowed(collectionId, {Ethereum: userEth})).to.be.false; + expect(await collectionEvm.methods.allowlistedCross(userCrossSub).call({from: owner})).to.be.false; + expect(await collectionEvm.methods.allowlistedCross(userCrossEth).call({from: owner})).to.be.false; + + // cannot transfer anymore + await collectionEvm.methods.mint(userEth).send(); + await expect(collectionEvm.methods.transfer(owner, 2).send({from: userEth})).to.be.rejectedWith(/Transaction has been reverted/); }); // Soft-deprecated --- a/tests/src/eth/collectionAdmin.test.ts +++ b/tests/src/eth/collectionAdmin.test.ts @@ -39,80 +39,98 @@ }); }); - // Soft-deprecated - itEth('Add admin by owner', async ({helper}) => { + itEth('can add account admin by owner', async ({helper, privateKey}) => { + // arrange const owner = await helper.eth.createAccountWithBalance(donor); + const adminSub = await privateKey('//admin2'); + const adminEth = helper.eth.createAccount().toLowerCase(); + + const adminDeprecated = helper.eth.createAccount().toLowerCase(); + const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub); + const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth); + const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true); - const newAdmin = helper.eth.createAccount(); + // Soft-deprecated: can addCollectionAdmin + await collectionEvm.methods.addCollectionAdmin(adminDeprecated).send(); + // Can addCollectionAdminCross for substrate and ethereum address + await collectionEvm.methods.addCollectionAdminCross(adminCrossSub).send(); + await collectionEvm.methods.addCollectionAdminCross(adminCrossEth).send(); - await collectionEvm.methods.addCollectionAdmin(newAdmin).send(); - const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]); - expect(adminList[0].asEthereum.toString().toLocaleLowerCase()) - .to.be.eq(newAdmin.toLocaleLowerCase()); + // 1. Expect api.rpc.unique.adminlist returns admins: + const adminListRpc = await helper.collection.getAdmins(collectionId); + expect(adminListRpc).to.has.length(3); + expect(adminListRpc).to.be.deep.contain.members([{Substrate: adminSub.address}, {Ethereum: adminEth}, {Ethereum: adminDeprecated}]); + + // 2. Expect methods.collectionAdmins == api.rpc.unique.adminlist + let adminListEth = await collectionEvm.methods.collectionAdmins().call(); + adminListEth = adminListEth.map((element: IEthCrossAccountId) => { + return helper.address.convertCrossAccountFromEthCrossAccount(element); + }); + expect(adminListRpc).to.be.like(adminListEth); }); - itEth('Add cross account admin by owner', async ({helper, privateKey}) => { + itEth('cross account admin can mint', async ({helper}) => { + // arrange: create collection and accounts const owner = await helper.eth.createAccountWithBalance(donor); - - const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); - const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + const {collectionAddress, collectionId} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', 'uri'); + const adminEth = (await helper.eth.createAccountWithBalance(donor)).toLowerCase(); + const adminCrossEth = helper.ethCrossAccount.fromAddress(adminEth); + const [adminSub] = await helper.arrange.createAccounts([100n], donor); + const adminCrossSub = helper.ethCrossAccount.fromKeyringPair(adminSub); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true); + + // cannot mint while not admin + await expect(collectionEvm.methods.mint(owner).send({from: adminEth})).to.be.rejected; + await expect(helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}})).to.be.rejectedWith(/common.PublicMintingNotAllowed/); - const newAdmin = await privateKey('//Bob'); - const newAdminCross = helper.ethCrossAccount.fromKeyringPair(newAdmin); - await collectionEvm.methods.addCollectionAdminCross(newAdminCross).send(); + // admin (sub and eth) can mint token: + await collectionEvm.methods.addCollectionAdminCross(adminCrossEth).send(); + await collectionEvm.methods.addCollectionAdminCross(adminCrossSub).send(); + await collectionEvm.methods.mint(owner).send({from: adminEth}); + await helper.nft.mintToken(adminSub, {collectionId, owner: {Ethereum: owner}}); - const adminList = await helper.collection.getAdmins(collectionId); - expect(adminList).to.be.like([{Substrate: newAdmin.address}]); + expect(await helper.collection.getLastTokenId(collectionId)).to.eq(2); }); - itEth('Check adminlist', async ({helper, privateKey}) => { + itEth('cannot add invalid cross account admin', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); - - const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); - const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true); + const [admin] = await helper.arrange.createAccounts([100n, 100n], donor); - const admin1 = helper.eth.createAccount(); - const admin2 = await privateKey('admin'); - const admin2Cross = helper.ethCrossAccount.fromKeyringPair(admin2); - - // Soft-deprecated - await collectionEvm.methods.addCollectionAdmin(admin1).send(); - await collectionEvm.methods.addCollectionAdminCross(admin2Cross).send(); + const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - const adminListRpc = await helper.collection.getAdmins(collectionId); - let adminListEth = await collectionEvm.methods.collectionAdmins().call(); - adminListEth = adminListEth.map((element: IEthCrossAccountId) => { - return helper.address.convertCrossAccountFromEthCrossAcoount(element); - }); - expect(adminListRpc).to.be.like(adminListEth); + const adminCross = { + eth: helper.address.substrateToEth(admin.address), + sub: admin.addressRaw, + }; + await expect(collectionEvm.methods.addCollectionAdminCross(adminCross).send()).to.be.rejected; }); - // Soft-deprecated - itEth('Verify owner or admin', async ({helper}) => { + itEth('can verify owner with methods.isOwnerOrAdmin[Cross]', async ({helper, privateKey}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); - const newAdmin = helper.eth.createAccount(); + const adminDeprecated = helper.eth.createAccount(); + const admin1Cross = helper.ethCrossAccount.fromKeyringPair(await privateKey('admin')); + const admin2Cross = helper.ethCrossAccount.fromAddress(helper.address.substrateToEth((await privateKey('admin3')).address)); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner, true); - expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.false; - await collectionEvm.methods.addCollectionAdmin(newAdmin).send(); - expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.true; - }); + // Soft-deprecated: + expect(await collectionEvm.methods.isOwnerOrAdmin(adminDeprecated).call()).to.be.false; + expect(await collectionEvm.methods.isOwnerOrAdminCross(admin1Cross).call()).to.be.false; + expect(await collectionEvm.methods.isOwnerOrAdminCross(admin2Cross).call()).to.be.false; - itEth('Verify owner or admin cross', async ({helper, privateKey}) => { - const owner = await helper.eth.createAccountWithBalance(donor); - const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); + await collectionEvm.methods.addCollectionAdmin(adminDeprecated).send(); + await collectionEvm.methods.addCollectionAdminCross(admin1Cross).send(); + await collectionEvm.methods.addCollectionAdminCross(admin2Cross).send(); - const newAdmin = await privateKey('admin'); - const newAdminCross = helper.ethCrossAccount.fromKeyringPair(newAdmin); - const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); - - expect(await collectionEvm.methods.isOwnerOrAdminCross(newAdminCross).call()).to.be.false; - await collectionEvm.methods.addCollectionAdminCross(newAdminCross).send(); - expect(await collectionEvm.methods.isOwnerOrAdminCross(newAdminCross).call()).to.be.true; + // Soft-deprecated: isOwnerOrAdmin returns true + expect(await collectionEvm.methods.isOwnerOrAdmin(adminDeprecated).call()).to.be.true; + // Expect isOwnerOrAdminCross return true + expect(await collectionEvm.methods.isOwnerOrAdminCross(admin1Cross).call()).to.be.true; + expect(await collectionEvm.methods.isOwnerOrAdminCross(admin2Cross).call()).to.be.true; }); // Soft-deprecated @@ -154,12 +172,11 @@ const owner = await helper.eth.createAccountWithBalance(donor); const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); - const [admin] = await helper.arrange.createAccounts([10n], donor); + const [admin, notAdmin] = await helper.arrange.createAccounts([10n, 10n], donor); const adminCross = helper.ethCrossAccount.fromKeyringPair(admin); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); await collectionEvm.methods.addCollectionAdminCross(adminCross).send(); - const [notAdmin] = await helper.arrange.createAccounts([10n], donor); const notAdminCross = helper.ethCrossAccount.fromKeyringPair(notAdmin); await expect(collectionEvm.methods.addCollectionAdminCross(notAdminCross).call({from: adminCross.eth})) .to.be.rejectedWith('NoPermission'); --- a/tests/src/eth/fungible.test.ts +++ b/tests/src/eth/fungible.test.ts @@ -314,7 +314,7 @@ } }); - itEth('Can perform transferFromCross()', async ({helper, privateKey}) => { + itEth('Can perform transferFromCross()', async ({helper}) => { const sender = await helper.eth.createAccountWithBalance(donor, 100n); const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0); @@ -508,7 +508,7 @@ expect(event.returnValues.value).to.be.equal('51'); }); - itEth('Events emitted for transferFromCross()', async ({helper, privateKey}) => { + itEth('Events emitted for transferFromCross()', async ({helper}) => { const sender = await helper.eth.createAccountWithBalance(donor, 100n); const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0); --- a/tests/src/eth/nonFungible.test.ts +++ b/tests/src/eth/nonFungible.test.ts @@ -17,7 +17,6 @@ import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util'; import {IKeyringPair} from '@polkadot/types/types'; import {Contract} from 'web3-eth-contract'; -import exp from 'constants'; describe('NFT: Information getting', () => { @@ -280,30 +279,51 @@ }); itEth('Can perform approveCross()', async ({helper}) => { + // arrange: create accounts + const owner = await helper.eth.createAccountWithBalance(donor, 100n); + const ownerCross = helper.ethCrossAccount.fromAddress(owner); + const receiverSub = charlie; + const recieverCrossSub = helper.ethCrossAccount.fromKeyringPair(receiverSub); + const receiverEth = await helper.eth.createAccountWithBalance(donor, 100n); + const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth); + + // arrange: create collection and tokens: const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'}); + const token1 = await collection.mintToken(minter, {Ethereum: owner}); + const token2 = await collection.mintToken(minter, {Ethereum: owner}); - const owner = await helper.eth.createAccountWithBalance(donor, 100n); - const receiver = charlie; + const collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(collection.collectionId), 'nft'); - const token = await collection.mintToken(minter, {Ethereum: owner}); - - const address = helper.ethAddress.fromCollectionId(collection.collectionId); - const contract = helper.ethNativeContract.collection(address, 'nft'); + // Can approveCross substrate and ethereum address: + const resultSub = await collectionEvm.methods.approveCross(recieverCrossSub, token1.tokenId).send({from: owner}); + const resultEth = await collectionEvm.methods.approveCross(receiverCrossEth, token2.tokenId).send({from: owner}); + const eventSub = resultSub.events.Approval; + const eventEth = resultEth.events.Approval; + expect(eventSub).to.be.like({ + address: helper.ethAddress.fromCollectionId(collection.collectionId), + event: 'Approval', + returnValues: { + owner, + approved: helper.address.substrateToEth(receiverSub.address), + tokenId: token1.tokenId.toString(), + }, + }); + expect(eventEth).to.be.like({ + address: helper.ethAddress.fromCollectionId(collection.collectionId), + event: 'Approval', + returnValues: { + owner, + approved: receiverEth, + tokenId: token2.tokenId.toString(), + }, + }); - { - const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver); - const result = await contract.methods.approveCross(recieverCross, token.tokenId).send({from: owner}); - const event = result.events.Approval; - expect(event).to.be.like({ - address: helper.ethAddress.fromCollectionId(collection.collectionId), - event: 'Approval', - returnValues: { - owner, - approved: helper.address.substrateToEth(receiver.address), - tokenId: token.tokenId.toString(), - }, - }); - } + // Substrate address can transferFrom approved tokens: + await helper.nft.transferTokenFrom(receiverSub, collection.collectionId, token1.tokenId, {Ethereum: owner}, {Substrate: receiverSub.address}); + expect(await helper.nft.getTokenOwner(collection.collectionId, token1.tokenId)).to.deep.eq({Substrate: receiverSub.address}); + // Ethereum address can transferFromCross approved tokens: + await collectionEvm.methods.transferFromCross(ownerCross, receiverCrossEth, token2.tokenId).send({from: receiverEth}); + expect(await helper.nft.getTokenOwner(collection.collectionId, token2.tokenId)).to.deep.eq({Ethereum: receiverEth.toLowerCase()}); }); itEth('Can perform transferFrom()', async ({helper}) => { @@ -340,13 +360,11 @@ } }); - itEth('Can perform transferFromCross()', async ({helper, privateKey}) => { - const minter = await privateKey('//Alice'); + itEth('Can perform transferFromCross()', async ({helper}) => { const collection = await helper.nft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'}); - const owner = await privateKey('//Bob'); + const [owner, receiver] = await helper.arrange.createAccounts([100n, 100n], donor); const spender = await helper.eth.createAccountWithBalance(donor); - const receiver = await privateKey('//Charlie'); const token = await collection.mintToken(minter, {Substrate: owner.address}); @@ -501,7 +519,7 @@ expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal()))); }); - itEth('Can perform transferFromCross()', async ({helper, privateKey}) => { + itEth('Can perform transferFromCross()', async ({helper}) => { const collectionMinter = alice; const owner = bob; const receiver = charlie; --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -2429,7 +2429,7 @@ * @param ethCrossAccount etherium cross account * @returns substrate cross account id */ - convertCrossAccountFromEthCrossAcoount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId { + convertCrossAccountFromEthCrossAccount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId { if (ethCrossAccount.sub === '0') { return {Ethereum: ethCrossAccount.eth.toLocaleLowerCase()}; }