--- a/tests/src/eth/util/helpers.ts +++ b/tests/src/eth/util/helpers.ts @@ -98,7 +98,7 @@ ...encodeIntBE(collection), ...encodeIntBE(token), ]); - return Web3.utils.toChecksumAddress('0x' + buf.toString('hex')); + return Web3.utils.toChecksumAddress('0x' + buf.toString('hex')).toLowerCase(); } export function tokenIdToCross(collection: number, token: number): CrossAccountId { return { --- a/tests/src/nesting/nest.test.ts +++ b/tests/src/nesting/nest.test.ts @@ -2,6 +2,7 @@ import {tokenIdToAddress} from '../eth/util/helpers'; import usingApi, {executeTransaction} from '../substrate/substrate-api'; import { + addCollectionAdminExpectSuccess, addToAllowListExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, @@ -21,9 +22,9 @@ let alice: IKeyringPair; let bob: IKeyringPair; -describe('Integration Test: Nesting', () => { +describe('Integration Test: Composite nesting tests', () => { before(async () => { - await usingApi(async (api, privateKeyWrapper) => { + await usingApi(async (_, privateKeyWrapper) => { alice = privateKeyWrapper('//Alice'); bob = privateKeyWrapper('//Bob'); }); @@ -145,7 +146,58 @@ ], 'Children contents check at deeper nesting'); }); }); +}); + +describe('Integration Test: Various token type nesting', async () => { + before(async () => { + await usingApi(async (_, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + bob = privateKeyWrapper('//Bob'); + }); + }); + + it('Admin (NFT): allows an Admin to nest a token', async () => { + await usingApi(async api => { + const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {admin: true}}); + await addCollectionAdminExpectSuccess(alice, collection, bob.address); + const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + + // Create a nested token + const nestedToken = await createItemExpectSuccess(bob, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, targetToken)}); + expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: alice.address}); + expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + + // Create a token to be nested and nest + const newToken = await createItemExpectSuccess(bob, collection, 'NFT'); + await transferExpectSuccess(collection, newToken, bob, {Ethereum: tokenIdToAddress(collection, targetToken)}); + expect(await getTopmostTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: alice.address}); + expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collection, targetToken).toLowerCase()}); + }); + }); + it('Admin (NFT): allows an Admin to nest a token (Restricted nesting)', async () => { + await usingApi(async api => { + const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + await addCollectionAdminExpectSuccess(alice, collectionA, bob.address); + const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + await addCollectionAdminExpectSuccess(alice, collectionB, bob.address); + await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {admin: true, restricted:[collectionA, collectionB]}}); + const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT'); + + // Create a nested token + const nestedToken = await createItemExpectSuccess(bob, collectionB, 'NFT', {Ethereum: tokenIdToAddress(collectionA, targetToken)}); + expect(await getTopmostTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Substrate: alice.address}); + expect(await getTokenOwner(api, collectionB, nestedToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()}); + + // Create a token to be nested and nest + const newToken = await createItemExpectSuccess(bob, collectionB, 'NFT'); + await transferExpectSuccess(collectionB, newToken, bob, {Ethereum: tokenIdToAddress(collectionA, targetToken)}); + expect(await getTopmostTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: alice.address}); + expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Ethereum: tokenIdToAddress(collectionA, targetToken).toLowerCase()}); + }); + }); + // ---------- Non-Fungible ---------- it('NFT: allows an Owner to nest/unnest their token', async () => { @@ -283,7 +335,7 @@ describe('Negative Test: Nesting', async() => { before(async () => { - await usingApi(async (api, privateKeyWrapper) => { + await usingApi(async (_, privateKeyWrapper) => { alice = privateKeyWrapper('//Alice'); bob = privateKeyWrapper('//Bob'); }); @@ -321,6 +373,106 @@ }); }); + // ---------- Admin ------------ + + it('Admin (NFT): disallows an Admin to operate nesting when only TokenOwner is allowed', async () => { + await usingApi(async api => { + const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {tokenOwner: true}}); + await addCollectionAdminExpectSuccess(alice, collection, bob.address); + const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + + // Try to create a nested token as collection admin when it's disallowed + await expect(executeTransaction(api, bob, api.tx.unique.createItem( + collection, + {Ethereum: tokenIdToAddress(collection, targetToken)}, + {nft: {const_data: [], variable_data: []}} as any, + )), 'while creating nested token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + + // Try to create and nest a token in the wrong collection + const newToken = await createItemExpectSuccess(bob, collection, 'NFT'); + await expect(executeTransaction(api, bob, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.UserIsNotAllowedToNest/); + expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address}); + }); + }); + + it('Admin (NFT): disallows a Token Owner to operate nesting when only Admin is allowed', async () => { + await usingApi(async api => { + const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {admin: true}}); + await addToAllowListExpectSuccess(alice, collection, bob.address); + await enableAllowListExpectSuccess(alice, collection); + await enablePublicMintingExpectSuccess(alice, collection); + const targetToken = await createItemExpectSuccess(alice, collection, 'NFT'); + + // Try to create a nested token as collection admin when it's disallowed + await expect(executeTransaction(api, bob, api.tx.unique.createItem( + collection, + {Ethereum: tokenIdToAddress(collection, targetToken)}, + {nft: {const_data: [], variable_data: []}} as any, + )), 'while creating nested token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); // todo is this right? + + // Try to create and nest a token in the wrong collection + const newToken = await createItemExpectSuccess(bob, collection, 'NFT'); + await expect(executeTransaction(api, bob, api.tx.unique.transfer({Ethereum: tokenIdToAddress(collection, targetToken)}, collection, newToken, 1)), 'while nesting new token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); + expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address}); + }); + }); + + it('Admin (NFT): disallows an Admin to nest and unnest someone else\'s token', async () => { + await usingApi(async api => { + const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: {admin: true}}); + + await addToAllowListExpectSuccess(alice, collection, bob.address); + await enableAllowListExpectSuccess(alice, collection); + await enablePublicMintingExpectSuccess(alice, collection); + + // Create a token to attempt to be nested into + const targetToken = await createItemExpectSuccess(bob, collection, 'NFT'); + const targetAddress = {Ethereum: tokenIdToAddress(collection, targetToken)}; + + // Try to nest somebody else's token + const newToken = await createItemExpectSuccess(bob, collection, 'NFT'); + await expect(executeTransaction( + api, + alice, + api.tx.unique.transfer(targetAddress, collection, newToken, 1), + ), 'while nesting another\'s token token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); + expect(await getTokenOwner(api, collection, newToken)).to.be.deep.equal({Substrate: bob.address}); + + // Nest a token as admin and try to unnest it, now belonging to someone else + const nestedToken = await createItemExpectSuccess(alice, collection, 'NFT', targetAddress); + await expect(executeTransaction( + api, + alice, + api.tx.unique.transferFrom(targetAddress, normalizeAccountId(alice), collection, nestedToken, 1), + ), 'while unnesting another\'s token').to.be.rejectedWith(/common\.AddressNotInAllowlist/); + expect(await getTokenOwner(api, collection, nestedToken)).to.be.deep.equal(targetAddress); + expect(await getTopmostTokenOwner(api, collection, nestedToken)).to.be.deep.equal({Substrate: bob.address}); + }); + }); + + it('Admin (NFT): disallows an Admin to nest a token from an unlisted collection (Restricted nesting)', async () => { + await usingApi(async api => { + const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + const collectionB = await createCollectionExpectSuccess({mode: {type: 'NFT'}}); + await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: {admin: true, restricted:[collectionA]}}); + + // Create a token to attempt to be nested into + const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT'); + + // Try to create and nest a token in the wrong collection + const newToken = await createItemExpectSuccess(alice, collectionB, 'NFT'); + await expect(executeTransaction( + api, + alice, + api.tx.unique.transfer({Ethereum: tokenIdToAddress(collectionA, targetToken)}, collectionB, newToken, 1), + ), 'while nesting a foreign token').to.be.rejectedWith(/common\.SourceCollectionIsNotAllowedToNest/); + expect(await getTokenOwner(api, collectionB, newToken)).to.be.deep.equal({Substrate: alice.address}); + }); + }); + // ---------- Non-Fungible ---------- it('NFT: disallows to nest token if nesting is disabled', async () => {