git.delta.rocks / unique-network / refs/commits / 6e6dff56f545

difftreelog

added coderTest via Conract

PraetorP2022-11-08parent: #7aaf8c5.patch.diff
in: master

4 files changed

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth
--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: OTHER
+// This code is automatically generated
+
+pragma solidity >=0.8.0 <0.9.0;
+
+/// @dev common stubs holder
+contract Dummy {
+	uint8 dummy;
+	string stub_error = "this contract is implemented in native";
+}
+
+contract ERC165 is Dummy {
+	function supportsInterface(bytes4 interfaceID) external view returns (bool) {
+		require(false, stub_error);
+		interfaceID;
+		return true;
+	}
+}
+
+/// @dev inlined interface
+contract CollectionHelpersEvents {
+	event CollectionCreated(address indexed owner, address indexed collectionId);
+	event CollectionDestroyed(address indexed collectionId);
+}
+
+/// @title Contract, which allows users to operate with collections
+/// @dev the ERC-165 identifier for this interface is 0x7dea03b1
+contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
+	/// Create an NFT collection
+	/// @param name Name of the collection
+	/// @param description Informative description of the collection
+	/// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
+	/// @return address Address of the newly created collection
+	/// @dev EVM selector for this function is: 0x844af658,
+	///  or in textual repr: createNFTCollection(string,string,string)
+	function createNFTCollection(
+		string memory name,
+		string memory description,
+		string memory tokenPrefix
+	) public payable returns (address) {
+		require(false, stub_error);
+		name;
+		description;
+		tokenPrefix;
+		dummy = 0;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	// /// Create an NFT collection
+	// /// @param name Name of the collection
+	// /// @param description Informative description of the collection
+	// /// @param tokenPrefix Token prefix to represent the collection tokens in UI and user applications
+	// /// @return address Address of the newly created collection
+	// /// @dev EVM selector for this function is: 0xe34a6844,
+	// ///  or in textual repr: createNonfungibleCollection(string,string,string)
+	// function createNonfungibleCollection(string memory name, string memory description, string memory tokenPrefix) public payable returns (address) {
+	// 	require(false, stub_error);
+	// 	name;
+	// 	description;
+	// 	tokenPrefix;
+	// 	dummy = 0;
+	// 	return 0x0000000000000000000000000000000000000000;
+	// }
+
+	/// @dev EVM selector for this function is: 0xab173450,
+	///  or in textual repr: createRFTCollection(string,string,string)
+	function createRFTCollection(
+		string memory name,
+		string memory description,
+		string memory tokenPrefix
+	) public payable returns (address) {
+		require(false, stub_error);
+		name;
+		description;
+		tokenPrefix;
+		dummy = 0;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	/// @dev EVM selector for this function is: 0x7335b79f,
+	///  or in textual repr: createFTCollection(string,uint8,string,string)
+	function createFTCollection(
+		string memory name,
+		uint8 decimals,
+		string memory description,
+		string memory tokenPrefix
+	) public payable returns (address) {
+		require(false, stub_error);
+		name;
+		decimals;
+		description;
+		tokenPrefix;
+		dummy = 0;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	/// @dev EVM selector for this function is: 0x85624258,
+	///  or in textual repr: makeCollectionERC721MetadataCompatible(address,string)
+	function makeCollectionERC721MetadataCompatible(address collection, string memory baseUri) public {
+		require(false, stub_error);
+		collection;
+		baseUri;
+		dummy = 0;
+	}
+
+	/// @dev EVM selector for this function is: 0x564e321f,
+	///  or in textual repr: destroyCollection(address)
+	function destroyCollection(address collectionAddress) public {
+		require(false, stub_error);
+		collectionAddress;
+		dummy = 0;
+	}
+
+	/// Check if a collection exists
+	/// @param collectionAddress Address of the collection in question
+	/// @return bool Does the collection exist?
+	/// @dev EVM selector for this function is: 0xc3de1494,
+	///  or in textual repr: isCollectionExist(address)
+	function isCollectionExist(address collectionAddress) public view returns (bool) {
+		require(false, stub_error);
+		collectionAddress;
+		dummy;
+		return false;
+	}
+
+	/// @dev EVM selector for this function is: 0xd23a7ab1,
+	///  or in textual repr: collectionCreationFee()
+	function collectionCreationFee() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+}
modifiedtests/src/eth/evmToSubstrate/EvmToSubstrateHelper.soldiffbeforeafterboth
before · tests/src/eth/evmToSubstrate/EvmToSubstrateHelper.sol
1// SPDX-License-Identifier:  Apache License2pragma solidity >=0.8.0;3import {CollectionHelpers} from "../api/CollectionHelpers.sol";4import {ContractHelpers} from "../api/ContractHelpers.sol";5import {UniqueRefungibleToken} from "../api/UniqueRefungibleToken.sol";6import {UniqueRefungible, Collection, EthCrossAccount as RftCrossAccountId, Tuple20 as RftProperties} from "../api/UniqueRefungible.sol";7import {UniqueNFT, EthCrossAccount as NftCrossAccountId, Tuple21 as NftProperty, TokenProperties} from "../api/UniqueNFT.sol";89struct Property {10	string key;11	bytes value;12}1314// interface Foo {15// 	struct Tuple19 {16// 		string field_0;17// 		bytes field_1;18// 	}1920// }2122contract EvmToSubstrate {23	bytes32 constant REFUNGIBLE_COLLECTION_TYPE = keccak256(bytes("ReFungible"));24	bytes32 constant NONFUNGIBLE_COLLECTION_TYPE = keccak256(bytes("NFT"));25	// bytes32 collectionType;2627	// Collection commonContract;28	// UniqueNFT nftCollection;29	// UniqueRefungible rftCollection;3031	// function(address, uint256) external returns (bool) mintInvoke;32	// function(address, address, uint256) external transferInvoke;3334	modifier checkRestrictions(address _collection) {35		Collection commonContract = Collection(_collection);36		require(commonContract.isOwnerOrAdmin(msg.sender), "Only collection admin/owner can call this method");3738		// bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType()));39		// uint256 tokenId;4041		// if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {42		// 	UniqueRefungible rftCollection = UniqueRefungible(_collection);43		// 	mintInvoke = rftCollection.mint;44		// 	transferInvoke = rftCollection.transferFrom;45		// 	tokenId = rftCollection.nextTokenId();46		// } else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) {47		// 	UniqueNFT nftCollection = UniqueNFT(_collection);48		// 	mintInvoke = nftCollection.mint;49		// 	transferInvoke = nftCollection.transferFrom;5051		// 	tokenId = nftCollection.nextTokenId();52		// } else {53		// 	revert("Wrong collection type. Works only with NFT or RFT");54		// }5556		_;57	}5859	/// @dev This emits when a mint to a substrate address has been made.60	event MintToSub(address _toEth, uint256 _toSub, address _collection, uint256 _tokenId);6162	function mintToSubstrate(address _collection, uint256 _substrateReceiver) external checkRestrictions(_collection) {63		// function(address, uint256) external returns (bool) mintInvoke;64		// function(Tuple8 memory, Tuple8 memory, uint256) external transferInvoke;65		Collection commonContract = Collection(_collection);66		bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType()));67		uint256 tokenId;6869		if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {70			UniqueRefungible rftCollection = UniqueRefungible(_collection);71			72			tokenId = rftCollection.mint(address(this));7374			rftCollection.transferFromCross(75				RftCrossAccountId(address(this), 0),76				RftCrossAccountId(address(0), _substrateReceiver),77				tokenId78			);79		} else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) {80			UniqueNFT nftCollection = UniqueNFT(_collection);81			// tokenId = nftCollection.nextTokenId();82			tokenId = nftCollection.mint(address(this));8384			nftCollection.transferFromCross(85				NftCrossAccountId(address(this), 0),86				NftCrossAccountId(address(0), _substrateReceiver),87				tokenId88			);89		} else {90			revert("Wrong collection type. Works only with NFT or RFT");91		}9293		// mintInvoke(address(this), tokenId);94		// Tuple8 memory sender = Tuple8(address(this), 0);95		// Tuple8 memory receiver = Tuple8(address(0), _substrateReceiver);9697		// transferInvoke(sender, receiver, tokenId);9899		emit MintToSub(address(0), _substrateReceiver, _collection, tokenId);100	}101102	function mintToSubstrateWithProperty(103		address _collection,104		uint256 _substrateReceiver,105		Property[] calldata properties106	) external checkRestrictions(_collection) {107		// function(address, uint256, string memory) external returns (bool) mintInvoke;108		// function(address, address, uint256) external transferInvoke;109		uint256 propertiesLength = properties.length;110		require(propertiesLength > 0, "Properies is empty");111112		Collection commonContract = Collection(_collection);113		bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType()));114		uint256 tokenId;115116		if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {117			UniqueRefungible rftCollection = UniqueRefungible(_collection);118			tokenId = rftCollection.nextTokenId();119			rftCollection.mint(address(this));120			for (uint256 i = 0; i < propertiesLength; ++i) {121				rftCollection.setProperty(tokenId, properties[i].key, properties[i].value);122			}123			rftCollection.transferFromCross(124				RftCrossAccountId(address(this), 0),125				RftCrossAccountId(address(0), _substrateReceiver),126				tokenId127			);128		} else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) {129			UniqueNFT nftCollection = UniqueNFT(_collection);130			// tokenId = nftCollection.nextTokenId();131			tokenId = nftCollection.mint(address(this));132			for (uint256 i = 0; i < propertiesLength; ++i) {133				nftCollection.setProperty(tokenId, properties[i].key, properties[i].value);134			}135			nftCollection.transferFromCross(136				NftCrossAccountId(address(this), 0),137				NftCrossAccountId(address(0), _substrateReceiver),138				tokenId139			);140		} else {141			revert("Wrong collection type. Works only with NFT or RFT");142		}143144		// transferInvoke(address(this), _gap, tokenId);145146		emit MintToSub(address(0), _substrateReceiver, _collection, tokenId);147	}148149	function mintToSubstrateBulkProperty(150		address _collection,151		uint256 _substrateReceiver,152		NftProperty[] calldata _properties153	) external checkRestrictions(_collection) {154		uint256 propertiesLength = _properties.length;155		require(propertiesLength > 0, "Properies is empty");156157		Collection commonContract = Collection(_collection);158		bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType()));159		uint256 tokenId;160161		if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {} else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) {162			UniqueNFT nftCollection = UniqueNFT(_collection);163			// tokenId = nftCollection.nextTokenId();164			tokenId = nftCollection.mint(address(this));165166			nftCollection.setProperties(tokenId, _properties);167168			nftCollection.transferFromCross(169				NftCrossAccountId(address(this), 0),170				NftCrossAccountId(address(0), _substrateReceiver),171				tokenId172			);173		} else {174			revert("Wrong collection type. Works only with NFT or RFT");175		}176177		emit MintToSub(address(0), _substrateReceiver, _collection, tokenId);178	}179}
after · tests/src/eth/evmToSubstrate/EvmToSubstrateHelper.sol
1// SPDX-License-Identifier:  Apache License2pragma solidity >=0.8.0;3import {CollectionHelpers} from "../api/CollectionHelpers.sol";4import {ContractHelpers} from "../api/ContractHelpers.sol";5import {UniqueRefungibleToken} from "../api/UniqueRefungibleToken.sol";6import {UniqueRefungible, Collection, EthCrossAccount as RftCrossAccountId, Tuple20 as RftProperties} from "../api/UniqueRefungible.sol";7import {UniqueNFT, EthCrossAccount as NftCrossAccountId, Tuple21 as NftProperty, TokenProperties} from "../api/UniqueNFT.sol";89struct Property {10	string key;11	bytes value;12}1314// interface Foo {15// 	struct Tuple19 {16// 		string field_0;17// 		bytes field_1;18// 	}1920// }2122contract EvmToSubstrate {23	bytes32 constant REFUNGIBLE_COLLECTION_TYPE = keccak256(bytes("ReFungible"));24	bytes32 constant NONFUNGIBLE_COLLECTION_TYPE = keccak256(bytes("NFT"));25	// bytes32 collectionType;2627	// Collection commonContract;28	// UniqueNFT nftCollection;29	// UniqueRefungible rftCollection;3031	// function(address, uint256) external returns (bool) mintInvoke;32	// function(address, address, uint256) external transferInvoke;3334	modifier checkRestrictions(address _collection) {35		Collection commonContract = Collection(_collection);36		require(commonContract.isOwnerOrAdmin(msg.sender), "Only collection admin/owner can call this method");3738		// bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType()));39		// uint256 tokenId;4041		// if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {42		// 	UniqueRefungible rftCollection = UniqueRefungible(_collection);43		// 	mintInvoke = rftCollection.mint;44		// 	transferInvoke = rftCollection.transferFrom;45		// 	tokenId = rftCollection.nextTokenId();46		// } else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) {47		// 	UniqueNFT nftCollection = UniqueNFT(_collection);48		// 	mintInvoke = nftCollection.mint;49		// 	transferInvoke = nftCollection.transferFrom;5051		// 	tokenId = nftCollection.nextTokenId();52		// } else {53		// 	revert("Wrong collection type. Works only with NFT or RFT");54		// }5556		_;57	}5859	/// @dev This emits when a mint to a substrate address has been made.60	event MintToSub(address _toEth, uint256 _toSub, address _collection, uint256 _tokenId);6162	function mintToSubstrate(address _collection, uint256 _substrateReceiver) external checkRestrictions(_collection) {63		// function(address, uint256) external returns (bool) mintInvoke;64		// function(Tuple8 memory, Tuple8 memory, uint256) external transferInvoke;65		Collection commonContract = Collection(_collection);66		bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType()));67		uint256 tokenId;6869		if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {70			UniqueRefungible rftCollection = UniqueRefungible(_collection);7172			tokenId = rftCollection.mint(address(this));7374			rftCollection.transferFromCross(75				RftCrossAccountId(address(this), 0),76				RftCrossAccountId(address(0), _substrateReceiver),77				tokenId78			);79		} else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) {80			UniqueNFT nftCollection = UniqueNFT(_collection);81			// tokenId = nftCollection.nextTokenId();82			tokenId = nftCollection.mint(address(this));8384			nftCollection.transferFromCross(85				NftCrossAccountId(address(this), 0),86				NftCrossAccountId(address(0), _substrateReceiver),87				tokenId88			);89		} else {90			revert("Wrong collection type. Works only with NFT or RFT");91		}9293		// mintInvoke(address(this), tokenId);94		// Tuple8 memory sender = Tuple8(address(this), 0);95		// Tuple8 memory receiver = Tuple8(address(0), _substrateReceiver);9697		// transferInvoke(sender, receiver, tokenId);9899		emit MintToSub(address(0), _substrateReceiver, _collection, tokenId);100	}101102	function mintToSubstrateWithProperty(103		address _collection,104		uint256 _substrateReceiver,105		Property[] calldata properties106	) external checkRestrictions(_collection) {107		// function(address, uint256, string memory) external returns (bool) mintInvoke;108		// function(address, address, uint256) external transferInvoke;109		uint256 propertiesLength = properties.length;110		require(propertiesLength > 0, "Properies is empty");111112		Collection commonContract = Collection(_collection);113		bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType()));114		uint256 tokenId;115116		if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {117			UniqueRefungible rftCollection = UniqueRefungible(_collection);118			tokenId = rftCollection.nextTokenId();119			rftCollection.mint(address(this));120			for (uint256 i = 0; i < propertiesLength; ++i) {121				rftCollection.setProperty(tokenId, properties[i].key, properties[i].value);122			}123			rftCollection.transferFromCross(124				RftCrossAccountId(address(this), 0),125				RftCrossAccountId(address(0), _substrateReceiver),126				tokenId127			);128		} else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) {129			UniqueNFT nftCollection = UniqueNFT(_collection);130			// tokenId = nftCollection.nextTokenId();131			tokenId = nftCollection.mint(address(this));132			for (uint256 i = 0; i < propertiesLength; ++i) {133				nftCollection.setProperty(tokenId, properties[i].key, properties[i].value);134			}135			nftCollection.transferFromCross(136				NftCrossAccountId(address(this), 0),137				NftCrossAccountId(address(0), _substrateReceiver),138				tokenId139			);140		} else {141			revert("Wrong collection type. Works only with NFT or RFT");142		}143144		// transferInvoke(address(this), _gap, tokenId);145146		emit MintToSub(address(0), _substrateReceiver, _collection, tokenId);147	}148149	function mintToSubstrateBulkProperty(150		address _collection,151		uint256 _substrateReceiver,152		NftProperty[] calldata _properties153	) external checkRestrictions(_collection) {154		uint256 propertiesLength = _properties.length;155		require(propertiesLength > 0, "Properies is empty");156157		Collection commonContract = Collection(_collection);158		bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType()));159		uint256 tokenId;160161		if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {} else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) {162			UniqueNFT nftCollection = UniqueNFT(_collection);163			// tokenId = nftCollection.nextTokenId();164			tokenId = nftCollection.mint(address(this));165166			nftCollection.setProperties(tokenId, _properties);167168			nftCollection.transferFromCross(169				NftCrossAccountId(address(this), 0),170				NftCrossAccountId(address(0), _substrateReceiver),171				tokenId172			);173		} else {174			revert("Wrong collection type. Works only with NFT or RFT");175		}176177		emit MintToSub(address(0), _substrateReceiver, _collection, tokenId);178	}179180	function proxyProperties(181		address _collection,182		uint256 _tokenId,183		NftProperty[] calldata _properties184	) external checkRestrictions(_collection) {185		uint256 propertiesLength = _properties.length;186		require(propertiesLength > 0, "Properies is empty");187188		Collection commonContract = Collection(_collection);189		bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType()));190	191		if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {192			revert("Wrong collection type. Works only with NFT or RFT");193		} else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) {194			UniqueNFT nftCollection = UniqueNFT(_collection);195			196197			nftCollection.setProperties(_tokenId, _properties);198		} else {199			revert("Wrong collection type. Works only with NFT or RFT");200		}201	}202}
addedtests/src/eth/evmToSubstrate/coderTest.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/evmToSubstrate/coderTest.ts
@@ -0,0 +1,215 @@
+import { EthUniqueHelper, usingEthPlaygrounds } from '../util';
+import { readFile } from 'fs/promises';
+import { ContractImports } from '../util/playgrounds/types';
+import { Contract } from '@polkadot/api-contract/base';
+import Web3 from 'web3';
+import {
+	IProperty,
+	ITokenPropertyPermission,
+} from '../../util/playgrounds/types';
+import { addressToEvm, decodeAddress } from '@polkadot/util-crypto';
+import nonFungibleAbi from '../nonFungibleAbi.json';
+import { IKeyringPair } from '@polkadot/types/types';
+
+enum SponsoringMode {
+	Disabled = 0,
+	Allowlisted = 1,
+	Generous = 2,
+}
+
+const WS_ENDPOINT = 'wss://ws-rc.unique.network';
+const CONTRACT_IMPORT: ContractImports[] = [
+	{
+		fsPath: `${__dirname}/../api/CollectionHelpers.sol`,
+		solPath: 'api/CollectionHelpers.sol',
+	},
+	{
+		fsPath: `${__dirname}/../api/ContractHelpers.sol`,
+		solPath: 'api/ContractHelpers.sol',
+	},
+	{
+		fsPath: `${__dirname}/../api/UniqueRefungibleToken.sol`,
+		solPath: 'api/UniqueRefungibleToken.sol',
+	},
+	{
+		fsPath: `${__dirname}/../api/UniqueRefungible.sol`,
+		solPath: 'api/UniqueRefungible.sol',
+	},
+	{
+		fsPath: `${__dirname}/../api/UniqueNFT.sol`,
+		solPath: 'api/UniqueNFT.sol',
+	},
+];
+
+const main = async () => {
+	await usingEthPlaygrounds(async (helper, privateKey) => {
+		const contract_source = (
+			await readFile(`${__dirname}/EvmToSubstrateHelper.sol`)
+		).toString();
+
+		const donor = await privateKey('//Alice'); // Seed from account with balance on this network
+		const myAccount = await privateKey('//Bob'); // replace with account from polkadot extension
+		const signer = await helper.eth.createAccountWithBalance(donor, 100n);
+		console.log(`signer data: ${Uint8Array.from(Buffer.from(signer.slice(2), 'hex'))}`)
+		const collection = await helper.nft.mintCollection(donor, {
+			name: 'test mintToSubstrate',
+			description: 'EVMHelpers',
+			tokenPrefix: 'ap',
+			tokenPropertyPermissions: [
+				{
+					key: 'url',
+					permission: {
+						tokenOwner: true,
+						collectionAdmin: true,
+						mutable: true,
+					},
+				},
+			],
+			limits: { sponsorTransferTimeout: 0, sponsorApproveTimeout: 0 },
+			permissions: { mintMode: true },
+		});
+
+		await collection.addToAllowList(donor, {
+			Ethereum: helper.address.substrateToEth(donor.address),
+		});
+		await collection.addToAllowList(donor, { Substrate: donor.address });
+		await collection.addAdmin(donor, { Ethereum: signer });
+		await collection.addAdmin(donor, {
+			Ethereum: helper.address.substrateToEth(donor.address),
+		});
+
+		console.log('collection admin(s)): ', await collection.getAdmins());
+		console.log('collection owner(s)): ', await collection.getAllowList());
+
+		const collectionEthAddress = helper.ethAddress.fromCollectionId(
+			collection.collectionId,
+		);
+
+		const collectionEthContract = helper.ethNativeContract.collection(
+			collectionEthAddress,
+			'nft',
+		);
+
+		const receiverEthAddress = helper.address.substrateToEth(myAccount.address);
+			
+		const contract = await helper.ethContract.deployByCode(
+			signer,
+			'EvmToSubstrate',
+			contract_source,
+			CONTRACT_IMPORT,
+		);
+		console.log(`contract has been deployed\naddres: ${contract.options.address} || ${Uint8Array.from(Buffer.from(contract.options.address.slice(2), 'hex'))}`);
+
+		await helper.eth.transferBalanceFromSubstrate(
+			donor,
+			contract.options.address,
+			100n,
+		);
+
+		console.log(`transfer has been completed`);
+
+		await collection.addToAllowList(donor, {
+			Ethereum: contract.options.address,
+		});
+		await collection.addAdmin(donor, { Ethereum: contract.options.address });
+
+		console.log(`setup has been completed`);
+
+		console.log('\t\t\t *** Properties Fees ***\n');
+
+		const propertiesNumber = 20;
+
+		const properties = Array(40)
+			.fill(0)
+			.map((_, i) => {
+				return {
+					key: `key_${i}`,
+					value: Uint8Array.from(Buffer.from(`value_${i}`)),
+				};
+			});
+
+		const permissions: ITokenPropertyPermission[] = properties.map((p) => {
+			return {
+				key: p.key,
+				permission: {
+					tokenOwner: true,
+					collectionAdmin: true,
+					mutable: true,
+				},
+			};
+		});
+
+		//    *** ProxyContract Bulk ***
+
+		const token = await collection.mintToken(donor, { Ethereum: signer });
+
+		const mintWithBulkPropProxyContractFee = await helper.arrange.calculcateFee(
+			{ Ethereum: signer },
+			async () => {
+				await contract.methods
+					.proxyProperties(
+						collectionEthContract.options.address,
+						token.tokenId,
+						properties.slice(0, propertiesNumber).map((p) => {
+							return { field_0: p.key, field_1: p.value };
+						}),
+					)
+					.send({ from: signer });
+			},
+		);
+		console.log(
+			`token mint from contract(with bulk prop.) to the Substrate Id: ${mintWithBulkPropProxyContractFee}`,
+		);
+
+		console.log('All done');
+	});
+};
+
+main()
+	.then(() => process.exit(0))
+	.catch((e) => {
+		console.log(e);
+		process.exit(1);
+	});
+
+async function createCollectionForPropertiesBenchmarks(
+	helper: EthUniqueHelper,
+	privateKey: (seed: string) => Promise<IKeyringPair>,
+	ethSigner: string,
+	proxyContract: string,
+	permissions: ITokenPropertyPermission[],
+) {
+	const donor = await privateKey('//Alice'); // Seed from account with balance on this network
+
+	const collection = await helper.nft.mintCollection(donor, {
+		name: 'test mintToSubstrate',
+		description: 'EVMHelpers',
+		tokenPrefix: 'ap',
+		tokenPropertyPermissions: [
+			{
+				key: 'url',
+				permission: {
+					tokenOwner: true,
+					collectionAdmin: true,
+					mutable: true,
+				},
+			},
+		],
+		limits: { sponsorTransferTimeout: 0, sponsorApproveTimeout: 0 },
+		permissions: { mintMode: true },
+	});
+
+	await collection.addToAllowList(donor, {
+		Ethereum: helper.address.substrateToEth(donor.address),
+	});
+	await collection.addToAllowList(donor, { Substrate: donor.address });
+	await collection.addAdmin(donor, { Ethereum: ethSigner });
+	await collection.addAdmin(donor, {
+		Ethereum: helper.address.substrateToEth(donor.address),
+	});
+	await collection.addToAllowList(donor, { Ethereum: proxyContract });
+	await collection.addAdmin(donor, { Ethereum: proxyContract });
+	await collection.setTokenPropertyPermissions(donor, permissions);
+
+	return collection;
+}