--- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -1041,7 +1041,11 @@ /// @notice Function to mint a token. /// @param tokenProperties Properties of minted token - #[weight(>::create_multiple_items(token_properties.len() as u32) + >::set_token_properties(token_properties.len() as u32))] + #[weight(if token_properties.len() == 1 { + >::create_multiple_items_ex_multiple_owners(token_properties.iter().next().unwrap().owners.len() as u32) + } else { + >::create_multiple_items_ex_multiple_items(token_properties.len() as u32) + } + >::set_token_properties(token_properties.len() as u32))] fn mint_bulk_cross( &mut self, caller: Caller, @@ -1051,9 +1055,17 @@ let budget = self .recorder .weight_calls_budget(>::find_parent()); + let has_multiple_tokens = token_properties.len() > 1; let mut create_rft_data = Vec::with_capacity(token_properties.len()); for MintTokenData { owners, properties } in token_properties { + let has_multiple_owners = owners.len() > 1; + if has_multiple_tokens & has_multiple_owners { + return Err( + "creation of multiple tokens supported only if they have single owner each" + .into(), + ); + } let users: BoundedBTreeMap<_, _, _> = owners .into_iter() .map(|data| Ok((data.owner.into_sub_cross_account::()?, data.pieces))) --- a/tests/src/eth/reFungible.test.ts +++ b/tests/src/eth/reFungible.test.ts @@ -126,13 +126,11 @@ } }); - itEth('Can perform mintBulkCross()', async ({helper}) => { + itEth('Can perform mintBulkCross() with multiple tokens', async ({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); const callerCross = helper.ethCrossAccount.fromAddress(caller); const receiver = helper.eth.createAccount(); const receiverCross = helper.ethCrossAccount.fromAddress(receiver); - const receiver2 = helper.eth.createAccount(); - const receiver2Cross = helper.ethCrossAccount.fromAddress(receiver2); const permissions = [ {code: TokenPermissionField.Mutable, value: true}, @@ -160,81 +158,135 @@ ).send(); const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller); - { - const nextTokenId = await contract.methods.nextTokenId().call(); - expect(nextTokenId).to.be.equal('1'); - const result = await contract.methods.mintBulkCross([ + const nextTokenId = await contract.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + const result = await contract.methods.mintBulkCross([ + { + owners: [{ + owner: receiverCross, + pieces: 1, + }], + properties: [ + {key: 'key_0_0', value: Buffer.from('value_0_0')}, + ], + }, + { + owners: [{ + owner: receiverCross, + pieces: 2, + }], + properties: [ + {key: 'key_1_0', value: Buffer.from('value_1_0')}, + {key: 'key_1_1', value: Buffer.from('value_1_1')}, + ], + }, + { + owners: [{ + owner: receiverCross, + pieces: 1, + }], + properties: [ + {key: 'key_2_0', value: Buffer.from('value_2_0')}, + {key: 'key_2_1', value: Buffer.from('value_2_1')}, + {key: 'key_2_2', value: Buffer.from('value_2_2')}, + ], + }, + ]).send({from: caller}); + const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId); + const bulkSize = 3; + for(let i = 0; i < bulkSize; i++) { + const event = events[i]; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.equal(receiver); + expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`); + } + + const properties = [ + await contract.methods.properties(+nextTokenId, []).call(), + await contract.methods.properties(+nextTokenId + 1, []).call(), + await contract.methods.properties(+nextTokenId + 2, []).call(), + ]; + expect(properties).to.be.deep.equal([ + [ + ['key_0_0', helper.getWeb3().utils.toHex('value_0_0')], + ], + [ + ['key_1_0', helper.getWeb3().utils.toHex('value_1_0')], + ['key_1_1', helper.getWeb3().utils.toHex('value_1_1')], + ], + [ + ['key_2_0', helper.getWeb3().utils.toHex('value_2_0')], + ['key_2_1', helper.getWeb3().utils.toHex('value_2_1')], + ['key_2_2', helper.getWeb3().utils.toHex('value_2_2')], + ], + ]); + }); + + itEth('Can perform mintBulkCross() with multiple owners', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const callerCross = helper.ethCrossAccount.fromAddress(caller); + const receiver = helper.eth.createAccount(); + const receiverCross = helper.ethCrossAccount.fromAddress(receiver); + const receiver2 = helper.eth.createAccount(); + const receiver2Cross = helper.ethCrossAccount.fromAddress(receiver2); + + const permissions = [ + {code: TokenPermissionField.Mutable, value: true}, + {code: TokenPermissionField.TokenOwner, value: true}, + {code: TokenPermissionField.CollectionAdmin, value: true}, + ]; + const {collectionAddress} = await helper.eth.createCollection( + caller, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: 'rft', + adminList: [callerCross], + tokenPropertyPermissions: [ + {key: 'key_2_0', permissions}, + {key: 'key_2_1', permissions}, + {key: 'key_2_2', permissions}, + ], + }, + ).send(); + + const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller); + const nextTokenId = await contract.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + const result = await contract.methods.mintBulkCross([{ + owners: [ { - owners: [{ - owner: receiverCross, - pieces: 1, - }], - properties: [ - {key: 'key_0_0', value: Buffer.from('value_0_0')}, - ], + owner: receiverCross, + pieces: 1, }, { - owners: [{ - owner: receiverCross, - pieces: 2, - }], - properties: [ - {key: 'key_1_0', value: Buffer.from('value_1_0')}, - {key: 'key_1_1', value: Buffer.from('value_1_1')}, - ], + owner: receiver2Cross, + pieces: 2, }, - { - owners: [ - { - owner: receiverCross, - pieces: 1, - }, - { - owner: receiver2Cross, - pieces: 2, - }, - ], - properties: [ - {key: 'key_2_0', value: Buffer.from('value_2_0')}, - {key: 'key_2_1', value: Buffer.from('value_2_1')}, - {key: 'key_2_2', value: Buffer.from('value_2_2')}, - ], - }, - ]).send({from: caller}); - const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId); - const bulkSize = 3; - for(let i = 0; i < bulkSize; i++) { - const event = events[i]; - expect(event.address).to.equal(collectionAddress); - expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000'); - if(i == 0 || i == 1) - expect(event.returnValues.to).to.equal(receiver); - else - expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF'); - - expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`); - } + ], + properties: [ + {key: 'key_2_0', value: Buffer.from('value_2_0')}, + {key: 'key_2_1', value: Buffer.from('value_2_1')}, + {key: 'key_2_2', value: Buffer.from('value_2_2')}, + ], + }]).send({from: caller}); + const event = result.events.Transfer; + expect(event.address).to.equal(collectionAddress); + expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000'); + expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF'); + expect(event.returnValues.tokenId).to.equal(`${+nextTokenId}`); - const properties = [ - await contract.methods.properties(+nextTokenId, []).call(), - await contract.methods.properties(+nextTokenId + 1, []).call(), - await contract.methods.properties(+nextTokenId + 2, []).call(), - ]; - expect(properties).to.be.deep.equal([ - [ - ['key_0_0', helper.getWeb3().utils.toHex('value_0_0')], - ], - [ - ['key_1_0', helper.getWeb3().utils.toHex('value_1_0')], - ['key_1_1', helper.getWeb3().utils.toHex('value_1_1')], - ], - [ - ['key_2_0', helper.getWeb3().utils.toHex('value_2_0')], - ['key_2_1', helper.getWeb3().utils.toHex('value_2_1')], - ['key_2_2', helper.getWeb3().utils.toHex('value_2_2')], - ], - ]); - } + const properties = [ + await contract.methods.properties(+nextTokenId, []).call(), + ]; + expect(properties).to.be.deep.equal([[ + ['key_2_0', helper.getWeb3().utils.toHex('value_2_0')], + ['key_2_1', helper.getWeb3().utils.toHex('value_2_1')], + ['key_2_2', helper.getWeb3().utils.toHex('value_2_2')], + ]]); }); itEth('Can perform setApprovalForAll()', async ({helper}) => { @@ -898,4 +950,70 @@ await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected; }); + + itEth('[negative] Can perform mintBulkCross() with multiple owners and multiple tokens', async ({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + const callerCross = helper.ethCrossAccount.fromAddress(caller); + const receiver = helper.eth.createAccount(); + const receiverCross = helper.ethCrossAccount.fromAddress(receiver); + const receiver2 = helper.eth.createAccount(); + const receiver2Cross = helper.ethCrossAccount.fromAddress(receiver2); + + const permissions = [ + {code: TokenPermissionField.Mutable, value: true}, + {code: TokenPermissionField.TokenOwner, value: true}, + {code: TokenPermissionField.CollectionAdmin, value: true}, + ]; + const {collectionAddress} = await helper.eth.createCollection( + caller, + { + ...CREATE_COLLECTION_DATA_DEFAULTS, + name: 'A', + description: 'B', + tokenPrefix: 'C', + collectionMode: 'rft', + adminList: [callerCross], + tokenPropertyPermissions: [ + {key: 'key_0_0', permissions}, + {key: 'key_2_0', permissions}, + {key: 'key_2_1', permissions}, + {key: 'key_2_2', permissions}, + ], + }, + ).send(); + + const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller); + const nextTokenId = await contract.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + const createData = [ + { + owners: [{ + owner: receiverCross, + pieces: 1, + }], + properties: [ + {key: 'key_0_0', value: Buffer.from('value_0_0')}, + ], + }, + { + owners: [ + { + owner: receiverCross, + pieces: 1, + }, + { + owner: receiver2Cross, + pieces: 2, + }, + ], + properties: [ + {key: 'key_2_0', value: Buffer.from('value_2_0')}, + {key: 'key_2_1', value: Buffer.from('value_2_1')}, + {key: 'key_2_2', value: Buffer.from('value_2_2')}, + ], + }, + ]; + + await expect(contract.methods.mintBulkCross(createData).call({from: caller})).to.be.rejectedWith('creation of multiple tokens supported only if they have single owner each'); + }); });