1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';18import {Contract} from 'web3-eth-contract';19import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';20import {ITokenPropertyPermission, TCollectionMode} from '../util/playgrounds/types';21import {Pallets} from '../util';22import {UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection} from '../util/playgrounds/unique';2324describe('EVM token properties', () => {25 let donor: IKeyringPair;26 let alice: IKeyringPair;2728 before(async function() {29 await usingEthPlaygrounds(async (helper, privateKey) => {30 donor = await privateKey({filename: __filename});31 [alice] = await helper.arrange.createAccounts([100n], donor);32 });33 });3435 itEth('Can be reconfigured', async({helper}) => {36 const caller = await helper.eth.createAccountWithBalance(donor);37 for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {38 const collection = await helper.nft.mintCollection(alice);39 await collection.addAdmin(alice, {Ethereum: caller});40 41 const address = helper.ethAddress.fromCollectionId(collection.collectionId);42 const contract = helper.ethNativeContract.collection(address, 'nft', caller);43 44 await contract.methods.setTokenPropertyPermission('testKey', mutable, collectionAdmin, tokenOwner).send({from: caller});45 46 expect(await collection.getPropertyPermissions()).to.be.deep.equal([{47 key: 'testKey',48 permission: {mutable, collectionAdmin, tokenOwner},49 }]);50 }51 });5253 [54 {55 method: 'setProperties',56 methodParams: [[{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}]],57 expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}],58 },59 {60 method: 'setProperty' , 61 methodParams: ['testKey1', Buffer.from('testValue1')],62 expectedProps: [{key: 'testKey1', value: 'testValue1'}],63 },64 ].map(testCase => 65 itEth(`[${testCase.method}] Can be set`, async({helper}) => {66 const caller = await helper.eth.createAccountWithBalance(donor);67 const collection = await helper.nft.mintCollection(alice, {68 tokenPropertyPermissions: [{69 key: 'testKey1',70 permission: {71 collectionAdmin: true,72 },73 }, {74 key: 'testKey2',75 permission: {76 collectionAdmin: true,77 },78 }],79 });8081 await collection.addAdmin(alice, {Ethereum: caller});82 const token = await collection.mintToken(alice);83 84 const collectionEvm = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller, testCase.method === 'setProperty');85 86 await collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller});87 88 const properties = await token.getProperties();89 expect(properties).to.deep.equal(testCase.expectedProps);90 }));91 92 [93 {mode: 'nft' as const, requiredPallets: [Pallets.ReFungible]},94 {mode: 'rft' as const, requiredPallets: []},95 ].map(testCase => 96 itEth.ifWithPallets(`Can be multiple set/read for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {97 const caller = await helper.eth.createAccountWithBalance(donor);98 99 const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });100 const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,101 collectionAdmin: true,102 mutable: true}}; });103 104 const collection = await helper[testCase.mode].mintCollection(alice, {105 tokenPrefix: 'ethp',106 tokenPropertyPermissions: permissions,107 }) as UniqueNFTCollection | UniqueRFTCollection;108 109 const token = await collection.mintToken(alice);110 111 const valuesBefore = await token.getProperties(properties.map(p => p.key));112 expect(valuesBefore).to.be.deep.equal([]);113 114 115 await collection.addAdmin(alice, {Ethereum: caller});116 117 const address = helper.ethAddress.fromCollectionId(collection.collectionId);118 const contract = helper.ethNativeContract.collection(address, testCase.mode, caller);119 120 expect(await contract.methods.properties(token.tokenId, []).call()).to.be.deep.equal([]);121 122 await contract.methods.setProperties(token.tokenId, properties).send({from: caller});123 124 const values = await token.getProperties(properties.map(p => p.key));125 expect(values).to.be.deep.equal(properties.map(p => { return {key: p.key, value: p.value.toString()}; }));126 127 expect(await contract.methods.properties(token.tokenId, []).call()).to.be.like(properties128 .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));129 130 expect(await contract.methods.properties(token.tokenId, [properties[0].key]).call())131 .to.be.like([helper.ethProperty.property(properties[0].key, properties[0].value.toString())]);132 }));133 134 [135 {mode: 'nft' as const, requiredPallets: [Pallets.ReFungible]},136 {mode: 'rft' as const, requiredPallets: []},137 ].map(testCase => 138 itEth.ifWithPallets(`Can be deleted fro ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {139 const caller = await helper.eth.createAccountWithBalance(donor);140 const collection = await helper[testCase.mode].mintCollection(alice, {141 tokenPropertyPermissions: [{142 key: 'testKey',143 permission: {144 mutable: true,145 collectionAdmin: true,146 },147 },148 {149 key: 'testKey_1',150 permission: {151 mutable: true,152 collectionAdmin: true,153 },154 }],155 });156 157 const token = await collection.mintToken(alice);158 await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}, {key: 'testKey_1', value: 'testValue_1'}]);159 expect(await token.getProperties()).to.has.length(2);160161 await collection.addAdmin(alice, {Ethereum: caller});162163 const address = helper.ethAddress.fromCollectionId(collection.collectionId);164 const contract = helper.ethNativeContract.collection(address, testCase.mode, caller);165166 await contract.methods.deleteProperties(token.tokenId, ['testKey', 'testKey_1']).send({from: caller});167168 const result = await token.getProperties(['testKey', 'testKey_1']);169 expect(result.length).to.equal(0);170 }));171172 itEth('Can be read', async({helper}) => {173 const caller = helper.eth.createAccount();174 const collection = await helper.nft.mintCollection(alice, {175 tokenPropertyPermissions: [{176 key: 'testKey',177 permission: {178 collectionAdmin: true,179 },180 }],181 });182 183 const token = await collection.mintToken(alice);184 await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);185186 const address = helper.ethAddress.fromCollectionId(collection.collectionId);187 const contract = helper.ethNativeContract.collection(address, 'nft', caller);188189 const value = await contract.methods.property(token.tokenId, 'testKey').call();190 expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));191 });192});193194describe('EVM token properties negative', () => {195 let donor: IKeyringPair;196 let alice: IKeyringPair;197 let caller: string;198 let aliceCollection: UniqueNFTCollection;199 let token: UniqueNFToken;200 const tokenProps = [{key: 'testKey_1', value: 'testValue_1'}, {key: 'testKey_2', value: 'testValue_2'}];201 let collectionEvm: Contract;202203 before(async function() {204 await usingEthPlaygrounds(async (helper, privateKey) => {205 donor = await privateKey({filename: __filename});206 [alice] = await helper.arrange.createAccounts([100n], donor);207 });208 });209210 beforeEach(async () => {211 212 213 await usingEthPlaygrounds(async (helper) => {214 aliceCollection = await helper.nft.mintCollection(alice, {215 tokenPropertyPermissions: [{216 key: 'testKey_1',217 permission: {218 mutable: true,219 collectionAdmin: true,220 },221 },222 {223 key: 'testKey_2',224 permission: {225 mutable: true,226 collectionAdmin: true,227 },228 }],229 }); 230 token = await aliceCollection.mintToken(alice);231 await token.setProperties(alice, tokenProps);232 collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);233 });234 });235236 [237 {method: 'setProperty', methodParams: [tokenProps[1].key, Buffer.from('newValue')]},238 {method: 'setProperties', methodParams: [[{key: tokenProps[1].key, value: Buffer.from('newValue')}]]},239 ].map(testCase =>240 itEth(`[${testCase.method}] Cannot set properties of non-owned collection`, async ({helper}) => {241 caller = await helper.eth.createAccountWithBalance(donor);242 collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);243 244 245 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');246 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;247248 249 const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));250 const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();251 expect(actualProps).to.deep.eq(expectedProps);252 }));253254 [255 {method: 'setProperty', methodParams: ['testKey_3', Buffer.from('testValue3')]},256 {method: 'setProperties', methodParams: [[{key: 'testKey_3', value: Buffer.from('testValue3')}]]},257 ].map(testCase =>258 itEth(`[${testCase.method}] Cannot set non-existing properties`, async ({helper}) => {259 caller = await helper.eth.createAccountWithBalance(donor);260 collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);261 await helper.collection.addAdmin(alice, aliceCollection.collectionId, {Ethereum: caller});262263 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');264 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;265266 267 const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));268 const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();269 expect(actualProps).to.deep.eq(expectedProps);270 }));271272 [273 {method: 'deleteProperty', methodParams: ['testKey_2']}, 274 {method: 'deleteProperties', methodParams: [['testKey_2']]},275 ].map(testCase => 276 itEth(`[${testCase.method}] Cannot delete properties of non-owned collection`, async ({helper}) => {277 caller = await helper.eth.createAccountWithBalance(donor);278 collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, testCase.method == 'deleteProperty');279 280 281 282 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');283 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;284285 286 const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));287 const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();288 expect(actualProps).to.deep.eq(expectedProps);289 }));290291 itEth('Cannot delete non-existing properties', async () => {292293 });294});295296297type ElementOf<A> = A extends readonly (infer T)[] ? T : never;298function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {299 if(args.length === 0) {300 yield internalRest as any;301 return;302 }303 for(const value of args[0]) {304 yield* cartesian([...internalRest, value], ...args.slice(1)) as any;305 }306}