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
--- a/tests/src/eth/fungible.test.ts
+++ b/tests/src/eth/fungible.test.ts
@@ -78,6 +78,25 @@
     expect(event.returnValues.to).to.equal(receiver);
     expect(event.returnValues.value).to.equal('100');
   });
+  
+  
+  itEth('Can perform mintCross()', async ({helper}) => {
+    const receiverCross = helper.ethCrossAccount.fromKeyringPair(owner);
+    const ethOwner = await helper.eth.createAccountWithBalance(donor);
+    const collection = await helper.ft.mintCollection(alice);
+    await collection.addAdmin(alice, {Ethereum: ethOwner});
+
+    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);
+    const contract = helper.ethNativeContract.collection(collectionAddress, 'ft', ethOwner);
+
+    const result = await contract.methods.mintCross(receiverCross, 100).send();
+    
+    const event = result.events.Transfer;
+    expect(event.address).to.equal(collectionAddress);
+    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
+    expect(event.returnValues.to).to.equal(helper.address.substrateToEth(owner.address));
+    expect(event.returnValues.value).to.equal('100');
+  });
 
   itEth('Can perform mintBulk()', async ({helper}) => {
     const owner = await helper.eth.createAccountWithBalance(donor);
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
before · tests/src/eth/reFungible.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 {Pallets, requirePalletsOrSkip} from '../util';18import {expect, itEth, usingEthPlaygrounds} from './util';19import {IKeyringPair} from '@polkadot/types/types';2021describe('Refungible: Information getting', () => {22  let donor: IKeyringPair;2324  before(async function() {25    await usingEthPlaygrounds(async (helper, privateKey) => {26      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);2728      donor = await privateKey({filename: __filename});29    });30  });3132  itEth('totalSupply', async ({helper}) => {33    const caller = await helper.eth.createAccountWithBalance(donor);34    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'TotalSupply', '6', '6');35    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);3637    await contract.methods.mint(caller).send();3839    const totalSupply = await contract.methods.totalSupply().call();40    expect(totalSupply).to.equal('1');41  });4243  itEth('balanceOf', async ({helper}) => {44    const caller = await helper.eth.createAccountWithBalance(donor);45    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'BalanceOf', '6', '6');46    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);4748    await contract.methods.mint(caller).send();49    await contract.methods.mint(caller).send();50    await contract.methods.mint(caller).send();5152    const balance = await contract.methods.balanceOf(caller).call();53    expect(balance).to.equal('3');54  });5556  itEth('ownerOf', async ({helper}) => {57    const caller = await helper.eth.createAccountWithBalance(donor);58    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf', '6', '6');59    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);6061    const result = await contract.methods.mint(caller).send();62    const tokenId = result.events.Transfer.returnValues.tokenId;6364    const owner = await contract.methods.ownerOf(tokenId).call();65    expect(owner).to.equal(caller);66  });6768  itEth('ownerOf after burn', async ({helper}) => {69    const caller = await helper.eth.createAccountWithBalance(donor);70    const receiver = helper.eth.createAccount();71    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf-AfterBurn', '6', '6');72    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);7374    const result = await contract.methods.mint(caller).send();75    const tokenId = result.events.Transfer.returnValues.tokenId;76    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);7778    await tokenContract.methods.repartition(2).send();79    await tokenContract.methods.transfer(receiver, 1).send();8081    await tokenContract.methods.burnFrom(caller, 1).send();8283    const owner = await contract.methods.ownerOf(tokenId).call();84    expect(owner).to.equal(receiver);85  });8687  itEth('ownerOf for partial ownership', async ({helper}) => {88    const caller = await helper.eth.createAccountWithBalance(donor);89    const receiver = helper.eth.createAccount();90    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Partial-OwnerOf', '6', '6');91    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);9293    const result = await contract.methods.mint(caller).send();94    const tokenId = result.events.Transfer.returnValues.tokenId;95    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);9697    await tokenContract.methods.repartition(2).send();98    await tokenContract.methods.transfer(receiver, 1).send();99100    const owner = await contract.methods.ownerOf(tokenId).call();101    expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');102  });103});104105describe('Refungible: Plain calls', () => {106  let donor: IKeyringPair;107  let minter: IKeyringPair;108  let bob: IKeyringPair;109  let charlie: IKeyringPair;110111  before(async function() {112    await usingEthPlaygrounds(async (helper, privateKey) => {113      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);114115      donor = await privateKey({filename: __filename});116      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);117    });118  });119120  itEth('Can perform mint() & crossOwnerOf()', async ({helper}) => {121    const owner = await helper.eth.createAccountWithBalance(donor);122    const receiver = helper.eth.createAccount();123    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Minty', '6', '6', '');124    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);125126    const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();127128    const event = result.events.Transfer;129    expect(event.address).to.equal(collectionAddress);130    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');131    expect(event.returnValues.to).to.equal(receiver);132    const tokenId = event.returnValues.tokenId;133    expect(tokenId).to.be.equal('1');134135    expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);136    expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');137  });138139  itEth.skip('Can perform mintBulk()', async ({helper}) => {140    const owner = await helper.eth.createAccountWithBalance(donor);141    const receiver = helper.eth.createAccount();142    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'MintBulky', '6', '6', '');143    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);144145    {146      const nextTokenId = await contract.methods.nextTokenId().call();147      expect(nextTokenId).to.be.equal('1');148      const result = await contract.methods.mintBulkWithTokenURI(149        receiver,150        [151          [nextTokenId, 'Test URI 0'],152          [+nextTokenId + 1, 'Test URI 1'],153          [+nextTokenId + 2, 'Test URI 2'],154        ],155      ).send();156157      const events = result.events.Transfer;158      for (let i = 0; i < 2; i++) {159        const event = events[i];160        expect(event.address).to.equal(collectionAddress);161        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');162        expect(event.returnValues.to).to.equal(receiver);163        expect(event.returnValues.tokenId).to.equal(String(+nextTokenId + i));164      }165166      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');167      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');168      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');169    }170  });171172  itEth('Can perform setApprovalForAll()', async ({helper}) => {173    const owner = await helper.eth.createAccountWithBalance(donor);174    const operator = helper.eth.createAccount();175176    const collection = await helper.rft.mintCollection(minter, {});177178    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);179    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);180181    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();182    expect(approvedBefore).to.be.equal(false);183184    {185      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});186187      expect(result.events.ApprovalForAll).to.be.like({188        address: collectionAddress,189        event: 'ApprovalForAll',190        returnValues: {191          owner,192          operator,193          approved: true,194        },195      });196197      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();198      expect(approvedAfter).to.be.equal(true);199    }200201    {202      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});203204      expect(result.events.ApprovalForAll).to.be.like({205        address: collectionAddress,206        event: 'ApprovalForAll',207        returnValues: {208          owner,209          operator,210          approved: false,211        },212      });213214      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();215      expect(approvedAfter).to.be.equal(false);216    }217  });218219  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {220    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});221222    const owner = await helper.eth.createAccountWithBalance(donor);223    const operator = await helper.eth.createAccountWithBalance(donor, 100n);224225    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});226227    const address = helper.ethAddress.fromCollectionId(collection.collectionId);228    const contract = helper.ethNativeContract.collection(address, 'rft');229230    {231      await contract.methods.setApprovalForAll(operator, true).send({from: owner});232      const ownerCross = helper.ethCrossAccount.fromAddress(owner);233      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});234      const events = result.events.Transfer;235236      expect(events).to.be.like({237        address,238        event: 'Transfer',239        returnValues: {240          from: owner,241          to: '0x0000000000000000000000000000000000000000',242          tokenId: token.tokenId.toString(),243        },244      });245    }246  });247248  itEth('Can perform burn with approve and approvalForAll', async ({helper}) => {249    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});250251    const owner = await helper.eth.createAccountWithBalance(donor);252    const operator = await helper.eth.createAccountWithBalance(donor, 100n);253254    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});255256    const address = helper.ethAddress.fromCollectionId(collection.collectionId);257    const contract = helper.ethNativeContract.collection(address, 'rft');258259    const rftToken = helper.ethNativeContract.rftTokenById(token.collectionId, token.tokenId, owner);260261    {262      await rftToken.methods.approve(operator, 15n).send({from: owner});263      await contract.methods.setApprovalForAll(operator, true).send({from: owner});264      await rftToken.methods.burnFrom(owner, 10n).send({from: operator});265      const allowance = await rftToken.methods.allowance(owner, operator).call();266      expect(allowance).to.be.equal('5');267    }268  });269  270  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {271    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});272273    const owner = await helper.eth.createAccountWithBalance(donor);274    const operator = await helper.eth.createAccountWithBalance(donor);275    const receiver = charlie;276277    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});278279    const address = helper.ethAddress.fromCollectionId(collection.collectionId);280    const contract = helper.ethNativeContract.collection(address, 'rft');281282    {283      await contract.methods.setApprovalForAll(operator, true).send({from: owner});284      const ownerCross = helper.ethCrossAccount.fromAddress(owner);285      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);286      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});287      const event = result.events.Transfer;288      expect(event).to.be.like({289        address: helper.ethAddress.fromCollectionId(collection.collectionId),290        event: 'Transfer',291        returnValues: {292          from: owner,293          to: helper.address.substrateToEth(receiver.address),294          tokenId: token.tokenId.toString(),295        },296      });297    }298299    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);300  });301302  itEth('Can perform burn()', async ({helper}) => {303    const caller = await helper.eth.createAccountWithBalance(donor);304    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');305    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);306307    const result = await contract.methods.mint(caller).send();308    const tokenId = result.events.Transfer.returnValues.tokenId;309    {310      const result = await contract.methods.burn(tokenId).send();311      const event = result.events.Transfer;312      expect(event.address).to.equal(collectionAddress);313      expect(event.returnValues.from).to.equal(caller);314      expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');315      expect(event.returnValues.tokenId).to.equal(tokenId.toString());316    }317  });318319  itEth('Can perform transferFrom()', async ({helper}) => {320    const caller = await helper.eth.createAccountWithBalance(donor);321    const receiver = helper.eth.createAccount();322    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');323    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);324325    const result = await contract.methods.mint(caller).send();326    const tokenId = result.events.Transfer.returnValues.tokenId;327328    const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId);329330    const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, caller);331    await tokenContract.methods.repartition(15).send();332333    {334      const tokenEvents: any = [];335      tokenContract.events.allEvents((_: any, event: any) => {336        tokenEvents.push(event);337      });338      const result = await contract.methods.transferFrom(caller, receiver, tokenId).send();339      if (tokenEvents.length == 0) await helper.wait.newBlocks(1);340341      let event = result.events.Transfer;342      expect(event.address).to.equal(collectionAddress);343      expect(event.returnValues.from).to.equal(caller);344      expect(event.returnValues.to).to.equal(receiver);345      expect(event.returnValues.tokenId).to.equal(tokenId.toString());346347      event = tokenEvents[0];348      expect(event.address).to.equal(tokenAddress);349      expect(event.returnValues.from).to.equal(caller);350      expect(event.returnValues.to).to.equal(receiver);351      expect(event.returnValues.value).to.equal('15');352    }353354    {355      const balance = await contract.methods.balanceOf(receiver).call();356      expect(+balance).to.equal(1);357    }358359    {360      const balance = await contract.methods.balanceOf(caller).call();361      expect(+balance).to.equal(0);362    }363  });364365  // Soft-deprecated366  itEth('Can perform burnFrom()', async ({helper}) => {367    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});368369    const owner = await helper.eth.createAccountWithBalance(donor, 100n);370    const spender = await helper.eth.createAccountWithBalance(donor, 100n);371372    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});373374    const address = helper.ethAddress.fromCollectionId(collection.collectionId);375    const contract = helper.ethNativeContract.collection(address, 'rft', spender, true);376377    const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);378    const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, owner);379    await tokenContract.methods.repartition(15).send();380    await tokenContract.methods.approve(spender, 15).send();381382    {383      const result = await contract.methods.burnFrom(owner, token.tokenId).send();384      const event = result.events.Transfer;385      expect(event).to.be.like({386        address: helper.ethAddress.fromCollectionId(collection.collectionId),387        event: 'Transfer',388        returnValues: {389          from: owner,390          to: '0x0000000000000000000000000000000000000000',391          tokenId: token.tokenId.toString(),392        },393      });394    }395396    expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);397  });398399  itEth('Can perform burnFromCross()', async ({helper}) => {400    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});401    402    const owner = bob;403    const spender = await helper.eth.createAccountWithBalance(donor, 100n);404405    const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});406407    const address = helper.ethAddress.fromCollectionId(collection.collectionId);408    const contract = helper.ethNativeContract.collection(address, 'rft');409410    await token.repartition(owner, 15n);411    await token.approve(owner, {Ethereum: spender}, 15n);412413    {414      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);415      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});416      const event = result.events.Transfer;417      expect(event).to.be.like({418        address: helper.ethAddress.fromCollectionId(collection.collectionId),419        event: 'Transfer',420        returnValues: {421          from: helper.address.substrateToEth(owner.address),422          to: '0x0000000000000000000000000000000000000000',423          tokenId: token.tokenId.toString(),424        },425      });426    }427428    expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);429  });430431  itEth('Can perform transferFromCross()', async ({helper}) => {432    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});433434    const owner = bob;435    const spender = await helper.eth.createAccountWithBalance(donor, 100n);436    const receiver = charlie;437438    const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});439440    const address = helper.ethAddress.fromCollectionId(collection.collectionId);441    const contract = helper.ethNativeContract.collection(address, 'rft');442443    await token.repartition(owner, 15n);444    await token.approve(owner, {Ethereum: spender}, 15n);445446    {447      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);448      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);449      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});450      const event = result.events.Transfer;451      expect(event).to.be.like({452        address: helper.ethAddress.fromCollectionId(collection.collectionId),453        event: 'Transfer',454        returnValues: {455          from: helper.address.substrateToEth(owner.address),456          to: helper.address.substrateToEth(receiver.address),457          tokenId: token.tokenId.toString(),458        },459      });460    }461462    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);463  });464465  itEth('Can perform transfer()', async ({helper}) => {466    const caller = await helper.eth.createAccountWithBalance(donor);467    const receiver = helper.eth.createAccount();468    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');469    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);470471    const result = await contract.methods.mint(caller).send();472    const tokenId = result.events.Transfer.returnValues.tokenId;473474    {475      const result = await contract.methods.transfer(receiver, tokenId).send();476477      const event = result.events.Transfer;478      expect(event.address).to.equal(collectionAddress);479      expect(event.returnValues.from).to.equal(caller);480      expect(event.returnValues.to).to.equal(receiver);481      expect(event.returnValues.tokenId).to.equal(tokenId.toString());482    }483484    {485      const balance = await contract.methods.balanceOf(caller).call();486      expect(+balance).to.equal(0);487    }488489    {490      const balance = await contract.methods.balanceOf(receiver).call();491      expect(+balance).to.equal(1);492    }493  });494  495  itEth('Can perform transferCross()', async ({helper}) => {496    const sender = await helper.eth.createAccountWithBalance(donor);497    const receiverEth = await helper.eth.createAccountWithBalance(donor);498    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);499    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);500501    const collection = await helper.rft.mintCollection(minter, {});502    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);503    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);504505    const token = await collection.mintToken(minter, 50n, {Ethereum: sender});506507    {508      // Can transferCross to ethereum address:509      const result = await collectionEvm.methods.transferCross(receiverCrossEth, token.tokenId).send({from: sender});510      // Check events:511      const event = result.events.Transfer;512      expect(event.address).to.equal(collectionAddress);513      expect(event.returnValues.from).to.equal(sender);514      expect(event.returnValues.to).to.equal(receiverEth);515      expect(event.returnValues.tokenId).to.equal(token.tokenId.toString());516      // Sender's balance decreased:517      const senderBalance = await collectionEvm.methods.balanceOf(sender).call();518      expect(+senderBalance).to.equal(0);519      expect(await token.getBalance({Ethereum: sender})).to.eq(0n);520      // Receiver's balance increased:521      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();522      expect(+receiverBalance).to.equal(1);523      expect(await token.getBalance({Ethereum: receiverEth})).to.eq(50n);524    }525    526    {527      // Can transferCross to substrate address:528      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, token.tokenId).send({from: receiverEth});529      // Check events:530      const event = substrateResult.events.Transfer;531      expect(event.address).to.be.equal(collectionAddress);532      expect(event.returnValues.from).to.be.equal(receiverEth);533      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));534      expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);535      // Sender's balance decreased:536      const senderBalance = await collectionEvm.methods.balanceOf(receiverEth).call();537      expect(+senderBalance).to.equal(0);538      expect(await token.getBalance({Ethereum: receiverEth})).to.eq(0n);539      // Receiver's balance increased:540      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});541      expect(receiverBalance).to.contain(token.tokenId);542      expect(await token.getBalance({Substrate: minter.address})).to.eq(50n);543    }544  });545546  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {547    const sender = await helper.eth.createAccountWithBalance(donor);548    const tokenOwner = await helper.eth.createAccountWithBalance(donor);549    const receiverSub = minter;550    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);551552    const collection = await helper.rft.mintCollection(minter, {});553    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);554    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);555556    await collection.mintToken(minter, 50n, {Ethereum: sender});557    const nonSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});558559    // Cannot transferCross someone else's token:560    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;561    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;562    // Cannot transfer token if it does not exist:563    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;564  }));565566  itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {567    const caller = await helper.eth.createAccountWithBalance(donor);568    const receiver = helper.eth.createAccount();569    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');570    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);571572    const result = await contract.methods.mint(caller).send();573    const tokenId = result.events.Transfer.returnValues.tokenId;574575    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);576577    await tokenContract.methods.repartition(2).send();578    await tokenContract.methods.transfer(receiver, 1).send();579580    const events: any = [];581    contract.events.allEvents((_: any, event: any) => {582      events.push(event);583    });584585    await tokenContract.methods.transfer(receiver, 1).send();586    if (events.length == 0) await helper.wait.newBlocks(1);587    const event = events[0];588589    expect(event.address).to.equal(collectionAddress);590    expect(event.returnValues.from).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');591    expect(event.returnValues.to).to.equal(receiver);592    expect(event.returnValues.tokenId).to.equal(tokenId.toString());593  });594595  itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {596    const caller = await helper.eth.createAccountWithBalance(donor);597    const receiver = helper.eth.createAccount();598    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');599    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);600601    const result = await contract.methods.mint(caller).send();602    const tokenId = result.events.Transfer.returnValues.tokenId;603604    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);605606    await tokenContract.methods.repartition(2).send();607608    const events: any = [];609    contract.events.allEvents((_: any, event: any) => {610      events.push(event);611    });612613    await tokenContract.methods.transfer(receiver, 1).send();614    if (events.length == 0) await helper.wait.newBlocks(1);615    const event = events[0];616617    expect(event.address).to.equal(collectionAddress);618    expect(event.returnValues.from).to.equal(caller);619    expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');620    expect(event.returnValues.tokenId).to.equal(tokenId.toString());621  });622});623624describe('RFT: Fees', () => {625  let donor: IKeyringPair;626627  before(async function() {628    await usingEthPlaygrounds(async (helper, privateKey) => {629      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);630631      donor = await privateKey({filename: __filename});632    });633  });634635  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {636    const caller = await helper.eth.createAccountWithBalance(donor);637    const receiver = helper.eth.createAccount();638    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');639    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);640641    const result = await contract.methods.mint(caller).send();642    const tokenId = result.events.Transfer.returnValues.tokenId;643644    const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send());645    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));646    expect(cost > 0n);647  });648649  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {650    const caller = await helper.eth.createAccountWithBalance(donor);651    const receiver = helper.eth.createAccount();652    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');653    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);654655    const result = await contract.methods.mint(caller).send();656    const tokenId = result.events.Transfer.returnValues.tokenId;657658    const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transfer(receiver, tokenId).send());659    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));660    expect(cost > 0n);661  });662});663664describe('Common metadata', () => {665  let donor: IKeyringPair;666  let alice: IKeyringPair;667668  before(async function() {669    await usingEthPlaygrounds(async (helper, privateKey) => {670      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);671672      donor = await privateKey({filename: __filename});673      [alice] = await helper.arrange.createAccounts([20n], donor);674    });675  });676677  itEth('Returns collection name', async ({helper}) => {678    const caller = helper.eth.createAccount();679    const tokenPropertyPermissions = [{680      key: 'URI',681      permission: {682        mutable: true,683        collectionAdmin: true,684        tokenOwner: false,685      },686    }];687    const collection = await helper.rft.mintCollection(688      alice,689      {690        name: 'Leviathan',691        tokenPrefix: '11',692        properties: [{key: 'ERC721Metadata', value: '1'}],693        tokenPropertyPermissions,694      },695    );696697    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'rft', caller);698    const name = await contract.methods.name().call();699    expect(name).to.equal('Leviathan');700  });701702  itEth('Returns symbol name', async ({helper}) => {703    const caller = await helper.eth.createAccountWithBalance(donor);704    const tokenPropertyPermissions = [{705      key: 'URI',706      permission: {707        mutable: true,708        collectionAdmin: true,709        tokenOwner: false,710      },711    }];712    const {collectionId} = await helper.rft.mintCollection(713      alice,714      {715        name: 'Leviathan',716        tokenPrefix: '12',717        properties: [{key: 'ERC721Metadata', value: '1'}],718        tokenPropertyPermissions,719      },720    );721722    const contract = helper.ethNativeContract.collectionById(collectionId, 'rft', caller);723    const symbol = await contract.methods.symbol().call();724    expect(symbol).to.equal('12');725  });726});727728describe('Negative tests', () => {729  let donor: IKeyringPair;730  let minter: IKeyringPair;731  let alice: IKeyringPair;732733  before(async function() {734    await usingEthPlaygrounds(async (helper, privateKey) => {735      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);736      737      donor = await privateKey({filename: __filename});738      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);739    });740  });741742  itEth('[negative] Cant perform burn without approval', async ({helper}) => {743    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});744745    const owner = await helper.eth.createAccountWithBalance(donor, 100n);746    const spender = await helper.eth.createAccountWithBalance(donor, 100n);747748    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});749750    const address = helper.ethAddress.fromCollectionId(collection.collectionId);751    const contract = helper.ethNativeContract.collection(address, 'rft');752753    const ownerCross = helper.ethCrossAccount.fromAddress(owner);754755    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;756757    await contract.methods.setApprovalForAll(spender, true).send({from: owner});758    await contract.methods.setApprovalForAll(spender, false).send({from: owner});759760    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;761  });762763  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {764    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});765    const owner = await helper.eth.createAccountWithBalance(donor, 100n);766    const receiver = alice;767768    const spender = await helper.eth.createAccountWithBalance(donor, 100n);769770    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});771772    const address = helper.ethAddress.fromCollectionId(collection.collectionId);773    const contract = helper.ethNativeContract.collection(address, 'rft');774775    const ownerCross = helper.ethCrossAccount.fromAddress(owner);776    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);777    778    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;779780    await contract.methods.setApprovalForAll(spender, true).send({from: owner});781    await contract.methods.setApprovalForAll(spender, false).send({from: owner});782    783    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;784  });785});
after · tests/src/eth/reFungible.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 {Pallets, requirePalletsOrSkip} from '../util';18import {expect, itEth, usingEthPlaygrounds} from './util';19import {IKeyringPair} from '@polkadot/types/types';20import { ITokenPropertyPermission } from '../util/playgrounds/types';2122describe('Refungible: Information getting', () => {23  let donor: IKeyringPair;2425  before(async function() {26    await usingEthPlaygrounds(async (helper, privateKey) => {27      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);2829      donor = await privateKey({filename: __filename});30    });31  });3233  itEth('totalSupply', async ({helper}) => {34    const caller = await helper.eth.createAccountWithBalance(donor);35    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'TotalSupply', '6', '6');36    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);3738    await contract.methods.mint(caller).send();3940    const totalSupply = await contract.methods.totalSupply().call();41    expect(totalSupply).to.equal('1');42  });4344  itEth('balanceOf', async ({helper}) => {45    const caller = await helper.eth.createAccountWithBalance(donor);46    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'BalanceOf', '6', '6');47    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);4849    await contract.methods.mint(caller).send();50    await contract.methods.mint(caller).send();51    await contract.methods.mint(caller).send();5253    const balance = await contract.methods.balanceOf(caller).call();54    expect(balance).to.equal('3');55  });5657  itEth('ownerOf', async ({helper}) => {58    const caller = await helper.eth.createAccountWithBalance(donor);59    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf', '6', '6');60    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);6162    const result = await contract.methods.mint(caller).send();63    const tokenId = result.events.Transfer.returnValues.tokenId;6465    const owner = await contract.methods.ownerOf(tokenId).call();66    expect(owner).to.equal(caller);67  });6869  itEth('ownerOf after burn', async ({helper}) => {70    const caller = await helper.eth.createAccountWithBalance(donor);71    const receiver = helper.eth.createAccount();72    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'OwnerOf-AfterBurn', '6', '6');73    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);7475    const result = await contract.methods.mint(caller).send();76    const tokenId = result.events.Transfer.returnValues.tokenId;77    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);7879    await tokenContract.methods.repartition(2).send();80    await tokenContract.methods.transfer(receiver, 1).send();8182    await tokenContract.methods.burnFrom(caller, 1).send();8384    const owner = await contract.methods.ownerOf(tokenId).call();85    expect(owner).to.equal(receiver);86  });8788  itEth('ownerOf for partial ownership', async ({helper}) => {89    const caller = await helper.eth.createAccountWithBalance(donor);90    const receiver = helper.eth.createAccount();91    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Partial-OwnerOf', '6', '6');92    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);9394    const result = await contract.methods.mint(caller).send();95    const tokenId = result.events.Transfer.returnValues.tokenId;96    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);9798    await tokenContract.methods.repartition(2).send();99    await tokenContract.methods.transfer(receiver, 1).send();100101    const owner = await contract.methods.ownerOf(tokenId).call();102    expect(owner).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');103  });104});105106describe('Refungible: Plain calls', () => {107  let donor: IKeyringPair;108  let minter: IKeyringPair;109  let bob: IKeyringPair;110  let charlie: IKeyringPair;111112  before(async function() {113    await usingEthPlaygrounds(async (helper, privateKey) => {114      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);115116      donor = await privateKey({filename: __filename});117      [minter, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);118    });119  });120121  itEth('Can perform mint() & crossOwnerOf()', async ({helper}) => {122    const owner = await helper.eth.createAccountWithBalance(donor);123    const receiver = helper.eth.createAccount();124    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Minty', '6', '6', '');125    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);126127    const result = await contract.methods.mintWithTokenURI(receiver, 'Test URI').send();128129    const event = result.events.Transfer;130    expect(event.address).to.equal(collectionAddress);131    expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');132    expect(event.returnValues.to).to.equal(receiver);133    const tokenId = event.returnValues.tokenId;134    expect(tokenId).to.be.equal('1');135136    expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']);137    expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI');138  });139  140  itEth('Can perform mintCross()', async ({helper}) => {141    const caller = await helper.eth.createAccountWithBalance(donor);142    const receiverCross = helper.ethCrossAccount.fromKeyringPair(bob);143    const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; });144    const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true,145      collectionAdmin: true,146      mutable: true}}; });147    148    149    const collection = await helper.rft.mintCollection(minter, {150      tokenPrefix: 'ethp',151      tokenPropertyPermissions: permissions,152    });153    await collection.addAdmin(minter, {Ethereum: caller});154    155    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);156    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller, true);157    let expectedTokenId = await contract.methods.nextTokenId().call();158    let result = await contract.methods.mintCross(receiverCross, []).send();159    let tokenId = result.events.Transfer.returnValues.tokenId;160    expect(tokenId).to.be.equal(expectedTokenId);161162    const event = result.events.Transfer;163    expect(event.address).to.be.equal(collectionAddress);164    expect(event.returnValues.from).to.be.equal('0x0000000000000000000000000000000000000000');165    expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(bob.address));166    expect(await contract.methods.properties(tokenId, []).call()).to.be.like([]);167    168    expectedTokenId = await contract.methods.nextTokenId().call();169    result = await contract.methods.mintCross(receiverCross, properties).send();170    tokenId = result.events.Transfer.returnValues.tokenId;171172    expect(tokenId).to.be.equal(expectedTokenId);173    174    expect(await contract.methods.properties(tokenId, []).call()).to.be.like(properties175      .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); }));176  });177178  itEth.skip('Can perform mintBulk()', async ({helper}) => {179    const owner = await helper.eth.createAccountWithBalance(donor);180    const receiver = helper.eth.createAccount();181    const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'MintBulky', '6', '6', '');182    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);183184    {185      const nextTokenId = await contract.methods.nextTokenId().call();186      expect(nextTokenId).to.be.equal('1');187      const result = await contract.methods.mintBulkWithTokenURI(188        receiver,189        [190          [nextTokenId, 'Test URI 0'],191          [+nextTokenId + 1, 'Test URI 1'],192          [+nextTokenId + 2, 'Test URI 2'],193        ],194      ).send();195196      const events = result.events.Transfer;197      for (let i = 0; i < 2; i++) {198        const event = events[i];199        expect(event.address).to.equal(collectionAddress);200        expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');201        expect(event.returnValues.to).to.equal(receiver);202        expect(event.returnValues.tokenId).to.equal(String(+nextTokenId + i));203      }204205      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');206      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');207      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');208    }209  });210211  itEth('Can perform setApprovalForAll()', async ({helper}) => {212    const owner = await helper.eth.createAccountWithBalance(donor);213    const operator = helper.eth.createAccount();214215    const collection = await helper.rft.mintCollection(minter, {});216217    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);218    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);219220    const approvedBefore = await contract.methods.isApprovedForAll(owner, operator).call();221    expect(approvedBefore).to.be.equal(false);222223    {224      const result = await contract.methods.setApprovalForAll(operator, true).send({from: owner});225226      expect(result.events.ApprovalForAll).to.be.like({227        address: collectionAddress,228        event: 'ApprovalForAll',229        returnValues: {230          owner,231          operator,232          approved: true,233        },234      });235236      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();237      expect(approvedAfter).to.be.equal(true);238    }239240    {241      const result = await contract.methods.setApprovalForAll(operator, false).send({from: owner});242243      expect(result.events.ApprovalForAll).to.be.like({244        address: collectionAddress,245        event: 'ApprovalForAll',246        returnValues: {247          owner,248          operator,249          approved: false,250        },251      });252253      const approvedAfter = await contract.methods.isApprovedForAll(owner, operator).call();254      expect(approvedAfter).to.be.equal(false);255    }256  });257258  itEth('Can perform burn with ApprovalForAll', async ({helper}) => {259    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});260261    const owner = await helper.eth.createAccountWithBalance(donor);262    const operator = await helper.eth.createAccountWithBalance(donor, 100n);263264    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});265266    const address = helper.ethAddress.fromCollectionId(collection.collectionId);267    const contract = helper.ethNativeContract.collection(address, 'rft');268269    {270      await contract.methods.setApprovalForAll(operator, true).send({from: owner});271      const ownerCross = helper.ethCrossAccount.fromAddress(owner);272      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: operator});273      const events = result.events.Transfer;274275      expect(events).to.be.like({276        address,277        event: 'Transfer',278        returnValues: {279          from: owner,280          to: '0x0000000000000000000000000000000000000000',281          tokenId: token.tokenId.toString(),282        },283      });284    }285  });286287  itEth('Can perform burn with approve and approvalForAll', async ({helper}) => {288    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});289290    const owner = await helper.eth.createAccountWithBalance(donor);291    const operator = await helper.eth.createAccountWithBalance(donor, 100n);292293    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});294295    const address = helper.ethAddress.fromCollectionId(collection.collectionId);296    const contract = helper.ethNativeContract.collection(address, 'rft');297298    const rftToken = helper.ethNativeContract.rftTokenById(token.collectionId, token.tokenId, owner);299300    {301      await rftToken.methods.approve(operator, 15n).send({from: owner});302      await contract.methods.setApprovalForAll(operator, true).send({from: owner});303      await rftToken.methods.burnFrom(owner, 10n).send({from: operator});304      const allowance = await rftToken.methods.allowance(owner, operator).call();305      expect(allowance).to.be.equal('5');306    }307  });308  309  itEth('Can perform transfer with ApprovalForAll', async ({helper}) => {310    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});311312    const owner = await helper.eth.createAccountWithBalance(donor);313    const operator = await helper.eth.createAccountWithBalance(donor);314    const receiver = charlie;315316    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});317318    const address = helper.ethAddress.fromCollectionId(collection.collectionId);319    const contract = helper.ethNativeContract.collection(address, 'rft');320321    {322      await contract.methods.setApprovalForAll(operator, true).send({from: owner});323      const ownerCross = helper.ethCrossAccount.fromAddress(owner);324      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);325      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: operator});326      const event = result.events.Transfer;327      expect(event).to.be.like({328        address: helper.ethAddress.fromCollectionId(collection.collectionId),329        event: 'Transfer',330        returnValues: {331          from: owner,332          to: helper.address.substrateToEth(receiver.address),333          tokenId: token.tokenId.toString(),334        },335      });336    }337338    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);339  });340341  itEth('Can perform burn()', async ({helper}) => {342    const caller = await helper.eth.createAccountWithBalance(donor);343    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Burny', '6', '6');344    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);345346    const result = await contract.methods.mint(caller).send();347    const tokenId = result.events.Transfer.returnValues.tokenId;348    {349      const result = await contract.methods.burn(tokenId).send();350      const event = result.events.Transfer;351      expect(event.address).to.equal(collectionAddress);352      expect(event.returnValues.from).to.equal(caller);353      expect(event.returnValues.to).to.equal('0x0000000000000000000000000000000000000000');354      expect(event.returnValues.tokenId).to.equal(tokenId.toString());355    }356  });357358  itEth('Can perform transferFrom()', async ({helper}) => {359    const caller = await helper.eth.createAccountWithBalance(donor);360    const receiver = helper.eth.createAccount();361    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'TransferFromy', '6', '6');362    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);363364    const result = await contract.methods.mint(caller).send();365    const tokenId = result.events.Transfer.returnValues.tokenId;366367    const tokenAddress = helper.ethAddress.fromTokenId(collectionId, tokenId);368369    const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, caller);370    await tokenContract.methods.repartition(15).send();371372    {373      const tokenEvents: any = [];374      tokenContract.events.allEvents((_: any, event: any) => {375        tokenEvents.push(event);376      });377      const result = await contract.methods.transferFrom(caller, receiver, tokenId).send();378      if (tokenEvents.length == 0) await helper.wait.newBlocks(1);379380      let event = result.events.Transfer;381      expect(event.address).to.equal(collectionAddress);382      expect(event.returnValues.from).to.equal(caller);383      expect(event.returnValues.to).to.equal(receiver);384      expect(event.returnValues.tokenId).to.equal(tokenId.toString());385386      event = tokenEvents[0];387      expect(event.address).to.equal(tokenAddress);388      expect(event.returnValues.from).to.equal(caller);389      expect(event.returnValues.to).to.equal(receiver);390      expect(event.returnValues.value).to.equal('15');391    }392393    {394      const balance = await contract.methods.balanceOf(receiver).call();395      expect(+balance).to.equal(1);396    }397398    {399      const balance = await contract.methods.balanceOf(caller).call();400      expect(+balance).to.equal(0);401    }402  });403404  // Soft-deprecated405  itEth('Can perform burnFrom()', async ({helper}) => {406    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});407408    const owner = await helper.eth.createAccountWithBalance(donor, 100n);409    const spender = await helper.eth.createAccountWithBalance(donor, 100n);410411    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});412413    const address = helper.ethAddress.fromCollectionId(collection.collectionId);414    const contract = helper.ethNativeContract.collection(address, 'rft', spender, true);415416    const tokenAddress = helper.ethAddress.fromTokenId(collection.collectionId, token.tokenId);417    const tokenContract = helper.ethNativeContract.rftToken(tokenAddress, owner);418    await tokenContract.methods.repartition(15).send();419    await tokenContract.methods.approve(spender, 15).send();420421    {422      const result = await contract.methods.burnFrom(owner, token.tokenId).send();423      const event = result.events.Transfer;424      expect(event).to.be.like({425        address: helper.ethAddress.fromCollectionId(collection.collectionId),426        event: 'Transfer',427        returnValues: {428          from: owner,429          to: '0x0000000000000000000000000000000000000000',430          tokenId: token.tokenId.toString(),431        },432      });433    }434435    expect(await collection.getTokenBalance(token.tokenId, {Ethereum: owner})).to.be.eq(0n);436  });437438  itEth('Can perform burnFromCross()', async ({helper}) => {439    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});440    441    const owner = bob;442    const spender = await helper.eth.createAccountWithBalance(donor, 100n);443444    const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});445446    const address = helper.ethAddress.fromCollectionId(collection.collectionId);447    const contract = helper.ethNativeContract.collection(address, 'rft');448449    await token.repartition(owner, 15n);450    await token.approve(owner, {Ethereum: spender}, 15n);451452    {453      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);454      const result = await contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender});455      const event = result.events.Transfer;456      expect(event).to.be.like({457        address: helper.ethAddress.fromCollectionId(collection.collectionId),458        event: 'Transfer',459        returnValues: {460          from: helper.address.substrateToEth(owner.address),461          to: '0x0000000000000000000000000000000000000000',462          tokenId: token.tokenId.toString(),463        },464      });465    }466467    expect(await collection.getTokenBalance(token.tokenId, {Substrate: owner.address})).to.be.eq(0n);468  });469470  itEth('Can perform transferFromCross()', async ({helper}) => {471    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});472473    const owner = bob;474    const spender = await helper.eth.createAccountWithBalance(donor, 100n);475    const receiver = charlie;476477    const token = await collection.mintToken(minter, 100n, {Substrate: owner.address});478479    const address = helper.ethAddress.fromCollectionId(collection.collectionId);480    const contract = helper.ethNativeContract.collection(address, 'rft');481482    await token.repartition(owner, 15n);483    await token.approve(owner, {Ethereum: spender}, 15n);484485    {486      const ownerCross = helper.ethCrossAccount.fromKeyringPair(owner);487      const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);488      const result = await contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender});489      const event = result.events.Transfer;490      expect(event).to.be.like({491        address: helper.ethAddress.fromCollectionId(collection.collectionId),492        event: 'Transfer',493        returnValues: {494          from: helper.address.substrateToEth(owner.address),495          to: helper.address.substrateToEth(receiver.address),496          tokenId: token.tokenId.toString(),497        },498      });499    }500501    expect(await token.getTop10Owners()).to.be.like([{Substrate: receiver.address}]);502  });503504  itEth('Can perform transfer()', async ({helper}) => {505    const caller = await helper.eth.createAccountWithBalance(donor);506    const receiver = helper.eth.createAccount();507    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry', '6', '6');508    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);509510    const result = await contract.methods.mint(caller).send();511    const tokenId = result.events.Transfer.returnValues.tokenId;512513    {514      const result = await contract.methods.transfer(receiver, tokenId).send();515516      const event = result.events.Transfer;517      expect(event.address).to.equal(collectionAddress);518      expect(event.returnValues.from).to.equal(caller);519      expect(event.returnValues.to).to.equal(receiver);520      expect(event.returnValues.tokenId).to.equal(tokenId.toString());521    }522523    {524      const balance = await contract.methods.balanceOf(caller).call();525      expect(+balance).to.equal(0);526    }527528    {529      const balance = await contract.methods.balanceOf(receiver).call();530      expect(+balance).to.equal(1);531    }532  });533  534  itEth('Can perform transferCross()', async ({helper}) => {535    const sender = await helper.eth.createAccountWithBalance(donor);536    const receiverEth = await helper.eth.createAccountWithBalance(donor);537    const receiverCrossEth = helper.ethCrossAccount.fromAddress(receiverEth);538    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);539540    const collection = await helper.rft.mintCollection(minter, {});541    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);542    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);543544    const token = await collection.mintToken(minter, 50n, {Ethereum: sender});545546    {547      // Can transferCross to ethereum address:548      const result = await collectionEvm.methods.transferCross(receiverCrossEth, token.tokenId).send({from: sender});549      // Check events:550      const event = result.events.Transfer;551      expect(event.address).to.equal(collectionAddress);552      expect(event.returnValues.from).to.equal(sender);553      expect(event.returnValues.to).to.equal(receiverEth);554      expect(event.returnValues.tokenId).to.equal(token.tokenId.toString());555      // Sender's balance decreased:556      const senderBalance = await collectionEvm.methods.balanceOf(sender).call();557      expect(+senderBalance).to.equal(0);558      expect(await token.getBalance({Ethereum: sender})).to.eq(0n);559      // Receiver's balance increased:560      const receiverBalance = await collectionEvm.methods.balanceOf(receiverEth).call();561      expect(+receiverBalance).to.equal(1);562      expect(await token.getBalance({Ethereum: receiverEth})).to.eq(50n);563    }564    565    {566      // Can transferCross to substrate address:567      const substrateResult = await collectionEvm.methods.transferCross(receiverCrossSub, token.tokenId).send({from: receiverEth});568      // Check events:569      const event = substrateResult.events.Transfer;570      expect(event.address).to.be.equal(collectionAddress);571      expect(event.returnValues.from).to.be.equal(receiverEth);572      expect(event.returnValues.to).to.be.equal(helper.address.substrateToEth(minter.address));573      expect(event.returnValues.tokenId).to.be.equal(`${token.tokenId}`);574      // Sender's balance decreased:575      const senderBalance = await collectionEvm.methods.balanceOf(receiverEth).call();576      expect(+senderBalance).to.equal(0);577      expect(await token.getBalance({Ethereum: receiverEth})).to.eq(0n);578      // Receiver's balance increased:579      const receiverBalance = await helper.nft.getTokensByAddress(collection.collectionId, {Substrate: minter.address});580      expect(receiverBalance).to.contain(token.tokenId);581      expect(await token.getBalance({Substrate: minter.address})).to.eq(50n);582    }583  });584585  ['transfer', 'transferCross'].map(testCase => itEth(`Cannot ${testCase} non-owned token`, async ({helper}) => {586    const sender = await helper.eth.createAccountWithBalance(donor);587    const tokenOwner = await helper.eth.createAccountWithBalance(donor);588    const receiverSub = minter;589    const receiverCrossSub = helper.ethCrossAccount.fromKeyringPair(minter);590591    const collection = await helper.rft.mintCollection(minter, {});592    const collectionAddress = helper.ethAddress.fromCollectionId(collection.collectionId);593    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'rft', sender);594595    await collection.mintToken(minter, 50n, {Ethereum: sender});596    const nonSendersToken = await collection.mintToken(minter, 50n, {Ethereum: tokenOwner});597598    // Cannot transferCross someone else's token:599    const receiver = testCase === 'transfer' ? helper.address.substrateToEth(receiverSub.address) : receiverCrossSub;600    await expect(collectionEvm.methods[testCase](receiver, nonSendersToken.tokenId).send({from: sender})).to.be.rejected;601    // Cannot transfer token if it does not exist:602    await expect(collectionEvm.methods[testCase](receiver, 999999).send({from: sender})).to.be.rejected;603  }));604605  itEth('transfer event on transfer from partial ownership to full ownership', async ({helper}) => {606    const caller = await helper.eth.createAccountWithBalance(donor);607    const receiver = helper.eth.createAccount();608    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Partial-to-Full', '6', '6');609    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);610611    const result = await contract.methods.mint(caller).send();612    const tokenId = result.events.Transfer.returnValues.tokenId;613614    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);615616    await tokenContract.methods.repartition(2).send();617    await tokenContract.methods.transfer(receiver, 1).send();618619    const events: any = [];620    contract.events.allEvents((_: any, event: any) => {621      events.push(event);622    });623624    await tokenContract.methods.transfer(receiver, 1).send();625    if (events.length == 0) await helper.wait.newBlocks(1);626    const event = events[0];627628    expect(event.address).to.equal(collectionAddress);629    expect(event.returnValues.from).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');630    expect(event.returnValues.to).to.equal(receiver);631    expect(event.returnValues.tokenId).to.equal(tokenId.toString());632  });633634  itEth('transfer event on transfer from full ownership to partial ownership', async ({helper}) => {635    const caller = await helper.eth.createAccountWithBalance(donor);636    const receiver = helper.eth.createAccount();637    const {collectionId, collectionAddress} = await helper.eth.createRFTCollection(caller, 'Transferry-Full-to-Partial', '6', '6');638    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);639640    const result = await contract.methods.mint(caller).send();641    const tokenId = result.events.Transfer.returnValues.tokenId;642643    const tokenContract = helper.ethNativeContract.rftTokenById(collectionId, tokenId, caller);644645    await tokenContract.methods.repartition(2).send();646647    const events: any = [];648    contract.events.allEvents((_: any, event: any) => {649      events.push(event);650    });651652    await tokenContract.methods.transfer(receiver, 1).send();653    if (events.length == 0) await helper.wait.newBlocks(1);654    const event = events[0];655656    expect(event.address).to.equal(collectionAddress);657    expect(event.returnValues.from).to.equal(caller);658    expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');659    expect(event.returnValues.tokenId).to.equal(tokenId.toString());660  });661});662663describe('RFT: Fees', () => {664  let donor: IKeyringPair;665666  before(async function() {667    await usingEthPlaygrounds(async (helper, privateKey) => {668      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);669670      donor = await privateKey({filename: __filename});671    });672  });673674  itEth('transferFrom() call fee is less than 0.2UNQ', async ({helper}) => {675    const caller = await helper.eth.createAccountWithBalance(donor);676    const receiver = helper.eth.createAccount();677    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer-From', '6', '6');678    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);679680    const result = await contract.methods.mint(caller).send();681    const tokenId = result.events.Transfer.returnValues.tokenId;682683    const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transferFrom(caller, receiver, tokenId).send());684    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));685    expect(cost > 0n);686  });687688  itEth('transfer() call fee is less than 0.2UNQ', async ({helper}) => {689    const caller = await helper.eth.createAccountWithBalance(donor);690    const receiver = helper.eth.createAccount();691    const {collectionAddress} = await helper.eth.createRFTCollection(caller, 'Feeful-Transfer', '6', '6');692    const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', caller);693694    const result = await contract.methods.mint(caller).send();695    const tokenId = result.events.Transfer.returnValues.tokenId;696697    const cost = await helper.eth.recordCallFee(caller, () => contract.methods.transfer(receiver, tokenId).send());698    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));699    expect(cost > 0n);700  });701});702703describe('Common metadata', () => {704  let donor: IKeyringPair;705  let alice: IKeyringPair;706707  before(async function() {708    await usingEthPlaygrounds(async (helper, privateKey) => {709      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);710711      donor = await privateKey({filename: __filename});712      [alice] = await helper.arrange.createAccounts([20n], donor);713    });714  });715716  itEth('Returns collection name', async ({helper}) => {717    const caller = helper.eth.createAccount();718    const tokenPropertyPermissions = [{719      key: 'URI',720      permission: {721        mutable: true,722        collectionAdmin: true,723        tokenOwner: false,724      },725    }];726    const collection = await helper.rft.mintCollection(727      alice,728      {729        name: 'Leviathan',730        tokenPrefix: '11',731        properties: [{key: 'ERC721Metadata', value: '1'}],732        tokenPropertyPermissions,733      },734    );735736    const contract = helper.ethNativeContract.collectionById(collection.collectionId, 'rft', caller);737    const name = await contract.methods.name().call();738    expect(name).to.equal('Leviathan');739  });740741  itEth('Returns symbol name', async ({helper}) => {742    const caller = await helper.eth.createAccountWithBalance(donor);743    const tokenPropertyPermissions = [{744      key: 'URI',745      permission: {746        mutable: true,747        collectionAdmin: true,748        tokenOwner: false,749      },750    }];751    const {collectionId} = await helper.rft.mintCollection(752      alice,753      {754        name: 'Leviathan',755        tokenPrefix: '12',756        properties: [{key: 'ERC721Metadata', value: '1'}],757        tokenPropertyPermissions,758      },759    );760761    const contract = helper.ethNativeContract.collectionById(collectionId, 'rft', caller);762    const symbol = await contract.methods.symbol().call();763    expect(symbol).to.equal('12');764  });765});766767describe('Negative tests', () => {768  let donor: IKeyringPair;769  let minter: IKeyringPair;770  let alice: IKeyringPair;771772  before(async function() {773    await usingEthPlaygrounds(async (helper, privateKey) => {774      requirePalletsOrSkip(this, helper, [Pallets.ReFungible]);775      776      donor = await privateKey({filename: __filename});777      [minter, alice] = await helper.arrange.createAccounts([100n, 100n], donor);778    });779  });780781  itEth('[negative] Cant perform burn without approval', async ({helper}) => {782    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});783784    const owner = await helper.eth.createAccountWithBalance(donor, 100n);785    const spender = await helper.eth.createAccountWithBalance(donor, 100n);786787    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});788789    const address = helper.ethAddress.fromCollectionId(collection.collectionId);790    const contract = helper.ethNativeContract.collection(address, 'rft');791792    const ownerCross = helper.ethCrossAccount.fromAddress(owner);793794    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;795796    await contract.methods.setApprovalForAll(spender, true).send({from: owner});797    await contract.methods.setApprovalForAll(spender, false).send({from: owner});798799    await expect(contract.methods.burnFromCross(ownerCross, token.tokenId).send({from: spender})).to.be.rejected;800  });801802  itEth('[negative] Cant perform transfer without approval', async ({helper}) => {803    const collection = await helper.rft.mintCollection(minter, {name: 'A', description: 'B', tokenPrefix: 'C'});804    const owner = await helper.eth.createAccountWithBalance(donor, 100n);805    const receiver = alice;806807    const spender = await helper.eth.createAccountWithBalance(donor, 100n);808809    const token = await collection.mintToken(minter, 100n, {Ethereum: owner});810811    const address = helper.ethAddress.fromCollectionId(collection.collectionId);812    const contract = helper.ethNativeContract.collection(address, 'rft');813814    const ownerCross = helper.ethCrossAccount.fromAddress(owner);815    const recieverCross = helper.ethCrossAccount.fromKeyringPair(receiver);816    817    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;818819    await contract.methods.setApprovalForAll(spender, true).send({from: owner});820    await contract.methods.setApprovalForAll(spender, false).send({from: owner});821    822    await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;823  });824});