--- a/tests/src/eth/tokenProperties.test.ts +++ b/tests/src/eth/tokenProperties.test.ts @@ -14,11 +14,12 @@ // 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 {Contract} from 'web3-eth-contract'; import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util'; -import {IKeyringPair} from '@polkadot/types/types'; import {ITokenPropertyPermission, TCollectionMode} from '../util/playgrounds/types'; import {Pallets} from '../util'; -import {UniqueNFTCollection, UniqueRFTCollection} from '../util/playgrounds/unique'; +import {UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection} from '../util/playgrounds/unique'; describe('EVM token properties', () => { let donor: IKeyringPair; @@ -88,84 +89,85 @@ expect(properties).to.deep.equal(testCase.expectedProps); })); - async function checkProps(helper: EthUniqueHelper, mode: TCollectionMode) { - const caller = await helper.eth.createAccountWithBalance(donor); - - const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; }); - const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true, - collectionAdmin: true, - mutable: true}}; }); - - const collection = await helper[mode].mintCollection(alice, { - tokenPrefix: 'ethp', - tokenPropertyPermissions: permissions, - }) as UniqueNFTCollection | UniqueRFTCollection; - - const token = await collection.mintToken(alice); - - const valuesBefore = await token.getProperties(properties.map(p => p.key)); - expect(valuesBefore).to.be.deep.equal([]); - - - await collection.addAdmin(alice, {Ethereum: caller}); - - const address = helper.ethAddress.fromCollectionId(collection.collectionId); - const contract = helper.ethNativeContract.collection(address, mode, caller); - - expect(await contract.methods.properties(token.tokenId, []).call()).to.be.deep.equal([]); - - await contract.methods.setProperties(token.tokenId, properties).send({from: caller}); - - const values = await token.getProperties(properties.map(p => p.key)); - expect(values).to.be.deep.equal(properties.map(p => { return {key: p.key, value: p.value.toString()}; })); - - expect(await contract.methods.properties(token.tokenId, []).call()).to.be.like(properties - .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); })); - - expect(await contract.methods.properties(token.tokenId, [properties[0].key]).call()) - .to.be.like([helper.ethProperty.property(properties[0].key, properties[0].value.toString())]); - } + [ + {mode: 'nft' as const, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, requiredPallets: []}, + ].map(testCase => + itEth.ifWithPallets(`Can be multiple set/read for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + + const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; }); + const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true, + collectionAdmin: true, + mutable: true}}; }); + + const collection = await helper[testCase.mode].mintCollection(alice, { + tokenPrefix: 'ethp', + tokenPropertyPermissions: permissions, + }) as UniqueNFTCollection | UniqueRFTCollection; + + const token = await collection.mintToken(alice); + + const valuesBefore = await token.getProperties(properties.map(p => p.key)); + expect(valuesBefore).to.be.deep.equal([]); + + + await collection.addAdmin(alice, {Ethereum: caller}); + + const address = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(address, testCase.mode, caller); + + expect(await contract.methods.properties(token.tokenId, []).call()).to.be.deep.equal([]); - itEth('Can be multiple set/read for NFT ', async({helper}) => { - await checkProps(helper, 'nft'); - }); + await contract.methods.setProperties(token.tokenId, properties).send({from: caller}); - itEth.ifWithPallets('Can be multiple set/read for RFT ', [Pallets.ReFungible], async({helper}) => { - await checkProps(helper, 'rft'); - }); + const values = await token.getProperties(properties.map(p => p.key)); + expect(values).to.be.deep.equal(properties.map(p => { return {key: p.key, value: p.value.toString()}; })); + + expect(await contract.methods.properties(token.tokenId, []).call()).to.be.like(properties + .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); })); + + expect(await contract.methods.properties(token.tokenId, [properties[0].key]).call()) + .to.be.like([helper.ethProperty.property(properties[0].key, properties[0].value.toString())]); + })); - itEth('Can be deleted', async({helper}) => { - const caller = await helper.eth.createAccountWithBalance(donor); - const collection = await helper.nft.mintCollection(alice, { - tokenPropertyPermissions: [{ - key: 'testKey', - permission: { - mutable: true, - collectionAdmin: true, + [ + {mode: 'nft' as const, requiredPallets: [Pallets.ReFungible]}, + {mode: 'rft' as const, requiredPallets: []}, + ].map(testCase => + itEth.ifWithPallets(`Can be deleted fro ${testCase.mode}`, testCase.requiredPallets, async({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const collection = await helper[testCase.mode].mintCollection(alice, { + tokenPropertyPermissions: [{ + key: 'testKey', + permission: { + mutable: true, + collectionAdmin: true, + }, }, - }, - { - key: 'testKey_1', - permission: { - mutable: true, - collectionAdmin: true, - }, - }], - }); + { + key: 'testKey_1', + permission: { + mutable: true, + collectionAdmin: true, + }, + }], + }); - const token = await collection.mintToken(alice); - await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}, {key: 'testKey_1', value: 'testValue_1'}]); + const token = await collection.mintToken(alice); + await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}, {key: 'testKey_1', value: 'testValue_1'}]); + expect(await token.getProperties()).to.has.length(2); - await collection.addAdmin(alice, {Ethereum: caller}); + await collection.addAdmin(alice, {Ethereum: caller}); - const address = helper.ethAddress.fromCollectionId(collection.collectionId); - const contract = helper.ethNativeContract.collection(address, 'nft', caller); + const address = helper.ethAddress.fromCollectionId(collection.collectionId); + const contract = helper.ethNativeContract.collection(address, testCase.mode, caller); - await contract.methods.deleteProperties(token.tokenId, ['testKey', 'testKey_1']).send({from: caller}); + await contract.methods.deleteProperties(token.tokenId, ['testKey', 'testKey_1']).send({from: caller}); - const result = await token.getProperties(['testKey']); - expect(result.length).to.equal(0); - }); + const result = await token.getProperties(['testKey', 'testKey_1']); + expect(result.length).to.equal(0); + })); itEth('Can be read', async({helper}) => { const caller = helper.eth.createAccount(); @@ -189,6 +191,94 @@ }); }); +describe('EVM token properties negative', () => { + let donor: IKeyringPair; + let alice: IKeyringPair; + let caller: string; + let aliceCollection: UniqueNFTCollection; + let token: UniqueNFToken; + const tokenProps = [{key: 'testKey_1', value: 'testValue_1'}, {key: 'testKey_2', value: 'testValue_2'}]; + let collectionEvm: Contract; + + before(async function() { + await usingEthPlaygrounds(async (helper, privateKey) => { + donor = await privateKey({filename: __filename}); + [alice] = await helper.arrange.createAccounts([100n], donor); + }); + }); + + beforeEach(async () => { + // 1. create collection with props: testKey_1, testKey_2 + // 2. create token and set props testKey_1, testKey_2 + await usingEthPlaygrounds(async (helper) => { + aliceCollection = await helper.nft.mintCollection(alice, { + tokenPropertyPermissions: [{ + key: 'testKey_1', + permission: { + mutable: true, + collectionAdmin: true, + }, + }, + { + key: 'testKey_2', + permission: { + mutable: true, + collectionAdmin: true, + }, + }], + }); + token = await aliceCollection.mintToken(alice); + await token.setProperties(alice, tokenProps); + collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true); + }); + }); + + [ + {method: 'setProperty', methodParams: [tokenProps[1].key, Buffer.from('newValue')]}, + {method: 'setProperties', methodParams: [[{key: tokenProps[1].key, value: Buffer.from('newValue')}]]}, + ].map(testCase => + itEth(`[${testCase.method}] Cannot set properties of non-owned collection`, async ({helper}) => { + caller = await helper.eth.createAccountWithBalance(donor); + collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true); + // Caller an owner and not an admin, so he cannot set properties: + // FIXME: Can setProperties as non owner and non admin + await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission'); + await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected; + + // Props have not changed: + const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString())); + const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call(); + expect(actualProps).to.deep.eq(expectedProps); + })); + + [ + {method: 'setProperty', methodParams: ['testKey_3', Buffer.from('testValue3')]}, + {method: 'setProperties', methodParams: [[{key: 'testKey_3', value: Buffer.from('testValue3')}]]}, + ].map(testCase => + itEth(`[${testCase.method}] Cannot set non-existing properties`, async ({helper}) => { + caller = await helper.eth.createAccountWithBalance(donor); + collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true); + await helper.collection.addAdmin(alice, aliceCollection.collectionId, {Ethereum: caller}); + + await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission'); + await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected; + + // Props have not changed: + const actualProps = await collectionEvm.methods.properties(token.tokenId, ['testKey2']).call(); + expect(actualProps).to.deep.eq(tokenProps + .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); + })); + })); + + itEth('Cannot delete properties of non-owned collection', async () => { + + }); + + itEth('Cannot delete non-existing properties', async () => { + + }); +}); + 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}]> {