From ee86073ebf66648ded5329e3e01f06fd0916ca17 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 11 Nov 2022 09:57:37 +0000 Subject: [PATCH] Style fixes --- --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1 +1,2 @@ /node_modules/ +properties.csv --- a/tests/package.json +++ b/tests/package.json @@ -101,6 +101,7 @@ "testXcmTransferAcala": "mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmTransferAcala.test.ts acalaId=2000 uniqueId=5000", "testXcmTransferStatemine": "mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000", "testXcmTransferMoonbeam": "mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmTransferMoonbeam.test.ts", + "benchMintingFee": "ts-node src/benchmarks/mintFee/benchmark.ts", "load": "mocha --timeout 9999999 -r ts-node/register './**/*.load.ts'", "loadTransfer": "ts-node src/transfer.nload.ts", "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json", --- /dev/null +++ b/tests/src/benchmarks/mintFee/benchmark.ts @@ -0,0 +1,436 @@ +import {EthUniqueHelper, usingEthPlaygrounds} from '../../eth/util'; +import {readFile} from 'fs/promises'; +import {ContractImports} from '../../eth/util/playgrounds/types'; +import { + ICrossAccountId, + ITokenPropertyPermission, +} from '../../util/playgrounds/types'; +import {IKeyringPair} from '@polkadot/types/types'; +import {UniqueNFTCollection} from '../../util/playgrounds/unique'; +import {Contract} from 'web3-eth-contract'; +import {createObjectCsvWriter} from 'csv-writer'; + +const CONTRACT_IMPORT: ContractImports[] = [ + { + fsPath: `${__dirname}/../../eth/api/CollectionHelpers.sol`, + solPath: 'eth/api/CollectionHelpers.sol', + }, + { + fsPath: `${__dirname}/../../eth/api/ContractHelpers.sol`, + solPath: 'eth/api/ContractHelpers.sol', + }, + { + fsPath: `${__dirname}/../../eth/api/UniqueRefungibleToken.sol`, + solPath: 'eth/api/UniqueRefungibleToken.sol', + }, + { + fsPath: `${__dirname}/../../eth/api/UniqueRefungible.sol`, + solPath: 'eth/api/UniqueRefungible.sol', + }, + { + fsPath: `${__dirname}/../../eth/api/UniqueNFT.sol`, + solPath: 'eth/api/UniqueNFT.sol', + }, +]; + +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, + }, + }; +}); + +interface IBenchmarkResultForProp { + propertiesNumber: number; + substrateFee: number; + ethFee: number; + ethBulkFee: number; + evmProxyContractFee: number; + evmProxyContractBulkFee: number; +} + +const main = async () => { + const benchmarks = [ + 'substrateFee', + 'ethFee', + 'ethBulkFee', + 'evmProxyContractFee', + 'evmProxyContractBulkFee', + ]; + const headers = [ + 'propertiesNumber', + ...benchmarks, + ]; + + + const csvWriter = createObjectCsvWriter({ + path: 'properties.csv', + header: headers, + }); + + await usingEthPlaygrounds(async (helper, privateKey) => { + const CONTRACT_SOURCE = ( + await readFile(`${__dirname}/proxyContract.sol`) + ).toString(); + + const donor = await privateKey('//Alice'); // Seed from account with balance on this network + const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n); + + const contract = await helper.ethContract.deployByCode( + ethSigner, + 'ProxyMint', + CONTRACT_SOURCE, + CONTRACT_IMPORT, + ); + + const fees = await benchMintFee(helper, privateKey, contract); + console.log('Minting without properties'); + console.table(fees); + + const result: IBenchmarkResultForProp[] = []; + const csvResult: IBenchmarkResultForProp[] = []; + + for (let i = 1; i <= 20; i++) { + const benchResult = await benchMintWithProperties(helper, privateKey, contract, { + propertiesNumber: i, + }) as any; + + csvResult.push(benchResult); + + const minFee = Math.min(...(benchmarks.map(x => benchResult[x]))); + for(const key of benchmarks) { + const keyPercent = Math.round((benchResult[key] / minFee) * 100); + benchResult[key] = `${benchResult[key]} (${keyPercent}%)`; + } + + result.push(benchResult); + } + + await csvWriter.writeRecords(csvResult); + + console.log('Minting with properties'); + console.table(result, headers); + }); +}; + +main() + .then(() => process.exit(0)) + .catch((e) => { + console.log(e); + process.exit(1); + }); + +async function createCollectionForBenchmarks( + helper: EthUniqueHelper, + privateKey: (seed: string) => Promise, + ethSigner: string, + proxyContract: string, + permissions: ITokenPropertyPermission[], +) { + const donor = await privateKey('//Alice'); + + 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; +} + +async function benchMintFee( + helper: EthUniqueHelper, + privateKey: (seed: string) => Promise, + proxyContract: Contract, +): Promise<{ + substrateFee: number; + ethFee: number; + evmProxyContractFee: number; +}> { + const donor = await privateKey('//Alice'); + const substrateReceiver = await privateKey('//Bob'); + const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n); + + const nominal = helper.balance.getOneTokenNominal(); + + await helper.eth.transferBalanceFromSubstrate( + donor, + proxyContract.options.address, + 100n, + ); + + const collection = await createCollectionForBenchmarks( + helper, + privateKey, + ethSigner, + proxyContract.options.address, + PERMISSIONS, + ); + + const substrateFee = await helper.arrange.calculcateFee( + {Substrate: donor.address}, + () => collection.mintToken(donor, {Substrate: substrateReceiver.address}), + ); + + const collectionEthAddress = helper.ethAddress.fromCollectionId(collection.collectionId); + const collectionContract = helper.ethNativeContract.collection( + collectionEthAddress, + 'nft', + ); + + const receiverEthAddress = helper.address.substrateToEth(substrateReceiver.address); + + const encodedCall = collectionContract.methods + .mint(receiverEthAddress) + .encodeABI(); + + const ethFee = await helper.arrange.calculcateFee( + {Substrate: donor.address}, + async () => { + await helper.eth.sendEVM( + donor, + collectionContract.options.address, + encodedCall, + '0', + ); + }, + ); + + const evmProxyContractFee = await helper.arrange.calculcateFee( + {Ethereum: ethSigner}, + async () => { + await proxyContract.methods + .mintToSubstrate( + helper.ethAddress.fromCollectionId(collection.collectionId), + substrateReceiver.addressRaw, + ) + .send({from: ethSigner}); + }, + ); + + return { + substrateFee: convertToTokens(substrateFee, nominal), + ethFee: convertToTokens(ethFee, nominal), + evmProxyContractFee: convertToTokens(evmProxyContractFee, nominal), + }; +} + +async function benchMintWithProperties( + helper: EthUniqueHelper, + privateKey: (seed: string) => Promise, + proxyContract: Contract, + setup: { propertiesNumber: number }, +): Promise { + const donor = await privateKey('//Alice'); // Seed from account with balance on this network + const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n); + + const susbstrateReceiver = await privateKey('//Bob'); + const receiverEthAddress = helper.address.substrateToEth(susbstrateReceiver.address); + + const nominal = helper.balance.getOneTokenNominal(); + + const substrateFee = await calculateFeeNftMintWithProperties( + helper, + privateKey, + {Substrate: donor.address}, + ethSigner, + proxyContract.options.address, + async (collection) => { + await collection.mintToken( + donor, + {Substrate: susbstrateReceiver.address}, + PROPERTIES.slice(0, setup.propertiesNumber).map((p) => { + return {key: p.key, value: Buffer.from(p.value).toString()}; + }), + ); + }, + ); + + const ethFee = await calculateFeeNftMintWithProperties( + helper, + privateKey, + {Substrate: donor.address}, + ethSigner, + proxyContract.options.address, + async (collection) => { + const evmContract = helper.ethNativeContract.collection( + helper.ethAddress.fromCollectionId(collection.collectionId), + 'nft', + ); + + const subTokenId = await evmContract.methods.nextTokenId().call(); + + let encodedCall = evmContract.methods + .mint(receiverEthAddress) + .encodeABI(); + + await helper.eth.sendEVM( + donor, + evmContract.options.address, + encodedCall, + '0', + ); + + for (const val of PROPERTIES.slice(0, setup.propertiesNumber)) { + encodedCall = await evmContract.methods + .setProperty(subTokenId, val.key, Buffer.from(val.value)) + .encodeABI(); + + await helper.eth.sendEVM( + donor, + evmContract.options.address, + encodedCall, + '0', + ); + } + }, + ); + + const ethBulkFee = await calculateFeeNftMintWithProperties( + helper, + privateKey, + {Substrate: donor.address}, + ethSigner, + proxyContract.options.address, + async (collection) => { + const evmContract = helper.ethNativeContract.collection( + helper.ethAddress.fromCollectionId(collection.collectionId), + 'nft', + ); + + const subTokenId = await evmContract.methods.nextTokenId().call(); + + let encodedCall = evmContract.methods + .mint(receiverEthAddress) + .encodeABI(); + + await helper.eth.sendEVM( + donor, + evmContract.options.address, + encodedCall, + '0', + ); + + encodedCall = await evmContract.methods + .setProperties( + subTokenId, + PROPERTIES.slice(0, setup.propertiesNumber).map((p) => { + return {field_0: p.key, field_1: p.value}; + }), + ) + .encodeABI(); + + await helper.eth.sendEVM( + donor, + evmContract.options.address, + encodedCall, + '0', + ); + }, + ); + + const proxyContractFee = await calculateFeeNftMintWithProperties( + helper, + privateKey, + {Ethereum: ethSigner}, + ethSigner, + proxyContract.options.address, + async (collection) => { + await proxyContract.methods + .mintToSubstrateWithProperty( + helper.ethAddress.fromCollectionId(collection.collectionId), + susbstrateReceiver.addressRaw, + PROPERTIES.slice(0, setup.propertiesNumber), + ) + .send({from: ethSigner}); + }, + ); + + const proxyContractBulkFee = await calculateFeeNftMintWithProperties( + helper, + privateKey, + {Ethereum: ethSigner}, + ethSigner, + proxyContract.options.address, + async (collection) => { + await proxyContract.methods + .mintToSubstrateBulkProperty( + helper.ethAddress.fromCollectionId(collection.collectionId), + susbstrateReceiver.addressRaw, + PROPERTIES.slice(0, setup.propertiesNumber).map((p) => { + return {field_0: p.key, field_1: p.value}; + }), + ) + .send({from: ethSigner, gas: 25_000_000}); + }, + ); + + return { + propertiesNumber: setup.propertiesNumber, + substrateFee: convertToTokens(substrateFee, nominal), + ethFee: convertToTokens(ethFee, nominal), + ethBulkFee: convertToTokens(ethBulkFee, nominal), + evmProxyContractFee: convertToTokens(proxyContractFee, nominal), + evmProxyContractBulkFee: convertToTokens(proxyContractBulkFee, nominal), + }; +} + +async function calculateFeeNftMintWithProperties( + helper: EthUniqueHelper, + privateKey: (seed: string) => Promise, + payer: ICrossAccountId, + ethSigner: string, + proxyContractAddress: string, + calculatedCall: (collection: UniqueNFTCollection) => Promise, +): Promise { + const collection = await createCollectionForBenchmarks( + helper, + privateKey, + ethSigner, + proxyContractAddress, + PERMISSIONS, + ); + return helper.arrange.calculcateFee(payer, async () => { + await calculatedCall(collection); + }); +} +function convertToTokens(value: bigint, nominal: bigint): number { + return Number((value * 1000n) / nominal) / 1000; +} --- /dev/null +++ b/tests/src/benchmarks/mintFee/proxyContract.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache License +pragma solidity >=0.8.0; +import {CollectionHelpers} from "../../eth/api/CollectionHelpers.sol"; +import {ContractHelpers} from "../../eth/api/ContractHelpers.sol"; +import {UniqueRefungibleToken} from "../../eth/api/UniqueRefungibleToken.sol"; +import {UniqueRefungible, Collection, EthCrossAccount as RftCrossAccountId, Tuple20 as RftProperties} from "../../eth/api/UniqueRefungible.sol"; +import {UniqueNFT, EthCrossAccount as NftCrossAccountId, Tuple21 as NftProperty, TokenProperties} from "../../eth/api/UniqueNFT.sol"; + +struct Property { + string key; + bytes value; +} + +contract ProxyMint { + bytes32 constant REFUNGIBLE_COLLECTION_TYPE = keccak256(bytes("ReFungible")); + bytes32 constant NONFUNGIBLE_COLLECTION_TYPE = keccak256(bytes("NFT")); + + modifier checkRestrictions(address _collection) { + Collection commonContract = Collection(_collection); + require(commonContract.isOwnerOrAdmin(msg.sender), "Only collection admin/owner can call this method"); + _; + } + + /// @dev This emits when a mint to a substrate address has been made. + event MintToSub(address _toEth, uint256 _toSub, address _collection, uint256 _tokenId); + + function mintToSubstrate(address _collection, uint256 _substrateReceiver) external checkRestrictions(_collection) { + Collection commonContract = Collection(_collection); + bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType())); + uint256 tokenId; + + if (collectionType == REFUNGIBLE_COLLECTION_TYPE) { + UniqueRefungible rftCollection = UniqueRefungible(_collection); + + tokenId = rftCollection.mint(address(this)); + + rftCollection.transferFromCross( + RftCrossAccountId(address(this), 0), + RftCrossAccountId(address(0), _substrateReceiver), + tokenId + ); + } else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) { + UniqueNFT nftCollection = UniqueNFT(_collection); + tokenId = nftCollection.mint(address(this)); + + nftCollection.transferFromCross( + NftCrossAccountId(address(this), 0), + NftCrossAccountId(address(0), _substrateReceiver), + tokenId + ); + } else { + revert("Wrong collection type. Works only with NFT or RFT"); + } + + emit MintToSub(address(0), _substrateReceiver, _collection, tokenId); + } + + function mintToSubstrateWithProperty( + address _collection, + uint256 _substrateReceiver, + Property[] calldata properties + ) external checkRestrictions(_collection) { + uint256 propertiesLength = properties.length; + require(propertiesLength > 0, "Properies is empty"); + + Collection commonContract = Collection(_collection); + bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType())); + uint256 tokenId; + + if (collectionType == REFUNGIBLE_COLLECTION_TYPE) { + UniqueRefungible rftCollection = UniqueRefungible(_collection); + tokenId = rftCollection.nextTokenId(); + rftCollection.mint(address(this)); + for (uint256 i = 0; i < propertiesLength; ++i) { + rftCollection.setProperty(tokenId, properties[i].key, properties[i].value); + } + rftCollection.transferFromCross( + RftCrossAccountId(address(this), 0), + RftCrossAccountId(address(0), _substrateReceiver), + tokenId + ); + } else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) { + UniqueNFT nftCollection = UniqueNFT(_collection); + tokenId = nftCollection.mint(address(this)); + for (uint256 i = 0; i < propertiesLength; ++i) { + nftCollection.setProperty(tokenId, properties[i].key, properties[i].value); + } + nftCollection.transferFromCross( + NftCrossAccountId(address(this), 0), + NftCrossAccountId(address(0), _substrateReceiver), + tokenId + ); + } else { + revert("Wrong collection type. Works only with NFT or RFT"); + } + + emit MintToSub(address(0), _substrateReceiver, _collection, tokenId); + } + + function mintToSubstrateBulkProperty( + address _collection, + uint256 _substrateReceiver, + NftProperty[] calldata _properties + ) external checkRestrictions(_collection) { + uint256 propertiesLength = _properties.length; + require(propertiesLength > 0, "Properies is empty"); + + Collection commonContract = Collection(_collection); + bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType())); + uint256 tokenId; + + if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {} else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) { + UniqueNFT nftCollection = UniqueNFT(_collection); + tokenId = nftCollection.mint(address(this)); + + nftCollection.setProperties(tokenId, _properties); + + nftCollection.transferFromCross( + NftCrossAccountId(address(this), 0), + NftCrossAccountId(address(0), _substrateReceiver), + tokenId + ); + } else { + revert("Wrong collection type. Works only with NFT or RFT"); + } + + emit MintToSub(address(0), _substrateReceiver, _collection, tokenId); + } +} --- a/tests/src/benchmarks/mintFeeBench/feeBench.ts +++ /dev/null @@ -1,418 +0,0 @@ -import {EthUniqueHelper, usingEthPlaygrounds} from '../../eth/util'; -import {readFile} from 'fs/promises'; -import {ContractImports} from '../../eth/util/playgrounds/types'; -import { - ICrossAccountId, - ITokenPropertyPermission, -} from '../../util/playgrounds/types'; -import {IKeyringPair} from '@polkadot/types/types'; -import {UniqueNFTCollection} from '../../util/playgrounds/unique'; -import {Contract} from 'web3-eth-contract'; -import {createObjectCsvWriter} from 'csv-writer'; - -const CONTRACT_IMPORT: ContractImports[] = [ - { - fsPath: `${__dirname}/../../eth/api/CollectionHelpers.sol`, - solPath: 'eth/api/CollectionHelpers.sol', - }, - { - fsPath: `${__dirname}/../../eth/api/ContractHelpers.sol`, - solPath: 'eth/api/ContractHelpers.sol', - }, - { - fsPath: `${__dirname}/../../eth/api/UniqueRefungibleToken.sol`, - solPath: 'eth/api/UniqueRefungibleToken.sol', - }, - { - fsPath: `${__dirname}/../../eth/api/UniqueRefungible.sol`, - solPath: 'eth/api/UniqueRefungible.sol', - }, - { - fsPath: `${__dirname}/../../eth/api/UniqueNFT.sol`, - solPath: 'eth/api/UniqueNFT.sol', - }, -]; - -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, - }, - }; -}); - -interface IBenchmarkResultForProp { - propertiesNumber: number; - substrateFee: number; - ethFee: number; - ethBulkFee: number; - evmProxyContractFee: number; - evmProxyContractBulkFee: number; -} - -const main = async () => { - const csvWriter = createObjectCsvWriter({ - path: 'properties.csv', - header: [ - 'propertiesNumber', - 'substrateFee', - 'ethFee', - 'ethBulkFee', - 'evmProxyContractFee', - 'evmProxyContractBulkFee', - ], - }); - - await usingEthPlaygrounds(async (helper, privateKey) => { - const CONTRACT_SOURCE = ( - await readFile(`${__dirname}/proxyContract.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 ethSigner = await helper.eth.createAccountWithBalance(donor, 100n); - - const contract = await helper.ethContract.deployByCode( - ethSigner, - 'ProxyMint', - CONTRACT_SOURCE, - CONTRACT_IMPORT, - ); - - const fees = await benchMintFee(helper, privateKey, contract); - console.log(fees); - - const result: IBenchmarkResultForProp[] = []; - - for (let i = 1; i <= 20; i++) { - result.push(await benchMintWithProperties(helper, privateKey, contract, { - propertiesNumber: i, - })); - } - - await csvWriter.writeRecords(result); - - console.table(result); - }); -}; - -main() - .then(() => process.exit(0)) - .catch((e) => { - console.log(e); - process.exit(1); - }); - -async function createCollectionForBenchmarks( - helper: EthUniqueHelper, - privateKey: (seed: string) => Promise, - ethSigner: string, - proxyContract: string, - permissions: ITokenPropertyPermission[], -) { - const donor = await privateKey('//Alice'); - - 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; -} - -async function benchMintFee( - helper: EthUniqueHelper, - privateKey: (seed: string) => Promise, - proxyContract: Contract, -): Promise<{ - substrateFee: number; - ethFee: number; - evmProxyContractFee: number; -}> { - const donor = await privateKey('//Alice'); - const substrateReceiver = await privateKey('//Bob'); - const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n); - - const nominal = helper.balance.getOneTokenNominal(); - - await helper.eth.transferBalanceFromSubstrate( - donor, - proxyContract.options.address, - 100n, - ); - - const collection = await createCollectionForBenchmarks( - helper, - privateKey, - ethSigner, - proxyContract.options.address, - PERMISSIONS, - ); - - const substrateFee = await helper.arrange.calculcateFee( - {Substrate: donor.address}, - () => collection.mintToken(donor, {Substrate: substrateReceiver.address}), - ); - - const collectionEthAddress = helper.ethAddress.fromCollectionId(collection.collectionId); - const collectionContract = helper.ethNativeContract.collection( - collectionEthAddress, - 'nft', - ); - - const receiverEthAddress = helper.address.substrateToEth(substrateReceiver.address); - - const encodedCall = collectionContract.methods - .mint(receiverEthAddress) - .encodeABI(); - - const ethFee = await helper.arrange.calculcateFee( - {Substrate: donor.address}, - async () => { - await helper.eth.sendEVM( - donor, - collectionContract.options.address, - encodedCall, - '0', - ); - }, - ); - - const evmProxyContractFee = await helper.arrange.calculcateFee( - {Ethereum: ethSigner}, - async () => { - await proxyContract.methods - .mintToSubstrate( - helper.ethAddress.fromCollectionId(collection.collectionId), - substrateReceiver.addressRaw, - ) - .send({from: ethSigner}); - }, - ); - - return { - substrateFee: convertToTokens(substrateFee, nominal), - ethFee: convertToTokens(ethFee, nominal), - evmProxyContractFee: convertToTokens(evmProxyContractFee, nominal), - }; -} - -async function benchMintWithProperties( - helper: EthUniqueHelper, - privateKey: (seed: string) => Promise, - proxyContract: Contract, - setup: { propertiesNumber: number }, -): Promise { - const donor = await privateKey('//Alice'); // Seed from account with balance on this network - const ethSigner = await helper.eth.createAccountWithBalance(donor, 100n); - - const susbstrateReceiver = await privateKey('//Bob'); - const receiverEthAddress = helper.address.substrateToEth(susbstrateReceiver.address); - - const nominal = helper.balance.getOneTokenNominal(); - - const substrateFee = await calculateFeeNftMintWithProperties( - helper, - privateKey, - {Substrate: donor.address}, - ethSigner, - proxyContract.options.address, - async (collection) => { - await collection.mintToken( - donor, - {Substrate: susbstrateReceiver.address}, - PROPERTIES.slice(0, setup.propertiesNumber).map((p) => { - return {key: p.key, value: Buffer.from(p.value).toString()}; - }), - ); - }, - ); - - const ethFee = await calculateFeeNftMintWithProperties( - helper, - privateKey, - {Substrate: donor.address}, - ethSigner, - proxyContract.options.address, - async (collection) => { - const evmContract = helper.ethNativeContract.collection( - helper.ethAddress.fromCollectionId(collection.collectionId), - 'nft', - ); - - const subTokenId = await evmContract.methods.nextTokenId().call(); - - let encodedCall = evmContract.methods - .mint(receiverEthAddress) - .encodeABI(); - - await helper.eth.sendEVM( - donor, - evmContract.options.address, - encodedCall, - '0', - ); - - for (const val of PROPERTIES.slice(0, setup.propertiesNumber)) { - encodedCall = await evmContract.methods - .setProperty(subTokenId, val.key, Buffer.from(val.value)) - .encodeABI(); - - await helper.eth.sendEVM( - donor, - evmContract.options.address, - encodedCall, - '0', - ); - } - }, - ); - - const ethBulkFee = await calculateFeeNftMintWithProperties( - helper, - privateKey, - {Substrate: donor.address}, - ethSigner, - proxyContract.options.address, - async (collection) => { - const evmContract = helper.ethNativeContract.collection( - helper.ethAddress.fromCollectionId(collection.collectionId), - 'nft', - ); - - const subTokenId = await evmContract.methods.nextTokenId().call(); - - let encodedCall = evmContract.methods - .mint(receiverEthAddress) - .encodeABI(); - - await helper.eth.sendEVM( - donor, - evmContract.options.address, - encodedCall, - '0', - ); - - encodedCall = await evmContract.methods - .setProperties( - subTokenId, - PROPERTIES.slice(0, setup.propertiesNumber).map((p) => { - return {field_0: p.key, field_1: p.value}; - }), - ) - .encodeABI(); - - await helper.eth.sendEVM( - donor, - evmContract.options.address, - encodedCall, - '0', - ); - }, - ); - - const proxyContractFee = await calculateFeeNftMintWithProperties( - helper, - privateKey, - {Ethereum: ethSigner}, - ethSigner, - proxyContract.options.address, - async (collection) => { - await proxyContract.methods - .mintToSubstrateWithProperty( - helper.ethAddress.fromCollectionId(collection.collectionId), - susbstrateReceiver.addressRaw, - PROPERTIES.slice(0, setup.propertiesNumber), - ) - .send({from: ethSigner}); - }, - ); - - const proxyContractBulkFee = await calculateFeeNftMintWithProperties( - helper, - privateKey, - {Ethereum: ethSigner}, - ethSigner, - proxyContract.options.address, - async (collection) => { - await proxyContract.methods - .mintToSubstrateBulkProperty( - helper.ethAddress.fromCollectionId(collection.collectionId), - susbstrateReceiver.addressRaw, - PROPERTIES.slice(0, setup.propertiesNumber).map((p) => { - return {field_0: p.key, field_1: p.value}; - }), - ) - .send({from: ethSigner, gas: 25_000_000}); - }, - ); - - return { - propertiesNumber: setup.propertiesNumber, - substrateFee: convertToTokens(substrateFee, nominal), - ethFee: convertToTokens(ethFee, nominal), - ethBulkFee: convertToTokens(ethBulkFee, nominal), - evmProxyContractFee: convertToTokens(proxyContractFee, nominal), - evmProxyContractBulkFee: convertToTokens(proxyContractBulkFee, nominal), - }; -} - -async function calculateFeeNftMintWithProperties( - helper: EthUniqueHelper, - privateKey: (seed: string) => Promise, - payer: ICrossAccountId, - ethSigner: string, - proxyContractAddress: string, - calculatedCall: (collection: UniqueNFTCollection) => Promise, -): Promise { - const collection = await createCollectionForBenchmarks( - helper, - privateKey, - ethSigner, - proxyContractAddress, - PERMISSIONS, - ); - return helper.arrange.calculcateFee(payer, async () => { - await calculatedCall(collection); - }); -} -function convertToTokens(value: bigint, nominal: bigint): number { - return Number((value * 1000n) / nominal) / 1000; -} --- a/tests/src/benchmarks/mintFeeBench/proxyContract.sol +++ /dev/null @@ -1,129 +0,0 @@ -// SPDX-License-Identifier: Apache License -pragma solidity >=0.8.0; -import {CollectionHelpers} from "../../eth/api/CollectionHelpers.sol"; -import {ContractHelpers} from "../../eth/api/ContractHelpers.sol"; -import {UniqueRefungibleToken} from "../../eth/api/UniqueRefungibleToken.sol"; -import {UniqueRefungible, Collection, EthCrossAccount as RftCrossAccountId, Tuple20 as RftProperties} from "../../eth/api/UniqueRefungible.sol"; -import {UniqueNFT, EthCrossAccount as NftCrossAccountId, Tuple21 as NftProperty, TokenProperties} from "../../eth/api/UniqueNFT.sol"; - -struct Property { - string key; - bytes value; -} - -contract ProxyMint { - bytes32 constant REFUNGIBLE_COLLECTION_TYPE = keccak256(bytes("ReFungible")); - bytes32 constant NONFUNGIBLE_COLLECTION_TYPE = keccak256(bytes("NFT")); - - modifier checkRestrictions(address _collection) { - Collection commonContract = Collection(_collection); - require(commonContract.isOwnerOrAdmin(msg.sender), "Only collection admin/owner can call this method"); - _; - } - - /// @dev This emits when a mint to a substrate address has been made. - event MintToSub(address _toEth, uint256 _toSub, address _collection, uint256 _tokenId); - - function mintToSubstrate(address _collection, uint256 _substrateReceiver) external checkRestrictions(_collection) { - Collection commonContract = Collection(_collection); - bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType())); - uint256 tokenId; - - if (collectionType == REFUNGIBLE_COLLECTION_TYPE) { - UniqueRefungible rftCollection = UniqueRefungible(_collection); - - tokenId = rftCollection.mint(address(this)); - - rftCollection.transferFromCross( - RftCrossAccountId(address(this), 0), - RftCrossAccountId(address(0), _substrateReceiver), - tokenId - ); - } else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) { - UniqueNFT nftCollection = UniqueNFT(_collection); - tokenId = nftCollection.mint(address(this)); - - nftCollection.transferFromCross( - NftCrossAccountId(address(this), 0), - NftCrossAccountId(address(0), _substrateReceiver), - tokenId - ); - } else { - revert("Wrong collection type. Works only with NFT or RFT"); - } - - emit MintToSub(address(0), _substrateReceiver, _collection, tokenId); - } - - function mintToSubstrateWithProperty( - address _collection, - uint256 _substrateReceiver, - Property[] calldata properties - ) external checkRestrictions(_collection) { - uint256 propertiesLength = properties.length; - require(propertiesLength > 0, "Properies is empty"); - - Collection commonContract = Collection(_collection); - bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType())); - uint256 tokenId; - - if (collectionType == REFUNGIBLE_COLLECTION_TYPE) { - UniqueRefungible rftCollection = UniqueRefungible(_collection); - tokenId = rftCollection.nextTokenId(); - rftCollection.mint(address(this)); - for (uint256 i = 0; i < propertiesLength; ++i) { - rftCollection.setProperty(tokenId, properties[i].key, properties[i].value); - } - rftCollection.transferFromCross( - RftCrossAccountId(address(this), 0), - RftCrossAccountId(address(0), _substrateReceiver), - tokenId - ); - } else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) { - UniqueNFT nftCollection = UniqueNFT(_collection); - tokenId = nftCollection.mint(address(this)); - for (uint256 i = 0; i < propertiesLength; ++i) { - nftCollection.setProperty(tokenId, properties[i].key, properties[i].value); - } - nftCollection.transferFromCross( - NftCrossAccountId(address(this), 0), - NftCrossAccountId(address(0), _substrateReceiver), - tokenId - ); - } else { - revert("Wrong collection type. Works only with NFT or RFT"); - } - - emit MintToSub(address(0), _substrateReceiver, _collection, tokenId); - } - - function mintToSubstrateBulkProperty( - address _collection, - uint256 _substrateReceiver, - NftProperty[] calldata _properties - ) external checkRestrictions(_collection) { - uint256 propertiesLength = _properties.length; - require(propertiesLength > 0, "Properies is empty"); - - Collection commonContract = Collection(_collection); - bytes32 collectionType = keccak256(bytes(commonContract.uniqueCollectionType())); - uint256 tokenId; - - if (collectionType == REFUNGIBLE_COLLECTION_TYPE) {} else if (collectionType == NONFUNGIBLE_COLLECTION_TYPE) { - UniqueNFT nftCollection = UniqueNFT(_collection); - tokenId = nftCollection.mint(address(this)); - - nftCollection.setProperties(tokenId, _properties); - - nftCollection.transferFromCross( - NftCrossAccountId(address(this), 0), - NftCrossAccountId(address(0), _substrateReceiver), - tokenId - ); - } else { - revert("Wrong collection type. Works only with NFT or RFT"); - } - - emit MintToSub(address(0), _substrateReceiver, _collection, tokenId); - } -} -- gitstuff