From 448629b28a12eb33f47cc8ccbda31ebb78e8a20f Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Mon, 25 Sep 2023 09:38:18 +0000 Subject: [PATCH] Merge pull request #986 from UniqueNetwork/feature/add_mint_bulk_cross Add mintBulkCross to NFT and RFT collections --- --- a/node/cli/src/command.rs +++ b/node/cli/src/command.rs @@ -425,7 +425,7 @@ .map(|cfg| &cfg.registry); let task_manager = sc_service::TaskManager::new(runner.config().tokio_handle.clone(), *registry) - .map_err(|e| format!("Error: {:?}", e))?; + .map_err(|e| format!("Error: {e:?}"))?; let info_provider = Some(timestamp_with_aura_info(12000)); runner.async_run(|config| -> Result<(Pin>>, _)> { --- a/pallets/common/src/eth.rs +++ b/pallets/common/src/eth.rs @@ -119,7 +119,7 @@ } else if self.sub == Default::default() { Ok(Some(T::CrossAccountId::from_eth(self.eth))) } else { - Err(format!("All fields of cross account is non zeroed {:?}", self).into()) + Err(format!("All fields of cross account is non zeroed {self:?}").into()) } } --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -26,7 +26,7 @@ char::{REPLACEMENT_CHARACTER, decode_utf16}, convert::TryInto, }; -use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*}; +use evm_coder::{abi::AbiType, AbiCoder, ToLog, generate_stubgen, solidity_interface, types::*}; use frame_support::BoundedVec; use up_data_structs::{ TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey, @@ -64,6 +64,15 @@ }, } +/// Token minting parameters +#[derive(AbiCoder, Default, Debug)] +pub struct MintTokenData { + /// Minted token owner + pub owner: eth::CrossAddress, + /// Minted token properties + pub properties: Vec, +} + frontier_contract! { macro_rules! NonfungibleHandle_result {...} impl Contract for NonfungibleHandle {...} @@ -981,13 +990,41 @@ Ok(true) } + /// @notice Function to mint a token. + /// @param data Array of pairs of token owner and token's properties for minted token + #[weight(>::create_multiple_items(data.len() as u32) + >::set_token_properties(data.len() as u32))] + fn mint_bulk_cross(&mut self, caller: Caller, data: Vec) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + let budget = self + .recorder + .weight_calls_budget(>::find_parent()); + + let mut create_nft_data = Vec::with_capacity(data.len()); + for MintTokenData { owner, properties } in data { + let owner = owner.into_sub_cross_account::()?; + create_nft_data.push(CreateItemData:: { + properties: properties + .into_iter() + .map(|property| property.try_into()) + .collect::>>()? + .try_into() + .map_err(|_| "too many properties")?, + owner, + }); + } + + >::create_multiple_items(self, &caller, create_nft_data, &budget) + .map_err(dispatch_to_evm::)?; + Ok(true) + } + /// @notice Function to mint multiple tokens with the given tokenUris. /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive /// numbers and first number should be obtained with `nextTokenId` method /// @param to The new owner /// @param tokens array of pairs of token ID and token URI for minted tokens #[solidity(hide, rename_selector = "mintBulkWithTokenURI")] - #[weight(>::create_multiple_items(tokens.len() as u32) + >::set_token_properties(tokens.len() as u32))] + #[weight(>::create_multiple_items(tokens.len() as u32) + >::set_token_properties(tokens.len() as u32))] fn mint_bulk_with_token_uri( &mut self, caller: Caller, --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -800,7 +800,7 @@ } /// @title Unique extensions for ERC721. -/// @dev the ERC-165 identifier for this interface is 0x307b061a +/// @dev the ERC-165 identifier for this interface is 0x9b397d16 contract ERC721UniqueExtensions is Dummy, ERC165 { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev EVM selector for this function is: 0x06fdde03, @@ -997,6 +997,17 @@ // return false; // } + /// @notice Function to mint a token. + /// @param data Array of pairs of token owner and token's properties for minted token + /// @dev EVM selector for this function is: 0xab427b0c, + /// or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[]) + function mintBulkCross(MintTokenData[] memory data) public returns (bool) { + require(false, stub_error); + data; + dummy = 0; + return false; + } + // /// @notice Function to mint multiple tokens with the given tokenUris. // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive // /// numbers and first number should be obtained with `nextTokenId` method @@ -1044,6 +1055,14 @@ string uri; } +/// Token minting parameters +struct MintTokenData { + /// Minted token owner + CrossAddress owner; + /// Minted token properties + Property[] properties; +} + /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension /// @dev See https://eips.ethereum.org/EIPS/eip-721 /// @dev the ERC-165 identifier for this interface is 0x780e9d63 --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -26,7 +26,7 @@ char::{REPLACEMENT_CHARACTER, decode_utf16}, convert::TryInto, }; -use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*}; +use evm_coder::{abi::AbiType, AbiCoder, ToLog, generate_stubgen, solidity_interface, types::*}; use frame_support::{BoundedBTreeMap, BoundedVec}; use pallet_common::{ CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, @@ -71,6 +71,24 @@ }, } +/// Token minting parameters +#[derive(AbiCoder, Default, Debug)] +pub struct OwnerPieces { + /// Minted token owner + pub owner: eth::CrossAddress, + /// Number of token pieces + pub pieces: u128, +} + +/// Token minting parameters +#[derive(AbiCoder, Default, Debug)] +pub struct MintTokenData { + /// Minted token owner and number of pieces + pub owners: Vec, + /// Minted token properties + pub properties: Vec, +} + /// @title A contract that allows to set and delete token properties and change token property permissions. #[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))] impl RefungibleHandle { @@ -1021,6 +1039,55 @@ Ok(true) } + /// @notice Function to mint a token. + /// @param tokenProperties Properties of minted token + #[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, + token_properties: Vec, + ) -> Result { + let caller = T::CrossAccountId::from_eth(caller); + 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))) + .collect::>>()? + .try_into() + .map_err(|_| "too many users")?; + create_rft_data.push(CreateItemData:: { + properties: properties + .into_iter() + .map(|property| property.try_into()) + .collect::>>()? + .try_into() + .map_err(|_| "too many properties")?, + users, + }); + } + + >::create_multiple_items(self, &caller, create_rft_data, &budget) + .map_err(dispatch_to_evm::)?; + Ok(true) + } + /// @notice Function to mint multiple tokens with the given tokenUris. /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive /// numbers and first number should be obtained with `nextTokenId` method --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -800,7 +800,7 @@ } /// @title Unique extensions for ERC721. -/// @dev the ERC-165 identifier for this interface is 0x95c0f66c +/// @dev the ERC-165 identifier for this interface is 0x4abaabdb contract ERC721UniqueExtensions is Dummy, ERC165 { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev EVM selector for this function is: 0x06fdde03, @@ -986,6 +986,17 @@ // return false; // } + /// @notice Function to mint a token. + /// @param tokenProperties Properties of minted token + /// @dev EVM selector for this function is: 0xdf7a5db7, + /// or in textual repr: mintBulkCross((((address,uint256),uint128)[],(string,bytes)[])[]) + function mintBulkCross(MintTokenData[] memory tokenProperties) public returns (bool) { + require(false, stub_error); + tokenProperties; + dummy = 0; + return false; + } + // /// @notice Function to mint multiple tokens with the given tokenUris. // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive // /// numbers and first number should be obtained with `nextTokenId` method @@ -1045,6 +1056,22 @@ string uri; } +/// Token minting parameters +struct MintTokenData { + /// Minted token owner and number of pieces + OwnerPieces[] owners; + /// Minted token properties + Property[] properties; +} + +/// Token minting parameters +struct OwnerPieces { + /// Minted token owner + CrossAddress owner; + /// Number of token pieces + uint128 pieces; +} + /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension /// @dev See https://eips.ethereum.org/EIPS/eip-721 /// @dev the ERC-165 identifier for this interface is 0x780e9d63 --- a/pallets/scheduler-v2/src/benchmarking.rs +++ b/pallets/scheduler-v2/src/benchmarking.rs @@ -136,8 +136,10 @@ let bound = EncodedCall::bound() as u32; let mut len = match maybe_lookup_len { Some(len) => { - len.min(>::MaxSize::get() - 2) - .max(bound) - 3 + len.clamp( + bound, + >::MaxSize::get() - 2, + ) - 3 } None => bound.saturating_sub(4), }; --- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol +++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol @@ -25,12 +25,12 @@ } /// @title Contract, which allows users to operate with collections -/// @dev the ERC-165 identifier for this interface is 0x4135fff1 +/// @dev the ERC-165 identifier for this interface is 0x94e5af0d contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents { /// Create a collection /// @return address Address of the newly created collection - /// @dev EVM selector for this function is: 0xa765ee5b, - /// or in textual repr: createCollection(((address,uint256),string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],uint8)) + /// @dev EVM selector for this function is: 0x72b5bea7, + /// or in textual repr: createCollection((string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],(address,uint256),uint8)) function createCollection(CreateCollectionData memory data) public payable returns (address) { require(false, stub_error); data; @@ -170,8 +170,6 @@ /// Collection properties struct CreateCollectionData { - /// Collection sponsor - CrossAddress pending_sponsor; /// Collection name string name; /// Collection description @@ -192,11 +190,12 @@ CollectionNestingAndPermission nesting_settings; /// Collection limits CollectionLimitValue[] limits; + /// Collection sponsor + CrossAddress pending_sponsor; /// Extra collection flags CollectionFlags flags; } -/// Cross account struct type CollectionFlags is uint8; library CollectionFlagsLib { @@ -207,13 +206,19 @@ /// External collections can't be managed using `unique` api CollectionFlags constant externalField = CollectionFlags.wrap(1); - /// Reserved bits + /// Reserved flags function reservedField(uint8 value) public pure returns (CollectionFlags) { require(value < 1 << 5, "out of bound value"); return CollectionFlags.wrap(value << 1); } } +/// Cross account struct +struct CrossAddress { + address eth; + uint256 sub; +} + /// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. struct CollectionLimitValue { CollectionLimitField field; @@ -250,12 +255,6 @@ bool collection_admin; /// If set - only tokens from specified collections can be nested. address[] restricted; -} - -/// Cross account struct -struct CrossAddress { - address eth; - uint256 sub; } /// Ethereum representation of Token Property Permissions. @@ -292,10 +291,10 @@ /// Type of tokens in collection enum CollectionMode { - /// Fungible - Fungible, /// Nonfungible Nonfungible, + /// Fungible + Fungible, /// Refungible Refungible } --- a/runtime/common/ethereum/sponsoring/refungible.rs +++ b/runtime/common/ethereum/sponsoring/refungible.rs @@ -242,6 +242,7 @@ BurnFrom { .. } | BurnFromCross { .. } | MintBulk { .. } + | MintBulkCross { .. } | MintBulkWithTokenUri { .. } => None, MintCross { .. } => withdraw_create_item::( --- a/runtime/common/tests/mod.rs +++ b/runtime/common/tests/mod.rs @@ -33,7 +33,7 @@ const PARA_ID: u32 = 2037; fn get_from_seed(seed: &str) -> ::Public { - TPublic::Pair::from_string(&format!("//{}", seed), None) + TPublic::Pair::from_string(&format!("//{seed}"), None) .expect("static values are valid; qed") .public() } --- a/tests/src/eth/abi/nonFungible.json +++ b/tests/src/eth/abi/nonFungible.json @@ -469,6 +469,39 @@ "inputs": [ { "components": [ + { + "components": [ + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } + ], + "internalType": "struct CrossAddress", + "name": "owner", + "type": "tuple" + }, + { + "components": [ + { "internalType": "string", "name": "key", "type": "string" }, + { "internalType": "bytes", "name": "value", "type": "bytes" } + ], + "internalType": "struct Property[]", + "name": "properties", + "type": "tuple[]" + } + ], + "internalType": "struct MintTokenData[]", + "name": "data", + "type": "tuple[]" + } + ], + "name": "mintBulkCross", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ { "internalType": "address", "name": "eth", "type": "address" }, { "internalType": "uint256", "name": "sub", "type": "uint256" } ], --- a/tests/src/eth/abi/reFungible.json +++ b/tests/src/eth/abi/reFungible.json @@ -451,6 +451,55 @@ "inputs": [ { "components": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "eth", + "type": "address" + }, + { + "internalType": "uint256", + "name": "sub", + "type": "uint256" + } + ], + "internalType": "struct CrossAddress", + "name": "owner", + "type": "tuple" + }, + { "internalType": "uint128", "name": "pieces", "type": "uint128" } + ], + "internalType": "struct OwnerPieces[]", + "name": "owners", + "type": "tuple[]" + }, + { + "components": [ + { "internalType": "string", "name": "key", "type": "string" }, + { "internalType": "bytes", "name": "value", "type": "bytes" } + ], + "internalType": "struct Property[]", + "name": "properties", + "type": "tuple[]" + } + ], + "internalType": "struct MintTokenData[]", + "name": "tokenProperties", + "type": "tuple[]" + } + ], + "name": "mintBulkCross", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ { "internalType": "address", "name": "eth", "type": "address" }, { "internalType": "uint256", "name": "sub", "type": "uint256" } ], --- a/tests/src/eth/api/CollectionHelpers.sol +++ b/tests/src/eth/api/CollectionHelpers.sol @@ -20,12 +20,12 @@ } /// @title Contract, which allows users to operate with collections -/// @dev the ERC-165 identifier for this interface is 0x4135fff1 +/// @dev the ERC-165 identifier for this interface is 0x94e5af0d interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents { /// Create a collection /// @return address Address of the newly created collection - /// @dev EVM selector for this function is: 0xa765ee5b, - /// or in textual repr: createCollection(((address,uint256),string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],uint8)) + /// @dev EVM selector for this function is: 0x72b5bea7, + /// or in textual repr: createCollection((string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],(address,uint256),uint8)) function createCollection(CreateCollectionData memory data) external payable returns (address); /// Create an NFT collection @@ -103,8 +103,6 @@ /// Collection properties struct CreateCollectionData { - /// Collection sponsor - CrossAddress pending_sponsor; /// Collection name string name; /// Collection description @@ -125,11 +123,12 @@ CollectionNestingAndPermission nesting_settings; /// Collection limits CollectionLimitValue[] limits; + /// Collection sponsor + CrossAddress pending_sponsor; /// Extra collection flags CollectionFlags flags; } -/// Cross account struct type CollectionFlags is uint8; library CollectionFlagsLib { @@ -140,13 +139,19 @@ /// External collections can't be managed using `unique` api CollectionFlags constant externalField = CollectionFlags.wrap(1); - /// Reserved bits + /// Reserved flags function reservedField(uint8 value) public pure returns (CollectionFlags) { require(value < 1 << 5, "out of bound value"); return CollectionFlags.wrap(value << 1); } } +/// Cross account struct +struct CrossAddress { + address eth; + uint256 sub; +} + /// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM. struct CollectionLimitValue { CollectionLimitField field; @@ -183,12 +188,6 @@ bool collection_admin; /// If set - only tokens from specified collections can be nested. address[] restricted; -} - -/// Cross account struct -struct CrossAddress { - address eth; - uint256 sub; } /// Ethereum representation of Token Property Permissions. @@ -225,10 +224,10 @@ /// Type of tokens in collection enum CollectionMode { - /// Fungible - Fungible, /// Nonfungible Nonfungible, + /// Fungible + Fungible, /// Refungible Refungible } --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -551,7 +551,7 @@ } /// @title Unique extensions for ERC721. -/// @dev the ERC-165 identifier for this interface is 0x307b061a +/// @dev the ERC-165 identifier for this interface is 0x9b397d16 interface ERC721UniqueExtensions is Dummy, ERC165 { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev EVM selector for this function is: 0x06fdde03, @@ -674,6 +674,12 @@ // /// or in textual repr: mintBulk(address,uint256[]) // function mintBulk(address to, uint256[] memory tokenIds) external returns (bool); + /// @notice Function to mint a token. + /// @param data Array of pairs of token owner and token's properties for minted token + /// @dev EVM selector for this function is: 0xab427b0c, + /// or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[]) + function mintBulkCross(MintTokenData[] memory data) external returns (bool); + // /// @notice Function to mint multiple tokens with the given tokenUris. // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive // /// numbers and first number should be obtained with `nextTokenId` method @@ -705,6 +711,14 @@ string uri; } +/// Token minting parameters +struct MintTokenData { + /// Minted token owner + CrossAddress owner; + /// Minted token properties + Property[] properties; +} + /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension /// @dev See https://eips.ethereum.org/EIPS/eip-721 /// @dev the ERC-165 identifier for this interface is 0x780e9d63 --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -551,7 +551,7 @@ } /// @title Unique extensions for ERC721. -/// @dev the ERC-165 identifier for this interface is 0x95c0f66c +/// @dev the ERC-165 identifier for this interface is 0x4abaabdb interface ERC721UniqueExtensions is Dummy, ERC165 { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev EVM selector for this function is: 0x06fdde03, @@ -668,6 +668,12 @@ // /// or in textual repr: mintBulk(address,uint256[]) // function mintBulk(address to, uint256[] memory tokenIds) external returns (bool); + /// @notice Function to mint a token. + /// @param tokenProperties Properties of minted token + /// @dev EVM selector for this function is: 0xdf7a5db7, + /// or in textual repr: mintBulkCross((((address,uint256),uint128)[],(string,bytes)[])[]) + function mintBulkCross(MintTokenData[] memory tokenProperties) external returns (bool); + // /// @notice Function to mint multiple tokens with the given tokenUris. // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive // /// numbers and first number should be obtained with `nextTokenId` method @@ -706,6 +712,22 @@ string uri; } +/// Token minting parameters +struct MintTokenData { + /// Minted token owner and number of pieces + OwnerPieces[] owners; + /// Minted token properties + Property[] properties; +} + +/// Token minting parameters +struct OwnerPieces { + /// Minted token owner + CrossAddress owner; + /// Number of token pieces + uint128 pieces; +} + /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension /// @dev See https://eips.ethereum.org/EIPS/eip-721 /// @dev the ERC-165 identifier for this interface is 0x780e9d63 --- a/tests/src/eth/nonFungible.test.ts +++ b/tests/src/eth/nonFungible.test.ts @@ -18,6 +18,7 @@ import {IKeyringPair} from '@polkadot/types/types'; import {Contract} from 'web3-eth-contract'; import {ITokenPropertyPermission} from '../util/playgrounds/types'; +import {CREATE_COLLECTION_DATA_DEFAULTS, CollectionMode, CreateCollectionData, TokenPermissionField} from './util/playgrounds/types'; describe('Check ERC721 token URI for NFT', () => { let donor: IKeyringPair; @@ -197,6 +198,96 @@ } }); + itEth('Can perform mintBulkCross()', 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 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: 'nft', + adminList: [callerCross], + tokenPropertyPermissions: [ + {key: 'key_0_0', permissions}, + {key: 'key_1_0', permissions}, + {key: 'key_1_1', permissions}, + {key: 'key_2_0', permissions}, + {key: 'key_2_1', permissions}, + {key: 'key_2_2', permissions}, + ], + }, + ).send(); + + const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller); + { + const nextTokenId = await contract.methods.nextTokenId().call(); + expect(nextTokenId).to.be.equal('1'); + const result = await contract.methods.mintBulkCross([ + { + owner: receiverCross, + properties: [ + {key: 'key_0_0', value: Buffer.from('value_0_0')}, + ], + }, + { + owner: receiverCross, + properties: [ + {key: 'key_1_0', value: Buffer.from('value_1_0')}, + {key: 'key_1_1', value: Buffer.from('value_1_1')}, + ], + }, + { + owner: receiverCross, + 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 burn()', async ({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); --- a/tests/src/eth/reFungible.test.ts +++ b/tests/src/eth/reFungible.test.ts @@ -18,6 +18,7 @@ import {expect, itEth, usingEthPlaygrounds} from './util'; import {IKeyringPair} from '@polkadot/types/types'; import {ITokenPropertyPermission} from '../util/playgrounds/types'; +import {CREATE_COLLECTION_DATA_DEFAULTS, TokenPermissionField} from './util/playgrounds/types'; describe('Refungible: Plain calls', () => { let donor: IKeyringPair; @@ -125,6 +126,169 @@ } }); + 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 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_1_0', permissions}, + {key: 'key_1_1', 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 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: [ + { + 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 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(), + ]; + 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}) => { const owner = await helper.eth.createAccountWithBalance(donor); const operator = helper.eth.createAccount(); @@ -786,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'); + }); }); -- gitstuff