git.delta.rocks / unique-network / refs/commits / b29d888786e1

difftreelog

added `mintCross` function for `UniqueExtensoins` interfaces

PraetorP2022-12-01parent: #0a1b898.patch.diff
in: master

20 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6085,7 +6085,7 @@
 
 [[package]]
 name = "pallet-fungible"
-version = "0.1.7"
+version = "0.1.9"
 dependencies = [
  "ethereum 0.14.0",
  "evm-coder",
modifiedpallets/fungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/fungible/CHANGELOG.md
+++ b/pallets/fungible/CHANGELOG.md
@@ -4,6 +4,12 @@
 
 <!-- bureaucrate goes here -->
 
+## [0.1.9] - 2022-12-01
+
+### Added
+
+- The functions `mintCross` to `ERC20UniqueExtensions` interface.
+
 ## [0.1.8] - 2022-11-18
 
 ### Added
modifiedpallets/fungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-fungible"
-version = "0.1.7"
+version = "0.1.9"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -174,6 +174,19 @@
 			.collect::<string>())
 	}
 
+	#[weight(<SelfWeightOf<T>>::create_item())]
+	fn mint_cross(&mut self, caller: caller, to: EthCrossAccount, amount: uint256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = to.into_sub_cross_account::<T>()?;
+		let amount = amount.try_into().map_err(|_| "amount overflow")?;
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+		<Pallet<T>>::create_item(&self, &caller, (to, amount), &budget)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+
 	#[weight(<SelfWeightOf<T>>::approve())]
 	fn approve_cross(
 		&mut self,
modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -152,10 +152,10 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() public view returns (Tuple8 memory) {
+	function collectionSponsor() public view returns (Tuple9 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple8(0x0000000000000000000000000000000000000000, 0);
+		return Tuple9(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Get current collection limits.
@@ -522,7 +522,7 @@
 	bytes value;
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x5b7038cf
+/// @dev the ERC-165 identifier for this interface is 0x7dee5997
 contract ERC20UniqueExtensions is Dummy, ERC165 {
 	/// @notice A description for the collection.
 	/// @dev EVM selector for this function is: 0x7284e416,
@@ -533,6 +533,16 @@
 		return "";
 	}
 
+	/// @dev EVM selector for this function is: 0x269e6158,
+	///  or in textual repr: mintCross((address,uint256),uint256)
+	function mintCross(EthCrossAccount memory to, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		to;
+		amount;
+		dummy = 0;
+		return false;
+	}
+
 	/// @dev EVM selector for this function is: 0x0ecd0ab0,
 	///  or in textual repr: approveCross((address,uint256),uint256)
 	function approveCross(EthCrossAccount memory spender, uint256 amount) public returns (bool) {
@@ -577,7 +587,7 @@
 	/// @param amounts array of pairs of account address and amount
 	/// @dev EVM selector for this function is: 0x1acf2d55,
 	///  or in textual repr: mintBulk((address,uint256)[])
-	function mintBulk(Tuple8[] memory amounts) public returns (bool) {
+	function mintBulk(Tuple9[] memory amounts) public returns (bool) {
 		require(false, stub_error);
 		amounts;
 		dummy = 0;
@@ -611,7 +621,7 @@
 }
 
 /// @dev anonymous struct
-struct Tuple8 {
+struct Tuple9 {
 	address field_0;
 	uint256 field_1;
 }
modifiedpallets/nonfungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/nonfungible/CHANGELOG.md
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -4,7 +4,7 @@
 
 <!-- bureaucrate goes here -->
 
-## [0.1.11] - 2022-12-16
+## [0.1.12] - 2022-12-16
 
 ### Added
 
@@ -14,6 +14,12 @@
 
 - Hide `setTokenPropertyPermission` function in `TokenProperties` interface.
 
+## [0.1.11] - 2022-12-01
+
+### Added
+
+- The functions `mintCross` to `ERC721UniqueExtensions` interface.
+
 ## [0.1.10] - 2022-11-18
 
 ### Added
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -1069,6 +1069,58 @@
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(true)
 	}
+
+	/// @notice Function to mint token.
+	/// @param to The new owner crossAccountId
+	/// @param properties Properties of minted token
+	/// @return uint256 The id of the newly minted token
+	#[weight(<SelfWeightOf<T>>::create_item())]
+	fn mint_cross(
+		&mut self,
+		caller: caller,
+		to: EthCrossAccount,
+		properties: Vec<PropertyStruct>,
+	) -> Result<uint256> {
+		let token_id = <TokensMinted<T>>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?;
+
+		let to = to.into_sub_cross_account::<T>()?;
+
+		let properties = properties
+			.into_iter()
+			.map(|PropertyStruct { key, value }| {
+				let key = <Vec<u8>>::from(key)
+					.try_into()
+					.map_err(|_| "key too large")?;
+
+				let value = value.0.try_into().map_err(|_| "value too large")?;
+
+				Ok(Property { key, value })
+			})
+			.collect::<Result<Vec<_>>>()?
+			.try_into()
+			.map_err(|_| Error::Revert(alloc::format!("too many properties")))?;
+
+		let caller = T::CrossAccountId::from_eth(caller);
+
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		<Pallet<T>>::create_item(
+			self,
+			&caller,
+			CreateItemData::<T> {
+				properties,
+				owner: to,
+			},
+			&budget,
+		)
+		.map_err(dispatch_to_evm::<T>)?;
+
+		Ok(token_id.into())
+	}
 }
 
 #[solidity_interface(
modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -290,10 +290,10 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() public view returns (Tuple30 memory) {
+	function collectionSponsor() public view returns (Tuple32 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple30(0x0000000000000000000000000000000000000000, 0);
+		return Tuple32(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Get current collection limits.
@@ -655,7 +655,7 @@
 }
 
 /// @dev anonymous struct
-struct Tuple30 {
+struct Tuple32 {
 	address field_0;
 	uint256 field_1;
 }
@@ -804,7 +804,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0xb74c26b7
+/// @dev the ERC-165 identifier for this interface is 0x0e48fdb4
 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,
@@ -961,6 +961,7 @@
 		dummy;
 		return 0;
 	}
+
 	// /// @notice Function to mint multiple tokens.
 	// /// @dev `tokenIds` should be an array of consecutive numbers and first number
 	// ///  should be obtained with `nextTokenId` method
@@ -991,6 +992,19 @@
 	// 	return false;
 	// }
 
+	/// @notice Function to mint token.
+	/// @param to The new owner crossAccountId
+	/// @param properties Properties of minted token
+	/// @return uint256 The id of the newly minted token
+	/// @dev EVM selector for this function is: 0xb904db03,
+	///  or in textual repr: mintCross((address,uint256),(string,bytes)[])
+	function mintCross(EthCrossAccount memory to, Property[] memory properties) public returns (uint256) {
+		require(false, stub_error);
+		to;
+		properties;
+		dummy = 0;
+		return 0;
+	}
 }
 
 /// @dev anonymous struct
modifiedpallets/refungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -4,7 +4,7 @@
 
 <!-- bureaucrate goes here -->
 
-## [0.2.10] - 2022-12-16
+## [0.2.11] - 2022-12-16
 
 ### Added
 
@@ -14,6 +14,12 @@
 
 - Hide `setTokenPropertyPermission` function in `TokenProperties` interface.
 
+## [0.2.10] - 2022-12-01
+
+### Added
+
+- The functions `mintCross` to `ERC721UniqueExtensions` interface.
+
 ## [0.2.9] - 2022-11-18
 
 ### Added
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -1120,6 +1120,60 @@
 		Ok(true)
 	}
 
+	/// @notice Function to mint token.
+	/// @param to The new owner crossAccountId
+	/// @param properties Properties of minted token
+	/// @return uint256 The id of the newly minted token
+	#[weight(<SelfWeightOf<T>>::create_item())]
+	fn mint_cross(
+		&mut self,
+		caller: caller,
+		to: EthCrossAccount,
+		properties: Vec<PropertyStruct>,
+	) -> Result<uint256> {
+		let token_id = <TokensMinted<T>>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?;
+
+		let to = to.into_sub_cross_account::<T>()?;
+
+		let properties = properties
+			.into_iter()
+			.map(|PropertyStruct { key, value }| {
+				let key = <Vec<u8>>::from(key)
+					.try_into()
+					.map_err(|_| "key too large")?;
+
+				let value = value.0.try_into().map_err(|_| "value too large")?;
+
+				Ok(Property { key, value })
+			})
+			.collect::<Result<Vec<_>>>()?
+			.try_into()
+			.map_err(|_| Error::Revert(alloc::format!("too many properties")))?;
+
+		let caller = T::CrossAccountId::from_eth(caller);
+
+		let budget = self
+			.recorder
+			.weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+		let users = [(to, 1)]
+			.into_iter()
+			.collect::<BTreeMap<_, _>>()
+			.try_into()
+			.unwrap();
+		<Pallet<T>>::create_item(
+			self,
+			&caller,
+			CreateItemData::<T::CrossAccountId> { users, properties },
+			&budget,
+		)
+		.map_err(dispatch_to_evm::<T>)?;
+
+		Ok(token_id.into())
+	}
+
 	/// Returns EVM address for refungible token
 	///
 	/// @param token ID of the token
modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -290,10 +290,10 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() public view returns (Tuple29 memory) {
+	function collectionSponsor() public view returns (Tuple31 memory) {
 		require(false, stub_error);
 		dummy;
-		return Tuple29(0x0000000000000000000000000000000000000000, 0);
+		return Tuple31(0x0000000000000000000000000000000000000000, 0);
 	}
 
 	/// Get current collection limits.
@@ -655,7 +655,7 @@
 }
 
 /// @dev anonymous struct
-struct Tuple29 {
+struct Tuple31 {
 	address field_0;
 	uint256 field_1;
 }
@@ -802,7 +802,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x12f7d6c1
+/// @dev the ERC-165 identifier for this interface is 0xabf30dc2
 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,
@@ -979,6 +979,20 @@
 	// 	return false;
 	// }
 
+	/// @notice Function to mint token.
+	/// @param to The new owner crossAccountId
+	/// @param properties Properties of minted token
+	/// @return uint256 The id of the newly minted token
+	/// @dev EVM selector for this function is: 0xb904db03,
+	///  or in textual repr: mintCross((address,uint256),(string,bytes)[])
+	function mintCross(EthCrossAccount memory to, Property[] memory properties) public returns (uint256) {
+		require(false, stub_error);
+		to;
+		properties;
+		dummy = 0;
+		return 0;
+	}
+
 	/// Returns EVM address for refungible token
 	///
 	/// @param token ID of the token
modifiedtests/src/eth/abi/fungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/fungible.json
+++ b/tests/src/eth/abi/fungible.json
@@ -322,7 +322,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple8",
+        "internalType": "struct Tuple9",
         "name": "",
         "type": "tuple"
       }
@@ -408,7 +408,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple8[]",
+        "internalType": "struct Tuple9[]",
         "name": "amounts",
         "type": "tuple[]"
       }
@@ -419,6 +419,24 @@
     "type": "function"
   },
   {
+    "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct EthCrossAccount",
+        "name": "to",
+        "type": "tuple"
+      },
+      { "internalType": "uint256", "name": "amount", "type": "uint256" }
+    ],
+    "name": "mintCross",
+    "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
     "inputs": [],
     "name": "name",
     "outputs": [{ "internalType": "string", "name": "", "type": "string" }],
modifiedtests/src/eth/abi/nonFungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/nonFungible.json
+++ b/tests/src/eth/abi/nonFungible.json
@@ -352,7 +352,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple30",
+        "internalType": "struct Tuple32",
         "name": "",
         "type": "tuple"
       }
@@ -478,6 +478,32 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct EthCrossAccount",
+        "name": "to",
+        "type": "tuple"
+      },
+      {
+        "components": [
+          { "internalType": "string", "name": "key", "type": "string" },
+          { "internalType": "bytes", "name": "value", "type": "bytes" }
+        ],
+        "internalType": "struct Property[]",
+        "name": "properties",
+        "type": "tuple[]"
+      }
+    ],
+    "name": "mintCross",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "string", "name": "tokenUri", "type": "string" }
     ],
modifiedtests/src/eth/abi/reFungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -334,7 +334,7 @@
           { "internalType": "address", "name": "field_0", "type": "address" },
           { "internalType": "uint256", "name": "field_1", "type": "uint256" }
         ],
-        "internalType": "struct Tuple29",
+        "internalType": "struct Tuple31",
         "name": "",
         "type": "tuple"
       }
@@ -460,6 +460,32 @@
   },
   {
     "inputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "eth", "type": "address" },
+          { "internalType": "uint256", "name": "sub", "type": "uint256" }
+        ],
+        "internalType": "struct EthCrossAccount",
+        "name": "to",
+        "type": "tuple"
+      },
+      {
+        "components": [
+          { "internalType": "string", "name": "key", "type": "string" },
+          { "internalType": "bytes", "name": "value", "type": "bytes" }
+        ],
+        "internalType": "struct Property[]",
+        "name": "properties",
+        "type": "tuple[]"
+      }
+    ],
+    "name": "mintCross",
+    "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "address", "name": "to", "type": "address" },
       { "internalType": "string", "name": "tokenUri", "type": "string" }
     ],
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -102,7 +102,7 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() external view returns (Tuple8 memory);
+	function collectionSponsor() external view returns (Tuple9 memory);
 
 	/// Get current collection limits.
 	///
@@ -362,13 +362,17 @@
 	bytes value;
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x5b7038cf
+/// @dev the ERC-165 identifier for this interface is 0x7dee5997
 interface ERC20UniqueExtensions is Dummy, ERC165 {
 	/// @notice A description for the collection.
 	/// @dev EVM selector for this function is: 0x7284e416,
 	///  or in textual repr: description()
 	function description() external view returns (string memory);
 
+	/// @dev EVM selector for this function is: 0x269e6158,
+	///  or in textual repr: mintCross((address,uint256),uint256)
+	function mintCross(EthCrossAccount memory to, uint256 amount) external returns (bool);
+
 	/// @dev EVM selector for this function is: 0x0ecd0ab0,
 	///  or in textual repr: approveCross((address,uint256),uint256)
 	function approveCross(EthCrossAccount memory spender, uint256 amount) external returns (bool);
@@ -395,7 +399,7 @@
 	/// @param amounts array of pairs of account address and amount
 	/// @dev EVM selector for this function is: 0x1acf2d55,
 	///  or in textual repr: mintBulk((address,uint256)[])
-	function mintBulk(Tuple8[] memory amounts) external returns (bool);
+	function mintBulk(Tuple9[] memory amounts) external returns (bool);
 
 	/// @dev EVM selector for this function is: 0x2ada85ff,
 	///  or in textual repr: transferCross((address,uint256),uint256)
@@ -411,7 +415,7 @@
 }
 
 /// @dev anonymous struct
-struct Tuple8 {
+struct Tuple9 {
 	address field_0;
 	uint256 field_1;
 }
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -198,7 +198,7 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() external view returns (Tuple27 memory);
+	function collectionSponsor() external view returns (Tuple29 memory);
 
 	/// Get current collection limits.
 	///
@@ -553,7 +553,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0xb74c26b7
+/// @dev the ERC-165 identifier for this interface is 0x0e48fdb4
 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,
@@ -652,6 +652,7 @@
 	/// @dev EVM selector for this function is: 0x75794a3c,
 	///  or in textual repr: nextTokenId()
 	function nextTokenId() external view returns (uint256);
+
 	// /// @notice Function to mint multiple tokens.
 	// /// @dev `tokenIds` should be an array of consecutive numbers and first number
 	// ///  should be obtained with `nextTokenId` method
@@ -670,6 +671,13 @@
 	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
 	// function mintBulkWithTokenURI(address to, Tuple13[] memory tokens) external returns (bool);
 
+	/// @notice Function to mint token.
+	/// @param to The new owner crossAccountId
+	/// @param properties Properties of minted token
+	/// @return uint256 The id of the newly minted token
+	/// @dev EVM selector for this function is: 0xb904db03,
+	///  or in textual repr: mintCross((address,uint256),(string,bytes)[])
+	function mintCross(EthCrossAccount memory to, Property[] memory properties) external returns (uint256);
 }
 
 /// @dev anonymous struct
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -198,7 +198,7 @@
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	/// @dev EVM selector for this function is: 0x6ec0a9f1,
 	///  or in textual repr: collectionSponsor()
-	function collectionSponsor() external view returns (Tuple26 memory);
+	function collectionSponsor() external view returns (Tuple28 memory);
 
 	/// Get current collection limits.
 	///
@@ -551,7 +551,7 @@
 }
 
 /// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x12f7d6c1
+/// @dev the ERC-165 identifier for this interface is 0xabf30dc2
 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,
@@ -663,6 +663,14 @@
 	// ///  or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
 	// function mintBulkWithTokenURI(address to, Tuple12[] memory tokens) external returns (bool);
 
+	/// @notice Function to mint token.
+	/// @param to The new owner crossAccountId
+	/// @param properties Properties of minted token
+	/// @return uint256 The id of the newly minted token
+	/// @dev EVM selector for this function is: 0xb904db03,
+	///  or in textual repr: mintCross((address,uint256),(string,bytes)[])
+	function mintCross(EthCrossAccount memory to, Property[] memory properties) external returns (uint256);
+
 	/// Returns EVM address for refungible token
 	///
 	/// @param token ID of the token
modifiedtests/src/eth/fungible.test.tsdiffbeforeafterboth
before · tests/src/eth/fungible.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {expect, itEth, usingEthPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';1920describe('Fungible: Information getting', () => {21  let donor: IKeyringPair;22  let alice: IKeyringPair;2324  before(async function() {25    await usingEthPlaygrounds(async (helper, privateKey) => {26      donor = await privateKey({filename: __filename});27      [alice] = await helper.arrange.createAccounts([20n], donor);28    });29  });3031  itEth('totalSupply', async ({helper}) => {32    const caller = await helper.eth.createAccountWithBalance(donor);33    const collection = await helper.ft.mintCollection(alice);34    await collection.mint(alice, 200n);3536    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);37    const totalSupply = await contract.methods.totalSupply().call();38    expect(totalSupply).to.equal('200');39  });4041  itEth('balanceOf', async ({helper}) => {42    const caller = await helper.eth.createAccountWithBalance(donor);43    const collection = await helper.ft.mintCollection(alice);44    await collection.mint(alice, 200n, {Ethereum: caller});4546    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);47    const balance = await contract.methods.balanceOf(caller).call();48    expect(balance).to.equal('200');49  });50});5152describe('Fungible: Plain calls', () => {53  let donor: IKeyringPair;54  let alice: IKeyringPair;55  let owner: IKeyringPair;5657  before(async function() {58    await usingEthPlaygrounds(async (helper, privateKey) => {59      donor = await privateKey({filename: __filename});60      [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);61    });62  });6364  itEth('Can perform mint()', async ({helper}) => {65    const owner = await helper.eth.createAccountWithBalance(donor);66    const receiver = helper.eth.createAccount();67    const collection = await helper.ft.mintCollection(alice);68    await collection.addAdmin(alice, {Ethereum: owner});6970    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);71    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);7273    const result = await contract.methods.mint(receiver, 100).send();74    75    const event = result.events.Transfer;76    expect(event.address).to.equal(collectionAddress);77    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');78    expect(event.returnValues.to).to.equal(receiver);79    expect(event.returnValues.value).to.equal('100');80  });8182  itEth('Can perform mintBulk()', async ({helper}) => {83    const owner = await helper.eth.createAccountWithBalance(donor);84    const bulkSize = 3;85    const receivers = [...new Array(bulkSize)].map(() => helper.eth.createAccount());86    const collection = await helper.ft.mintCollection(alice);87    await collection.addAdmin(alice, {Ethereum: owner});8889    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);90    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);9192    const result = await contract.methods.mintBulk(Array.from({length: bulkSize}, (_, i) => (93      [receivers[i], (i + 1) * 10]94    ))).send();95    const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.value - b.returnValues.value);96    for (let i = 0; i < bulkSize; i++) {97      const event = events[i];98      expect(event.address).to.equal(collectionAddress);99      expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');100      expect(event.returnValues.to).to.equal(receivers[i]);101      expect(event.returnValues.value).to.equal(String(10 * (i + 1)));102    }103  });104105  // Soft-deprecated106  itEth('Can perform burn()', async ({helper}) => {107    const owner = await helper.eth.createAccountWithBalance(donor);108    const receiver = await helper.eth.createAccountWithBalance(donor);109    const collection = await helper.ft.mintCollection(alice);110    await collection.addAdmin(alice, {Ethereum: owner});111112    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);113    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);114    await contract.methods.mint(receiver, 100).send();115116    const result = await contract.methods.burnFrom(receiver, 49).send({from: receiver});117    118    const event = result.events.Transfer;119    expect(event.address).to.equal(collectionAddress);120    expect(event.returnValues.from).to.equal(receiver);121    expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');122    expect(event.returnValues.value).to.equal('49');123124    const balance = await contract.methods.balanceOf(receiver).call();125    expect(balance).to.equal('51');126  });127128  itEth('Can perform approve()', async ({helper}) => {129    const owner = await helper.eth.createAccountWithBalance(donor);130    const spender = helper.eth.createAccount();131    const collection = await helper.ft.mintCollection(alice);132    await collection.mint(alice, 200n, {Ethereum: owner});133134    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);135    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);136137    {138      const result = await contract.methods.approve(spender, 100).send({from: owner});139140      const event = result.events.Approval;141      expect(event.address).to.be.equal(collectionAddress);142      expect(event.returnValues.owner).to.be.equal(owner);143      expect(event.returnValues.spender).to.be.equal(spender);144      expect(event.returnValues.value).to.be.equal('100');145    }146147    {148      const allowance = await contract.methods.allowance(owner, spender).call();149      expect(+allowance).to.equal(100);150    }151  });152153  itEth('Can perform burnFromCross()', async ({helper}) => {154    const sender = await helper.eth.createAccountWithBalance(donor, 100n);155156    const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);157158    await collection.mint(owner, 200n, {Substrate: owner.address});159    await collection.approveTokens(owner, {Ethereum: sender}, 100n);160161    const address = helper.ethAddress.fromCollectionId(collection.collectionId);162    const contract = helper.ethNativeContract.collection(address, 'ft');163164    const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});165    166    const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);167    const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender});168    const events = result.events;169170    expect(events).to.be.like({171      Transfer: {172        address: helper.ethAddress.fromCollectionId(collection.collectionId),173        event: 'Transfer',174        returnValues: {175          from: helper.address.substrateToEth(owner.address),176          to: '0x0000000000000000000000000000000000000000',177          value: '49',178        },179      },180      Approval: {181        address: helper.ethAddress.fromCollectionId(collection.collectionId),182        returnValues: {183          owner: helper.address.substrateToEth(owner.address),184          spender: sender,185          value: '51',186        },187        event: 'Approval',188      },189    });190191    const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});192    expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n);193  });194195  itEth('Can perform transferFrom()', async ({helper}) => {196    const owner = await helper.eth.createAccountWithBalance(donor);197    const spender = await helper.eth.createAccountWithBalance(donor);198    const receiver = helper.eth.createAccount();199    const collection = await helper.ft.mintCollection(alice);200    await collection.mint(alice, 200n, {Ethereum: owner});201202    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);203    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);204205    await contract.methods.approve(spender, 100).send();206207    {208      const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});209      210      let event = result.events.Transfer;211      expect(event.address).to.be.equal(collectionAddress);212      expect(event.returnValues.from).to.be.equal(owner);213      expect(event.returnValues.to).to.be.equal(receiver);214      expect(event.returnValues.value).to.be.equal('49');215216      event = result.events.Approval;217      expect(event.address).to.be.equal(collectionAddress);218      expect(event.returnValues.owner).to.be.equal(owner);219      expect(event.returnValues.spender).to.be.equal(spender);220      expect(event.returnValues.value).to.be.equal('51');221    }222223    {224      const balance = await contract.methods.balanceOf(receiver).call();225      expect(+balance).to.equal(49);226    }227228    {229      const balance = await contract.methods.balanceOf(owner).call();230      expect(+balance).to.equal(151);231    }232  });233234  itEth('Can perform transferCross()', async ({helper}) => {235    const sender = await helper.eth.createAccountWithBalance(donor);236    const receiverEth = await helper.eth.createAccountWithBalance(donor);237    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);238    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(donor);239    const collection = await helper.ft.mintCollection(alice);240    await collection.mint(alice, 200n, {Ethereum: sender});241242    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);243    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', sender);244245    {246      // Can transferCross to ethereum address:247      const result = await collectionEvm.methods.transferCross(receiverCrossEth, 50).send({from: sender});248      // Check events:249      const event = result.events.Transfer;250      expect(event.address).to.be.equal(collectionAddress);251      expect(event.returnValues.from).to.be.equal(sender);252      expect(event.returnValues.to).to.be.equal(receiverEth);253      expect(event.returnValues.value).to.be.equal('50');254      // Sender's balance decreased:255      const ownerBalance = await collectionEvm.methods.balanceOf(sender).call();256      expect(+ownerBalance).to.equal(150);257      // Receiver's balance increased:258      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();259      expect(+receiverBalance).to.equal(50);260    }261    262    {263      // Can transferCross to substrate address:264      const result = await collectionEvm.methods.transferCross(receiverCrossSub, 50).send({from: sender});265      // Check events:266      const event = result.events.Transfer;267      expect(event.address).to.be.equal(collectionAddress);268      expect(event.returnValues.from).to.be.equal(sender);269      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(donor.address));270      expect(event.returnValues.value).to.be.equal('50');271      // Sender's balance decreased:272      const senderBalance = await collection.getBalance({Ethereum: sender});273      expect(senderBalance).to.equal(100n);274      // Receiver's balance increased:275      const balance = await collection.getBalance({Substrate: donor.address});276      expect(balance).to.equal(50n);277    }278  });279280  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} incorrect amount`, async ({helper}) => {281    const sender = await helper.eth.createAccountWithBalance(donor);282    const receiverEth = await helper.eth.createAccountWithBalance(donor);283    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);284    const BALANCE = 200n;285    const BALANCE_TO_TRANSFER = BALANCE + 100n;286287    const collection = await helper.ft.mintCollection(alice);288    await collection.mint(alice, BALANCE, {Ethereum: sender});289    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);290    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', sender);291292    // 1. Cannot transfer more than have293    const receiver = testCase === 'transfer' ? receiverEth : receiverCrossEth;294    await expect(collectionEvm.methods[testCase](receiver, BALANCE_TO_TRANSFER).send({from: sender})).to.be.rejected;295    // 2. Zero transfer allowed (EIP-20):296    await collectionEvm.methods[testCase](receiver, 0n).send({from: sender});297  }));298  299  300  itEth('Can perform transfer()', async ({helper}) => {301    const owner = await helper.eth.createAccountWithBalance(donor);302    const receiver = await helper.eth.createAccountWithBalance(donor);303    const collection = await helper.ft.mintCollection(alice);304    await collection.mint(alice, 200n, {Ethereum: owner});305306    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);307    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);308309    {310      const result = await contract.methods.transfer(receiver, 50).send({from: owner});311      312      const event = result.events.Transfer;313      expect(event.address).to.be.equal(collectionAddress);314      expect(event.returnValues.from).to.be.equal(owner);315      expect(event.returnValues.to).to.be.equal(receiver);316      expect(event.returnValues.value).to.be.equal('50');317    }318319    {320      const balance = await contract.methods.balanceOf(owner).call();321      expect(+balance).to.equal(150);322    }323324    {325      const balance = await contract.methods.balanceOf(receiver).call();326      expect(+balance).to.equal(50);327    }328  });329330  itEth('Can perform transferFromCross()', async ({helper}) => {331    const sender = await helper.eth.createAccountWithBalance(donor, 100n);332333    const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);334335    const receiver = helper.eth.createAccount();336337    await collection.mint(owner, 200n, {Substrate: owner.address});338    await collection.approveTokens(owner, {Ethereum: sender}, 100n);339340    const address = helper.ethAddress.fromCollectionId(collection.collectionId);341    const contract = helper.ethNativeContract.collection(address, 'ft');342343    const from = helper.ethCrossAccount.fromKeyringPair(owner);344    const to = helper.ethCrossAccount.fromAddress(receiver);345346    const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});347    const toBalanceBefore = await collection.getBalance({Ethereum: receiver});348    349    const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});350351    expect(result.events).to.be.like({352      Transfer: {353        address,354        event: 'Transfer',355        returnValues: {356          from: helper.address.substrateToEth(owner.address),357          to: receiver,358          value: '51',359        },360      },361      Approval: {362        address,363        event: 'Approval',364        returnValues: {365          owner: helper.address.substrateToEth(owner.address),366          spender: sender,367          value: '49',368        },369      }});370371    const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});372    expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n);373    const toBalanceAfter = await collection.getBalance({Ethereum: receiver});374    expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n);375  });376});377378describe('Fungible: Fees', () => {379  let donor: IKeyringPair;380  let alice: IKeyringPair;381382  before(async function() {383    await usingEthPlaygrounds(async (helper, privateKey) => {384      donor = await privateKey({filename: __filename});385      [alice] = await helper.arrange.createAccounts([20n], donor);386    });387  });388  389  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {390    const owner = await helper.eth.createAccountWithBalance(donor);391    const spender = helper.eth.createAccount();392    const collection = await helper.ft.mintCollection(alice);393    await collection.mint(alice, 200n, {Ethereum: owner});394395    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);396    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);397398    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, 100).send({from: owner}));399    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));400  });401402  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {403    const owner = await helper.eth.createAccountWithBalance(donor);404    const spender = await helper.eth.createAccountWithBalance(donor);405    const collection = await helper.ft.mintCollection(alice);406    await collection.mint(alice, 200n, {Ethereum: owner});407408    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);409    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);410411    await contract.methods.approve(spender, 100).send({from: owner});412413    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));414    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));415  });416417  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {418    const owner = await helper.eth.createAccountWithBalance(donor);419    const receiver = helper.eth.createAccount();420    const collection = await helper.ft.mintCollection(alice);421    await collection.mint(alice, 200n, {Ethereum: owner});422423    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);424    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);425426    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));427    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));428  });429});430431describe('Fungible: Substrate calls', () => {432  let donor: IKeyringPair;433  let alice: IKeyringPair;434  let owner: IKeyringPair;435436  before(async function() {437    await usingEthPlaygrounds(async (helper, privateKey) => {438      donor = await privateKey({filename: __filename});439      [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);440    });441  });442443  itEth('Events emitted for approve()', async ({helper}) => {444    const receiver = helper.eth.createAccount();445    const collection = await helper.ft.mintCollection(alice);446    await collection.mint(alice, 200n);447448    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);449    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');450451    const events: any = [];452    contract.events.allEvents((_: any, event: any) => {453      events.push(event);454    });455    456    await collection.approveTokens(alice, {Ethereum: receiver}, 100n);457    if (events.length == 0) await helper.wait.newBlocks(1);458    const event = events[0];459460    expect(event.event).to.be.equal('Approval');461    expect(event.address).to.be.equal(collectionAddress);462    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));463    expect(event.returnValues.spender).to.be.equal(receiver);464    expect(event.returnValues.value).to.be.equal('100');465  });466467  itEth('Events emitted for transferFrom()', async ({helper}) => {468    const [bob] = await helper.arrange.createAccounts([10n], donor);469    const receiver = helper.eth.createAccount();470    const collection = await helper.ft.mintCollection(alice);471    await collection.mint(alice, 200n);472    await collection.approveTokens(alice, {Substrate: bob.address}, 100n);473474    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);475    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');476477    const events: any = [];478    contract.events.allEvents((_: any, event: any) => {479      events.push(event);480    });481482    await collection.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}, 51n);483    if (events.length == 0) await helper.wait.newBlocks(1);484    let event = events[0];485486    expect(event.event).to.be.equal('Transfer');487    expect(event.address).to.be.equal(collectionAddress);488    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));489    expect(event.returnValues.to).to.be.equal(receiver);490    expect(event.returnValues.value).to.be.equal('51');491492    event = events[1];493    expect(event.event).to.be.equal('Approval');494    expect(event.address).to.be.equal(collectionAddress);495    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));496    expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(bob.address));497    expect(event.returnValues.value).to.be.equal('49');498  });499500  itEth('Events emitted for transfer()', async ({helper}) => {501    const receiver = helper.eth.createAccount();502    const collection = await helper.ft.mintCollection(alice);503    await collection.mint(alice, 200n);504505    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);506    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');507508    const events: any = [];509    contract.events.allEvents((_: any, event: any) => {510      events.push(event);511    });512    513    await collection.transfer(alice, {Ethereum:receiver}, 51n);514    if (events.length == 0) await helper.wait.newBlocks(1);515    const event = events[0];516517    expect(event.event).to.be.equal('Transfer');518    expect(event.address).to.be.equal(collectionAddress);519    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));520    expect(event.returnValues.to).to.be.equal(receiver);521    expect(event.returnValues.value).to.be.equal('51');522  });523524  itEth('Events emitted for transferFromCross()', async ({helper}) => {525    const sender = await helper.eth.createAccountWithBalance(donor, 100n);526527    const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);528529    const receiver = helper.eth.createAccount();530531    await collection.mint(owner, 200n, {Substrate: owner.address});532    await collection.approveTokens(owner, {Ethereum: sender}, 100n);533534    const address = helper.ethAddress.fromCollectionId(collection.collectionId);535    const contract = helper.ethNativeContract.collection(address, 'ft');536537    const from = helper.ethCrossAccount.fromKeyringPair(owner);538    const to = helper.ethCrossAccount.fromAddress(receiver);539    540    const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});541542    expect(result.events).to.be.like({543      Transfer: {544        address,545        event: 'Transfer',546        returnValues: {547          from: helper.address.substrateToEth(owner.address),548          to: receiver,549          value: '51',550        },551      },552      Approval: {553        address,554        event: 'Approval',555        returnValues: {556          owner: helper.address.substrateToEth(owner.address),557          spender: sender,558          value: '49',559        },560      }});561  });562});
after · tests/src/eth/fungible.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {expect, itEth, usingEthPlaygrounds} from './util';18import {IKeyringPair} from '@polkadot/types/types';1920describe('Fungible: Information getting', () => {21  let donor: IKeyringPair;22  let alice: IKeyringPair;2324  before(async function() {25    await usingEthPlaygrounds(async (helper, privateKey) => {26      donor = await privateKey({filename: __filename});27      [alice] = await helper.arrange.createAccounts([20n], donor);28    });29  });3031  itEth('totalSupply', async ({helper}) => {32    const caller = await helper.eth.createAccountWithBalance(donor);33    const collection = await helper.ft.mintCollection(alice);34    await collection.mint(alice, 200n);3536    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);37    const totalSupply = await contract.methods.totalSupply().call();38    expect(totalSupply).to.equal('200');39  });4041  itEth('balanceOf', async ({helper}) => {42    const caller = await helper.eth.createAccountWithBalance(donor);43    const collection = await helper.ft.mintCollection(alice);44    await collection.mint(alice, 200n, {Ethereum: caller});4546    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'ft', caller);47    const balance = await contract.methods.balanceOf(caller).call();48    expect(balance).to.equal('200');49  });50});5152describe('Fungible: Plain calls', () => {53  let donor: IKeyringPair;54  let alice: IKeyringPair;55  let owner: IKeyringPair;5657  before(async function() {58    await usingEthPlaygrounds(async (helper, privateKey) => {59      donor = await privateKey({filename: __filename});60      [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);61    });62  });6364  itEth('Can perform mint()', async ({helper}) => {65    const owner = await helper.eth.createAccountWithBalance(donor);66    const receiver = helper.eth.createAccount();67    const collection = await helper.ft.mintCollection(alice);68    await collection.addAdmin(alice, {Ethereum: owner});6970    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);71    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);7273    const result = await contract.methods.mint(receiver, 100).send();74    75    const event = result.events.Transfer;76    expect(event.address).to.equal(collectionAddress);77    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');78    expect(event.returnValues.to).to.equal(receiver);79    expect(event.returnValues.value).to.equal('100');80  });81  82  83  itEth('Can perform mintCross()', async ({helper}) => {84    const receiverCross = helper.ethCrossAccount.fromKeyringPair(owner);85    const ethOwner = await helper.eth.createAccountWithBalance(donor);86    const collection = await helper.ft.mintCollection(alice);87    await collection.addAdmin(alice, {Ethereum: ethOwner});8889    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);90    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', ethOwner);9192    const result = await contract.methods.mintCross(receiverCross, 100).send();93    94    const event = result.events.Transfer;95    expect(event.address).to.equal(collectionAddress);96    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');97    expect(event.returnValues.to).to.equal(helper.address.substrateToEth(owner.address));98    expect(event.returnValues.value).to.equal('100');99  });100101  itEth('Can perform mintBulk()', async ({helper}) => {102    const owner = await helper.eth.createAccountWithBalance(donor);103    const bulkSize = 3;104    const receivers = [...new Array(bulkSize)].map(() => helper.eth.createAccount());105    const collection = await helper.ft.mintCollection(alice);106    await collection.addAdmin(alice, {Ethereum: owner});107108    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);109    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);110111    const result = await contract.methods.mintBulk(Array.from({length: bulkSize}, (_, i) => (112      [receivers[i], (i + 1) * 10]113    ))).send();114    const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.value - b.returnValues.value);115    for (let i = 0; i < bulkSize; i++) {116      const event = events[i];117      expect(event.address).to.equal(collectionAddress);118      expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');119      expect(event.returnValues.to).to.equal(receivers[i]);120      expect(event.returnValues.value).to.equal(String(10 * (i + 1)));121    }122  });123124  // Soft-deprecated125  itEth('Can perform burn()', async ({helper}) => {126    const owner = await helper.eth.createAccountWithBalance(donor);127    const receiver = await helper.eth.createAccountWithBalance(donor);128    const collection = await helper.ft.mintCollection(alice);129    await collection.addAdmin(alice, {Ethereum: owner});130131    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);132    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true);133    await contract.methods.mint(receiver, 100).send();134135    const result = await contract.methods.burnFrom(receiver, 49).send({from: receiver});136    137    const event = result.events.Transfer;138    expect(event.address).to.equal(collectionAddress);139    expect(event.returnValues.from).to.equal(receiver);140    expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');141    expect(event.returnValues.value).to.equal('49');142143    const balance = await contract.methods.balanceOf(receiver).call();144    expect(balance).to.equal('51');145  });146147  itEth('Can perform approve()', async ({helper}) => {148    const owner = await helper.eth.createAccountWithBalance(donor);149    const spender = helper.eth.createAccount();150    const collection = await helper.ft.mintCollection(alice);151    await collection.mint(alice, 200n, {Ethereum: owner});152153    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);154    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);155156    {157      const result = await contract.methods.approve(spender, 100).send({from: owner});158159      const event = result.events.Approval;160      expect(event.address).to.be.equal(collectionAddress);161      expect(event.returnValues.owner).to.be.equal(owner);162      expect(event.returnValues.spender).to.be.equal(spender);163      expect(event.returnValues.value).to.be.equal('100');164    }165166    {167      const allowance = await contract.methods.allowance(owner, spender).call();168      expect(+allowance).to.equal(100);169    }170  });171172  itEth('Can perform burnFromCross()', async ({helper}) => {173    const sender = await helper.eth.createAccountWithBalance(donor, 100n);174175    const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);176177    await collection.mint(owner, 200n, {Substrate: owner.address});178    await collection.approveTokens(owner, {Ethereum: sender}, 100n);179180    const address = helper.ethAddress.fromCollectionId(collection.collectionId);181    const contract = helper.ethNativeContract.collection(address, 'ft');182183    const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});184    185    const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);186    const result = await contract.methods.burnFromCross(ownerCross, 49).send({from: sender});187    const events = result.events;188189    expect(events).to.be.like({190      Transfer: {191        address: helper.ethAddress.fromCollectionId(collection.collectionId),192        event: 'Transfer',193        returnValues: {194          from: helper.address.substrateToEth(owner.address),195          to: '0x0000000000000000000000000000000000000000',196          value: '49',197        },198      },199      Approval: {200        address: helper.ethAddress.fromCollectionId(collection.collectionId),201        returnValues: {202          owner: helper.address.substrateToEth(owner.address),203          spender: sender,204          value: '51',205        },206        event: 'Approval',207      },208    });209210    const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});211    expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(49n);212  });213214  itEth('Can perform transferFrom()', async ({helper}) => {215    const owner = await helper.eth.createAccountWithBalance(donor);216    const spender = await helper.eth.createAccountWithBalance(donor);217    const receiver = helper.eth.createAccount();218    const collection = await helper.ft.mintCollection(alice);219    await collection.mint(alice, 200n, {Ethereum: owner});220221    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);222    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);223224    await contract.methods.approve(spender, 100).send();225226    {227      const result = await contract.methods.transferFrom(owner, receiver, 49).send({from: spender});228      229      let event = result.events.Transfer;230      expect(event.address).to.be.equal(collectionAddress);231      expect(event.returnValues.from).to.be.equal(owner);232      expect(event.returnValues.to).to.be.equal(receiver);233      expect(event.returnValues.value).to.be.equal('49');234235      event = result.events.Approval;236      expect(event.address).to.be.equal(collectionAddress);237      expect(event.returnValues.owner).to.be.equal(owner);238      expect(event.returnValues.spender).to.be.equal(spender);239      expect(event.returnValues.value).to.be.equal('51');240    }241242    {243      const balance = await contract.methods.balanceOf(receiver).call();244      expect(+balance).to.equal(49);245    }246247    {248      const balance = await contract.methods.balanceOf(owner).call();249      expect(+balance).to.equal(151);250    }251  });252253  itEth('Can perform transferCross()', async ({helper}) => {254    const sender = await helper.eth.createAccountWithBalance(donor);255    const receiverEth = await helper.eth.createAccountWithBalance(donor);256    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);257    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(donor);258    const collection = await helper.ft.mintCollection(alice);259    await collection.mint(alice, 200n, {Ethereum: sender});260261    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);262    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', sender);263264    {265      // Can transferCross to ethereum address:266      const result = await collectionEvm.methods.transferCross(receiverCrossEth, 50).send({from: sender});267      // Check events:268      const event = result.events.Transfer;269      expect(event.address).to.be.equal(collectionAddress);270      expect(event.returnValues.from).to.be.equal(sender);271      expect(event.returnValues.to).to.be.equal(receiverEth);272      expect(event.returnValues.value).to.be.equal('50');273      // Sender's balance decreased:274      const ownerBalance = await collectionEvm.methods.balanceOf(sender).call();275      expect(+ownerBalance).to.equal(150);276      // Receiver's balance increased:277      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();278      expect(+receiverBalance).to.equal(50);279    }280    281    {282      // Can transferCross to substrate address:283      const result = await collectionEvm.methods.transferCross(receiverCrossSub, 50).send({from: sender});284      // Check events:285      const event = result.events.Transfer;286      expect(event.address).to.be.equal(collectionAddress);287      expect(event.returnValues.from).to.be.equal(sender);288      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(donor.address));289      expect(event.returnValues.value).to.be.equal('50');290      // Sender's balance decreased:291      const senderBalance = await collection.getBalance({Ethereum: sender});292      expect(senderBalance).to.equal(100n);293      // Receiver's balance increased:294      const balance = await collection.getBalance({Substrate: donor.address});295      expect(balance).to.equal(50n);296    }297  });298299  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} incorrect amount`, async ({helper}) => {300    const sender = await helper.eth.createAccountWithBalance(donor);301    const receiverEth = await helper.eth.createAccountWithBalance(donor);302    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);303    const BALANCE = 200n;304    const BALANCE_TO_TRANSFER = BALANCE + 100n;305306    const collection = await helper.ft.mintCollection(alice);307    await collection.mint(alice, BALANCE, {Ethereum: sender});308    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);309    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'ft', sender);310311    // 1. Cannot transfer more than have312    const receiver = testCase === 'transfer' ? receiverEth : receiverCrossEth;313    await expect(collectionEvm.methods[testCase](receiver, BALANCE_TO_TRANSFER).send({from: sender})).to.be.rejected;314    // 2. Zero transfer allowed (EIP-20):315    await collectionEvm.methods[testCase](receiver, 0n).send({from: sender});316  }));317  318  319  itEth('Can perform transfer()', async ({helper}) => {320    const owner = await helper.eth.createAccountWithBalance(donor);321    const receiver = await helper.eth.createAccountWithBalance(donor);322    const collection = await helper.ft.mintCollection(alice);323    await collection.mint(alice, 200n, {Ethereum: owner});324325    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);326    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);327328    {329      const result = await contract.methods.transfer(receiver, 50).send({from: owner});330      331      const event = result.events.Transfer;332      expect(event.address).to.be.equal(collectionAddress);333      expect(event.returnValues.from).to.be.equal(owner);334      expect(event.returnValues.to).to.be.equal(receiver);335      expect(event.returnValues.value).to.be.equal('50');336    }337338    {339      const balance = await contract.methods.balanceOf(owner).call();340      expect(+balance).to.equal(150);341    }342343    {344      const balance = await contract.methods.balanceOf(receiver).call();345      expect(+balance).to.equal(50);346    }347  });348349  itEth('Can perform transferFromCross()', async ({helper}) => {350    const sender = await helper.eth.createAccountWithBalance(donor, 100n);351352    const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);353354    const receiver = helper.eth.createAccount();355356    await collection.mint(owner, 200n, {Substrate: owner.address});357    await collection.approveTokens(owner, {Ethereum: sender}, 100n);358359    const address = helper.ethAddress.fromCollectionId(collection.collectionId);360    const contract = helper.ethNativeContract.collection(address, 'ft');361362    const from = helper.ethCrossAccount.fromKeyringPair(owner);363    const to = helper.ethCrossAccount.fromAddress(receiver);364365    const fromBalanceBefore = await collection.getBalance({Substrate: owner.address});366    const toBalanceBefore = await collection.getBalance({Ethereum: receiver});367    368    const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});369370    expect(result.events).to.be.like({371      Transfer: {372        address,373        event: 'Transfer',374        returnValues: {375          from: helper.address.substrateToEth(owner.address),376          to: receiver,377          value: '51',378        },379      },380      Approval: {381        address,382        event: 'Approval',383        returnValues: {384          owner: helper.address.substrateToEth(owner.address),385          spender: sender,386          value: '49',387        },388      }});389390    const fromBalanceAfter = await collection.getBalance({Substrate: owner.address});391    expect(fromBalanceBefore - fromBalanceAfter).to.be.eq(51n);392    const toBalanceAfter = await collection.getBalance({Ethereum: receiver});393    expect(toBalanceAfter - toBalanceBefore).to.be.eq(51n);394  });395});396397describe('Fungible: Fees', () => {398  let donor: IKeyringPair;399  let alice: IKeyringPair;400401  before(async function() {402    await usingEthPlaygrounds(async (helper, privateKey) => {403      donor = await privateKey({filename: __filename});404      [alice] = await helper.arrange.createAccounts([20n], donor);405    });406  });407  408  itEth('approve() call fee is less than 0.2UNQ', async ({helper}) => {409    const owner = await helper.eth.createAccountWithBalance(donor);410    const spender = helper.eth.createAccount();411    const collection = await helper.ft.mintCollection(alice);412    await collection.mint(alice, 200n, {Ethereum: owner});413414    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);415    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);416417    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.approve(spender, 100).send({from: owner}));418    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));419  });420421  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {422    const owner = await helper.eth.createAccountWithBalance(donor);423    const spender = await helper.eth.createAccountWithBalance(donor);424    const collection = await helper.ft.mintCollection(alice);425    await collection.mint(alice, 200n, {Ethereum: owner});426427    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);428    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);429430    await contract.methods.approve(spender, 100).send({from: owner});431432    const cost = await helper.eth.recordCallFee(spender, () => contract.methods.transferFrom(owner, spender, 100).send({from: spender}));433    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));434  });435436  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {437    const owner = await helper.eth.createAccountWithBalance(donor);438    const receiver = helper.eth.createAccount();439    const collection = await helper.ft.mintCollection(alice);440    await collection.mint(alice, 200n, {Ethereum: owner});441442    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);443    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);444445    const cost = await helper.eth.recordCallFee(owner, () => contract.methods.transfer(receiver, 100).send({from: owner}));446    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));447  });448});449450describe('Fungible: Substrate calls', () => {451  let donor: IKeyringPair;452  let alice: IKeyringPair;453  let owner: IKeyringPair;454455  before(async function() {456    await usingEthPlaygrounds(async (helper, privateKey) => {457      donor = await privateKey({filename: __filename});458      [alice, owner] = await helper.arrange.createAccounts([20n, 20n], donor);459    });460  });461462  itEth('Events emitted for approve()', async ({helper}) => {463    const receiver = helper.eth.createAccount();464    const collection = await helper.ft.mintCollection(alice);465    await collection.mint(alice, 200n);466467    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);468    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');469470    const events: any = [];471    contract.events.allEvents((_: any, event: any) => {472      events.push(event);473    });474    475    await collection.approveTokens(alice, {Ethereum: receiver}, 100n);476    if (events.length == 0) await helper.wait.newBlocks(1);477    const event = events[0];478479    expect(event.event).to.be.equal('Approval');480    expect(event.address).to.be.equal(collectionAddress);481    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));482    expect(event.returnValues.spender).to.be.equal(receiver);483    expect(event.returnValues.value).to.be.equal('100');484  });485486  itEth('Events emitted for transferFrom()', async ({helper}) => {487    const [bob] = await helper.arrange.createAccounts([10n], donor);488    const receiver = helper.eth.createAccount();489    const collection = await helper.ft.mintCollection(alice);490    await collection.mint(alice, 200n);491    await collection.approveTokens(alice, {Substrate: bob.address}, 100n);492493    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);494    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');495496    const events: any = [];497    contract.events.allEvents((_: any, event: any) => {498      events.push(event);499    });500501    await collection.transferFrom(bob, {Substrate: alice.address}, {Ethereum: receiver}, 51n);502    if (events.length == 0) await helper.wait.newBlocks(1);503    let event = events[0];504505    expect(event.event).to.be.equal('Transfer');506    expect(event.address).to.be.equal(collectionAddress);507    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));508    expect(event.returnValues.to).to.be.equal(receiver);509    expect(event.returnValues.value).to.be.equal('51');510511    event = events[1];512    expect(event.event).to.be.equal('Approval');513    expect(event.address).to.be.equal(collectionAddress);514    expect(event.returnValues.owner).to.be.equal(helper.address.substrateToEth(alice.address));515    expect(event.returnValues.spender).to.be.equal(helper.address.substrateToEth(bob.address));516    expect(event.returnValues.value).to.be.equal('49');517  });518519  itEth('Events emitted for transfer()', async ({helper}) => {520    const receiver = helper.eth.createAccount();521    const collection = await helper.ft.mintCollection(alice);522    await collection.mint(alice, 200n);523524    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);525    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft');526527    const events: any = [];528    contract.events.allEvents((_: any, event: any) => {529      events.push(event);530    });531    532    await collection.transfer(alice, {Ethereum:receiver}, 51n);533    if (events.length == 0) await helper.wait.newBlocks(1);534    const event = events[0];535536    expect(event.event).to.be.equal('Transfer');537    expect(event.address).to.be.equal(collectionAddress);538    expect(event.returnValues.from).to.be.equal(helper.address.substrateToEth(alice.address));539    expect(event.returnValues.to).to.be.equal(receiver);540    expect(event.returnValues.value).to.be.equal('51');541  });542543  itEth('Events emitted for transferFromCross()', async ({helper}) => {544    const sender = await helper.eth.createAccountWithBalance(donor, 100n);545546    const collection = await helper.ft.mintCollection(owner, {name: 'A', description: 'B', tokenPrefix: 'C'}, 0);547548    const receiver = helper.eth.createAccount();549550    await collection.mint(owner, 200n, {Substrate: owner.address});551    await collection.approveTokens(owner, {Ethereum: sender}, 100n);552553    const address = helper.ethAddress.fromCollectionId(collection.collectionId);554    const contract = helper.ethNativeContract.collection(address, 'ft');555556    const from = helper.ethCrossAccount.fromKeyringPair(owner);557    const to = helper.ethCrossAccount.fromAddress(receiver);558    559    const result = await contract.methods.transferFromCross(from, to, 51).send({from: sender});560561    expect(result.events).to.be.like({562      Transfer: {563        address,564        event: 'Transfer',565        returnValues: {566          from: helper.address.substrateToEth(owner.address),567          to: receiver,568          value: '51',569        },570      },571      Approval: {572        address,573        event: 'Approval',574        returnValues: {575          owner: helper.address.substrateToEth(owner.address),576          spender: sender,577          value: '49',578        },579      }});580  });581});
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -17,6 +17,8 @@
 import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util';
 import {IKeyringPair} from '@polkadot/types/types';
 import {Contract} from 'web3-eth-contract';
+import exp from 'constants';
+import {ITokenPropertyPermission} from '../util/playgrounds/types';
 
 
 describe('NFT: Information getting', () => {
@@ -173,7 +175,45 @@
     // const tokenUri = await contract.methods.tokenURI(nextTokenId).call();
     // expect(tokenUri).to.be.equal(`https://offchain-service.local/token-info/${nextTokenId}`);
   });
+  
+  itEth('Can perform mintCross()', async ({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);
+    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.nft.mintCollection(minter, {
+      tokenPrefix: 'ethp',
+      tokenPropertyPermissions: permissions,
+    });
+    await collection.addAdmin(minter, {Ethereum: caller});
+    
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', caller, true);
+    let expectedTokenId = await contract.methods.nextTokenId().call();
+    let result = await contract.methods.mintCross(receiverCross, []).send();
+    let tokenId = result.events.Transfer.returnValues.tokenId;
+    expect(tokenId).to.be.equal(expectedTokenId);
+
+    const event = result.events.Transfer;
+    expect(event.address).to.be.equal(collectionAddress);
+    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');
+    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(bob.address));
+    expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);
+    
+    expectedTokenId = await contract.methods.nextTokenId().call();
+    result = await contract.methods.mintCross(receiverCross, properties).send();
+    tokenId = result.events.Transfer.returnValues.tokenId;
 
+    expect(tokenId).to.be.equal(expectedTokenId);
+    
+    expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties
+      .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));
+  });
+  
   //TODO: CORE-302 add eth methods
   itEth.skip('Can perform mintBulk()', async ({helper}) => {
     const caller = await helper.eth.createAccountWithBalance(donor);
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/reFungible.test.ts
+++ b/tests/src/eth/reFungible.test.ts
@@ -17,6 +17,7 @@
 import {Pallets, requirePalletsOrSkip} from '../util';
 import {expect, itEth, usingEthPlaygrounds} from './util';
 import {IKeyringPair} from '@polkadot/types/types';
+import { ITokenPropertyPermission } from '../util/playgrounds/types';
 
 describe('Refungible: Information getting', () => {
   let donor: IKeyringPair;
@@ -135,6 +136,44 @@
     expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);
     expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');
   });
+  
+  itEth('Can perform mintCross()', async ({helper}) => {
+    const caller = await helper.eth.createAccountWithBalance(donor);
+    const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);
+    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.rft.mintCollection(minter, {
+      tokenPrefix: 'ethp',
+      tokenPropertyPermissions: permissions,
+    });
+    await collection.addAdmin(minter, {Ethereum: caller});
+    
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller, true);
+    let expectedTokenId = await contract.methods.nextTokenId().call();
+    let result = await contract.methods.mintCross(receiverCross, []).send();
+    let tokenId = result.events.Transfer.returnValues.tokenId;
+    expect(tokenId).to.be.equal(expectedTokenId);
+
+    const event = result.events.Transfer;
+    expect(event.address).to.be.equal(collectionAddress);
+    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');
+    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(bob.address));
+    expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);
+    
+    expectedTokenId = await contract.methods.nextTokenId().call();
+    result = await contract.methods.mintCross(receiverCross, properties).send();
+    tokenId = result.events.Transfer.returnValues.tokenId;
+
+    expect(tokenId).to.be.equal(expectedTokenId);
+    
+    expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties
+      .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));
+  });
 
   itEth.skip('Can perform mintBulk()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);