1234567891011121314151617import {IKeyringPair} from '@polkadot/types/types';18import {Contract} from 'web3-eth-contract';19import {itEth, usingEthPlaygrounds, expect} from './util';20import {ITokenPropertyPermission} 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 owner = await helper.eth.createAccountWithBalance(donor);37 const caller = await helper.eth.createAccountWithBalance(donor);38 for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {39 const collection = await helper.nft.mintCollection(alice);40 await collection.addAdmin(alice, {Ethereum: caller});41 42 const address = helper.ethAddress.fromCollectionId(collection.collectionId);43 const contract = helper.ethNativeContract.collection(address, 'nft', caller);44 45 await contract.methods.setTokenPropertyPermissions([['testKey', [[0, mutable], [1, tokenOwner], [2, collectionAdmin]]]]).send({from: caller});46 47 expect(await collection.getPropertyPermissions()).to.be.deep.equal([{48 key: 'testKey',49 permission: {mutable, collectionAdmin, tokenOwner},50 }]);5152 expect(await contract.methods.tokenPropertyPermissions().call({from: caller})).to.be.like([53 ['testKey', [['0', mutable], ['1', tokenOwner], ['2', collectionAdmin]]]54 ]);55 }56 });5758 [59 {60 method: 'setProperties',61 methodParams: [[{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}]],62 expectedProps: [{key: 'testKey1', value: 'testValue1'}, {key: 'testKey2', value: 'testValue2'}],63 },64 {65 method: 'setProperty' , 66 methodParams: ['testKey1', Buffer.from('testValue1')],67 expectedProps: [{key: 'testKey1', value: 'testValue1'}],68 },69 ].map(testCase => 70 itEth(`[${testCase.method}] Can be set`, async({helper}) => {71 const caller = await helper.eth.createAccountWithBalance(donor);72 const collection = await helper.nft.mintCollection(alice, {73 tokenPropertyPermissions: [{74 key: 'testKey1',75 permission: {76 collectionAdmin: true,77 },78 }, {79 key: 'testKey2',80 permission: {81 collectionAdmin: true,82 },83 }],84 });8586 await collection.addAdmin(alice, {Ethereum: caller});87 const token = await collection.mintToken(alice);88 89 const collectionEvm = helper.ethNativeContract.collectionById(collection.collectionId, 'nft', caller, testCase.method === 'setProperty');90 91 await collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller});92 93 const properties = await token.getProperties();94 expect(properties).to.deep.equal(testCase.expectedProps);95 }));96 97 [98 {mode: 'nft' as const, requiredPallets: []},99 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},100 ].map(testCase => 101 itEth.ifWithPallets(`Can be multiple set/read for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {102 const caller = await helper.eth.createAccountWithBalance(donor);103 104 const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });105 const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,106 collectionAdmin: true,107 mutable: true}}; });108 109 const collection = await helper[testCase.mode].mintCollection(alice, {110 tokenPrefix: 'ethp',111 tokenPropertyPermissions: permissions,112 }) as UniqueNFTCollection | UniqueRFTCollection;113 114 const token = await collection.mintToken(alice);115 116 const valuesBefore = await token.getProperties(properties.map(p => p.key));117 expect(valuesBefore).to.be.deep.equal([]);118 119 120 await collection.addAdmin(alice, {Ethereum: caller});121 122 const address = helper.ethAddress.fromCollectionId(collection.collectionId);123 const contract = helper.ethNativeContract.collection(address, testCase.mode, caller);124 125 expect(await contract.methods.properties(token.tokenId, []).call()).to.be.deep.equal([]);126 127 await contract.methods.setProperties(token.tokenId, properties).send({from: caller});128 129 const values = await token.getProperties(properties.map(p => p.key));130 expect(values).to.be.deep.equal(properties.map(p => { return {key: p.key, value: p.value.toString()}; }));131 132 expect(await contract.methods.properties(token.tokenId, []).call()).to.be.like(properties133 .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));134 135 expect(await contract.methods.properties(token.tokenId, [properties[0].key]).call())136 .to.be.like([helper.ethProperty.property(properties[0].key, properties[0].value.toString())]);137 }));138 139 [140 {mode: 'nft' as const, requiredPallets: []},141 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},142 ].map(testCase => 143 itEth.ifWithPallets(`Can be deleted for ${testCase.mode}`, testCase.requiredPallets, async({helper}) => {144 const caller = await helper.eth.createAccountWithBalance(donor);145 const collection = await helper[testCase.mode].mintCollection(alice, {146 tokenPropertyPermissions: [{147 key: 'testKey',148 permission: {149 mutable: true,150 collectionAdmin: true,151 },152 },153 {154 key: 'testKey_1',155 permission: {156 mutable: true,157 collectionAdmin: true,158 },159 }],160 });161 162 const token = await collection.mintToken(alice);163 await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}, {key: 'testKey_1', value: 'testValue_1'}]);164 expect(await token.getProperties()).to.has.length(2);165166 await collection.addAdmin(alice, {Ethereum: caller});167168 const address = helper.ethAddress.fromCollectionId(collection.collectionId);169 const contract = helper.ethNativeContract.collection(address, testCase.mode, caller);170171 await contract.methods.deleteProperties(token.tokenId, ['testKey', 'testKey_1']).send({from: caller});172173 const result = await token.getProperties(['testKey', 'testKey_1']);174 expect(result.length).to.equal(0);175 }));176177 itEth('Can be read', async({helper}) => {178 const caller = helper.eth.createAccount();179 const collection = await helper.nft.mintCollection(alice, {180 tokenPropertyPermissions: [{181 key: 'testKey',182 permission: {183 collectionAdmin: true,184 },185 }],186 });187 188 const token = await collection.mintToken(alice);189 await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);190191 const address = helper.ethAddress.fromCollectionId(collection.collectionId);192 const contract = helper.ethNativeContract.collection(address, 'nft', caller);193194 const value = await contract.methods.property(token.tokenId, 'testKey').call();195 expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));196 });197});198199describe('EVM token properties negative', () => {200 let donor: IKeyringPair;201 let alice: IKeyringPair;202 let caller: string;203 let aliceCollection: UniqueNFTCollection;204 let token: UniqueNFToken;205 const tokenProps = [{key: 'testKey_1', value: 'testValue_1'}, {key: 'testKey_2', value: 'testValue_2'}];206 let collectionEvm: Contract;207208 before(async function() {209 await usingEthPlaygrounds(async (helper, privateKey) => {210 donor = await privateKey({filename: __filename});211 [alice] = await helper.arrange.createAccounts([100n], donor);212 });213 });214215 beforeEach(async () => {216 217 218 await usingEthPlaygrounds(async (helper) => {219 aliceCollection = await helper.nft.mintCollection(alice, {220 tokenPropertyPermissions: [{221 key: 'testKey_1',222 permission: {223 mutable: true,224 collectionAdmin: true,225 },226 },227 {228 key: 'testKey_2',229 permission: {230 mutable: true,231 collectionAdmin: true,232 },233 }],234 }); 235 token = await aliceCollection.mintToken(alice);236 await token.setProperties(alice, tokenProps);237 collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);238 });239 });240241 [242 {method: 'setProperty', methodParams: [tokenProps[1].key, Buffer.from('newValue')]},243 {method: 'setProperties', methodParams: [[{key: tokenProps[1].key, value: Buffer.from('newValue')}]]},244 ].map(testCase =>245 itEth(`[${testCase.method}] Cannot set properties of non-owned collection`, async ({helper}) => {246 caller = await helper.eth.createAccountWithBalance(donor);247 collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);248 249 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');250 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;251252 253 const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));254 const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();255 expect(actualProps).to.deep.eq(expectedProps);256 }));257258 [259 {method: 'setProperty', methodParams: ['testKey_3', Buffer.from('testValue3')]},260 {method: 'setProperties', methodParams: [[{key: 'testKey_3', value: Buffer.from('testValue3')}]]},261 ].map(testCase =>262 itEth(`[${testCase.method}] Cannot set non-existing properties`, async ({helper}) => {263 caller = await helper.eth.createAccountWithBalance(donor);264 collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, true);265 await helper.collection.addAdmin(alice, aliceCollection.collectionId, {Ethereum: caller});266267 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');268 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;269270 271 const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));272 const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();273 expect(actualProps).to.deep.eq(expectedProps);274 }));275276 [277 {method: 'deleteProperty', methodParams: ['testKey_2']},278 {method: 'deleteProperties', methodParams: [['testKey_2']]},279 ].map(testCase => 280 itEth(`[${testCase.method}] Cannot delete properties of non-owned collection`, async ({helper}) => {281 caller = await helper.eth.createAccountWithBalance(donor);282 collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, testCase.method == 'deleteProperty');283 284 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');285 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;286287 288 const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));289 const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();290 expect(actualProps).to.deep.eq(expectedProps);291 }));292 293 [294 {method: 'deleteProperty', methodParams: ['testKey_3']},295 {method: 'deleteProperties', methodParams: [['testKey_3']]},296 ].map(testCase => 297 itEth(`[${testCase.method}] Cannot delete non-existing properties`, async ({helper}) => {298 caller = await helper.eth.createAccountWithBalance(donor);299 collectionEvm = helper.ethNativeContract.collection(helper.ethAddress.fromCollectionId(aliceCollection.collectionId), 'nft', caller, testCase.method == 'deleteProperty');300 await helper.collection.addAdmin(alice, aliceCollection.collectionId, {Ethereum: caller});301 302 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).call({from: caller})).to.be.rejectedWith('NoPermission');303 await expect(collectionEvm.methods[testCase.method](token.tokenId, ...testCase.methodParams).send({from: caller})).to.be.rejected;304 305 const expectedProps = tokenProps.map(p => helper.ethProperty.property(p.key, p.value.toString()));306 const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call();307 expect(actualProps).to.deep.eq(expectedProps);308 }));309});310311312type ElementOf<A> = A extends readonly (infer T)[] ? T : never;313function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {314 if(args.length === 0) {315 yield internalRest as any;316 return;317 }318 for(const value of args[0]) {319 yield* cartesian([...internalRest, value], ...args.slice(1)) as any;320 }321}